From 356d7ab5ab2e510124f3aae07ab7b795722fc0dd Mon Sep 17 00:00:00 2001 From: canglan Date: Wed, 19 Aug 2026 20:23:04 +0800 Subject: [PATCH] chore: remove plugin from repo, add plugin guide, docs + AGPLv3 license - mc-report-plugin removed from git tracking (kept locally), gitignored; plugin code no longer distributed (old x-external-key auth, outdated) - docs/PLUGIN-GUIDE.md: Java plugin integration guide using new ID+Secret -> SESSION auth (session mgmt, submit ticket, track, bans) - README: updated external API table (SESSION), dynamic sources, removed obsolete plugin commands/tech row, new dir structure, doc links - INSTALL: drop restart requirement, add upgrade-migration/log/sources sections, license section - LICENSE: GNU AGPL v3 full text - verified: 24 checks (git tracking, docs consistency, license integrity) --- .gitignore | 4 +- INSTALL.md | 77 ++- LICENSE | 640 ++++++++++++++++++ README.md | 129 ++-- docs/PLUGIN-GUIDE.md | 148 ++++ mc-report-plugin/README.md | 53 -- mc-report-plugin/pom.xml | 47 -- .../java/com/mcreport/plugin/ApiClient.java | 102 --- .../com/mcreport/plugin/MCReportPlugin.java | 59 -- .../com/mcreport/plugin/ReportCommand.java | 213 ------ .../src/main/resources/config.yml | 11 - .../src/main/resources/plugin.yml | 25 - 12 files changed, 918 insertions(+), 590 deletions(-) create mode 100644 LICENSE create mode 100644 docs/PLUGIN-GUIDE.md delete mode 100644 mc-report-plugin/README.md delete mode 100644 mc-report-plugin/pom.xml delete mode 100644 mc-report-plugin/src/main/java/com/mcreport/plugin/ApiClient.java delete mode 100644 mc-report-plugin/src/main/java/com/mcreport/plugin/MCReportPlugin.java delete mode 100644 mc-report-plugin/src/main/java/com/mcreport/plugin/ReportCommand.java delete mode 100644 mc-report-plugin/src/main/resources/config.yml delete mode 100644 mc-report-plugin/src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore index b4953c9..59771d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ node_modules/ -# 本地运行配置与上传文件(含数据库密码,严禁入库) data/ -# 日志与临时文件 *.log *.tmp.js *.tmp.sh *.bak # 会话记录 session-*.md +# MC 插件代码不随仓库分发(见 docs/PLUGIN-GUIDE.md 示例自行实现) +mc-report-plugin/ diff --git a/INSTALL.md b/INSTALL.md index 1eb8c33..b4396d5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -3,7 +3,7 @@ ## 环境要求 - Node.js ≥ 18 / npm ≥ 9 -- MySQL ≥ 5.7(或 MariaDB ≥ 10.2) +- MySQL ≥ 5.7(或 MariaDB ≥ 10.2) ## 快速开始 @@ -16,17 +16,17 @@ node backend/server.js ### 安装向导 -**第1步:数据库配置** -- MySQL 主机(默认 localhost) -- 端口(默认 3306) +**第1步:数据库配置** +- MySQL 主机(默认 localhost) +- 端口(默认 3306) - 用户名 / 密码 -- 数据库名(不存在则自动创建) +- 数据库名(不存在则自动创建) -**第2步:创建服主账号** +**第2步:创建服主账号** - 站点名称、站点地址 - 服主用户名、密码、邮箱 -安装完成后 **重启服务器** 加载业务路由。 +> 安装完成后**无需重启**,页面自动刷新并进入登录。 ## 配置文件 `data/config.json` @@ -38,11 +38,12 @@ node backend/server.js "db_pass": "", "db_name": "mc_report", "jwt_secret": "(安装时随机64位)", - "api_key": "(安装时随机48位)", - "external_api_key": "(安装时随机48位)" + "api_key": "(安装时随机48位)" } ``` +> `data/` 目录不入库(git 忽略),包含 config.json 与上传文件,请做好备份。 + ## 生产部署 ```bash @@ -72,17 +73,49 @@ server { } ``` +## 升级迁移 + +服务启动时自动执行幂等迁移(`backend/db.js` 的 `migrateAdditions`),老库无需手工操作: + +- `user_identities` 表创建 + 从 `users` 主身份回填(多来源身份) +- `sources` 表创建并预置 `netease`/`skin`(动态来源) +- `api_clients` / `api_sessions` 表创建(外部 API SESSION 鉴权) +- `server_groups.alias`、`tickets/bans.server_name` 列(按子服归属) +- `email_logs` / `system_logs` 表创建(日志) +- `users.source` ENUM → VARCHAR、`game_uid` 可空(去除网易 UID 依赖) + +升级后建议重启一次并清浏览器缓存(HTML 中注入的 api_key 会随重装刷新)。 + ## 邮件配置 -后台 → 系统设置 → SMTP 填写。未配置时注册用户自动激活。 +后台 → 系统设置 → SMTP 填写。未配置时注册用户自动激活(dev 模式验证码直接显示在前端)。 -## Webhook +## Webhook 通知 -后台 → 通知配置 → 添加 Discord Webhook URL → 选择触发事件(工单创建/认领/转交/更新)。 +后台 → 通知配置 → 添加 Webhook URL(Discord/企微/QQ/飞书均可)→ 选择触发事件(工单创建/认领/转交/更新)。 + +## 日志排查 + +后台 → 系统日志(owner/admin): + +- **邮件日志**:收件人/模板/状态(成功/失败)/错误信息 +- **系统日志**:级别/来源(webhook、mailer、server 等)/消息/详情 + +配合 `pm2 logs mc-report` 可完整定位问题。 + +## 外部 API(机器人/插件) + +后台 → 外部API → 创建客户端(ID 16 位数字 + Secret 32 位随机,secret 仅显示一次)→ `POST /api/external/auth/session` 换取 SESSION → 其余接口带 `Authorization: Bearer `。 + +接口清单与插件对接示例见 [docs/EXTERNAL-API.md](docs/EXTERNAL-API.md)、[docs/PLUGIN-GUIDE.md](docs/PLUGIN-GUIDE.md)。 + +## 来源管理 + +后台 → 来源管理(owner):增删/停用/排序来源(如 网易端/皮肤站/正版/离线),不设默认。已被身份数据使用的来源不能删除,只能停用。 ## 数据库 -MySQL,安装时自动建表。备份: +MySQL,安装时自动建表。备份: ```bash mysqldump -u root -p mc_report > backup.sql @@ -90,21 +123,29 @@ mysqldump -u root -p mc_report > backup.sql ## 目录 -``` +```text mc-report/ ├── backend/ │ ├── server.js │ ├── db.js +│ ├── logger.js │ ├── mailer.js │ ├── webhook.js │ ├── middleware/{auth,security,upload}.js -│ └── routes/{auth,tickets,install,external,users,settings,export,polls,features,notifications,captcha,uploads}.js -├── public/ # 前端 SPA(16 页面) -├── mc-report-plugin/ # Paper 1.20+ 插件 +│ └── routes/{auth,tickets,install,external,sources,users,logs,settings,export,polls,features,notifications,captcha,uploads}.js +├── public/ # 前端 SPA +├── docs/ # API / 外部API / 插件指南 ├── nginx.inc ├── data/config.json ├── data/uploads/ ├── package.json ├── README.md -└── INSTALL.md +├── INSTALL.md +└── LICENSE # AGPLv3 ``` + +## 许可证 + +本项目采用 **GNU Affero General Public License v3.0(AGPLv3)**,详见 [LICENSE](LICENSE)。 + +> 使用/修改/分发本项目请遵守 AGPLv3:修改后的源码必须开源,且通过网络提供服务的修改版本需向用户提供源码。 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b1bc23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,640 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they receive +widespread use, become available for other developers to incorporate. +Many people have made generous contributions to the wide range of +software distributed through that system in reliance on consistent +application of that system; it is up to the author/donor to decide if +he or she is willing to distribute software through any other system +and a licensee cannot impose that choice. + + This License distinguishes two kinds of what we call "works". +A "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing +the Program or a portion of it, either verbatim or with modifications +and/or translated into another language. (Hereinafter, translation is +included without limitation in the term "modification".) Each licensee +is addressed as "you". + + "The Program" below refers to any such work, and its "work based on +the Program" means either the Program or a derivative work as defined +above. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in + section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + + A separable portion of the object code, whose source code is +excluded from the Corresponding Source as a System Library, need not +be included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include +a requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material +you add to a covered work, you may (if authorized by the copyright +holders of that material) supplement the terms of this License with +terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is conditioned +on the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement +or otherwise) that contradict the conditions of this License, they do +not excuse you from the conditions of this License. If you cannot +convey a covered work so as to satisfy simultaneously your obligations +under this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree +to terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify +the Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero +General Public License "or any later version" applies to it, you have +the option of following the terms and conditions either of that +numbered version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number +of the GNU Affero General Public License, you may choose any version +ever published by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that +proxy's public statement of acceptance of a version permanently +authorizes you to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES +AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR +DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL +DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM +(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 6799dde..e3fb54b 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,19 @@ | **接单制** | 管理员一人一单,可转单(带原因),处理备注(玩家不可见) | | **投票** | 分组/子服管理,定时/不定时投票,盲投制,管理员 1.5 票权 | | **更新内容** | Issues 风格,分状态(待定/计划中/已完成),讨论区,按时间/热度排序,分组筛选 | -| **通知** | SMTP 邮件 + Discord Webhook(SSRF 防护),邮件模板自定义(编辑变量) | +| **通知** | SMTP 邮件 + Webhook(Discord/企微/QQ/飞书,SSRF 防护),邮件模板自定义 | +| **日志** | 邮件日志 + 系统日志(Webhook 失败/服务器错误),后台可视化排查 | | **导出** | CSV(UTF-8 BOM),导出工单/被举报人统计/操作日志(仅服主) | -| **插件** | Paper 1.20+,统一 `/report` 命令,游戏内注册/登录/举报/追踪 | +| **外部 API** | ID+Secret → SESSION 鉴权;工单提交/追踪、封禁拉取/新增、按子服过滤(机器人/插件对接) | | **找回密码** | 邮箱验证,1小时有效重置链接 | -| **多来源身份** | 一个账号可同时绑定网易端 + 皮肤站身份,提交工单时选择身份 | +| **多来源身份** | 来源动态管理(后台增删),一个账号可绑定多个来源身份,无默认来源 | | **安装向导** | Web 页面配置 MySQL 连接 + 创建服主账号(完成后无需重启) | ## API 文档 -- **完整接口文档**:[docs/API.md](docs/API.md)(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部插件 API) -- **第三方外部 API(机器人/插件)**:[docs/EXTERNAL-API.md](docs/EXTERNAL-API.md)(ID+Secret 鉴权、提交工单、进度追踪、封禁拉取、按子服拉数据) +- **完整接口文档**:[docs/API.md](docs/API.md)(认证/工单/封禁/用户/设置/通知/投票/更新/导出/外部 API) +- **第三方外部 API(机器人/插件)**:[docs/EXTERNAL-API.md](docs/EXTERNAL-API.md)(ID+Secret 换取 SESSION、提交工单、进度追踪、封禁拉取、按子服拉数据) +- **MC 插件对接示例**:[docs/PLUGIN-GUIDE.md](docs/PLUGIN-GUIDE.md)(Java 实现:换取 SESSION、提交举报、同步封禁) ## 快速开始 @@ -45,12 +47,12 @@ node backend/server.js |----|------| | 后端 | Node.js + Express + mysql2 | | 前端 | Vanilla JS SPA(Hash路由)+ Inter 字体 | -| 数据库 | MySQL ≥ 5.7 | -| 认证 | JWT(256 位随机密钥,72h 有效期) | -| 安全 | Helmet 7 + Rate Limit(分路由限速)+ XSS 过滤 + CSP + API Key 常量时间比对 + SSRF 防护 | +| 数据库 | MySQL ≥ 5.7 / MariaDB ≥ 10.2 | +| 认证 | Web: JWT(256 位随机密钥,72h 有效期);外部 API: ID+Secret → SESSION(24h,单会话) | +| 安全 | Helmet 7 + Rate Limit(分路由限速)+ XSS 过滤 + CSP + SSRF 防护 | | 验证码 | SVG 混淆图片(svg-captcha,4 位字符 + 噪点 + 颜色变形) | | 文件上传 | Multer + MIME + 魔数字节校验,UUID 命名,web 外存储 | -| 插件 | Paper API 1.20.4 + java.net.HttpURLConnection | +| 许可证 | AGPLv3 | ## 角色权限矩阵 @@ -87,36 +89,39 @@ node backend/server.js ## 注册来源与多身份 -| 来源 | 标识 | UID 含义 | -|------|------|---------| -| 网易端 | `netease` | 网易 UID | -| 皮肤站 | `skin` | 皮肤站 UID / Minecraft UUID | +来源在后台「来源管理」页**动态维护**(可增删/停用/排序,不设默认)。初始预置: -插件注册默认 `skin`。 +| 来源 | 标识 | 说明 | +|------|------|------| +| 网易端 | `netease` | 网易端身份 | +| 皮肤站 | `skin` | 皮肤站身份 | -**多身份绑定**:一个账号可同时绑定网易端 + 皮肤站两个身份(控制台「我的身份」或 `POST /api/auth/identities` 管理)。提交工单时可选择使用哪个身份;`user_identities` 表由启动时自动迁移回填,`users` 表主身份字段保持不变(完全兼容旧数据)。 +**多身份绑定**:一个账号可绑定多个来源身份(控制台「我的身份」或 `POST /api/auth/identities` 管理)。提交工单时可选择使用哪个身份;`user_identities` 表由启动时自动迁移回填,`users` 表主身份字段保持不变(完全兼容旧数据)。 ## 工单提交流程 -1. 填写标题 + 游戏名 + UID(登录后自动填充) -2. 举报:选目标玩家 + 原因(内置选项或自定义)+ 可投诉管理员 -3. 建议:填写建议内容 -4. 申诉:填写封禁申诉理由 -5. 上传附件(可选,图片/视频 ≤50MB,最多5个) -6. 提交 → 生成 tracking_token(Cookie 留存) +1. 填写标题 + 游戏名(登录后自动填充) +2. 举报:选目标玩家 + 原因(内置选项或自定义)+ 可投诉管理员 +3. 建议:填写建议内容 +4. 申诉:填写封禁申诉理由 +5. 上传附件(可选,图片/视频 ≤50MB,最多5个) +6. 提交 → 生成 tracking_token(Cookie 留存) ## 外部 API | 端点 | 用途 | 认证 | |------|------|------| -| `POST /api/external/auth/register` | 插件注册 | x-external-key | -| `POST /api/external/auth/login` | 插件登录 | x-external-key | -| `GET /api/external/my-tickets` | 我的工单(JWT) | x-external-key + Bearer | -| `GET /api/external/my-tickets/:id` | 工单详情(JWT) | x-external-key + Bearer | -| `POST /api/external/tickets` | 提交工单 | x-external-key | -| `GET /api/external/all-tickets` | 全部工单 | x-external-key | -| `GET /api/external/reported-players` | 被举报统计 | x-external-key | -| `GET /api/external/stats` | 统计 | x-external-key | +| `POST /api/external/auth/session` | ID+Secret 换取 SESSION | x-api-client-id + x-api-secret | +| `POST /api/external/auth/register` | 插件注册 | Bearer SESSION | +| `POST /api/external/tickets` | 提交工单(带 server 可选) | Bearer SESSION | +| `GET /api/external/tickets/track` | 按追踪码查进度 | Bearer SESSION | +| `GET /api/external/all-tickets` | 工单列表(可按子服过滤) | Bearer SESSION | +| `GET/POST /api/external/bans` | 拉取/新增封禁 | Bearer SESSION | +| `GET /api/external/reported-players` | 被举报统计 | Bearer SESSION | +| `GET /api/external/servers` | 服务器列表(含别名) | Bearer SESSION | +| `GET /api/external/stats` | 统计 | Bearer SESSION | + +完整说明见 [docs/EXTERNAL-API.md](docs/EXTERNAL-API.md);MC 插件对接示例见 [docs/PLUGIN-GUIDE.md](docs/PLUGIN-GUIDE.md)。 ## 部署 @@ -131,48 +136,52 @@ pm2 start backend/server.js --name mc-report proxy_pass http://127.0.0.1:3100; ``` -## 插件命令 +完整部署步骤见 [INSTALL.md](INSTALL.md)。 -``` -/report reg <密码> <邮箱> [用户名] 注册(默认用户=游戏名) -/report login <密码> [用户名] 登录 -/report report [玩家] <原因> 举报 -/report suggest <内容> 建议 -/report track [id] 追踪工单 -``` +## MC 插件对接 -配置 `plugins/MCReport/config.yml` 填写 `api_url` 和 `external_key`。 +仓库不内置插件代码,按 [docs/PLUGIN-GUIDE.md](docs/PLUGIN-GUIDE.md) 用 Java 自行实现即可: + +- `POST /api/external/auth/session`:ID+Secret 换取 SESSION(自动续期) +- `POST /api/external/tickets`:游戏内提交举报/建议/申诉(带子服) +- `GET /api/external/tickets/track`:玩家查询处理进度(tracking_token) +- `GET/POST /api/external/bans`:同步本服封禁 ## 目录结构 -``` +```text ├── backend/ -│ ├── server.js # Express 入口 -│ ├── db.js # MySQL 连接池 + 建表 + 迁移 -│ ├── mailer.js # SMTP 邮件(缓存 transporter) -│ ├── webhook.js # Discord Webhook(SSRF 防护 + 10s 超时) +│ ├── server.js # Express 入口(动态加载业务路由) +│ ├── db.js # MySQL 连接池 + 建表 + 幂等迁移 +│ ├── logger.js # 邮件/系统日志(写库, 失败不影响主流程) +│ ├── mailer.js # SMTP 邮件(自动记录日志) +│ ├── webhook.js # Webhook 推送(SSRF 防护 + 10s 超时) │ ├── middleware/ -│ │ ├── auth.js # JWT(惰性缓存 secret) -│ │ ├── security.js # 限速 + XSS 递归 + API Key 常量时间比对 -│ │ └── upload.js # 文件上传(仅读 256B 头验证魔数) +│ │ ├── auth.js # JWT(Web 鉴权) +│ │ ├── security.js # 限速 + XSS + API Key 校验 +│ │ └── upload.js # 文件上传(魔数校验) │ └── routes/ -│ ├── auth.js # 注册/登录/验证/找回密码 -│ ├── tickets.js # 工单核心(自动关闭 + 状态流转) -│ ├── install.js # 安装向导(含 api_key 生成) -│ ├── external.js # 外部 API(插件 + Java 服务器) -│ ├── users.js # 用户管理(含 source 字段) -│ ├── settings.js # 站点设置 + 邮件模板编辑 -│ ├── export.js # CSV 导出(服主专享) -│ ├── polls.js # 投票(分组/子服/定时) -│ ├── features.js # 更新内容(Issues 风格 + 讨论) -│ ├── notifications.js # Webhook 配置 +│ ├── auth.js # 注册/登录/验证/找回密码/多身份 +│ ├── tickets.js # 工单核心(状态流转) +│ ├── install.js # 安装向导 +│ ├── external.js # 外部 API(SESSION 鉴权) +│ ├── sources.js # 动态来源管理 +│ ├── users.js # 用户管理 +│ ├── logs.js # 日志查询 +│ ├── settings.js # 站点设置 + UUID 查询 +│ ├── export.js # CSV 导出 +│ ├── polls.js # 投票 + 服务器分组 +│ ├── features.js # 更新内容 +│ ├── notifications.js # 通知配置 │ ├── captcha.js # SVG 验证码 -│ └── uploads.js # 附件下载(鉴权) -├── public/ # 前端 SPA (16 页面) -├── mc-report-plugin/ # Paper 1.20+ 插件 (Maven) +│ └── uploads.js # 附件下载 +├── public/ # 前端 SPA +├── docs/ # 文档(API / 外部API / 插件指南) ├── nginx.inc # Nginx 额外配置 -├── data/ # config.json + uploads/ +├── data/ # config.json + uploads/ (git 忽略) ├── README.md +├── INSTALL.md +├── LICENSE # AGPLv3 └── package.json ``` diff --git a/docs/PLUGIN-GUIDE.md b/docs/PLUGIN-GUIDE.md new file mode 100644 index 0000000..f36ee91 --- /dev/null +++ b/docs/PLUGIN-GUIDE.md @@ -0,0 +1,148 @@ +# MC 插件对接指南(外部 API) + +本文面向 Bukkit/Paper/Spigot 插件开发者,演示如何用 Java 对接 MC Report 外部 API(提交举报、同步封禁、查询进度)。完整接口定义见 [EXTERNAL-API.md](EXTERNAL-API.md)。 + +> 仓库不再内置插件代码 —— 按本指南即可自行实现;凭据(ID+Secret)在站点后台「外部API」页创建。 + +--- + +## 一、鉴权流程(Java 实现) + +外部 API 用 **ID + Secret 换取 SESSION**,之后所有请求带 `Authorization: Bearer `。 + +```java +public class McReportApi { + private final String baseUrl; // 如 https://mc.example.com/api/external + private final String clientId; // 后台创建: 16 位数字 + private final String clientSecret; // 后台创建: 32 位随机串 + private String sessionToken; + private long sessionExpiresAt; // 到期时间戳(ms) + + public McReportApi(String baseUrl, String clientId, String clientSecret) { + this.baseUrl = baseUrl; + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + /** 获取有效 SESSION, 过期则自动换新 */ + public synchronized String session() throws Exception { + if (sessionToken != null && System.currentTimeMillis() < sessionExpiresAt) { + return sessionToken; + } + JSONObject body = new JSONObject(); + HttpURLConnection conn = open("POST", "/auth/session", null); + conn.setRequestProperty("x-api-client-id", clientId); + conn.setRequestProperty("x-api-secret", clientSecret); + conn.setDoOutput(true); + conn.getOutputStream().write(body.toString().getBytes(StandardCharsets.UTF_8)); + + JSONObject resp = readJson(conn); + sessionToken = resp.getString("session_token"); + sessionExpiresAt = System.currentTimeMillis() + resp.getLong("expires_in") * 1000L - 60_000L; // 提前1分钟过期 + return sessionToken; + } + + private HttpURLConnection open(String method, String path, String token) throws Exception { + HttpURLConnection conn = (HttpURLConnection) new URL(baseUrl + path).openConnection(); + conn.setRequestMethod(method); + conn.setConnectTimeout(5000); + conn.setReadTimeout(10000); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("User-Agent", "MCReportPlugin/1.0"); + if (token != null) conn.setRequestProperty("Authorization", "Bearer " + token); + return conn; + } + + private JSONObject readJson(HttpURLConnection conn) throws Exception { + int code = conn.getResponseCode(); + InputStream in = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream(); + String text = new String(in.readAllBytes(), StandardCharsets.UTF_8); + JSONObject json = new JSONObject(text); + if (code >= 400) throw new RuntimeException(json.optString("error", "HTTP " + code)); + return json; + } +} +``` + +## 二、提交举报工单 + +```java +/** 玩家在游戏内执行 /report 命令时调用 */ +public JSONObject submitReport(String reporterName, String targetName, String reason, + String serverName, String description) throws Exception { + String token = session(); + JSONObject body = new JSONObject(); + body.put("type", "report"); + body.put("title", "游戏内举报: " + targetName); + body.put("reporter_game_name", reporterName); + body.put("target_game_name", targetName); + body.put("reason", reason); + body.put("description", description); + body.put("server", serverName); // 子服别名或「分组/子服」 + + HttpURLConnection conn = open("POST", "/tickets", token); + conn.setDoOutput(true); + conn.getOutputStream().write(body.toString().getBytes(StandardCharsets.UTF_8)); + JSONObject resp = readJson(conn); + // resp: {"id": 123, "tracking_token": "..."} + return resp; +} +``` + +**返回的 `tracking_token` 请保存**(例如写进玩家持久数据),玩家可用它查询处理进度: + +```java +public JSONObject trackTicket(String trackingToken) throws Exception { + return readJson(open("GET", "/tickets/track?token=" + URLEncoder.encode(trackingToken, "UTF-8"), session())); +} +``` + +## 三、拉取/同步封禁 + +```java +/** 定时任务: 每 5 分钟同步一次本服生效封禁 */ +public JSONArray fetchActiveBans(String serverName) throws Exception { + String token = session(); + String path = "/bans?status=active&limit=500" + + (serverName != null && !serverName.isEmpty() + ? "&server=" + URLEncoder.encode(serverName, "UTF-8") : ""); + return readJson(open("GET", path, token)).getJSONArray("data"); +} + +/** 服务器内封禁玩家后同步到系统 */ +public JSONObject createBan(String playerName, String reason, String type, + String duration, String serverName) throws Exception { + JSONObject body = new JSONObject(); + body.put("player_name", playerName); + body.put("reason", reason); + body.put("type", type); // ban / mute / warn + body.put("duration", duration); // 如 "7d", 空=永久 + body.put("server", serverName); + return readJson(open("POST", "/bans", session())); // 需带 body +} +``` + +> 注意:`POST /bans` 需设置 `setDoOutput(true)` 并写入 body(与 submitReport 相同写法)。 + +## 四、配置 + +`plugins/<你的插件>/config.yml`: + +```yaml +# MC Report 外部 API 配置 +api_url: "https://mc.example.com/api/external" +client_id: "1829473056482917" # 后台「外部API」创建 +client_secret: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" +server_name: "survival" # 本服别名(后台服务器管理配置) +``` + +--- + +## 常见问题 + +| 问题 | 处理 | +|------|------| +| `401` | ID/Secret 错误或客户端被停用;检查后台「外部API」 | +| `401 SESSION 无效或已过期` | 重新调用 `/auth/session` 换取(示例代码已自动处理) | +| `400 待处理工单已达上限` | 同一玩家待处理工单最多 5 个 | +| 换服/改名 | 用 `server` 参数按子服隔离数据;玩家身份以 `game_name` 为准 | diff --git a/mc-report-plugin/README.md b/mc-report-plugin/README.md deleted file mode 100644 index 6f8e116..0000000 --- a/mc-report-plugin/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# MC Report Plugin - Paper 1.20.4 - -游戏内举报/注册/登录插件 - -## 编译 - -```bash -cd mc-report-plugin -mvn clean package -``` - -输出 `target/mc-report-plugin-1.0.0.jar` - -## 安装 - -1. 将 jar 放入服务器 `plugins/` 目录 -2. 重启服务器 -3. 编辑 `plugins/MCReport/config.yml`: - -```yaml -api_url: "http://localhost:3100/api/external" -external_key: "你的external_api_key" -``` - -external_api_key 在 Web 安装时自动生成,位于 `data/config.json` - -## 权限 - -| 节点 | 默认 | 说明 | -|------|------|------| -| `SeaStudio.Report.use` | true | 使用举报命令 | -| `SeaStudio.Report.admin` | op | 管理员标识 | - -LP 命令: -``` -lp group admin permission set SeaStudio.Report.admin true -``` - -## 命令 - -| 命令 | 说明 | -|------|------| -| `/report reg <密码> <邮箱> [用户名]` | 注册(默认用户名=游戏名, 来源=皮肤站) | -| `/report login <密码> [用户名]` | 登录 | -| `/report report [玩家] <原因>` | 举报(自动检测管理员投诉) | -| `/report suggest <内容>` | 建议 | -| `/report track [id]` | 追踪工单 | - -## 依赖 - -- Spigot / Paper 1.8 ~ 1.21+(全版本通用) -- Java 17+ -- Maven 3.8+ diff --git a/mc-report-plugin/pom.xml b/mc-report-plugin/pom.xml deleted file mode 100644 index 601d721..0000000 --- a/mc-report-plugin/pom.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - 4.0.0 - - com.mcreport - mc-report-plugin - 1.0.0 - jar - - - 17 - 17 - UTF-8 - - - - - spigotmc-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - - - - org.spigotmc - spigot-api - 1.13.2-R0.1-SNAPSHOT - provided - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 17 - 17 - - - - - diff --git a/mc-report-plugin/src/main/java/com/mcreport/plugin/ApiClient.java b/mc-report-plugin/src/main/java/com/mcreport/plugin/ApiClient.java deleted file mode 100644 index 1d87222..0000000 --- a/mc-report-plugin/src/main/java/com/mcreport/plugin/ApiClient.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.mcreport.plugin; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; - -public class ApiClient { - - private final String apiUrl; - private final String externalKey; - private final Gson gson = new Gson(); - - public ApiClient(String apiUrl, String externalKey) { - this.apiUrl = apiUrl; - this.externalKey = externalKey; - } - - public JsonObject request(String method, String path, JsonObject body, String token) { - try { - URL url = new URI(apiUrl + path).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod(method); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("x-external-key", externalKey); - if (token != null) conn.setRequestProperty("Authorization", "Bearer " + token); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - if (body != null) { - conn.setDoOutput(true); - try (OutputStream os = conn.getOutputStream()) { - byte[] input = gson.toJson(body).getBytes(StandardCharsets.UTF_8); - os.write(input); - } - } - - int code = conn.getResponseCode(); - String response = code >= 200 && code < 300 - ? new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8) - : new String(conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); - conn.disconnect(); - - JsonObject json = JsonParser.parseString(response).getAsJsonObject(); - json.addProperty("_status", code); - return json; - } catch (Exception e) { - JsonObject err = new JsonObject(); - err.addProperty("error", e.getMessage()); - err.addProperty("_status", 500); - return err; - } - } - - public JsonObject login(String username, String password) { - JsonObject body = new JsonObject(); - body.addProperty("username", username); - body.addProperty("password", password); - return request("POST", "/auth/login", body, null); - } - - public JsonObject register(String username, String password, String email, String gameName, String gameUid) { - JsonObject body = new JsonObject(); - body.addProperty("username", username); - body.addProperty("password", password); - body.addProperty("email", email); - body.addProperty("game_name", gameName); - body.addProperty("game_uid", gameUid); - body.addProperty("source", "skin"); - return request("POST", "/auth/register", body, null); - } - - public JsonObject submitTicket(String token, String type, String title, - String reporterName, String reporterUid, - String targetName, String targetUid, - String reason, String description, boolean isAdminComplaint) { - JsonObject body = new JsonObject(); - body.addProperty("type", type); - body.addProperty("title", title); - body.addProperty("reporter_game_name", reporterName); - body.addProperty("reporter_game_uid", reporterUid); - if (targetName != null && !targetName.isEmpty()) body.addProperty("target_game_name", targetName); - if (targetUid != null) body.addProperty("target_game_uid", targetUid); - if (reason != null && !reason.isEmpty()) body.addProperty("reason", reason); - if (description != null && !description.isEmpty()) body.addProperty("description", description); - if (isAdminComplaint) body.addProperty("is_admin_complaint", "1"); - return request("POST", "/tickets", body, token); - } - - public JsonObject getMyTickets(String token) { - return request("GET", "/my-tickets?limit=10", null, token); - } - - public JsonObject getTicket(String token, String id) { - return request("GET", "/my-tickets/" + id, null, token); - } -} diff --git a/mc-report-plugin/src/main/java/com/mcreport/plugin/MCReportPlugin.java b/mc-report-plugin/src/main/java/com/mcreport/plugin/MCReportPlugin.java deleted file mode 100644 index d5597de..0000000 --- a/mc-report-plugin/src/main/java/com/mcreport/plugin/MCReportPlugin.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.mcreport.plugin; - -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.*; - -public final class MCReportPlugin extends JavaPlugin { - - private ApiClient apiClient; - private final Map sessions = new HashMap<>(); - private final Map loginNames = new HashMap<>(); - private List adminPerms; - - @Override - public void onEnable() { - saveDefaultConfig(); - String apiUrl = getConfig().getString("api_url"); - String externalKey = getConfig().getString("external_key"); - apiClient = new ApiClient(apiUrl, externalKey); - adminPerms = getConfig().getStringList("admin_permissions"); - - getCommand("report").setExecutor(new ReportCommand(this)); - - getLogger().info("MC举报系统插件已启用"); - } - - @Override - public void onDisable() { - getLogger().info("MC举报系统插件已卸载"); - } - - public ApiClient getApiClient() { return apiClient; } - public List getAdminPerms() { return adminPerms; } - - public String getToken(Player player) { - return sessions.get(player.getUniqueId()); - } - - public void setToken(Player player, String token) { - sessions.put(player.getUniqueId(), token); - } - - public void removeToken(Player player) { - sessions.remove(player.getUniqueId()); - loginNames.remove(player.getUniqueId()); - } - - public void setLoginName(Player player, String name) { loginNames.put(player.getUniqueId(), name); } - public String getLoginName(Player player) { return loginNames.getOrDefault(player.getUniqueId(), player.getName()); } - - public boolean isOnlineAdmin(Player target) { - if (target.isOp()) return true; - for (String perm : adminPerms) { - if (target.hasPermission(perm)) return true; - } - return false; - } -} diff --git a/mc-report-plugin/src/main/java/com/mcreport/plugin/ReportCommand.java b/mc-report-plugin/src/main/java/com/mcreport/plugin/ReportCommand.java deleted file mode 100644 index 5939493..0000000 --- a/mc-report-plugin/src/main/java/com/mcreport/plugin/ReportCommand.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.mcreport.plugin; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.*; - -public class ReportCommand implements CommandExecutor { - - private final MCReportPlugin plugin; - - public ReportCommand(MCReportPlugin plugin) { - this.plugin = plugin; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player player)) { - sender.sendMessage("§cPlayer only"); - return true; - } - - if (args.length == 0) { - showHelp(player); - return true; - } - - switch (args[0].toLowerCase()) { - case "reg": case "register": return handleRegister(player, args); - case "login": return handleLogin(player, args); - case "report": case "r": return handleSubmit(player, args, "report"); - case "suggest": case "s": return handleSubmit(player, args, "suggestion"); - case "track": case "t": return handleTrack(player, args); - default: showHelp(player); return true; - } - } - - private void showHelp(Player player) { - if (plugin.getToken(player) == null) { - player.sendMessage("§6===== MCReport ====="); - player.sendMessage("§e/report reg [username] §7- Register"); - player.sendMessage("§e/report login [username] §7- Login"); - } else { - player.sendMessage("§6===== MCReport ====="); - player.sendMessage("§e/report report [player] §7- Report a player"); - player.sendMessage("§e/report suggest §7- Submit suggestion"); - player.sendMessage("§e/report track [id] §7- List/view tickets"); - } - } - - private boolean handleLogin(Player player, String[] args) { - if (args.length < 2) { player.sendMessage("§cUsage: /report login [username]"); return true; } - String pwd = args[1]; - String username = args.length > 2 ? args[2] : player.getName(); - - player.sendMessage("§7Logging in..."); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - JsonObject res = plugin.getApiClient().login(username, pwd); - int status = res.get("_status").getAsInt(); - if (status == 200) { - plugin.setToken(player, res.get("token").getAsString()); - plugin.setLoginName(player, username); - player.sendMessage("§aLogged in as §e" + username); - } else { - player.sendMessage("§cLogin failed: " + res.get("error").getAsString()); - } - }); - return true; - } - - private boolean handleRegister(Player player, String[] args) { - if (args.length < 3) { player.sendMessage("§cUsage: /report reg [username]"); return true; } - String pwd = args[1]; - String email = args[2]; - String username = args.length > 3 ? args[3] : player.getName(); - - if (pwd.length() < 6) { player.sendMessage("§cPassword must be at least 6 characters"); return true; } - - player.sendMessage("§7Registering..."); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - JsonObject res = plugin.getApiClient().register( - username, pwd, email, - player.getName(), player.getUniqueId().toString() - ); - int status = res.get("_status").getAsInt(); - if (status == 201) { - player.sendMessage("§a" + res.get("message").getAsString()); - player.sendMessage("§6Username: §e" + username + " §6Game: §e" + player.getName()); - } else { - player.sendMessage("§cRegister failed: " + res.get("error").getAsString()); - } - }); - return true; - } - - private boolean handleSubmit(Player player, String[] args, String type) { - if (plugin.getToken(player) == null) { player.sendMessage("§cLogin first: /report login "); return true; } - - int contentStart = 1; - String targetName = null; - String targetUid = null; - boolean isAdminComplaint = false; - - if (type.equals("report") && args.length > 1) { - Player target = Bukkit.getPlayer(args[1]); - if (target != null && target.isOnline()) { - targetName = target.getName(); - targetUid = target.getUniqueId().toString(); - contentStart = 2; - isAdminComplaint = plugin.isOnlineAdmin(target); - } - } - - if (args.length <= contentStart) { - player.sendMessage("§cPlease provide " + (type.equals("report")?"a reason":"content")); - return true; - } - - String content = String.join(" ", Arrays.copyOfRange(args, contentStart, args.length)); - String title = targetName != null - ? "In-game report: " + targetName - : type.equals("suggestion") - ? "In-game suggestion: " + player.getName() - : "In-game report: " + player.getName(); - - player.sendMessage("§7Submitting..."); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - JsonObject res = plugin.getApiClient().submitTicket( - plugin.getToken(player), type, title, - player.getName(), player.getUniqueId().toString(), - targetName, targetUid, - type.equals("report")?content:null, - type.equals("suggestion")?content:null, - isAdminComplaint - ); - if (res.get("_status").getAsInt() == 200 || res.get("_status").getAsInt() == 201) { - player.sendMessage("§aSubmitted! Ticket §e#" + res.get("id").getAsInt()); - player.sendMessage("§7Use §e/report track §7to check progress"); - } else { - player.sendMessage("§cFailed: " + res.get("error").getAsString()); - } - }); - return true; - } - - private boolean handleTrack(Player player, String[] args) { - if (plugin.getToken(player) == null) { player.sendMessage("§cLogin first"); return true; } - - player.sendMessage("§7Loading..."); - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - if (args.length > 1) { - JsonObject res = plugin.getApiClient().getTicket(plugin.getToken(player), args[1]); - showTicketDetail(player, res); - } else { - JsonObject res = plugin.getApiClient().getMyTickets(plugin.getToken(player)); - showTicketList(player, res); - } - }); - return true; - } - - private void showTicketList(Player player, JsonObject res) { - if (res.get("_status").getAsInt() != 200 || !res.has("list")) { - player.sendMessage("§cFailed to load tickets"); - return; - } - JsonArray list = res.getAsJsonArray("list"); - if (list.size() == 0) { - player.sendMessage("§7No tickets yet"); - return; - } - player.sendMessage("§6===== Your Tickets (last " + list.size() + ") ====="); - for (int i = 0; i < list.size(); i++) { - JsonObject t = list.get(i).getAsJsonObject(); - String type = t.get("type").getAsString(); - String typeLabel = type.equals("report")?"雷":type.equals("suggestion")?"灯":type.equals("appeal")?"锤":"?"; - String status = t.get("status").getAsString(); - String statusColor = ""; - switch (status) { - case "pending": statusColor = "§e"; break; - case "processing": statusColor = "§d"; break; - case "resolved": statusColor = "§a"; break; - case "rejected": statusColor = "§c"; break; - case "closed": statusColor = "§7"; break; - case "awaiting_info": statusColor = "§6"; break; - case "appealing": statusColor = "§c"; break; - } - player.sendMessage(" §e#" + t.get("id").getAsInt() + " §7" + statusColor + status + " §f" + t.get("title").getAsString().substring(0, Math.min(30, t.get("title").getAsString().length()))); - } - player.sendMessage("§7Use §e/report track §7for details"); - } - - private void showTicketDetail(Player player, JsonObject res) { - if (res.get("_status").getAsInt() != 200 || res.has("error")) { - player.sendMessage("§cTicket not found"); - return; - } - JsonObject t = res; - player.sendMessage("§6===== Ticket #" + t.get("id").getAsInt() + " ====="); - player.sendMessage("§eTitle: §f" + t.get("title").getAsString()); - player.sendMessage("§eStatus: §f" + t.get("status").getAsString()); - player.sendMessage("§eType: §f" + t.get("type").getAsString()); - if (t.has("assigned_to") && !t.get("assigned_to").isJsonNull()) { - player.sendMessage("§eHandler: §f#" + t.get("assigned_to").getAsInt()); - } - player.sendMessage("§eDate: §7" + t.get("created_at").getAsString()); - } -} diff --git a/mc-report-plugin/src/main/resources/config.yml b/mc-report-plugin/src/main/resources/config.yml deleted file mode 100644 index 0efe26a..0000000 --- a/mc-report-plugin/src/main/resources/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -# MC举报系统 - 插件配置 -api_url: "http://localhost:3100/api/external" -external_key: "" - -# 管理权限检测 - 拥有以下任一权限的在线玩家视为管理员 -admin_permissions: - - "op" - - "SeaStudio.Report.admin" - -# 踢出提示 -appeal_url: "http://localhost:3100/#/home/appeal" diff --git a/mc-report-plugin/src/main/resources/plugin.yml b/mc-report-plugin/src/main/resources/plugin.yml deleted file mode 100644 index 35d563e..0000000 --- a/mc-report-plugin/src/main/resources/plugin.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: MCReport -version: 1.0.0 -main: com.mcreport.plugin.MCReportPlugin -author: Sea-Studio -description: MC Report System Plugin - -commands: - report: - description: Game reporting & account management - usage: | - §6===== MCReport ===== - §e/report reg [username] §7- Register - §e/report login [username] §7- Login - §e/report report [player] §7- Report player - §e/report suggest §7- Submit suggestion - §e/report track [id] §7- Track tickets - aliases: [jubao,举报] - -permissions: - SeaStudio.Report.use: - description: Use report functions - default: true - SeaStudio.Report.admin: - description: Identifies as report admin - default: op