├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── config.yml │ └── feature.md ├── dependabot.yml └── workflows │ └── dev.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts ├── zmusic-bukkit ├── build.gradle.kts └── src │ └── main │ ├── java │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ └── BukkitPlugin.java │ ├── kotlin │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ └── platform │ │ └── BukkitLoggerImpl.kt │ └── resources │ └── plugin.yml ├── zmusic-bungee ├── build.gradle.kts └── src │ └── main │ ├── java │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ └── BungeePlugin.java │ ├── kotlin │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ └── platform │ │ └── BungeeLoggerImpl.kt │ └── resources │ └── bungee.yml ├── zmusic-core ├── build.gradle.kts └── src │ └── main │ ├── java │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ └── Application.java │ ├── kotlin │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ ├── ZMusic.kt │ │ ├── config │ │ ├── Config.kt │ │ └── I18n.kt │ │ ├── entity │ │ └── VersionInfo.kt │ │ ├── enums │ │ └── Platform.kt │ │ ├── exception │ │ └── ZMusicException.kt │ │ ├── platform │ │ └── PlatformLogger.kt │ │ └── utils │ │ ├── Common.kt │ │ ├── Http.kt │ │ └── String.kt │ └── resources │ ├── config.toml │ └── i18n │ ├── en-US.toml │ └── zh-CN.toml ├── zmusic-runtime ├── build.gradle.kts └── src │ └── main │ ├── java-templates │ └── me │ │ └── zhenxin │ │ └── zmusic │ │ └── ZMusicConstants.java.peb │ └── java │ └── me │ └── zhenxin │ └── zmusic │ ├── ZMusicRuntime.java │ └── dependencies │ ├── DependencyScope.java │ ├── JarRelocation.java │ ├── ParsedDependency.java │ ├── RuntimeDependencies.java │ ├── RuntimeDependency.java │ ├── RuntimeEnv.java │ ├── RuntimeEnvDependency.java │ ├── aether │ └── AetherResolver.java │ ├── common │ ├── ClassAppender.java │ ├── PrimitiveIO.java │ └── RuntimeLogger.java │ └── legacy │ ├── AbstractXmlParser.java │ ├── Artifact.java │ ├── Dependency.java │ ├── DependencyDownloader.java │ ├── DependencyVersion.java │ ├── Repository.java │ └── VersionChecker.java └── zmusic-velocity ├── build.gradle.kts └── src └── main ├── java └── me │ └── zhenxin │ └── zmusic │ └── VelocityPlugin.java ├── kotlin └── me │ └── zhenxin │ └── zmusic │ └── platform │ └── VelocityLoggerImpl.kt └── resources └── velocity-plugin.json /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交Bug / Submit Bug 3 | about: 向我们报告一个Bug以帮助我们改进 4 | title: 'bug: ' 5 | labels: 'type: bug' 6 | --- 7 | 8 | 警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭 9 | 10 | - [ ] 确保您使用的是 [最新开发版本](https://github.com/zmusic-dev/zmusic-server/actions/workflows/dev.yml) 的 ZMusic 插件. 11 | - [ ] 确保您的问题尚未在 [Issues](https://github.com/zmusic-dev/zmusic-server/issues) 列表中提出. 12 | - [ ] 确保您的问题不是由于您的代码错误导致的. 13 | 14 | ### 描述 Bug 15 | 16 | 请清晰地描述此 Bug 的具体表现,并提供错误日志。 17 | 18 | ### 复现 Bug 19 | 20 | 请提供复现 Bug 的详细步骤,以确保我们可以理解并定位问题。 21 | 22 | ### 系统信息 23 | 24 | - Minecraft 版本: 25 | - ZMusic 版本: 26 | - 操作系统版本: 27 | - 服务端版本: -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: QQ 群 4 | url: https://jq.qq.com/?_wv=1027&k=9Xs1RMt5 5 | about: 加入我们的 QQ 交流群. 快速反馈问题, 并与其他用户交流. 6 | - name: Discord 7 | url: https://discord.gg/twQgJNufYn 8 | about: Ask questions and discuss with other ZMusic users in real time. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 功能请求 / Feature Request 3 | about: 向我们提交一个新功能的请求 4 | title: 'feat: ' 5 | labels: 'type: feature' 6 | --- 7 | 8 | 警告: 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭 9 | 10 | - [ ] 确保您使用的是 [最新开发版本](https://github.com/zmusic-dev/zmusic-server/actions/workflows/dev.yml) 的 ZMusic 插件. 11 | - [ ] 确保您的功能请求尚未在 [Issues](https://github.com/zmusic-dev/zmusic-server/issues) 列表中提出. 12 | - [ ] 确保您的功能请求是与 ZMusic 相关的,且可以实现. 13 | 14 | ### 描述功能 15 | 16 | 请清晰地描述您想要的功能,并提供相关的信息。 17 | 18 | ### 实现方法 19 | 20 | 请提供您认为可以实现此功能的方法,如果您不知道如何实现,可以留空。 21 | 当然,如果您有兴趣,也可以自己实现并向我们提交 [Pull Request](https://github.com/zmusic-dev/zmusic-server/pulls)。 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: Dev Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - ".github/workflows/**" 8 | - "gradle/libs.versions.toml" 9 | - "gradle/wrapper/gradle-wrapper.properties" 10 | - "gradle.properties" 11 | - "**/src/**" 12 | - "**/build.gradle.kts" 13 | - "settings.gradle.kts" 14 | - "gradle.properties" 15 | branches: 16 | - "v4" 17 | pull_request: 18 | branches: 19 | - "v4" 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: github/codeql-action/init@v3 27 | with: 28 | languages: java-kotlin 29 | - uses: actions/setup-java@v4 30 | with: 31 | distribution: 'zulu' 32 | java-version: '21' 33 | cache: 'gradle' 34 | - name: Build with Gradle 35 | run: ./gradlew clean build 36 | - uses: github/codeql-action/analyze@v3 37 | with: 38 | category: /language:java-kotlin 39 | - uses: actions/upload-artifact@v4 40 | with: 41 | name: 'zmusic-bukkit' 42 | path: zmusic-bukkit/build/libs/*.jar 43 | - uses: actions/upload-artifact@v4 44 | with: 45 | name: 'zmusic-bungee' 46 | path: zmusic-bungee/build/libs/*.jar 47 | - uses: actions/upload-artifact@v4 48 | with: 49 | name: 'zmusic-velocity' 50 | path: zmusic-velocity/build/libs/*.jar 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Gradle template 3 | .gradle 4 | **/build/ 5 | !src/**/build/ 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | 10 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 11 | !gradle-wrapper.jar 12 | 13 | # Cache of project 14 | .gradletasknamecache 15 | 16 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 17 | # gradle/wrapper/gradle-wrapper.properties 18 | 19 | local.properties 20 | 21 | .idea 22 | *.iml 23 | *.ipr 24 | *.iws 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | ZMusic 635 | Copyright (C) 2025 ZhenXin 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | ZMusic Copyright (C) 2025 ZhenXin 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | ![][banner] 5 | 6 | ![][tested-versions] 7 | ![][players] 8 | ![][servers] 9 | 10 | ![][releases] 11 | ![][downloads] 12 | ![][license] 13 | 14 | [文档][docs-link] | [QQ 群][qq-group-link] | [Discord][discord-link] 15 | 16 | 简体中文 | [English](README_EN.md) 17 | 18 |
19 | 20 | ## 简介 21 | 22 | 这是一个功能强大的音乐系统,支持以下功能。 23 | 24 | * 全服点歌 25 | * 单独播放 26 | * 歌词显示 27 | * 歌词翻译显示 28 | * 多搜索源 29 | * 关键词搜索 30 | * 个人歌单 31 | * 全服歌单 32 | * 歌单播放 33 | * 音量调节 34 | * 支持BungeeCord 35 | 36 | ## 文档 37 | 38 | [点击查看使用文档][docs-link] 39 | 40 | ## 开源协议 41 | 42 | 本项目使用 [GPL-3.0](LICENSE) 协议开放源代码 43 | 44 | ```text 45 | ZMusic - Powerful Music System 46 | Copyright (C) 2025 ZhenXin 47 | This program is free software: you can redistribute it and/or modify 48 | it under the terms of the GNU General Public License as published by 49 | the Free Software Foundation, either version 3 of the License, or 50 | (at your option) any later version. 51 | This program is distributed in the hope that it will be useful, 52 | but WITHOUT ANY WARRANTY; without even the implied warranty of 53 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 54 | GNU General Public License for more details. 55 | You should have received a copy of the GNU General Public License 56 | along with this program. If not, see . 57 | ``` 58 | 59 | ## 鸣谢 60 | 61 | * [Hutool](https://hutool.cn/) - 全面、强大的Java工具类库 62 | * [TabooLib](https://github.com/TabooLib/taboolib) - 依赖动态加载相关代码来源 63 | * [Kotlin](https://kotlinlang.org/) - 语法简洁、安全、易用的编程语言 64 | * [JetBrains](https://www.jetbrains.com/zh-cn/) - 提供了免费的开发工具 65 | 66 | ## 贡献者 67 | 68 | [![][contrib]](https://github.com/zmusic-dev/zmusic-server/graphs/contributors) 69 | 70 | [banner]: https://socialify.git.ci/zmusic-dev/zmusic-server/image?description=1&forks=1&issues=1&language=1&name=1&pulls=1&stargazers=1&theme=Auto 71 | 72 | [license]: https://img.shields.io/github/license/zmusic-dev/zmusic-server?style=for-the-badge 73 | 74 | [players]: https://img.shields.io/bstats/players/7291?label=players&style=for-the-badge 75 | 76 | [servers]: https://img.shields.io/bstats/servers/7291?label=servers&style=for-the-badge 77 | 78 | [tested-versions]: https://img.shields.io/spiget/tested-versions/83027?style=for-the-badge 79 | 80 | [releases]: https://img.shields.io/github/v/release/zmusic-dev/zmusic-server?style=for-the-badge 81 | 82 | [downloads]: https://img.shields.io/github/downloads/zmusic-dev/zmusic-server/total?style=for-the-badge 83 | 84 | [contrib]: https://contrib.rocks/image?repo=zmusic-dev/zmusic-server 85 | 86 | [docs-link]: https://zmusic.zhenxin.me 87 | 88 | [qq-group-link]: https://jq.qq.com/?_wv=1027&k=9Xs1RMt5 89 | 90 | [discord-link]: https://discord.gg/twQgJNufYn -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | ![][banner] 5 | 6 | ![][tested-versions] 7 | ![][players] 8 | ![][servers] 9 | 10 | ![][releases] 11 | ![][downloads] 12 | ![][license] 13 | 14 | [Documentation][docs-link] | [Discord][discord-link] 15 | 16 | [简体中文](README.md) | English 17 | 18 |
19 | 20 | ## Introduction 21 | 22 | This is a powerful music system that supports the following functions. 23 | 24 | * Full service song 25 | * Play separately 26 | * Lyrics display 27 | * Lyrics translation display 28 | * Multi -search source 29 | * Keyword search 30 | * Personal song list 31 | * Full service song list 32 | * Play of the song list 33 | * Volume adjustment 34 | * Support BungeeCord 35 | 36 | ## Open Source License 37 | 38 | This project uses [GPL-3.0](LICENSE) license for open source code 39 | 40 | ```text 41 | ZMusic - Powerful Music System 42 | Copyright (C) 2025 ZhenXin 43 | This program is free software: you can redistribute it and/or modify 44 | it under the terms of the GNU General Public License as published by 45 | the Free Software Foundation, either version 3 of the License, or 46 | (at your option) any later version. 47 | This program is distributed in the hope that it will be useful, 48 | but WITHOUT ANY WARRANTY; without even the implied warranty of 49 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 50 | GNU General Public License for more details. 51 | You should have received a copy of the GNU General Public License 52 | along with this program. If not, see . 53 | ``` 54 | 55 | ## Thanks 56 | 57 | * [Hutool](https://hutool.cn/) - A set of tools that keep Java sweet. 58 | * [TabooLib](https://github.com/TabooLib/taboolib) - Dynamic loading of dependent related code sources. 59 | * [Kotlin](https://kotlinlang.org/) - Statically typed programming language for modern multiplatform applications. 60 | * [JetBrains](https://www.jetbrains.com/zh-cn/) - Provides free development tools. 61 | 62 | ## Contributors 63 | 64 | [![][contrib]](https://github.com/zmusic-dev/zmusic-server/graphs/contributors) 65 | 66 | [banner]: https://socialify.git.ci/zmusic-dev/zmusic-server/image?description=1&forks=1&issues=1&language=1&name=1&pulls=1&stargazers=1&theme=Auto 67 | 68 | [license]: https://img.shields.io/github/license/zmusic-dev/zmusic-server?style=for-the-badge 69 | 70 | [players]: https://img.shields.io/bstats/players/7291?label=players&style=for-the-badge 71 | 72 | [servers]: https://img.shields.io/bstats/servers/7291?label=servers&style=for-the-badge 73 | 74 | [tested-versions]: https://img.shields.io/spiget/tested-versions/83027?style=for-the-badge 75 | 76 | [releases]: https://img.shields.io/github/v/release/zmusic-dev/zmusic-server?style=for-the-badge 77 | 78 | [downloads]: https://img.shields.io/github/downloads/zmusic-dev/zmusic-server/total?style=for-the-badge 79 | 80 | [contrib]: https://contrib.rocks/image?repo=zmusic-dev/zmusic-server 81 | 82 | [docs-link]: https://zmusic.zhenxin.me/ 83 | 84 | [discord-link]: https://discord.gg/twQgJNufYn -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | fun gitCommitHash(): String { 5 | val builder = ProcessBuilder("git", "rev-parse", "--short", "HEAD") 6 | val process = builder.start() 7 | val reader = process.inputReader() 8 | val hash = reader.readText().trim() 9 | return if (hash.isNotEmpty()) ".$hash" else "" 10 | } 11 | 12 | plugins { 13 | java 14 | alias(libs.plugins.kotlin.jvm) 15 | alias(libs.plugins.johnrengelman.shadow) 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | maven { 21 | name = "PaperMC" 22 | url = uri("https://repo.papermc.io/repository/maven-public/") 23 | } 24 | maven { 25 | name = "TabooProject" 26 | url = uri("https://repo.tabooproject.org/repository/releases/") 27 | } 28 | maven { 29 | name = "PlaceholderAPI" 30 | url = uri("https://repo.extendedclip.com/releases/") 31 | } 32 | mavenCentral() 33 | } 34 | } 35 | 36 | subprojects { 37 | group = "me.zhenxin" 38 | version = "4.0.0-dev" + gitCommitHash() 39 | 40 | apply { 41 | plugin("java") 42 | plugin("org.jetbrains.kotlin.jvm") 43 | plugin("com.gradleup.shadow") 44 | } 45 | 46 | java { 47 | sourceCompatibility = JavaVersion.VERSION_1_8 48 | targetCompatibility = JavaVersion.VERSION_1_8 49 | } 50 | 51 | tasks.build { 52 | dependsOn(tasks.shadowJar) 53 | } 54 | 55 | tasks.shadowJar { 56 | exclude("kotlin/**") 57 | exclude("org/jetbrains/**") 58 | exclude("org/intellij/**") 59 | 60 | relocate("kotlin", "me.zhenxin.zmusic.library.kotlin") 61 | relocate("me.lucko", "me.zhenxin.zmusic.library") 62 | relocate("org.objectweb", "me.zhenxin.zmusic.library") 63 | relocate("org.bstats", "me.zhenxin.zmusic.library.bstats") 64 | 65 | archiveClassifier.set("") 66 | minimize() 67 | 68 | manifest { 69 | attributes["Main-Class"] = "me.zhenxin.zmusic.Application" 70 | } 71 | } 72 | 73 | tasks.withType { 74 | compilerOptions { 75 | jvmTarget.set(JvmTarget.JVM_1_8) 76 | } 77 | } 78 | 79 | tasks.withType { 80 | options.encoding = "UTF-8" 81 | } 82 | } 83 | 84 | tasks.jar { enabled = false } 85 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1G 2 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "2.1.21" 3 | blossom = "2.1.0" 4 | shadow = "8.3.6" 5 | 6 | lucko-jar-relocator = "1.7" 7 | google-guava = "33.4.8-jre" 8 | google-gson = "2.13.1" 9 | maven-resolver = "1.9.23" 10 | maven-resolver-provider = "3.9.9" 11 | 12 | spigot = "1.21.5-R0.1-SNAPSHOT" 13 | bungeecord = "1.21-R0.2" 14 | velocity = "3.4.0-SNAPSHOT" 15 | 16 | bstats = "3.1.0" 17 | placeholderapi = "2.11.6" 18 | 19 | hutool = "6.0.0-M21" 20 | netty = "4.2.1.Final" 21 | nightconfig = "3.8.2" 22 | 23 | [plugins] 24 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 25 | kyori-blossom = { id = "net.kyori.blossom", version.ref = "blossom" } 26 | johnrengelman-shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } 27 | 28 | [libraries] 29 | # Lucko Jar Relocator 30 | lucko-jar-relocator = { module = "me.lucko:jar-relocator", version.ref = "lucko-jar-relocator" } 31 | # Google Guava 32 | google-guava = { module = "com.google.guava:guava", version.ref = "google-guava" } 33 | # Google Gson 34 | google-gson = { module = "com.google.code.gson:gson", version.ref = "google-gson" } 35 | # Maven Resolver 36 | maven-resolver-api = { module = "org.apache.maven.resolver:maven-resolver-api", version.ref = "maven-resolver" } 37 | maven-resolver-util = { module = "org.apache.maven.resolver:maven-resolver-util", version.ref = "maven-resolver" } 38 | maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "maven-resolver" } 39 | maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "maven-resolver" } 40 | maven-resolver-transport-wagon = { module = "org.apache.maven.resolver:maven-resolver-transport-wagon", version.ref = "maven-resolver" } 41 | maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "maven-resolver" } 42 | # Maven Resolver Provider 43 | maven-resolver-provider = { module = "org.apache.maven:maven-resolver-provider", version.ref = "maven-resolver-provider" } 44 | # Platform API 45 | spigot = { module = "org.spigotmc:spigot-api", version.ref = "spigot" } 46 | bungeecord = { module = "net.md-5:bungeecord-api", version.ref = "bungeecord" } 47 | velocity = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } 48 | # bStats 49 | bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" } 50 | bstats-bungeecord = { module = "org.bstats:bstats-bungeecord", version.ref = "bstats" } 51 | bstats-velocity = { module = "org.bstats:bstats-velocity", version.ref = "bstats" } 52 | # PlaceholderAPI 53 | placeholderapi = { module = "me.clip:placeholderapi", version.ref = "placeholderapi" } 54 | # Hutool 55 | hutool-core = { module = "org.dromara.hutool:hutool-core", version.ref = "hutool" } 56 | hutool-http = { module = "org.dromara.hutool:hutool-http", version.ref = "hutool" } 57 | hutool-log = { module = "org.dromara.hutool:hutool-log", version.ref = "hutool" } 58 | hutool-json = { module = "org.dromara.hutool:hutool-json", version.ref = "hutool" } 59 | # Netty 60 | netty-buffer = { module = "io.netty:netty-buffer", version.ref = "netty" } 61 | # Night Config 62 | nightconfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightconfig" } 63 | nightconfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightconfig" } 64 | 65 | 66 | [bundles] 67 | # Maven Resolver 68 | maven-resolver = ["maven-resolver-api", "maven-resolver-util", "maven-resolver-impl", "maven-resolver-connector-basic", "maven-resolver-transport-wagon", "maven-resolver-transport-http", "maven-resolver-provider"] 69 | # Hutool 70 | hutool = ["hutool-core", "hutool-http", "hutool-log", "hutool-json"] 71 | # Night Config 72 | nightconfig = ["nightconfig-core", "nightconfig-toml"] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zmusic-dev/zmusic-server/47ba7275e7a07cdc4e0dda6c280011227c2fdab2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "zmusic-server" 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | mavenCentral() 7 | } 8 | } 9 | 10 | include( 11 | "zmusic-bukkit", 12 | "zmusic-bungee", 13 | "zmusic-core", 14 | "zmusic-runtime", 15 | "zmusic-velocity" 16 | ) 17 | -------------------------------------------------------------------------------- /zmusic-bukkit/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(project(":zmusic-core")) 3 | 4 | compileOnly(libs.spigot) 5 | compileOnly(libs.placeholderapi) 6 | compileOnly(libs.bstats.bukkit) 7 | } 8 | 9 | tasks.processResources { 10 | inputs.property("version", version) 11 | 12 | filesMatching("plugin.yml") { 13 | expand(mapOf("version" to version)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /zmusic-bukkit/src/main/java/me/zhenxin/zmusic/BukkitPlugin.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic; 2 | 3 | import me.zhenxin.zmusic.dependencies.RuntimeDependency; 4 | import me.zhenxin.zmusic.enums.Platform; 5 | import me.zhenxin.zmusic.platform.BukkitLoggerImpl; 6 | import org.bstats.bukkit.Metrics; 7 | import org.bukkit.plugin.java.JavaPlugin; 8 | 9 | /** 10 | * ZMusic Bukkit 主入口 11 | * 12 | * @author 真心 13 | * @since 2022/7/14 11:35 14 | */ 15 | @SuppressWarnings({"SpellCheckingInspection", "unused"}) 16 | @RuntimeDependency( 17 | value = "!org.bstats:bstats-bukkit:" + ZMusicConstants.BSTATS_VERSION, 18 | test = "!me.zhenxin.zmusic.library.bstats.bukkit.Metrics", 19 | relocate = {"!org.bstats.", "!me.zhenxin.zmusic.library.bstats."} 20 | ) 21 | public class BukkitPlugin extends JavaPlugin { 22 | 23 | private static boolean isFolia() { 24 | try { 25 | Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); 26 | return true; 27 | } catch (ClassNotFoundException e) { 28 | return false; 29 | } 30 | } 31 | 32 | @Override 33 | public void onLoad() { 34 | ZMusicRuntime.setup(getDataFolder().getAbsolutePath(), BukkitPlugin.class); 35 | } 36 | 37 | @Override 38 | public void onDisable() { 39 | ZMusic.INSTANCE.onDisable(); 40 | } 41 | 42 | @Override 43 | public void onEnable() { 44 | ZMusicKt.setLogger(new BukkitLoggerImpl(getServer().getConsoleSender())); 45 | ZMusicKt.setDataFolder(getDataFolder()); 46 | if (isFolia()) { 47 | ZMusicKt.setCurrentPlatform(Platform.FOLIA); 48 | } else { 49 | ZMusicKt.setCurrentPlatform(Platform.BUKKIT); 50 | } 51 | new Metrics(this, 7291); 52 | ZMusic.INSTANCE.onEnable(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /zmusic-bukkit/src/main/kotlin/me/zhenxin/zmusic/platform/BukkitLoggerImpl.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.platform 2 | 3 | import me.zhenxin.zmusic.config.Config 4 | import org.bukkit.command.CommandSender 5 | 6 | /** 7 | * Bukkit 日志实现 8 | * 9 | * @author 真心 10 | * @since 2023/7/23 9:05 11 | */ 12 | class BukkitLoggerImpl(private val sender: CommandSender) : PlatformLogger { 13 | override fun log(msg: String) = sender.sendMessage("${Config.prefix}$msg") 14 | } -------------------------------------------------------------------------------- /zmusic-bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ZMusic 2 | main: me.zhenxin.zmusic.BukkitPlugin 3 | version: ${version} 4 | api-version: 1.13 5 | folia-supported: true 6 | author: ZhenXin 7 | description: ZMusic 8 | softdepend: 9 | - PlaceholderAPI 10 | -------------------------------------------------------------------------------- /zmusic-bungee/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(project(":zmusic-core")) 3 | 4 | compileOnly(libs.bungeecord) 5 | compileOnly(libs.bstats.bungeecord) 6 | } 7 | 8 | tasks.processResources { 9 | inputs.property("version", version) 10 | 11 | filesMatching("bungee.yml") { 12 | expand(mapOf("version" to version)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /zmusic-bungee/src/main/java/me/zhenxin/zmusic/BungeePlugin.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic; 2 | 3 | import me.zhenxin.zmusic.dependencies.RuntimeDependency; 4 | import me.zhenxin.zmusic.enums.Platform; 5 | import me.zhenxin.zmusic.platform.BungeeLoggerImpl; 6 | import net.md_5.bungee.api.plugin.Plugin; 7 | import org.bstats.bungeecord.Metrics; 8 | 9 | /** 10 | * BungeeCord 入口类 11 | * 12 | * @author 真心 13 | * @since 2023/8/28 12:26 14 | */ 15 | @SuppressWarnings({"SpellCheckingInspection", "unused"}) 16 | @RuntimeDependency( 17 | value = "!org.bstats:bstats-bungeecord:" + ZMusicConstants.BSTATS_VERSION, 18 | test = "!me.zhenxin.zmusic.library.bstats.bungeecord.Metrics", 19 | relocate = {"!org.bstats.", "!me.zhenxin.zmusic.library.bstats."} 20 | ) 21 | public class BungeePlugin extends Plugin { 22 | 23 | @Override 24 | public void onLoad() { 25 | ZMusicRuntime.setup(getDataFolder().getAbsolutePath(), BungeePlugin.class); 26 | } 27 | 28 | @Override 29 | public void onEnable() { 30 | ZMusicKt.setLogger(new BungeeLoggerImpl(getProxy().getConsole())); 31 | ZMusicKt.setDataFolder(getDataFolder()); 32 | ZMusicKt.setCurrentPlatform(Platform.BUNGEE); 33 | new Metrics(this, 8864); 34 | ZMusic.INSTANCE.onEnable(); 35 | } 36 | 37 | @Override 38 | public void onDisable() { 39 | ZMusic.INSTANCE.onDisable(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /zmusic-bungee/src/main/kotlin/me/zhenxin/zmusic/platform/BungeeLoggerImpl.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.platform 2 | 3 | import me.zhenxin.zmusic.config.Config 4 | import net.md_5.bungee.api.CommandSender 5 | 6 | 7 | /** 8 | * BungeeCord 日志实现 9 | * 10 | * @author 真心 11 | * @since 2023/8/28 12:28 12 | */ 13 | @Suppress("DEPRECATION") 14 | class BungeeLoggerImpl(private val sender: CommandSender) : PlatformLogger { 15 | override fun log(msg: String) = sender.sendMessage("${Config.prefix}$msg") 16 | } -------------------------------------------------------------------------------- /zmusic-bungee/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: ZMusic 2 | main: me.zhenxin.zmusic.BungeePlugin 3 | version: ${version} 4 | author: ZhenXin 5 | description: ZMusic 6 | -------------------------------------------------------------------------------- /zmusic-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(project(":zmusic-runtime")) 3 | 4 | compileOnly(libs.bundles.hutool) 5 | compileOnly(libs.bundles.nightconfig) 6 | } 7 | 8 | tasks.shadowJar { 9 | enabled = false 10 | } -------------------------------------------------------------------------------- /zmusic-core/src/main/java/me/zhenxin/zmusic/Application.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic; 2 | 3 | /** 4 | * 应用程序 5 | * 6 | * @author 真心 7 | * @since 2024/8/24 19:31 8 | */ 9 | public class Application { 10 | 11 | @SuppressWarnings("ResultOfMethodCallIgnored") 12 | public static void main(String[] args) { 13 | System.out.println("这是一个 Minecraft 插件,请使用 Bukkit, BungeeCord 或 Velocity 来运行这个插件。"); 14 | System.out.println("This is Minecraft Plugin, Please use Bukkit, BungeeCord or Velocity to run this plugin."); 15 | System.out.println("按任意键打开 ZMusic 使用文档。"); 16 | System.out.println("Press any key to open ZMusic usage document."); 17 | 18 | String url = "https://zmusic.zhenxin.me"; 19 | try { 20 | System.in.read(); 21 | 22 | String os = System.getProperty("os.name").toLowerCase(); 23 | if (os.contains("win")) { 24 | Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); 25 | } else if (os.contains("mac")) { 26 | Runtime.getRuntime().exec("open " + url); 27 | } else if (os.contains("nix") || os.contains("nux")) { 28 | String[] cmd = {"xdg-open", url}; 29 | Runtime.getRuntime().exec(cmd); 30 | } 31 | } catch (Exception e) { 32 | System.out.println("无法打开浏览器,请手动打开浏览器并访问 " + url); 33 | System.out.println("Can't open browser, Please open browser and visit " + url); 34 | System.out.println("按任意键退出。"); 35 | System.out.println("Press any key to exit."); 36 | try { 37 | System.in.read(); 38 | } catch (Exception ignored) { 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/ZMusic.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic 2 | 3 | import me.zhenxin.zmusic.config.I18n 4 | import me.zhenxin.zmusic.config.initConfig 5 | import me.zhenxin.zmusic.config.initI18n 6 | import me.zhenxin.zmusic.enums.Platform 7 | import me.zhenxin.zmusic.platform.PlatformLogger 8 | import me.zhenxin.zmusic.utils.checkUpdate 9 | import me.zhenxin.zmusic.utils.colored 10 | import java.io.File 11 | import kotlin.concurrent.thread 12 | 13 | const val baseApi = "https://api.zhenxin.me" 14 | 15 | /** 16 | * ZMusic 17 | * 18 | * @author 真心 19 | * @since 2023/5/23 16:57 20 | */ 21 | object ZMusic { 22 | 23 | private const val LOGO = "" + 24 | " ______ __ __ _ \n" + 25 | " |___ / | \\/ | (_) \n" + 26 | " / / | \\ / | _ _ ___ _ ___ \n" + 27 | " / / | |\\/| | | | | | / __| | | / __|\n" + 28 | " / /__ | | | | | |_| | \\__ \\ | | | (__ \n" + 29 | " /_____| |_| |_| \\__,_| |___/ |_| \\___|\n" 30 | 31 | /** 32 | * 插件启用 33 | */ 34 | fun onEnable() { 35 | LOGO.split("\n").forEach { logger.log("&b$it".colored()) } 36 | logger.log("\t&6v${ZMusicConstants.PLUGIN_VERSION}\tby ZhenXin".colored()) 37 | logger.log("") 38 | logger.info("Initializing ZMusic...") 39 | logger.info("Initializing configuration...") 40 | initConfig() 41 | logger.info("Initializing i18n...") 42 | initI18n() 43 | logger.info("ZMusic is initialized.") 44 | 45 | I18n.Init.loaded.forEach { 46 | logger.info( 47 | it.replace("{version}", ZMusicConstants.PLUGIN_VERSION) 48 | .replace("{platform}", currentPlatform.name.lowercase()) 49 | .replace("{docs-url}", "zmusic.zhenxin.me") 50 | .replace("{author}", "ZhenXin") 51 | ) 52 | } 53 | 54 | thread { checkUpdate() } 55 | } 56 | 57 | /** 58 | * 插件禁用 59 | */ 60 | fun onDisable() { 61 | logger.info("Disabling ZMusic...") 62 | logger.info("ZMusic is disabled.") 63 | } 64 | } 65 | 66 | /** 67 | * 当前平台 68 | */ 69 | lateinit var currentPlatform: Platform 70 | 71 | /** 72 | * 插件数据文件夹 73 | */ 74 | lateinit var dataFolder: File 75 | 76 | /** 77 | * 日志 78 | */ 79 | lateinit var logger: PlatformLogger 80 | -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/config/Config.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.config 2 | 3 | import com.electronwill.nightconfig.core.CommentedConfig 4 | import com.electronwill.nightconfig.core.file.CommentedFileConfig 5 | import me.zhenxin.zmusic.ZMusic 6 | import me.zhenxin.zmusic.dataFolder 7 | import me.zhenxin.zmusic.logger 8 | import me.zhenxin.zmusic.utils.colored 9 | 10 | /** 11 | * 配置文件 12 | * 13 | * @author 真心 14 | * @since 2023/7/24 11:06 15 | */ 16 | object Config { 17 | /** 18 | * 配置文件版本 19 | */ 20 | val version: Int 21 | get() = config.get("version") ?: 0 22 | 23 | /** 24 | * 调试模式 25 | * 26 | */ 27 | val debug: Boolean 28 | get() = config.get("debug") ?: false 29 | 30 | /** 31 | * 检查更新 32 | */ 33 | val checkUpdate: Boolean 34 | get() = config.get("check-update") ?: true 35 | 36 | /** 37 | * 语言 38 | */ 39 | val language: String 40 | get() = config.get("language") ?: "auto" 41 | 42 | /** 43 | * 消息前缀 44 | */ 45 | val prefix: String 46 | get() = (config.get("prefix") ?: "&bZMusic &e>>> &r").colored() 47 | 48 | /** 49 | * 网易云音乐API 50 | */ 51 | val neteaseApi: String 52 | get() = config.get("api.netease-api") ?: "" 53 | 54 | /** VIP QQ 55 | * 56 | */ 57 | val vipQQ: String 58 | get() = config.get("vip.qq") ?: "" 59 | 60 | /** 61 | * VIP Key 62 | */ 63 | val vipKey: String 64 | get() = config.get("vip.key") ?: "" 65 | 66 | /** 67 | * 启用代理 68 | */ 69 | val proxyEnable: Boolean 70 | get() = config.get("proxy.enable") ?: false 71 | 72 | /** 73 | * 代理类型 74 | */ 75 | val proxyType: String 76 | get() = config.get("proxy.type") ?: "HTTP" 77 | 78 | /** 79 | * 代理主机 80 | */ 81 | val proxyHost: String 82 | get() = config.get("proxy.host") ?: "" 83 | 84 | /** 85 | * 代理端口 86 | */ 87 | val proxyPort: Int 88 | get() = config.get("proxy.port") ?: 0 89 | } 90 | 91 | fun initConfig() { 92 | val configPath = dataFolder.resolve("config.toml") 93 | if (!configPath.exists()) { 94 | val stream = ZMusic::class.java.getResourceAsStream("/config.toml") 95 | stream?.copyTo(configPath.outputStream()) 96 | } 97 | 98 | config = CommentedFileConfig.builder(configPath).autoreload().charset(Charsets.UTF_8).build() 99 | (config as CommentedFileConfig).load() 100 | 101 | val currentVersion = 15 102 | if (Config.version != currentVersion) { 103 | logger.error("Config is outdated, please delete the config file and restart the server.") 104 | // TODO: disablePlugin() 105 | return 106 | } else { 107 | logger.info("Configuration is initialized.") 108 | } 109 | } 110 | 111 | private var config: CommentedConfig = CommentedConfig.inMemory() -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/config/I18n.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.config 2 | 3 | import com.electronwill.nightconfig.core.CommentedConfig 4 | import com.electronwill.nightconfig.toml.TomlParser 5 | import me.zhenxin.zmusic.ZMusic 6 | import me.zhenxin.zmusic.logger 7 | import java.util.* 8 | 9 | /** 10 | * 国际化 11 | * 12 | * @author 真心 13 | * @since 2024/2/7 9:19 14 | */ 15 | object I18n { 16 | object Platform { 17 | val netease: String 18 | get() = i18n.get("platform.netease") ?: "{platform.netease}" 19 | 20 | val bilibili: String 21 | get() = i18n.get("platform.bilibili") ?: "{platform.bilibili}" 22 | } 23 | 24 | object Init { 25 | val loaded: List 26 | get() = i18n.get("init.loaded") ?: listOf("{init.loaded}") 27 | } 28 | 29 | object Update { 30 | val checking: String 31 | get() = i18n.get("update.checking") ?: "{update.checking}" 32 | 33 | val checkFailed: String 34 | get() = i18n.get("update.check_failed") ?: "{update.check_failed}" 35 | 36 | val available: List 37 | get() = i18n.get("update.available") ?: listOf("{update.available}") 38 | 39 | val notAvailable: String 40 | get() = i18n.get("update.not_available") ?: "{update.not_available}" 41 | } 42 | 43 | object Help { 44 | val tips: String 45 | get() = i18n.get("help.tips") ?: "" 46 | } 47 | } 48 | 49 | /** 50 | * 初始化国际化配置 51 | */ 52 | fun initI18n() { 53 | // 支持的语言 54 | val languages = arrayOf("en-US", "zh-CN") 55 | logger.debug("Supported languages: ${languages.joinToString()}") 56 | 57 | // 获取配置文件中的语言 58 | var language = Config.language 59 | 60 | // 如果是自动则使用系统语言 61 | if (language == "auto") { 62 | val locale = Locale.getDefault() 63 | language = "${locale.language}-${locale.country}" 64 | } 65 | 66 | // 如果不支持则使用默认语言 67 | if (language !in languages) { 68 | logger.warn("Language $language is not supported, use default language en-US.") 69 | logger.warn("Supported languages: ${languages.joinToString()}") 70 | language = "en-US" 71 | } 72 | 73 | // 从资源文件加载 74 | val classLoader = ZMusic::class.java.classLoader 75 | val file = classLoader.getResource("i18n/$language.toml") 76 | logger.debug("I18n file: $file") 77 | 78 | // 如果文件存在则加载, 否则禁用插件 79 | if (file != null) { 80 | val parser = TomlParser() 81 | i18n = parser.parse(file) 82 | logger.info("I18n is initialized.") 83 | } else { 84 | logger.error("Failed to load $language.toml, please check your plugin jar.") 85 | // TODO: disablePlugin() 86 | } 87 | } 88 | 89 | private lateinit var i18n: CommentedConfig -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/entity/VersionInfo.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.entity 2 | 3 | /** 4 | * 版本信息 5 | * 6 | * @author 真心 7 | * @since 2024/2/20 10:27 8 | */ 9 | data class VersionInfo( 10 | /** 11 | * 版本号 12 | */ 13 | var version: String = "", 14 | /** 15 | * 版本代码 16 | */ 17 | var versionCode: Long = 0, 18 | /** 19 | * 更新日志 20 | */ 21 | var changelog: String = "", 22 | /** 23 | * 下载链接 24 | */ 25 | var download: String = "" 26 | ) 27 | -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/enums/Platform.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.enums 2 | 3 | /** 4 | * 平台 5 | * 6 | * @author 真心 7 | * @since 2024/2/7 14:19 8 | */ 9 | @Suppress("SpellCheckingInspection") 10 | enum class Platform { 11 | BUKKIT, 12 | FOLIA, 13 | BUNGEE, 14 | VELOCITY 15 | } -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/exception/ZMusicException.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.exception 2 | 3 | /** 4 | * ZMusic 异常 5 | * 6 | * @author 真心 7 | * @since 2023/7/24 10:07 8 | */ 9 | class ZMusicException( 10 | override val message: String = "ZMusic Internal Exception" 11 | ) : RuntimeException() -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/platform/PlatformLogger.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.platform 2 | 3 | import me.zhenxin.zmusic.config.Config 4 | import me.zhenxin.zmusic.utils.colored 5 | 6 | /** 7 | * 日志 8 | * 9 | * @author 真心 10 | * @since 2023/7/22 20:08 11 | */ 12 | interface PlatformLogger { 13 | /** 14 | * 信息日志 15 | * 16 | * @param msg 日志信息 17 | */ 18 | fun info(msg: String) { 19 | val color = "&a".colored() 20 | val message = msg.colored() 21 | log("$color$message") 22 | } 23 | 24 | /** 25 | * 警告日志 26 | * 27 | * @param msg 日志信息 28 | */ 29 | fun warn(msg: String) { 30 | val color = "&e".colored() 31 | val message = msg.colored() 32 | log("$color$message") 33 | } 34 | 35 | /** 36 | * 错误日志 37 | * 38 | * @param msg 日志信息 39 | */ 40 | fun error(msg: String) { 41 | val color = "&c".colored() 42 | val message = msg.colored() 43 | log("$color$message") 44 | } 45 | 46 | /** 47 | * 调试日志 48 | * 49 | * @param msg 日志信息 50 | */ 51 | fun debug(msg: String) { 52 | if (Config.debug) { 53 | val color = "&b[Debug] ".colored() 54 | log("$color$msg") 55 | } 56 | } 57 | 58 | /** 59 | * 打印日志 60 | * 61 | * @param msg 日志信息 62 | */ 63 | fun log(msg: String) 64 | } 65 | -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/utils/Common.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.utils 2 | 3 | import me.zhenxin.zmusic.ZMusicConstants 4 | import me.zhenxin.zmusic.baseApi 5 | import me.zhenxin.zmusic.config.I18n 6 | import me.zhenxin.zmusic.entity.VersionInfo 7 | import me.zhenxin.zmusic.logger 8 | import org.dromara.hutool.json.JSONUtil 9 | 10 | /** 11 | * 公共工具 12 | * 13 | * @author 真心 14 | * @since 2023/7/23 15:49 15 | */ 16 | 17 | /** 18 | * 检查更新 19 | */ 20 | fun checkUpdate() { 21 | logger.info(I18n.Update.checking) 22 | val res = httpGet("${baseApi}/zmusic/version?type=dev") 23 | val json = JSONUtil.parseObj(res) 24 | if (json.getInt("code") != 200) { 25 | logger.error(I18n.Update.checkFailed) 26 | return 27 | } 28 | 29 | val data = json.getJSONObject("data") 30 | val versionInfo = JSONUtil.toBean(data, VersionInfo::class.java) 31 | 32 | logger.debug("Current version: ${ZMusicConstants.PLUGIN_VERSION}(${ZMusicConstants.PLUGIN_VERSION_CODE})") 33 | logger.debug("Latest version: ${versionInfo.version}(${versionInfo.versionCode})") 34 | 35 | if (versionInfo.versionCode > ZMusicConstants.PLUGIN_VERSION_CODE.toLong()) { 36 | I18n.Update.available.forEach { 37 | if (it.contains("{version}")) { 38 | logger.info(it.replace("{version}", versionInfo.version)) 39 | } else if (it.contains("{changelog}")) { 40 | versionInfo.changelog.split("\n").forEach { log -> 41 | logger.info("&b$log") 42 | } 43 | } else if (it.contains("{download}")) { 44 | logger.info(it.replace("{download}", versionInfo.download)) 45 | } else { 46 | logger.info(it) 47 | } 48 | } 49 | } else { 50 | logger.info(I18n.Update.notAvailable) 51 | } 52 | } -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/utils/Http.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.utils 2 | 3 | 4 | import me.zhenxin.zmusic.ZMusicConstants 5 | import me.zhenxin.zmusic.exception.ZMusicException 6 | import me.zhenxin.zmusic.logger 7 | import org.dromara.hutool.core.net.url.UrlBuilder 8 | import org.dromara.hutool.http.HttpUtil 9 | import org.dromara.hutool.http.client.Request 10 | import org.dromara.hutool.http.meta.Method 11 | import org.dromara.hutool.json.JSONObject 12 | 13 | /** 14 | * HTTP 工具 15 | * 16 | * @author 真心 17 | * @since 2023/7/24 10:04 18 | */ 19 | 20 | /** 21 | * GET 获取 22 | * @param url 链接 23 | * @param params 参数 24 | * @param headers 请求头 25 | * @param cache 是否使用缓存 26 | */ 27 | fun httpGet( 28 | url: String, 29 | params: Map = mapOf(), 30 | headers: Map = mapOf(), 31 | cache: Boolean = true 32 | ): String { 33 | val httpUrl = buildUrl(url, cache, params) 34 | val request = HttpUtil.createGet(httpUrl) 35 | return request(request, headers) 36 | } 37 | 38 | /** 39 | * POST 获取 40 | * @param url 连接 41 | * @param data 参数 42 | * @param headers 请求头 43 | * @param cache 是否使用缓存 44 | */ 45 | fun httpPost( 46 | url: String, 47 | data: JSONObject, 48 | headers: Map = mapOf(), 49 | cache: Boolean = true 50 | ): String { 51 | val httpUrl = buildUrl(url, cache) 52 | val request = HttpUtil.createPost(httpUrl).body(data.toString()) 53 | return request(request, headers) 54 | } 55 | 56 | private fun buildUrl(url: String, cache: Boolean, params: Map = mapOf()): String { 57 | val builder = UrlBuilder.ofHttp(url) 58 | params.map { 59 | builder.addQuery(it.key, it.value) 60 | } 61 | if (!cache) { 62 | builder.addQuery("t", System.currentTimeMillis()) 63 | } 64 | return builder.build() 65 | } 66 | 67 | private fun request(request: Request, headers: Map = mapOf()): String { 68 | request.header("User-Agent", "ZMusic/${ZMusicConstants.PLUGIN_VERSION}") 69 | headers.forEach { 70 | request.header(it.key, it.value) 71 | } 72 | logger.debug("Request: ${request.method()} ${request.url()}") 73 | if (request.method() == Method.POST) { 74 | logger.debug("Request body: ${request.body()}") 75 | } 76 | val response = request.send() 77 | val body = response.body().string 78 | if (!response.isOk) { 79 | logger.error("Request failed: ${response.status}, body: $body") 80 | throw ZMusicException("Request failed: ${response.status}, body: $body") 81 | } 82 | logger.debug("Response: $body") 83 | return body 84 | } -------------------------------------------------------------------------------- /zmusic-core/src/main/kotlin/me/zhenxin/zmusic/utils/String.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.utils 2 | 3 | /** 4 | * 字符串工具 5 | * 6 | * @author 真心 7 | * @since 2024/2/20 10:22 8 | */ 9 | 10 | /** 11 | * 对字符串进行颜色处理 12 | */ 13 | fun String.colored() = replace("&", "§") 14 | 15 | /** 16 | * 对字符串取消颜色处理 17 | */ 18 | fun String.uncolored() = replace("§", "&") 19 | -------------------------------------------------------------------------------- /zmusic-core/src/main/resources/config.toml: -------------------------------------------------------------------------------- 1 | ############################################### 2 | # ______ __ __ _ # 3 | # |___ / | \/ | (_) # 4 | # / / | \ / | _ _ ___ _ ___ # 5 | # / / | |\/| | | | | | / __| | | / __| # 6 | # / /__ | | | | | |_| | \__ \ | | | (__ # 7 | # /_____| |_| |_| \__,_| |___/ |_| \___| # 8 | # # 9 | ############################################### 10 | 11 | # 配置文件格式为: TOML (请按规范修改) 12 | # 规范说明请参考 https://toml.io/cn/ 13 | 14 | # 配置文件版本 (请勿修改) 15 | version = 15 16 | # 是否开启调试模式 17 | debug = false 18 | # 是否检查更新 19 | check-update = true 20 | # 插件使用的语言 21 | # 支持的语言: 22 | # auto: 自动检测 (默认) 23 | # en-US: English 24 | # zh-CN: 简体中文 25 | language = "auto" 26 | # 消息前缀 27 | prefix = "&bZMusic &e>>> &r" 28 | 29 | # API设置 30 | [api] 31 | # 网易云音乐 32 | netease-link = "https://zm.armoe.cn" 33 | 34 | # ZMusic VIP设置 35 | [vip] 36 | # 授权QQ 37 | qq = "" 38 | # 授权Key 39 | key = "" 40 | 41 | # 代理设置 42 | [proxy] 43 | # 是否启用代理 44 | enable = false 45 | # 代理类型 HTTP/SOCKS 46 | type = "HTTP" 47 | # 主机名 48 | hostname = "127.0.0.1" 49 | # 端口 50 | port = 8080 -------------------------------------------------------------------------------- /zmusic-core/src/main/resources/i18n/en-US.toml: -------------------------------------------------------------------------------- 1 | ############################################### 2 | # ______ __ __ _ # 3 | # |___ / | \/ | (_) # 4 | # / / | \ / | _ _ ___ _ ___ # 5 | # / / | |\/| | | | | | / __| | | / __| # 6 | # / /__ | | | | | |_| | \__ \ | | | (__ # 7 | # /_____| |_| |_| \__,_| |___/ |_| \___| # 8 | # # 9 | ############################################### 10 | 11 | # Language file format: TOML (Please modify according to the specification) 12 | # For specification, please refer to https://toml.io/en/ 13 | 14 | [platform] 15 | netease = "Netease Cloud Music" 16 | bilibili = "BiliBili" 17 | 18 | [init] 19 | loaded = [ 20 | "&aPlugin Loaded!", 21 | "&aVersion: &b{version}", 22 | "&aPlatform: &b{platform}", 23 | "&aDocs: &b{docs-url}", 24 | "&aAuthor: &b{author}", 25 | ] 26 | 27 | [update] 28 | checking = "&aChecking for updates..." 29 | checking_failed = "&cFailed to check for updates." 30 | available = [ 31 | "&aNew version available: &b{version}", 32 | "&aChangeLog:", 33 | "{changelog}", 34 | "&aDownload: &b{download}", 35 | ] 36 | not_available = "&aNo new version available." 37 | 38 | [help] 39 | tips = "&aInput &a/&e{command} &bhelp &aShow Help." 40 | -------------------------------------------------------------------------------- /zmusic-core/src/main/resources/i18n/zh-CN.toml: -------------------------------------------------------------------------------- 1 | ############################################### 2 | # ______ __ __ _ # 3 | # |___ / | \/ | (_) # 4 | # / / | \ / | _ _ ___ _ ___ # 5 | # / / | |\/| | | | | | / __| | | / __| # 6 | # / /__ | | | | | |_| | \__ \ | | | (__ # 7 | # /_____| |_| |_| \__,_| |___/ |_| \___| # 8 | # # 9 | ############################################### 10 | 11 | # 语言文件格式为: TOML (请按规范修改) 12 | # 规范说明请参考 https://toml.io/cn/ 13 | 14 | [platform] 15 | netease = "网易云音乐" 16 | bilibili = "哔哩哔哩" 17 | 18 | [init] 19 | loaded = [ 20 | "&a插件加载完毕!", 21 | "&a插件版本: &b{version}", 22 | "&a当前平台: &b{platform}", 23 | "&a使用文档: &b{docs-url}", 24 | "&a插件作者: &b{author}", 25 | ] 26 | 27 | [update] 28 | checking = "&a正在检查更新..." 29 | checking_failed = "&c检查更新失败!" 30 | available = [ 31 | "&a发现新版本: &b{version}", 32 | "&a更新日志:", 33 | "{changelog}", 34 | "&a下载地址: &b{download}", 35 | ] 36 | not_available = "&a当前已是最新版本!" 37 | 38 | [help] 39 | tips = "&a输入 &a/&e{command} &bhelp &a查看帮助." 40 | -------------------------------------------------------------------------------- /zmusic-runtime/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.time.LocalDateTime 2 | import java.time.format.DateTimeFormatter 3 | 4 | plugins { 5 | alias(libs.plugins.kyori.blossom) 6 | } 7 | 8 | dependencies { 9 | implementation(libs.lucko.jar.relocator) 10 | 11 | compileOnly(libs.google.guava) 12 | compileOnly(libs.google.gson) 13 | 14 | compileOnly(libs.bundles.maven.resolver) 15 | 16 | compileOnly(libs.netty.buffer) 17 | } 18 | 19 | sourceSets { 20 | main { 21 | blossom { 22 | javaSources { 23 | property("pluginVersion", project.version.toString()) 24 | property("pluginVersionCode", versionCode()) 25 | property("kotlinVersion", libs.versions.kotlin.get()) 26 | property("hutoolVersion", libs.versions.hutool.get()) 27 | property("nettyVersion", libs.versions.netty.get()) 28 | property("nightConfigVersion", libs.versions.nightconfig.get()) 29 | property("bstatsVersion", libs.versions.bstats.get()) 30 | } 31 | } 32 | } 33 | } 34 | 35 | tasks.shadowJar { 36 | enabled = false 37 | } 38 | 39 | fun versionCode(): String { 40 | val runId = System.getenv("GITHUB_RUN_ID") ?: "" 41 | if (runId.isNotEmpty()) { 42 | return runId 43 | } 44 | 45 | val time = LocalDateTime.now() 46 | val formatter = DateTimeFormatter.ofPattern("yyMMddHHmm") 47 | return time.format(formatter) 48 | } -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java-templates/me/zhenxin/zmusic/ZMusicConstants.java.peb: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic; 2 | 3 | /** 4 | * ZMusic 常量 5 | * 6 | * @author 真心 7 | * @since 2024/2/5 13:48 8 | */ 9 | @SuppressWarnings("SpellCheckingInspection") 10 | public class ZMusicConstants { 11 | /** 12 | * 插件版本 13 | */ 14 | public static final String PLUGIN_VERSION = "{{ pluginVersion }}"; 15 | 16 | /** 17 | * 插件版本代码 18 | */ 19 | public static final String PLUGIN_VERSION_CODE = "{{ pluginVersionCode }}"; 20 | 21 | /** 22 | * Kotlin 版本 23 | */ 24 | public static final String KOTLIN_VERSION = "{{ kotlinVersion }}"; 25 | 26 | /** 27 | * HuTool 版本 28 | */ 29 | public static final String HUTOOL_VERSION = "{{ hutoolVersion }}"; 30 | 31 | /** 32 | * Netty 版本 33 | */ 34 | public static final String NETTY_VERSION = "{{ nettyVersion }}"; 35 | 36 | /** 37 | * Night Config 版本 38 | */ 39 | public static final String NIGHT_CONFIG_VERSION = "{{ nightConfigVersion }}"; 40 | 41 | /** 42 | * bStats 版本 43 | */ 44 | public static final String BSTATS_VERSION = "{{ bstatsVersion }}"; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/ZMusicRuntime.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic; 2 | 3 | import me.zhenxin.zmusic.dependencies.RuntimeDependency; 4 | import me.zhenxin.zmusic.dependencies.RuntimeEnv; 5 | import me.zhenxin.zmusic.dependencies.common.RuntimeLogger; 6 | 7 | import static me.zhenxin.zmusic.dependencies.common.PrimitiveIO.t; 8 | 9 | /** 10 | * ZMusic 运行时依赖 11 | * 12 | * @author 真心 13 | * @since 2023/7/24 18:14 14 | */ 15 | @RuntimeDependency( 16 | value = "!org.dromara.hutool:hutool-core:" + ZMusicConstants.HUTOOL_VERSION, 17 | test = "!org.dromara.hutool.core.text.StrUtil" 18 | ) 19 | @RuntimeDependency( 20 | value = "!org.dromara.hutool:hutool-http:" + ZMusicConstants.HUTOOL_VERSION, 21 | test = "!org.dromara.hutool.http.HttpUtil" 22 | ) 23 | @RuntimeDependency( 24 | value = "!org.dromara.hutool:hutool-log:" + ZMusicConstants.HUTOOL_VERSION, 25 | test = "!org.dromara.hutool.log.LogUtil" 26 | ) 27 | @RuntimeDependency( 28 | value = "!org.dromara.hutool:hutool-json:" + ZMusicConstants.HUTOOL_VERSION, 29 | test = "!org.dromara.hutool.json.JSONUtil" 30 | ) 31 | @RuntimeDependency( 32 | value = "!io.netty:netty-buffer:" + ZMusicConstants.NETTY_VERSION, 33 | test = "!io.netty.buffer.ByteBuf" 34 | ) 35 | @RuntimeDependency( 36 | value = "!com.electronwill.night-config:core:" + ZMusicConstants.NIGHT_CONFIG_VERSION, 37 | test = "!com.electronwill.nightconfig.core.Config" 38 | ) 39 | @RuntimeDependency( 40 | value = "!com.electronwill.night-config:toml:" + ZMusicConstants.NIGHT_CONFIG_VERSION, 41 | test = "!com.electronwill.nightconfig.toml.TomlFormat" 42 | ) 43 | @RuntimeDependency( 44 | value = "!org.bstats:bstats-base:" + ZMusicConstants.BSTATS_VERSION, 45 | test = "!me.zhenxin.zmusic.library.bstats.MetricsBase", 46 | relocate = {"!org.bstats.", "!me.zhenxin.zmusic.library.bstats."} 47 | ) 48 | public class ZMusicRuntime { 49 | 50 | public static void setup(String dataFolder, Class... classes) { 51 | // 目录检测 52 | RuntimeLogger.info(t( 53 | "正在初始化运行时依赖,请稍等...", 54 | "Initializing runtime dependencies, please wait..." 55 | )); 56 | RuntimeEnv.ENV.runtimeInit(dataFolder); 57 | try { 58 | RuntimeEnv.ENV.inject(ZMusicRuntime.class); 59 | for (Class clazz : classes) { 60 | RuntimeEnv.ENV.inject(clazz); 61 | } 62 | } catch (Throwable t) { 63 | RuntimeLogger.warning(t( 64 | "加载运行时依赖失败,请检查运行环境!", 65 | "Failed to load runtime dependencies, please check the runtime environment!" 66 | )); 67 | //noinspection CallToPrintStackTrace 68 | t.printStackTrace(); 69 | } 70 | RuntimeLogger.info(t( 71 | "运行时依赖加载完成!", 72 | "Runtime dependencies loaded!" 73 | )); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/DependencyScope.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | /** 4 | * The scope of a dependency 5 | * 6 | * @author Zach Deibert, sky 7 | * @since 1.0.0 8 | */ 9 | public enum DependencyScope { 10 | 11 | /** 12 | * 依赖项在编译代码时需要,因此它将在运行时解析依赖项时下载。 13 | */ 14 | COMPILE, 15 | 16 | /** 17 | * 依赖项由运行时环境提供,因此在运行时解析依赖项时无需下载。 18 | */ 19 | PROVIDED, 20 | 21 | /** 22 | * 依赖项在应用程序运行时需要,因此它将在运行时解析依赖项时下载。 23 | */ 24 | RUNTIME, 25 | 26 | /** 27 | * 依赖项在编译和运行单元测试时需要,因此在运行时解析依赖项时无需下载。 28 | */ 29 | TEST, 30 | 31 | /** 32 | * 依赖项应该已经在系统上,因此在运行时解析依赖项时无需下载。 33 | */ 34 | SYSTEM, 35 | 36 | /** 37 | * 依赖项实际上只是一个 pom 而不是一个 jar,因此我们不需要下载它。 38 | */ 39 | IMPORT 40 | } 41 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/JarRelocation.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | import me.lucko.jarrelocator.Relocation; 4 | 5 | /** 6 | * TabooLib 7 | * taboolib.common.env.JarRelocation 8 | * 9 | * @author 坏黑 10 | * @since 2022/5/17 00:45 11 | */ 12 | public class JarRelocation { 13 | 14 | private final String pattern; 15 | private final String relocatedPattern; 16 | 17 | public JarRelocation(String pattern, String relocatedPattern) { 18 | this.pattern = pattern; 19 | this.relocatedPattern = relocatedPattern; 20 | } 21 | 22 | public String getPattern() { 23 | return pattern; 24 | } 25 | 26 | public String getRelocatedPattern() { 27 | return relocatedPattern; 28 | } 29 | 30 | public Relocation toRelocation() { 31 | return new Relocation(pattern, relocatedPattern); 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) { 37 | return true; 38 | } 39 | if (!(o instanceof JarRelocation)) { 40 | return false; 41 | } 42 | JarRelocation that = (JarRelocation) o; 43 | if (getPattern() != null ? !getPattern().equals(that.getPattern()) : that.getPattern() != null) { 44 | return false; 45 | } 46 | return getRelocatedPattern() != null ? getRelocatedPattern().equals(that.getRelocatedPattern()) : that.getRelocatedPattern() == null; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = getPattern() != null ? getPattern().hashCode() : 0; 52 | result = 31 * result + (getRelocatedPattern() != null ? getRelocatedPattern().hashCode() : 0); 53 | return result; 54 | } 55 | } -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/ParsedDependency.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | import java.util.List; 4 | 5 | public class ParsedDependency { 6 | 7 | /** 8 | * 依赖地址,格式为: 9 | * :[:[:]]: 10 | */ 11 | private final String value; 12 | 13 | /** 14 | * 测试类 15 | *

16 | * 17 | * test = "!org.bukkit.Bukkit" // 前面带个感叹号避免在编译时重定向 18 | * 19 | */ 20 | private final String test; 21 | 22 | /** 23 | * 仓库地址,留空默认使用 阿里云中央仓库 24 | */ 25 | private final String repository; 26 | 27 | /** 28 | * 是否进行依赖传递 29 | */ 30 | private final boolean transitive; 31 | 32 | /** 33 | * 忽略可选依赖 34 | */ 35 | private final boolean ignoreOptional; 36 | 37 | /** 38 | * 忽略加载异常 39 | */ 40 | private final boolean ignoreException; 41 | 42 | /** 43 | * 依赖范围 44 | */ 45 | private final List scopes; 46 | 47 | /** 48 | * 依赖重定向 49 | *

50 | * 51 | * relocate = ["!taboolib.", "!taboolib610."] // 同 test 参数 52 | * 53 | */ 54 | private final List relocate; 55 | 56 | /** 57 | * 是否外部库(不会被扫到) 58 | */ 59 | private final boolean external; 60 | 61 | public ParsedDependency(String value, String test, String repository, boolean transitive, boolean ignoreOptional, boolean ignoreException, List scopes, List relocate, boolean external) { 62 | this.value = value; 63 | this.test = test; 64 | this.repository = repository; 65 | this.transitive = transitive; 66 | this.ignoreOptional = ignoreOptional; 67 | this.ignoreException = ignoreException; 68 | this.scopes = scopes; 69 | this.relocate = relocate; 70 | this.external = external; 71 | } 72 | 73 | public String value() { 74 | return value; 75 | } 76 | 77 | public String test() { 78 | return test; 79 | } 80 | 81 | public String repository() { 82 | return repository; 83 | } 84 | 85 | public boolean transitive() { 86 | return transitive; 87 | } 88 | 89 | public boolean ignoreOptional() { 90 | return ignoreOptional; 91 | } 92 | 93 | public boolean ignoreException() { 94 | return ignoreException; 95 | } 96 | 97 | public List scopes() { 98 | return scopes; 99 | } 100 | 101 | public List relocate() { 102 | return relocate; 103 | } 104 | 105 | public boolean external() { 106 | return external; 107 | } 108 | 109 | @Override 110 | public String toString() { 111 | return "ParsedDependency{" + 112 | "value='" + value + '\'' + 113 | ", test='" + test + '\'' + 114 | ", repository='" + repository + '\'' + 115 | ", transitive=" + transitive + 116 | ", ignoreOptional=" + ignoreOptional + 117 | ", ignoreException=" + ignoreException + 118 | ", scopes=" + scopes + 119 | ", relocate=" + relocate + 120 | ", external=" + external + 121 | '}'; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeDependencies.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface RuntimeDependencies { 11 | 12 | RuntimeDependency[] value(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeDependency.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target(ElementType.TYPE) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Repeatable(RuntimeDependencies.class) 8 | public @interface RuntimeDependency { 9 | 10 | /** 11 | * 依赖地址,格式为: 12 | * :[:[:]]: 13 | */ 14 | String value(); 15 | 16 | /** 17 | * 测试类 18 | *

19 | * 20 | * test = "!org.bukkit.Bukkit" // 前面带个感叹号避免在编译时重定向 21 | * 22 | */ 23 | String test() default ""; 24 | 25 | /** 26 | * 仓库地址,留空默认使用 阿里云中央仓库 27 | */ 28 | String repository() default ""; 29 | 30 | /** 31 | * 是否进行依赖传递 32 | */ 33 | boolean transitive() default true; 34 | 35 | /** 36 | * 忽略可选依赖 37 | */ 38 | boolean ignoreOptional() default true; 39 | 40 | /** 41 | * 忽略加载异常 42 | */ 43 | boolean ignoreException() default false; 44 | 45 | /** 46 | * 依赖范围 47 | */ 48 | DependencyScope[] scopes() default {DependencyScope.RUNTIME, DependencyScope.COMPILE}; 49 | 50 | /** 51 | * 依赖重定向 52 | *

53 | * 54 | * relocate = ["!taboolib.", "!taboolib610."] // 同 test 参数 55 | * 56 | */ 57 | String[] relocate() default {}; 58 | 59 | /** 60 | * 是否外部库(不会被扫到) 61 | */ 62 | boolean external() default true; 63 | } -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeEnv.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static me.zhenxin.zmusic.ZMusicConstants.KOTLIN_VERSION; 10 | 11 | /** 12 | * TabooLib 13 | * taboolib.common.env.RuntimeEnv 14 | * 15 | * @author sky 16 | * @since 2021/6/15 6:23 下午 17 | */ 18 | public class RuntimeEnv { 19 | public static final RuntimeEnv ENV = new RuntimeEnv(); 20 | public static final RuntimeEnvDependency ENV_DEPENDENCY = new RuntimeEnvDependency(); 21 | 22 | public void runtimeInit(String dataFolder) { 23 | // 数据目录是否存在 24 | File dataFolderFile = new File(dataFolder); 25 | if (!dataFolderFile.exists()) { 26 | //noinspection ResultOfMethodCallIgnored 27 | dataFolderFile.mkdirs(); 28 | } 29 | // 设置默认库路径 30 | String defaultLibrary = ENV_DEPENDENCY.getDefaultLibrary(); 31 | ENV_DEPENDENCY.setDefaultLibrary(dataFolder + "/" + defaultLibrary); 32 | 33 | // 加载 Kotlin 环境 34 | try { 35 | List relocations = new ArrayList<>(); 36 | // Kotlin Relocation 37 | String kotlinId = "!kotlin".substring(1); 38 | String kotlinRelocationId = "!me.zhenxin.zmusic.library.kotlin".substring(1); 39 | relocations.add(new JarRelocation(kotlinId + ".", kotlinRelocationId + ".")); 40 | // Kotlin Dependency 41 | String kotlinStdlib = "org.jetbrains.kotlin:kotlin-stdlib:" + KOTLIN_VERSION; 42 | String kotlinStdlibJdk8 = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:" + KOTLIN_VERSION; 43 | ENV_DEPENDENCY.loadDependency(kotlinStdlib, false, relocations); 44 | ENV_DEPENDENCY.loadDependency(kotlinStdlibJdk8, false, relocations); 45 | } catch (Throwable e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | 50 | public void inject(@NotNull Class clazz) throws Throwable { 51 | ENV_DEPENDENCY.loadDependency(clazz); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeEnvDependency.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies; 2 | 3 | import me.zhenxin.zmusic.dependencies.aether.AetherResolver; 4 | import me.zhenxin.zmusic.dependencies.common.ClassAppender; 5 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO; 6 | import me.zhenxin.zmusic.dependencies.legacy.Artifact; 7 | import me.zhenxin.zmusic.dependencies.legacy.Dependency; 8 | import me.zhenxin.zmusic.dependencies.legacy.DependencyDownloader; 9 | import me.zhenxin.zmusic.dependencies.legacy.Repository; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.io.File; 14 | import java.net.URL; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | 21 | public class RuntimeEnvDependency { 22 | 23 | private static boolean isAetherFound; 24 | 25 | static { 26 | // 当服务端版本在 1.17+ 时,可借助服务端自带的 Aether 库完成依赖下载,兼容性更高。 27 | // 同时停止对 Legacy 的支持。 28 | try { 29 | Class.forName("org.eclipse.aether.graph.Dependency"); 30 | isAetherFound = true; 31 | } catch (ClassNotFoundException e) { 32 | isAetherFound = false; 33 | } 34 | // Mohist 直接不用 Aether 35 | try { 36 | Class.forName("com.mohistmc.MohistMC"); 37 | isAetherFound = false; 38 | } catch (ClassNotFoundException ignored) { 39 | } 40 | } 41 | 42 | private String defaultLibrary = "libraries"; 43 | 44 | public String getDefaultLibrary() { 45 | return defaultLibrary; 46 | } 47 | 48 | public void setDefaultLibrary(String library) { 49 | this.defaultLibrary = library; 50 | } 51 | 52 | public ParsedDependency parseDependency(RuntimeDependency dependency) { 53 | String value = dependency.value(); 54 | String test = dependency.test(); 55 | String repository = dependency.repository(); 56 | boolean transitive = dependency.transitive(); 57 | boolean ignoreOptional = dependency.ignoreOptional(); 58 | boolean ignoreException = dependency.ignoreException(); 59 | boolean external = dependency.external(); 60 | List scopes = new ArrayList<>(Arrays.asList(dependency.scopes())); 61 | List relocate = new ArrayList<>(Arrays.asList(dependency.relocate())); 62 | return new ParsedDependency(value, test, repository, transitive, ignoreOptional, ignoreException, scopes, relocate, external); 63 | } 64 | 65 | public List getDependency(@NotNull Class clazz) { 66 | List dependencyList = new ArrayList<>(); 67 | RuntimeDependency[] dependencies = null; 68 | if (clazz.isAnnotationPresent(RuntimeDependency.class)) { 69 | dependencies = clazz.getAnnotationsByType(RuntimeDependency.class); 70 | } else { 71 | RuntimeDependencies annotation = clazz.getAnnotation(RuntimeDependencies.class); 72 | if (annotation != null) { 73 | dependencies = annotation.value(); 74 | } 75 | } 76 | if (dependencies != null) { 77 | for (RuntimeDependency dependency : dependencies) { 78 | ParsedDependency parsedDependency = parseDependency(dependency); 79 | if (parsedDependency != null) { 80 | dependencyList.add(parsedDependency); 81 | } 82 | } 83 | return dependencyList; 84 | } else { 85 | return Collections.emptyList(); 86 | } 87 | } 88 | 89 | public void loadDependency(@NotNull Class clazz) throws Throwable { 90 | List dependencies = getDependency(clazz); 91 | if (dependencies != null) { 92 | File baseFile = new File(defaultLibrary); 93 | for (ParsedDependency dep : dependencies) { 94 | String allTest = dep.test(); 95 | List tests = new ArrayList<>(); 96 | if (allTest.contains(",")) { 97 | tests.addAll(Arrays.asList(allTest.split(","))); 98 | } else { 99 | tests.add(allTest); 100 | } 101 | if (!tests.isEmpty() && tests.stream().allMatch(this::test)) { 102 | continue; 103 | } 104 | List relocation = new ArrayList<>(); 105 | List relocate = dep.relocate(); 106 | if (relocate.size() % 2 != 0) { 107 | throw new IllegalStateException("invalid relocate format"); 108 | } 109 | for (int i = 0; i + 1 < relocate.size(); i += 2) { 110 | String from = relocate.get(i); 111 | String to = relocate.get(i + 1); 112 | // 移除前缀 113 | if (from.startsWith("!")) { 114 | from = from.substring(1); 115 | } 116 | if (to.startsWith("!")) { 117 | to = to.substring(1); 118 | } 119 | relocation.add(new JarRelocation(from, to)); 120 | } 121 | String url = dep.value().startsWith("!") ? dep.value().substring(1) : dep.value(); 122 | loadDependency(url, baseFile, relocation, dep.repository(), dep.ignoreOptional(), dep.ignoreException(), dep.transitive(), dep.scopes(), dep.external()); 123 | } 124 | } 125 | } 126 | 127 | public void loadDependency(@NotNull String url) throws Throwable { 128 | loadDependency(url, new File(defaultLibrary)); 129 | } 130 | 131 | public void loadDependency(@NotNull String url, @Nullable String repository) throws Throwable { 132 | loadDependency(url, new File(defaultLibrary), repository); 133 | } 134 | 135 | public void loadDependency(@NotNull String url, @NotNull List relocation) throws Throwable { 136 | loadDependency(url, new File(defaultLibrary), relocation, null, true, false, true, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE)); 137 | } 138 | 139 | public void loadDependency(@NotNull String url, @NotNull List relocation, @Nullable String repository) throws Throwable { 140 | loadDependency(url, new File(defaultLibrary), relocation, repository, true, false, true, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE)); 141 | } 142 | 143 | public void loadDependency(@NotNull String url, boolean transitive, @NotNull List relocation) throws Throwable { 144 | loadDependency(url, new File(defaultLibrary), relocation, null, true, false, transitive, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE)); 145 | } 146 | 147 | public void loadDependency(@NotNull String url, boolean transitive, @NotNull List relocation, @Nullable String repository) throws Throwable { 148 | loadDependency(url, new File(defaultLibrary), relocation, repository, true, false, transitive, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE)); 149 | } 150 | 151 | public void loadDependency(@NotNull String url, @NotNull File baseDir) throws Throwable { 152 | loadDependency(url, baseDir, null); 153 | } 154 | 155 | public void loadDependency(@NotNull String url, @NotNull File baseDir, @Nullable String repository) throws Throwable { 156 | loadDependency(url, baseDir, new ArrayList<>(), repository, true, false, true, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE)); 157 | } 158 | 159 | public void loadDependency( 160 | @NotNull String url, 161 | @NotNull File baseDir, 162 | @NotNull List relocation, 163 | @Nullable String repository, 164 | boolean ignoreOptional, 165 | boolean ignoreException, 166 | boolean transitive, 167 | @NotNull List scope 168 | ) throws Throwable { 169 | loadDependency(url, baseDir, relocation, repository, ignoreOptional, ignoreException, transitive, scope, true); 170 | } 171 | 172 | public void loadDependency( 173 | @NotNull String url, 174 | @NotNull File baseDir, 175 | @NotNull List relocation, 176 | @Nullable String repository, 177 | boolean ignoreOptional, 178 | boolean ignoreException, 179 | boolean transitive, 180 | @NotNull List scope, 181 | boolean external 182 | ) throws Throwable { 183 | if (repository == null || repository.isEmpty()) { 184 | repository = "https://maven.aliyun.com/repository/central"; 185 | } 186 | // 使用 Aether 处理依赖 187 | if (isAetherFound) { 188 | AetherResolver.of(repository).resolve(url, scope, transitive, ignoreOptional).forEach(file -> { 189 | try { 190 | AetherResolver.inject(file, relocation, external); 191 | } catch (Throwable ex) { 192 | if (!ignoreException) { 193 | //noinspection CallToPrintStackTrace 194 | ex.printStackTrace(); 195 | } 196 | } 197 | }); 198 | } else { 199 | loadDependencyLegacy(url, baseDir, relocation, repository, ignoreOptional, ignoreException, transitive, scope, external); 200 | } 201 | } 202 | 203 | void loadDependencyLegacy( 204 | @NotNull String url, 205 | @NotNull File baseDir, 206 | @NotNull List relocation, 207 | String repository, 208 | boolean ignoreOptional, 209 | boolean ignoreException, 210 | boolean transitive, 211 | @NotNull List scope, 212 | boolean external 213 | ) throws Throwable { 214 | Artifact artifact = new Artifact(url); 215 | DependencyDownloader downloader = new DependencyDownloader(baseDir, relocation); 216 | downloader.addRepository(new Repository(repository)); 217 | downloader.setIgnoreOptional(ignoreOptional); 218 | downloader.setIgnoreException(ignoreException); 219 | downloader.setDependencyScopes(scope); 220 | downloader.setTransitive(transitive); 221 | // 解析依赖 222 | String pomPath = String.format( 223 | "%s/%s/%s/%s-%s.pom", 224 | artifact.getGroupId().replace('.', '/'), 225 | artifact.getArtifactId(), 226 | artifact.getVersion(), 227 | artifact.getArtifactId(), 228 | artifact.getVersion() 229 | ); 230 | File pomFile = new File(baseDir, pomPath); 231 | File pomFile1 = new File(pomFile.getPath() + ".sha1"); 232 | // 验证文件完整性 233 | if (PrimitiveIO.validation(pomFile, pomFile1)) { 234 | downloader.loadDependencyFromInputStream(pomFile.toPath().toUri().toURL().openStream()); 235 | } else { 236 | downloader.loadDependencyFromInputStream(new URL(repository + "/" + pomPath).openStream()); 237 | } 238 | // 加载自身 239 | Dependency dep = new Dependency(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), DependencyScope.RUNTIME); 240 | dep.setType(artifact.getExtension()); 241 | dep.setExternal(external); 242 | if (transitive) { 243 | downloader.injectClasspath(downloader.loadDependency(downloader.getRepositories(), dep)); 244 | } else { 245 | downloader.injectClasspath(Collections.singleton(dep)); 246 | } 247 | } 248 | 249 | boolean test(String path) { 250 | String test = path.startsWith("!") ? path.substring(1) : path; 251 | return !test.isEmpty() && ClassAppender.isExists(test); 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/aether/AetherResolver.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.aether; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.google.common.collect.Sets; 5 | import me.lucko.jarrelocator.JarRelocator; 6 | import me.lucko.jarrelocator.Relocation; 7 | import me.zhenxin.zmusic.dependencies.DependencyScope; 8 | import me.zhenxin.zmusic.dependencies.JarRelocation; 9 | import me.zhenxin.zmusic.dependencies.common.ClassAppender; 10 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO; 11 | import org.apache.maven.repository.internal.MavenRepositorySystemUtils; 12 | import org.eclipse.aether.DefaultRepositorySystemSession; 13 | import org.eclipse.aether.RepositorySystem; 14 | import org.eclipse.aether.artifact.DefaultArtifact; 15 | import org.eclipse.aether.collection.CollectRequest; 16 | import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; 17 | import org.eclipse.aether.graph.Dependency; 18 | import org.eclipse.aether.graph.DependencyFilter; 19 | import org.eclipse.aether.graph.DependencyNode; 20 | import org.eclipse.aether.impl.DefaultServiceLocator; 21 | import org.eclipse.aether.repository.LocalRepository; 22 | import org.eclipse.aether.repository.RemoteRepository; 23 | import org.eclipse.aether.resolution.DependencyRequest; 24 | import org.eclipse.aether.resolution.DependencyResolutionException; 25 | import org.eclipse.aether.resolution.DependencyResult; 26 | import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; 27 | import org.eclipse.aether.spi.connector.transport.TransporterFactory; 28 | import org.eclipse.aether.transport.http.HttpTransporterFactory; 29 | import org.jetbrains.annotations.NotNull; 30 | import org.jetbrains.annotations.Nullable; 31 | 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.util.Collections; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.Set; 38 | import java.util.stream.Collectors; 39 | 40 | /** 41 | * @author md_5, sky 42 | * @since 2024/7/20 20:31 43 | */ 44 | @SuppressWarnings("deprecation") 45 | 46 | public class AetherResolver { 47 | 48 | private static final Map RESOLVER_MAP = Maps.newConcurrentMap(); 49 | private static final Set INJECTED_DEPENDENCIES = Sets.newConcurrentHashSet(); 50 | 51 | private final RepositorySystem repository; 52 | private final DefaultRepositorySystemSession session; 53 | private final List repositories; 54 | 55 | public AetherResolver(String repo) { 56 | DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); 57 | locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); 58 | locator.addService(TransporterFactory.class, HttpTransporterFactory.class); 59 | this.repository = locator.getService(RepositorySystem.class); 60 | this.session = MavenRepositorySystemUtils.newSession(); 61 | this.session.setChecksumPolicy("fail"); 62 | this.session.setLocalRepositoryManager(this.repository.newLocalRepositoryManager(this.session, new LocalRepository("libraries"))); 63 | this.session.setReadOnly(); 64 | this.repositories = this.repository.newResolutionRepositories(this.session, Collections.singletonList( 65 | new RemoteRepository.Builder("central", "default", repo).build() 66 | )); 67 | } 68 | 69 | public static AetherResolver of(@NotNull String repository) { 70 | return RESOLVER_MAP.computeIfAbsent(repository, AetherResolver::new); 71 | } 72 | 73 | @SuppressWarnings("DuplicatedCode") 74 | public static void inject(@NotNull File file, @Nullable List relocation, boolean isExternal) throws Throwable { 75 | // 避免重复加载多个依赖 76 | if (INJECTED_DEPENDENCIES.contains(file.getPath())) { 77 | return; 78 | } else { 79 | INJECTED_DEPENDENCIES.add(file.getParent()); 80 | } 81 | // 如果没有重定向规则,直接注入 82 | if (relocation == null || relocation.isEmpty()) { 83 | ClassAppender.addPath(file.toPath(), isExternal); 84 | } else { 85 | // 获取重定向后的文件 86 | String name = file.getName().substring(0, file.getName().lastIndexOf('.')); 87 | File rel = new File(file.getParentFile(), name + "_r2_" + Math.abs(relocation.hashCode()) + ".jar"); 88 | // 如果文件不存在或者文件大小为 0,就执行重定向逻辑 89 | if (!rel.exists() || rel.length() == 0) { 90 | try { 91 | // 获取重定向规则 92 | List rules = relocation.stream().map(JarRelocation::toRelocation).collect(Collectors.toList()); 93 | // 获取临时文件 94 | File tempSourceFile = PrimitiveIO.copyFile(file, File.createTempFile(file.getName(), ".jar")); 95 | // 运行 96 | new JarRelocator(tempSourceFile, rel, rules).run(); 97 | } catch (IOException e) { 98 | throw new IllegalStateException(String.format("Unable to relocate %s%n", file), e); 99 | } 100 | } 101 | // 注入重定向后的文件 102 | ClassAppender.addPath(rel.toPath(), isExternal); 103 | } 104 | } 105 | 106 | public List resolve(@NotNull String library, List scope, boolean isTransitive, boolean ignoreOptional) throws DependencyResolutionException { 107 | Dependency dependency = new Dependency(new DefaultArtifact(library), null); 108 | DependencyResult result; 109 | DependencyRequest dependencyRequest = getDependencyRequest(dependency, scope, isTransitive, ignoreOptional); 110 | result = this.repository.resolveDependencies(this.session, dependencyRequest); 111 | return result.getArtifactResults().stream().map(it -> it.getArtifact().getFile()).collect(Collectors.toList()); 112 | } 113 | 114 | private @NotNull DependencyRequest getDependencyRequest(Dependency dependency, List scope, boolean isTransitive, boolean ignoreOptional) { 115 | return new DependencyRequest(new CollectRequest(dependency, null, repositories), new DependencyFilter() { 116 | boolean self = true; 117 | 118 | @Override 119 | public boolean accept(DependencyNode node, List parents) { 120 | // 忽略可选 121 | if (ignoreOptional && node.getDependency().isOptional()) { 122 | return false; 123 | } 124 | // 依赖传递 125 | if (isTransitive) { 126 | return true; 127 | } 128 | if (self) { 129 | self = false; 130 | return true; 131 | } 132 | return false; 133 | } 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/common/ClassAppender.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.common; 2 | 3 | import me.zhenxin.zmusic.ZMusicRuntime; 4 | import sun.misc.Unsafe; 5 | 6 | import java.io.File; 7 | import java.lang.invoke.MethodHandle; 8 | import java.lang.invoke.MethodHandles; 9 | import java.lang.invoke.MethodType; 10 | import java.lang.reflect.Field; 11 | import java.net.URL; 12 | import java.net.URLClassLoader; 13 | import java.nio.file.Path; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import static me.zhenxin.zmusic.dependencies.common.PrimitiveIO.t; 18 | 19 | /** 20 | * @author sky 21 | * @since 2020-04-12 22:39 22 | */ 23 | public class ClassAppender { 24 | 25 | final static List CALLBACKS = new ArrayList<>(); 26 | static MethodHandles.Lookup lookup; 27 | static Unsafe unsafe; 28 | 29 | static { 30 | try { 31 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 32 | field.setAccessible(true); 33 | unsafe = (Unsafe) field.get(null); 34 | Field lookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); 35 | Object lookupBase = unsafe.staticFieldBase(lookupField); 36 | long lookupOffset = unsafe.staticFieldOffset(lookupField); 37 | lookup = (MethodHandles.Lookup) unsafe.getObject(lookupBase, lookupOffset); 38 | // 如果第二个 IMPL_LOOKUP 没有找到,提示无法加载 39 | if (lookup == null) { 40 | RuntimeLogger.warning(t( 41 | "未能找到 Unsafe lookup,ZMusic 将无法正常工作。", 42 | "Unsafe lookup not found, ZMusic will not work properly." 43 | )); 44 | } 45 | } catch (Throwable ignored) { 46 | } 47 | } 48 | 49 | /** 50 | * 加载一个文件到 ClassLoader 51 | * 52 | * @param path 路径 53 | * @param isExternal 是否外部库(不加入 loadedClasses) 54 | */ 55 | public static ClassLoader addPath(Path path, boolean isExternal) throws Throwable { 56 | File file = new File(path.toUri().getPath()); 57 | ClassLoader loader = ZMusicRuntime.class.getClassLoader(); 58 | // Application 59 | if ("AppClassLoader".equals(loader.getClass().getSimpleName())) { 60 | addUrl(loader, ucp(loader.getClass()), file, isExternal); 61 | } 62 | // Hybrid 63 | else if ("net.minecraft.launchwrapper.LaunchClassLoader".equals(loader.getClass().getName())) { 64 | MethodHandle methodHandle = lookup.findVirtual(URLClassLoader.class, "addURL", MethodType.methodType(void.class, java.net.URL.class)); 65 | methodHandle.invoke(loader, file.toURI().toURL()); 66 | } 67 | // Bukkit 68 | else { 69 | addUrl(loader, ucp(loader), file, isExternal); 70 | } 71 | return loader; 72 | } 73 | 74 | /** 75 | * 获取 addPath 函数所使用的 ClassLoader(原函数为:judgeAddPathClassLoader) 76 | */ 77 | public static ClassLoader getClassLoader() { 78 | return ZMusicRuntime.class.getClassLoader(); 79 | } 80 | 81 | /** 82 | * 判断类是否粗在 83 | */ 84 | public static boolean isExists(String path) { 85 | try { 86 | Class.forName(path, false, getClassLoader()); 87 | return true; 88 | } catch (ClassNotFoundException ignored) { 89 | return false; 90 | } 91 | } 92 | 93 | private static void addUrl(ClassLoader loader, Field ucpField, File file, boolean isExternal) throws Throwable { 94 | if (ucpField == null) { 95 | throw new IllegalStateException("ucp field not found"); 96 | } 97 | if (lookup == null) { 98 | throw new IllegalStateException("lookup not found"); 99 | } 100 | Object ucp = unsafe.getObject(loader, unsafe.objectFieldOffset(ucpField)); 101 | try { 102 | MethodHandle methodHandle = lookup.findVirtual(ucp.getClass(), "addURL", MethodType.methodType(void.class, URL.class)); 103 | methodHandle.invoke(ucp, file.toURI().toURL()); 104 | for (Callback i : CALLBACKS) { 105 | i.add(loader, file, isExternal); 106 | } 107 | } catch (NoSuchMethodError e) { 108 | throw new IllegalStateException("Unsupported (classloader: " + loader.getClass().getName() + ", ucp: " + ucp.getClass().getName() + ")", e); 109 | } 110 | } 111 | 112 | private static Field ucp(ClassLoader loader) { 113 | try { 114 | return URLClassLoader.class.getDeclaredField("ucp"); 115 | } catch (NoSuchFieldError | NoSuchFieldException ignored) { 116 | return ucp(loader.getClass()); 117 | } 118 | } 119 | 120 | private static Field ucp(Class loader) { 121 | try { 122 | return loader.getDeclaredField("ucp"); 123 | } catch (NoSuchFieldError | NoSuchFieldException e2) { 124 | Class superclass = loader.getSuperclass(); 125 | if (superclass == Object.class) { 126 | return null; 127 | } 128 | return ucp(superclass); 129 | } 130 | } 131 | 132 | public static void registerCallback(Callback callback) { 133 | CALLBACKS.add(callback); 134 | } 135 | 136 | public interface Callback { 137 | 138 | void add(ClassLoader loader, File file, boolean isExternal); 139 | } 140 | } -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/common/PrimitiveIO.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.common; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.io.*; 6 | import java.net.URL; 7 | import java.nio.channels.FileChannel; 8 | import java.nio.charset.Charset; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Files; 11 | import java.security.MessageDigest; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.util.Locale; 14 | import java.util.UUID; 15 | 16 | /** 17 | * TabooLib 18 | * taboolib.common.env.IO 19 | * 20 | * @author 坏黑 21 | * @since 2023/3/31 14:59 22 | */ 23 | @SuppressWarnings("CallToPrintStackTrace") 24 | public class PrimitiveIO { 25 | private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); 26 | private static final int BUFFER_SIZE = 8192; 27 | private static final ThreadLocal DIGEST_THREAD_LOCAL = ThreadLocal.withInitial(() -> { 28 | try { 29 | return MessageDigest.getInstance("SHA-1"); 30 | } catch (NoSuchAlgorithmException e) { 31 | throw new RuntimeException(e); 32 | } 33 | }); 34 | 35 | /** 36 | * 是否为中文环境 37 | * 如果在获取的时候发生异常,默认视为中文环境 38 | */ 39 | private static boolean isChineseEnvironment = true; 40 | 41 | static { 42 | // 获取语言环境 43 | try { 44 | isChineseEnvironment = Locale.getDefault().toLanguageTag().startsWith("zh"); 45 | } catch (Throwable ignored) { 46 | } 47 | } 48 | 49 | /** 50 | * 验证文件完整性 51 | * 52 | * @param file 文件 53 | * @param hashFile 哈希文件 54 | */ 55 | public static boolean validation(File file, File hashFile) { 56 | return file.exists() && hashFile.exists() && PrimitiveIO.readFile(hashFile).startsWith(PrimitiveIO.getHash(file)); 57 | } 58 | 59 | /** 60 | * 获取文件哈希,使用 sha-1 算法 61 | */ 62 | @NotNull 63 | public static String getHash(File file) { 64 | MessageDigest digest = DIGEST_THREAD_LOCAL.get(); 65 | digest.reset(); // Ensure the MessageDigest is reset before each use 66 | try (InputStream inputStream = Files.newInputStream(file.toPath())) { 67 | byte[] buffer = new byte[BUFFER_SIZE]; 68 | int total; 69 | while ((total = inputStream.read(buffer)) != -1) { 70 | digest.update(buffer, 0, total); 71 | } 72 | byte[] hashBytes = digest.digest(); 73 | return bytesToHex(hashBytes); 74 | } catch (IOException ex) { 75 | ex.printStackTrace(); 76 | } 77 | return "null (" + UUID.randomUUID() + ")"; 78 | } 79 | 80 | public static String bytesToHex(byte[] bytes) { 81 | char[] hexChars = new char[bytes.length * 2]; 82 | for (int j = 0; j < bytes.length; j++) { 83 | int v = bytes[j] & 0xFF; 84 | hexChars[j * 2] = HEX_ARRAY[v >>> 4]; 85 | hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; 86 | } 87 | return new String(hexChars); 88 | } 89 | 90 | /** 91 | * 读取文件内容 92 | */ 93 | @NotNull 94 | public static String readFile(File file) { 95 | try (FileInputStream fileInputStream = new FileInputStream(file)) { 96 | return readFully(fileInputStream, StandardCharsets.UTF_8); 97 | } catch (IOException e) { 98 | e.printStackTrace(); 99 | } 100 | return "null (" + UUID.randomUUID() + ")"; 101 | } 102 | 103 | /** 104 | * 从 InputStream 读取全部内容 105 | * 106 | * @param inputStream 输入流 107 | * @param charset 编码 108 | */ 109 | @NotNull 110 | public static String readFully(InputStream inputStream, Charset charset) throws IOException { 111 | return new String(readFully(inputStream), charset); 112 | } 113 | 114 | /** 115 | * 从 InputStream 读取全部内容 116 | * 117 | * @param inputStream 输入流 118 | */ 119 | public static byte[] readFully(InputStream inputStream) throws IOException { 120 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 121 | byte[] buf = new byte[BUFFER_SIZE]; 122 | int len; 123 | while ((len = inputStream.read(buf)) > 0) { 124 | stream.write(buf, 0, len); 125 | } 126 | return stream.toByteArray(); 127 | } 128 | 129 | /** 130 | * 通过 FileChannel 复制文件 131 | */ 132 | @NotNull 133 | public static File copyFile(File from, File to) { 134 | try (FileInputStream fileIn = new FileInputStream(from); FileOutputStream fileOut = new FileOutputStream(to); FileChannel channelIn = fileIn.getChannel(); FileChannel channelOut = fileOut.getChannel()) { 135 | channelIn.transferTo(0, channelIn.size(), channelOut); 136 | } catch (IOException t) { 137 | t.printStackTrace(); 138 | } 139 | return to; 140 | } 141 | 142 | /** 143 | * 下载文件 144 | * 145 | * @param url 地址 146 | * @param out 目标文件 147 | */ 148 | @SuppressWarnings("StatementWithEmptyBody") 149 | public static void downloadFile(URL url, File out) throws IOException { 150 | //noinspection ResultOfMethodCallIgnored 151 | out.getParentFile().mkdirs(); 152 | InputStream ins = url.openStream(); 153 | OutputStream outs = Files.newOutputStream(out.toPath()); 154 | byte[] buffer = new byte[BUFFER_SIZE]; 155 | for (int len; (len = ins.read(buffer)) > 0; outs.write(buffer, 0, len)) { 156 | 157 | } 158 | outs.close(); 159 | ins.close(); 160 | } 161 | 162 | /** 163 | * 针对中文环境进行特殊适配,以支持在中文环境中输出本土化的提示信息。 164 | * 其他语言环境均输出英文。 165 | */ 166 | public static String t(String zh, String en) { 167 | if (isChineseEnvironment) { 168 | return zh; 169 | } else { 170 | return en; 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/common/RuntimeLogger.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.common; 2 | 3 | import java.util.logging.Level; 4 | 5 | /** 6 | * @author zhenxin 7 | */ 8 | public class RuntimeLogger { 9 | 10 | /** 11 | * 日志对象 12 | */ 13 | private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger("ZMusic"); 14 | 15 | /** 16 | * 输出 Info 日志 17 | * 18 | * @param message 日志内容 19 | */ 20 | public static void info(String message, Object... args) { 21 | LOGGER.log(Level.INFO, message, args); 22 | } 23 | 24 | /** 25 | * 输出 Warning 日志 26 | * 27 | * @param message 日志内容 28 | */ 29 | public static void warning(String message, Object... args) { 30 | LOGGER.log(Level.WARNING, message, args); 31 | } 32 | 33 | /** 34 | * 输出 Error 日志 35 | * 36 | * @param message 日志内容 37 | */ 38 | public static void error(String message, Object... args) { 39 | LOGGER.log(Level.SEVERE, message, args); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/AbstractXmlParser.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.w3c.dom.Element; 5 | import org.w3c.dom.Node; 6 | import org.w3c.dom.NodeList; 7 | 8 | import java.text.ParseException; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Base class for any class that needs to do XML parsing 14 | * 15 | * @author Zach Deibert, sky 16 | * @since 1.0.0 17 | */ 18 | public abstract class AbstractXmlParser { 19 | 20 | /** 21 | * The pattern to use to detect when a variable should be substituted in the 22 | * pom 23 | * 24 | * @since 1.0.0 25 | */ 26 | private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{([^}]+)}"); 27 | 28 | /** 29 | * Gets the replacement value for a substitution variable 30 | * 31 | * @param key The key of the variable 32 | * @param pom The pom document 33 | * @return The value that it should be replaced with 34 | * @throws ParseException If the variable could not be resolved 35 | * @since 1.0.0 36 | */ 37 | @NotNull 38 | private static String getReplacement(String key, Element pom) throws ParseException { 39 | if (key.startsWith("project.")) { 40 | return find(key.substring("project.".length()), pom); 41 | } else if (key.startsWith("pom.")) { 42 | return find(key.substring("pom.".length()), pom); 43 | } else { 44 | throw new ParseException(String.format("Unknown variable '%s'", key), -1); 45 | } 46 | } 47 | 48 | /** 49 | * Replaces all the variables in a string of text 50 | * 51 | * @param text The text to replace the variables in 52 | * @param pom The pom document 53 | * @return The text with all the variables replaced 54 | * @throws ParseException If the variable could not be resolved 55 | * @since 1.0.0 56 | */ 57 | @NotNull 58 | private static String replaceVariables(String text, Element pom) throws ParseException { 59 | Matcher matcher = SUBSTITUTION_PATTERN.matcher(text); 60 | while (matcher.find()) { 61 | text = matcher.replaceFirst(getReplacement(matcher.group(1), pom)); 62 | } 63 | return text; 64 | } 65 | 66 | 67 | @NotNull 68 | protected static String find(String name, Element node) throws ParseException { 69 | return find(name, node, null); 70 | } 71 | 72 | 73 | /** 74 | * Searches for a node and returns the text inside of it 75 | * 76 | * @param name The name of the node to search for 77 | * @param node The node to search inside of 78 | * @param def The default value, or null if the value is required 79 | * @return The text content of the node it found, or def if the node is not found 80 | * @throws ParseException If the node cannot be found and there is no default value 81 | * @since 1.0.0 82 | */ 83 | @NotNull 84 | protected static String find(String name, Element node, String def) throws ParseException { 85 | NodeList list = node.getChildNodes(); 86 | for (int i = 0; i < list.getLength(); ++i) { 87 | Node n = list.item(i); 88 | if (n.getNodeName().equals(name)) { 89 | try { 90 | return replaceVariables(n.getTextContent(), node.getOwnerDocument().getDocumentElement()); 91 | } catch (ParseException ex) { 92 | if (def == null) { 93 | throw ex; 94 | } else { 95 | return def; 96 | } 97 | } 98 | } 99 | } 100 | list = node.getElementsByTagName(name); 101 | if (list.getLength() > 0) { 102 | try { 103 | return replaceVariables(list.item(0).getTextContent(), node.getOwnerDocument().getDocumentElement()); 104 | } catch (ParseException ex) { 105 | if (def == null) { 106 | throw ex; 107 | } else { 108 | return def; 109 | } 110 | } 111 | } 112 | if (def == null) { 113 | throw new ParseException(String.format("Unable to find required tag '%s' in node", name), -1); 114 | } else { 115 | return def; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/Artifact.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * TabooLib 10 | * taboolib.common.env.legacy.Artifact 11 | * 12 | * @author 坏黑 13 | * @since 2024/7/20 22:24 14 | */ 15 | public class Artifact { 16 | 17 | private static final Pattern COORDINATE_PATTERN = Pattern.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)"); 18 | private final String groupId; 19 | private final String artifactId; 20 | private final String version; 21 | private final String classifier; 22 | private final String extension; 23 | 24 | public Artifact(String coords) { 25 | this(coords, Collections.emptyMap()); 26 | } 27 | 28 | public Artifact(String coords, Map properties) { 29 | Matcher m = COORDINATE_PATTERN.matcher(coords); 30 | if (!m.matches()) { 31 | throw new IllegalArgumentException("Bad artifact coordinates " + coords + ", expected format is :[:[:]]:"); 32 | } else { 33 | this.groupId = m.group(1); 34 | this.artifactId = m.group(2); 35 | this.extension = get(m.group(4), "jar"); 36 | this.classifier = get(m.group(6), ""); 37 | this.version = m.group(7); 38 | } 39 | } 40 | 41 | static String get(String value, String defaultValue) { 42 | return value != null && !value.isEmpty() ? value : defaultValue; 43 | } 44 | 45 | public String getGroupId() { 46 | return groupId; 47 | } 48 | 49 | public String getArtifactId() { 50 | return artifactId; 51 | } 52 | 53 | public String getVersion() { 54 | return version; 55 | } 56 | 57 | public String getClassifier() { 58 | return classifier; 59 | } 60 | 61 | public String getExtension() { 62 | return extension; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | StringBuilder buffer = new StringBuilder(128); 68 | buffer.append(this.getGroupId()); 69 | buffer.append(':').append(this.getArtifactId()); 70 | buffer.append(':').append(this.getExtension()); 71 | if (!this.getClassifier().isEmpty()) { 72 | buffer.append(':').append(this.getClassifier()); 73 | } 74 | buffer.append(':').append(this.getVersion()); 75 | return buffer.toString(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/Dependency.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import me.zhenxin.zmusic.dependencies.DependencyScope; 4 | import org.jetbrains.annotations.Nullable; 5 | import org.w3c.dom.Element; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | import java.text.ParseException; 12 | import java.util.Collection; 13 | import java.util.Objects; 14 | 15 | /** 16 | * Represents a dependency that needs to be downloaded and injected into the 17 | * classpath 18 | * 19 | * @author Zach Deibert, sky 20 | * @since 1.0.0 21 | */ 22 | public class Dependency extends AbstractXmlParser { 23 | 24 | /** 25 | * 当版本尚未指定时的占位符字符串 26 | */ 27 | private static final String LATEST_VERSION = "latest"; 28 | 29 | /** 30 | * 此依赖项的组 ID 31 | */ 32 | private final String groupId; 33 | 34 | /** 35 | * 此依赖项的工件 ID 36 | */ 37 | private final String artifactId; 38 | 39 | /** 40 | * 依赖项的范围 41 | */ 42 | private final DependencyScope scope; 43 | 44 | /** 45 | * 要下载的版本,或者如果在 pom 中没有指定依赖项的最新版本,则设置依赖项的最新版本 46 | */ 47 | private String version; 48 | 49 | /** 50 | * 类型(extension) 51 | * 例如:jar, pom... 只有 52 | */ 53 | private String type = "jar"; 54 | 55 | /** 56 | * 是否外部库(不加入 loadedClasses) 57 | */ 58 | private boolean isExternal; 59 | 60 | public Dependency(String groupId, String artifactId, String version, DependencyScope scope) { 61 | this.groupId = groupId; 62 | this.artifactId = artifactId; 63 | this.version = version.contains("$") || version.contains("[") || version.contains("(") ? LATEST_VERSION : version; 64 | this.scope = scope; 65 | } 66 | 67 | public Dependency(Element node) throws ParseException { 68 | this(find("groupId", node), find("artifactId", node), find("version", node, LATEST_VERSION), DependencyScope.valueOf(find("scope", node, "runtime").toUpperCase())); 69 | } 70 | 71 | /** 72 | * 获取依赖的下载地址 73 | */ 74 | public URL getURL(Repository repo, String ext) throws MalformedURLException { 75 | String name = String.format("%s-%s.%s", getArtifactId(), getVersion(), ext); 76 | return new URL(String.format("%s/%s/%s/%s/%s", repo.getUrl(), getGroupId().replace('.', '/'), getArtifactId(), getVersion(), name)); 77 | } 78 | 79 | /** 80 | * 检查依赖项的版本 81 | * 如果版本尚未指定,则尝试从仓库中获取最新版本 82 | */ 83 | public void checkVersion(Collection repositories, File baseDir) throws IOException { 84 | if (getVersion() == null) { 85 | // 获取本地最新版本 86 | DependencyVersion installedLatestVersion = getInstalledLatestVersion(baseDir); 87 | // 是否检查更新 88 | boolean checkUpdate = false; 89 | // 本地版本不存在 90 | if (installedLatestVersion == null) { 91 | checkUpdate = true; 92 | } 93 | // 2022/3/31 94 | // HikariCP 引用的 slf4j 为 latest 版本,因此每次开服都会尝试从仓库中获取最新版本 95 | else if (VersionChecker.isOutdated()) { 96 | checkUpdate = true; 97 | VersionChecker.updateCheckTime(); 98 | } 99 | IOException e = null; 100 | if (checkUpdate) { 101 | // 尝试从仓库中获取最新版本 102 | for (Repository repo : repositories) { 103 | try { 104 | repo.getLatestVersion(this); 105 | e = null; 106 | break; 107 | } catch (IOException ex) { 108 | e = new IOException(String.format("Unable to find latest version of %s", this), ex); 109 | } 110 | } 111 | if (e != null) { 112 | throw e; 113 | } 114 | } else { 115 | setVersion(installedLatestVersion.toString()); 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Get the latest version of this artifact that are currently 122 | * downloaded on this computer 123 | */ 124 | @Nullable 125 | public DependencyVersion getInstalledLatestVersion(File baseDir) { 126 | DependencyVersion max = null; 127 | for (DependencyVersion ver : getInstalledVersions(baseDir)) { 128 | if (max == null || ver.compareTo(max) > 0) { 129 | max = ver; 130 | } 131 | } 132 | return max; 133 | } 134 | 135 | /** 136 | * Gets a list of all the versions of this artifact that are currently 137 | * downloaded on this computer 138 | * 139 | * @return An array of the versions that are already downloaded 140 | */ 141 | public DependencyVersion[] getInstalledVersions(File dir) { 142 | for (String part : getGroupId().split("\\.")) { 143 | dir = new File(dir, part); 144 | } 145 | dir = new File(dir, getArtifactId()); 146 | String[] list = dir.list(); 147 | if (list == null) { 148 | return new DependencyVersion[0]; 149 | } 150 | DependencyVersion[] versions = new DependencyVersion[list.length]; 151 | for (int i = 0; i < list.length; ++i) { 152 | versions[i] = new DependencyVersion(list[i]); 153 | } 154 | return versions; 155 | } 156 | 157 | /** 158 | * Gets the file that the downloaded artifact should be stored in 159 | * 160 | * @param dir The directory to store downloaded artifacts in 161 | * @param ext The file extension to download (should be either "jar" or "pom") 162 | * @return The file to download into 163 | */ 164 | public File findFile(File dir, String ext) { 165 | if (getVersion() == null) { 166 | throw new IllegalStateException("Version is not resolved: " + this); 167 | } 168 | for (String part : getGroupId().split("\\.")) { 169 | dir = new File(dir, part); 170 | } 171 | dir = new File(dir, getArtifactId()); 172 | dir = new File(dir, getVersion()); 173 | dir = new File(dir, String.format("%s-%s.%s", getArtifactId(), getVersion(), ext)); 174 | return dir; 175 | } 176 | 177 | public String getGroupId() { 178 | return groupId; 179 | } 180 | 181 | public String getArtifactId() { 182 | return artifactId; 183 | } 184 | 185 | public String getVersion() { 186 | return version.equals(LATEST_VERSION) ? null : version; 187 | } 188 | 189 | /** 190 | * Sets the version of this dependency 191 | */ 192 | public void setVersion(String version) { 193 | if (!this.version.equals(LATEST_VERSION)) { 194 | throw new IllegalStateException("Version is already resolved"); 195 | } else if (version.equals(LATEST_VERSION)) { 196 | throw new IllegalArgumentException("Cannot set version to the latest"); 197 | } else { 198 | this.version = version; 199 | } 200 | } 201 | 202 | public String getType() { 203 | return type; 204 | } 205 | 206 | public void setType(String type) { 207 | this.type = type; 208 | } 209 | 210 | public boolean isExternal() { 211 | return isExternal; 212 | } 213 | 214 | public void setExternal(boolean external) { 215 | isExternal = external; 216 | } 217 | 218 | public DependencyScope getScope() { 219 | return scope; 220 | } 221 | 222 | @Override 223 | public String toString() { 224 | return String.format("%s:%s:%s", groupId, artifactId, version); 225 | } 226 | 227 | @Override 228 | public boolean equals(Object o) { 229 | if (this == o) { 230 | return true; 231 | } 232 | if (!(o instanceof Dependency)) { 233 | return false; 234 | } 235 | Dependency that = (Dependency) o; 236 | return Objects.equals(getGroupId(), that.getGroupId()) && Objects.equals(getArtifactId(), that.getArtifactId()) && Objects.equals(getVersion(), that.getVersion()); 237 | } 238 | 239 | @Override 240 | public int hashCode() { 241 | return Objects.hash(getGroupId(), getArtifactId()); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/DependencyDownloader.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import me.lucko.jarrelocator.JarRelocator; 4 | import me.lucko.jarrelocator.Relocation; 5 | import me.zhenxin.zmusic.dependencies.DependencyScope; 6 | import me.zhenxin.zmusic.dependencies.JarRelocation; 7 | import me.zhenxin.zmusic.dependencies.common.ClassAppender; 8 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO; 9 | import org.jetbrains.annotations.Nullable; 10 | import org.w3c.dom.Document; 11 | import org.w3c.dom.Element; 12 | import org.w3c.dom.Node; 13 | import org.w3c.dom.NodeList; 14 | import org.xml.sax.SAXException; 15 | 16 | import javax.xml.parsers.DocumentBuilder; 17 | import javax.xml.parsers.DocumentBuilderFactory; 18 | import javax.xml.parsers.ParserConfigurationException; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.text.ParseException; 23 | import java.util.*; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | import java.util.concurrent.CopyOnWriteArraySet; 26 | import java.util.stream.Collectors; 27 | 28 | 29 | /** 30 | * 包含所有需要下载和注入依赖项到类路径的方法的类。 31 | * 32 | * @author Zach Deibert, sky 33 | * @since 1.0.0 34 | */ 35 | @SuppressWarnings({"UnusedReturnValue", "ResultOfMethodCallIgnored"}) 36 | public class DependencyDownloader extends AbstractXmlParser { 37 | 38 | /** 39 | * 已注入的依赖 40 | */ 41 | private static final Map> injectedDependencies = new ConcurrentHashMap<>(); 42 | 43 | /** 44 | * 已下载的依赖 45 | */ 46 | private static final Set downloadedDependencies = new CopyOnWriteArraySet<>(); 47 | 48 | /** 49 | * 仓库 50 | */ 51 | private final Set repositories = new CopyOnWriteArraySet<>(); 52 | 53 | /** 54 | * 重定向规则 55 | */ 56 | private final Set relocation = new CopyOnWriteArraySet<>(); 57 | 58 | /** 59 | * 本地依赖目录 60 | */ 61 | private final File baseDir; 62 | 63 | /** 64 | * 依赖范围 65 | */ 66 | private List dependencyScopes = Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE); 67 | 68 | /** 69 | * 忽略可选依赖 70 | */ 71 | private boolean ignoreOptional = true; 72 | 73 | /** 74 | * 忽略异常 75 | */ 76 | private boolean ignoreException = false; 77 | 78 | /** 79 | * 是否传递依赖 80 | */ 81 | private boolean isTransitive = true; 82 | 83 | public DependencyDownloader(@Nullable File baseDir) { 84 | this.baseDir = baseDir; 85 | } 86 | 87 | public DependencyDownloader(@Nullable File baseDir, @Nullable List relocation) { 88 | this.baseDir = baseDir; 89 | if (relocation != null) { 90 | for (JarRelocation rel : relocation) { 91 | if (rel != null) { 92 | this.relocation.add(rel); 93 | } 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * 确保 {@link DependencyDownloader#baseDir} 存在 100 | */ 101 | private void createBaseDir() { 102 | baseDir.mkdirs(); 103 | } 104 | 105 | /** 106 | * 将一组依赖项注入到类路径中 107 | */ 108 | @SuppressWarnings("DuplicatedCode") 109 | public void injectClasspath(Set dependencies) throws Throwable { 110 | for (Dependency dep : dependencies) { 111 | // 如果已经注入过了,就跳过 112 | Set injectedDependencyClassLoaders = injectedDependencies.get(dep); 113 | if (injectedDependencyClassLoaders != null && injectedDependencyClassLoaders.contains(ClassAppender.getClassLoader())) { 114 | continue; 115 | } 116 | // 获取依赖项的文件 117 | File file = dep.findFile(baseDir, "jar"); 118 | // 如果文件存在 119 | if (file.exists()) { 120 | // 如果没有重定向规则,直接注入 121 | if (relocation.isEmpty()) { 122 | ClassLoader loader = ClassAppender.addPath(file.toPath(), dep.isExternal()); 123 | injectedDependencies.computeIfAbsent(dep, dependency -> new HashSet<>()).add(loader); 124 | } else { 125 | // 获取重定向后的文件 126 | String name = file.getName().substring(0, file.getName().lastIndexOf('.')); 127 | File rel = new File(file.getParentFile(), name + "_r2_" + Math.abs(relocation.hashCode()) + ".jar"); 128 | // 如果文件不存在或者文件大小为 0,就执行重定向逻辑 129 | if (!rel.exists() || rel.length() == 0) { 130 | try { 131 | // 获取重定向规则 132 | List rules = relocation.stream().map(JarRelocation::toRelocation).collect(Collectors.toList()); 133 | // 获取临时文件 134 | File tempSourceFile = PrimitiveIO.copyFile(file, File.createTempFile(file.getName(), ".jar")); 135 | // 运行 136 | new JarRelocator(tempSourceFile, rel, rules).run(); 137 | } catch (IOException e) { 138 | throw new IllegalStateException(String.format("Unable to relocate %s%n", dep), e); 139 | } 140 | } 141 | // 注入重定向后的文件 142 | ClassLoader loader = ClassAppender.addPath(rel.toPath(), dep.isExternal()); 143 | injectedDependencies.computeIfAbsent(dep, dependency -> new HashSet<>()).add(loader); 144 | } 145 | } else { 146 | try { 147 | // 下载依赖项 148 | loadDependency(repositories, dep); 149 | // 重新注入 150 | injectClasspath(Collections.singleton(dep)); 151 | } catch (IOException e) { 152 | // TODO: Disable Plugin 153 | throw new IllegalStateException("Unable to load dependency: " + dep, e); 154 | } 155 | } 156 | } 157 | } 158 | 159 | /** 160 | * 下载一个依赖项以及它的所有依赖项,并将它们存储在 {@link DependencyDownloader#baseDir} 中。 161 | */ 162 | public Set loadDependency(Collection repositories, Dependency dependency) throws IOException { 163 | // 未指定仓库 164 | if (repositories.isEmpty()) { 165 | throw new IllegalArgumentException("No repositories specified"); 166 | } 167 | // 检查依赖版本 168 | dependency.checkVersion(repositories, baseDir); 169 | // 如果已经下载过了,就直接返回 170 | if (downloadedDependencies.contains(dependency)) { 171 | Set singleton = new HashSet<>(); 172 | singleton.add(dependency); 173 | return singleton; 174 | } 175 | // 获取依赖项的 pom 文件和 jar 文件 176 | File pom = dependency.findFile(baseDir, "pom"); 177 | File pom1 = new File(pom.getPath() + ".sha1"); 178 | File jar = dependency.findFile(baseDir, "jar"); 179 | File jar1 = new File(jar.getPath() + ".sha1"); 180 | Set downloaded = new HashSet<>(); 181 | // 如果类型为 Type 才会下载自己 182 | if ("jar".equals(dependency.getType())) { 183 | downloaded.add(dependency); 184 | } 185 | // 检查文件的完整性 186 | if (PrimitiveIO.validation(pom, pom1) && PrimitiveIO.validation(jar, jar1)) { 187 | // 加载依赖项 188 | downloadedDependencies.add(dependency); 189 | if (pom.exists()) { 190 | downloaded.addAll(loadDependencyFromInputStream(pom.toURI().toURL().openStream())); 191 | } 192 | return downloaded; 193 | } 194 | // 创建所在目录 195 | pom.getParentFile().mkdirs(); 196 | // 下载文件 197 | IOException e = null; 198 | for (Repository repo : repositories) { 199 | try { 200 | repo.downloadFile(dependency, pom); 201 | repo.downloadFile(dependency, jar); 202 | e = null; 203 | break; 204 | } catch (Exception ex) { 205 | e = new IOException(String.format("Unable to find download for %s (%s)", dependency, repo.getUrl()), ex); 206 | } 207 | } 208 | // 如果存在异常,则抛出 209 | if (e != null) { 210 | throw e; 211 | } 212 | return downloaded; 213 | } 214 | 215 | /** 216 | * 下载一个依赖项列表以及它们的所有依赖项,并将它们存储在 {@link DependencyDownloader#baseDir} 中。 217 | */ 218 | public Set loadDependency(List repositories, List dependencies) throws IOException { 219 | createBaseDir(); 220 | Set downloaded = new HashSet<>(); 221 | for (Dependency dep : dependencies) { 222 | downloaded.addAll(loadDependency(repositories, dep)); 223 | } 224 | return downloaded; 225 | } 226 | 227 | /** 228 | * 下载 pom 中指定的所有依赖项 229 | */ 230 | public Set loadDependencyFromPom(Document pom, List scopes) throws IOException { 231 | List dependencies = new ArrayList<>(); 232 | Set scopeSet = new HashSet<>(scopes); 233 | NodeList nodes = pom.getDocumentElement().getChildNodes(); 234 | List repos = new ArrayList<>(repositories); 235 | if (repos.isEmpty()) { 236 | repos.add(new Repository()); 237 | } 238 | try { 239 | for (int i = 0; i < nodes.getLength(); ++i) { 240 | Node node = nodes.item(i); 241 | if ("repositories".equals(node.getNodeName())) { 242 | nodes = ((Element) node).getElementsByTagName("repository"); 243 | for (i = 0; i < nodes.getLength(); ++i) { 244 | Element e = (Element) nodes.item(i); 245 | repos.add(new Repository(e)); 246 | } 247 | break; 248 | } 249 | } 250 | } catch (ParseException ex) { 251 | throw new IOException("Unable to parse repositories", ex); 252 | } 253 | if (isTransitive) { 254 | nodes = pom.getElementsByTagName("dependency"); 255 | try { 256 | for (int i = 0; i < nodes.getLength(); ++i) { 257 | // ignore optional 258 | if (ignoreOptional && "true".equals(find("optional", (Element) nodes.item(i), "false"))) { 259 | continue; 260 | } 261 | Dependency dep = new Dependency((Element) nodes.item(i)); 262 | if (scopeSet.contains(dep.getScope())) { 263 | dependencies.add(dep); 264 | } 265 | } 266 | } catch (ParseException ex) { 267 | if (!ignoreException) { 268 | throw new IOException("Unable to parse dependencies", ex); 269 | } 270 | } 271 | } 272 | return loadDependency(repos, dependencies); 273 | } 274 | 275 | /** 276 | * 下载 pom 中指定的所有依赖项 277 | */ 278 | public Set loadDependencyFromInputStream(InputStream pom) throws IOException { 279 | return loadDependencyFromInputStream(pom, dependencyScopes); 280 | } 281 | 282 | /** 283 | * 下载 pom 中指定的所有依赖项 284 | */ 285 | @SuppressWarnings("HttpUrlsUsage") 286 | public Set loadDependencyFromInputStream(InputStream pom, List scopes) throws IOException { 287 | try { 288 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 289 | factory.setFeature("http://xml.org/sax/features/validation", false); 290 | factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); 291 | factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 292 | DocumentBuilder builder = factory.newDocumentBuilder(); 293 | Document xml = builder.parse(pom); 294 | return loadDependencyFromPom(xml, scopes); 295 | } catch (ParserConfigurationException ex) { 296 | throw new IOException("Unable to load pom.xml parser", ex); 297 | } catch (SAXException ex) { 298 | throw new IOException("Unable to parse pom.xml", ex); 299 | } 300 | } 301 | 302 | public void addRepository(Repository repository) { 303 | repositories.add(repository); 304 | } 305 | 306 | public File getBaseDir() { 307 | return baseDir; 308 | } 309 | 310 | public List getDependencyScopes() { 311 | return dependencyScopes; 312 | } 313 | 314 | public DependencyDownloader setDependencyScopes(List dependencyScopes) { 315 | this.dependencyScopes = dependencyScopes; 316 | return this; 317 | } 318 | 319 | public Map> getInjectedDependencies() { 320 | return injectedDependencies; 321 | } 322 | 323 | public Set getRepositories() { 324 | return repositories; 325 | } 326 | 327 | public boolean isIgnoreOptional() { 328 | return ignoreOptional; 329 | } 330 | 331 | public DependencyDownloader setIgnoreOptional(boolean ignoreOptional) { 332 | this.ignoreOptional = ignoreOptional; 333 | return this; 334 | } 335 | 336 | public DependencyDownloader setIgnoreException(boolean ignoreException) { 337 | this.ignoreException = ignoreException; 338 | return this; 339 | } 340 | 341 | public Set getRelocation() { 342 | return relocation; 343 | } 344 | 345 | public boolean isTransitive() { 346 | return isTransitive; 347 | } 348 | 349 | public void setTransitive(boolean transitive) { 350 | isTransitive = transitive; 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/DependencyVersion.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | 7 | /** 8 | * Represents a version parsed into its components 9 | * 10 | * @author Zach Deibert 11 | * @since 1.0.0 12 | */ 13 | public class DependencyVersion implements Comparable { 14 | 15 | /** 16 | * 版本的组件列表 17 | */ 18 | private final List parts; 19 | 20 | /** 21 | * 作为字符串的版本 22 | */ 23 | private final String version; 24 | 25 | public DependencyVersion(String version) { 26 | parts = new ArrayList<>(); 27 | for (String part : version.split("[^0-9]")) { 28 | if (!part.isEmpty()) { 29 | parts.add(Integer.parseInt(part)); 30 | } 31 | } 32 | this.version = version; 33 | } 34 | 35 | @Override 36 | public int compareTo(DependencyVersion o) { 37 | Iterator us = parts.iterator(); 38 | Iterator them = o.parts.iterator(); 39 | while (us.hasNext() && them.hasNext()) { 40 | int diff = us.next().compareTo(them.next()); 41 | if (diff != 0) { 42 | return diff; 43 | } 44 | } 45 | return us.hasNext() ? 1 : them.hasNext() ? -1 : 0; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return version; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/Repository.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO; 4 | import org.w3c.dom.Document; 5 | import org.w3c.dom.Element; 6 | 7 | import javax.xml.parsers.DocumentBuilder; 8 | import javax.xml.parsers.DocumentBuilderFactory; 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.net.URL; 13 | import java.text.ParseException; 14 | import java.util.Objects; 15 | 16 | /** 17 | * Represents a maven repository that artifacts can be downloaded from 18 | * 19 | * @author Zach Deibert, sky 20 | * @since 1.0.0 21 | */ 22 | public class Repository extends AbstractXmlParser { 23 | 24 | /** 25 | * 仓库地址 26 | */ 27 | private final String url; 28 | 29 | public Repository(String url) { 30 | this.url = url.endsWith("/") ? url.substring(0, url.length() - 1) : url; 31 | } 32 | 33 | public Repository(Element node) throws ParseException { 34 | this(find("url", node, null)); 35 | } 36 | 37 | public Repository() { 38 | this("https://maven.aliyun.com/repository/central"); 39 | } 40 | 41 | /** 42 | * 从仓库下载依赖文件,以及它的 sha1 文件 43 | */ 44 | public void downloadFile(Dependency dep, File out) throws IOException { 45 | // 获取文件扩展名 46 | String ext = out.getName().substring(out.getName().lastIndexOf('.') + 1); 47 | // 构建 URL 48 | URL url = dep.getURL(this, ext); 49 | // 下载文件 50 | PrimitiveIO.downloadFile(url, out); 51 | PrimitiveIO.downloadFile(dep.getURL(this, ext + ".sha1"), new File(out.getPath() + ".sha1")); 52 | } 53 | 54 | /** 55 | * 如果在 pom 中没有指定依赖项的最新版本,则设置依赖项的最新版本 56 | */ 57 | public void getLatestVersion(Dependency dep) throws IOException { 58 | URL url = new URL(String.format("%s/%s/%s/maven-metadata.xml", getUrl(), dep.getGroupId().replace('.', '/'), dep.getArtifactId())); 59 | try { 60 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 61 | DocumentBuilder builder = factory.newDocumentBuilder(); 62 | InputStream ins = url.openStream(); 63 | Document doc = builder.parse(ins); 64 | dep.setVersion(find("release", doc.getDocumentElement(), find("version", doc.getDocumentElement(), null))); 65 | } catch (IOException | RuntimeException ex) { 66 | throw ex; 67 | } catch (Exception ex) { 68 | throw new IOException(ex); 69 | } 70 | } 71 | 72 | public String getUrl() { 73 | return url; 74 | } 75 | 76 | @Override 77 | public boolean equals(Object o) { 78 | if (this == o) { 79 | return true; 80 | } 81 | if (!(o instanceof Repository)) { 82 | return false; 83 | } 84 | Repository that = (Repository) o; 85 | return Objects.equals(getUrl(), that.getUrl()); 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return Objects.hash(getUrl()); 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "Repository{" + "url='" + url + '\'' + '}'; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/VersionChecker.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.dependencies.legacy; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * TabooLib 9 | * taboolib.common.env.VersionChecker 10 | * 11 | * @author 坏黑 12 | * @since 2023/3/31 16:37 13 | */ 14 | @SuppressWarnings("ResultOfMethodCallIgnored") 15 | public class VersionChecker { 16 | 17 | private static final File checkFile = new File("version.lock"); 18 | 19 | /** 20 | * 是否需要检查更新 21 | * 距离上次版本检查的时间是否超过 7 天 22 | */ 23 | public static boolean isOutdated() { 24 | return System.currentTimeMillis() - getLatestCheckTime() > TimeUnit.DAYS.toMillis(7); 25 | } 26 | 27 | /** 28 | * 获取最后一次检查的时间 29 | */ 30 | public static long getLatestCheckTime() { 31 | return checkFile.lastModified(); 32 | } 33 | 34 | /** 35 | * 更新最后一次检查的时间 36 | */ 37 | public static void updateCheckTime() { 38 | if (checkFile.exists()) { 39 | checkFile.setLastModified(System.currentTimeMillis()); 40 | } else { 41 | try { 42 | checkFile.createNewFile(); 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /zmusic-velocity/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | dependencies { 5 | api(project(":zmusic-core")) 6 | compileOnly(libs.velocity) 7 | implementation(libs.bstats.velocity) 8 | } 9 | 10 | java { 11 | sourceCompatibility = JavaVersion.VERSION_21 12 | targetCompatibility = JavaVersion.VERSION_21 13 | } 14 | 15 | tasks.withType { 16 | compilerOptions { 17 | jvmTarget.set(JvmTarget.JVM_21) 18 | } 19 | } 20 | 21 | tasks.processResources { 22 | inputs.property("version", version) 23 | 24 | filesMatching("velocity-plugin.json") { 25 | expand(mapOf("version" to version)) 26 | } 27 | } -------------------------------------------------------------------------------- /zmusic-velocity/src/main/java/me/zhenxin/zmusic/VelocityPlugin.java: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic; 2 | 3 | import com.google.inject.Inject; 4 | import com.velocitypowered.api.event.Subscribe; 5 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 6 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 7 | import com.velocitypowered.api.plugin.Plugin; 8 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 9 | import com.velocitypowered.api.proxy.ProxyServer; 10 | import me.zhenxin.zmusic.dependencies.RuntimeDependency; 11 | import me.zhenxin.zmusic.enums.Platform; 12 | import me.zhenxin.zmusic.platform.VelocityLoggerImpl; 13 | import org.bstats.velocity.Metrics; 14 | 15 | import java.nio.file.Path; 16 | import java.util.logging.Logger; 17 | 18 | /** 19 | * Velocity 入口类 20 | * 21 | * @author 真心 22 | * @since 2023/8/28 12:22 23 | */ 24 | @SuppressWarnings({"SpellCheckingInspection", "unused"}) 25 | @RuntimeDependency( 26 | value = "!org.bstats:bstats-velocity:" + ZMusicConstants.BSTATS_VERSION, 27 | test = "!me.zhenxin.zmusic.library.bstats.velocity.Metrics", 28 | relocate = {"!org.bstats.", "!me.zhenxin.zmusic.library.bstats."} 29 | ) 30 | @Plugin(id = "zmusic") 31 | public class VelocityPlugin { 32 | private final ProxyServer server; 33 | private final Path dataDirectory; 34 | private final Metrics.Factory metricsFactory; 35 | 36 | @Inject 37 | public VelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { 38 | this.server = server; 39 | this.dataDirectory = dataDirectory; 40 | this.metricsFactory = metricsFactory; 41 | 42 | ZMusicRuntime.setup(dataDirectory.toFile().getAbsolutePath(), VelocityPlugin.class); 43 | } 44 | 45 | @Subscribe 46 | public void onProxyInitialization(ProxyInitializeEvent event) { 47 | ZMusicKt.setLogger(new VelocityLoggerImpl(server.getConsoleCommandSource())); 48 | ZMusicKt.setDataFolder(dataDirectory.toFile()); 49 | ZMusicKt.setCurrentPlatform(Platform.VELOCITY); 50 | metricsFactory.make(this, 12426); 51 | ZMusic.INSTANCE.onEnable(); 52 | } 53 | 54 | @Subscribe 55 | public void onProxyShutdown(ProxyShutdownEvent event) { 56 | ZMusic.INSTANCE.onDisable(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /zmusic-velocity/src/main/kotlin/me/zhenxin/zmusic/platform/VelocityLoggerImpl.kt: -------------------------------------------------------------------------------- 1 | package me.zhenxin.zmusic.platform 2 | 3 | import com.velocitypowered.api.command.CommandSource 4 | import me.zhenxin.zmusic.config.Config 5 | import me.zhenxin.zmusic.utils.uncolored 6 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer 7 | 8 | /** 9 | * Velocity 日志实现 10 | * 11 | * @author 真心 12 | * @since 2023/7/24 11:01 13 | */ 14 | class VelocityLoggerImpl(private val sender: CommandSource) : PlatformLogger { 15 | override fun log(msg: String) = sender.sendMessage( 16 | LegacyComponentSerializer.legacyAmpersand().deserialize("${Config.prefix}$msg".uncolored()) 17 | ) 18 | } -------------------------------------------------------------------------------- /zmusic-velocity/src/main/resources/velocity-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "zmusic", 3 | "name": "ZMusic", 4 | "main": "me.zhenxin.zmusic.VelocityPlugin", 5 | "version": "${version}", 6 | "authors": [ 7 | "ZhenXin" 8 | ] 9 | } --------------------------------------------------------------------------------