diff --git a/.gitignore b/.gitignore index 493ba0955..15516496b 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ dependency-reduced-pom.xml #run dirs /fabric/versions/*/run + +# Compound-engineering / agent review artifacts +.context/ diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 000000000..88ac42552 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,700 @@ +# Upgrade Notes + +End-user- and plugin-author-visible changes that may require attention when +upgrading. Versioned changes are listed newest-first. + +--- + +## v8.0.0 — Stable public API + MiniMessage + +v8 is the largest release in BanManager's history. The two most consequential +changes are: + +1. **A dedicated public API artifact** (`me.confuser.banmanager:BanManagerAPI`) + replaces the old `BmAPI` static facade and the per-platform event classes. +2. **MiniMessage / Adventure** replaces the legacy section-symbol colour + pipeline for kick / chat / broadcast messages. + +Server operators upgrading from v7 should read the [Server upgrade](#server-upgrade-v7--v8) +section. Plugin authors integrating against BanManager should read the +[Plugin author upgrade](#plugin-author-upgrade-v7--v8) section. + +### Server upgrade (v7 → v8) + +#### Required runtime: Java 17 or newer + +Same constraint as the v7 → v7.10 modernisation pass; reproduced here for +single-document upgraders. + +- BanManager v8 requires **Java 17+** at runtime (Java 21 to build from + source). +- This matches Spigot/Paper 1.20+, Velocity 3.3+, Sponge API 8+ and Fabric + 1.20.1+ — modern servers need no action. +- Java 8/11 deployments must update their JRE before installing v8. + +> **Symptom:** if you start an old JRE against the v8 jar you will see +> `java.lang.UnsupportedClassVersionError: ... has been compiled by a more +> recent version of the Java Runtime (class file version 61.0), this +> version of the Java Runtime only recognises class file versions up to +> 55.0` (or similar) in the server log, followed by the plugin failing to +> load. Class-file `61.0` is Java 17. Upgrade the JRE — there is no +> compatibility shim. + +#### Maintenance window guidance + +v8 ships several breaking changes that land at once. Plan a single +maintenance window rather than a partial rollout: + +1. **Verify the JRE first.** Run `java -version` on the server host before + touching plugins. If it reports anything below `17`, upgrade the JRE + and restart the server *before* swapping the BanManager jar — otherwise + you will hit the `UnsupportedClassVersionError` above and the server + will start without ban enforcement. +2. **Upgrade BanManager and every BanManager-aware companion plugin in the + same restart.** A v7 companion plugin (BanManager-WebEnhancer ≤ legacy, + any plugin compiled against `BanManagerCommon` / `BmAPI` / + per-platform event classes) will not load against v8 — those classes + no longer exist. Either upgrade companion jars to releases that target + `BanManagerAPI:8.x`, recompile your own integrations against the new + artifact (see [Plugin author upgrade](#plugin-author-upgrade-v7--v8)), + or remove the incompatible jar before restart. +3. **Deploy in this order on multi-instance setups (proxy + backends):** + stop all backends, stop the proxy, drop the new jars on every host, + start the proxy first (so `bungee`/`velocity` registers its API + instance), then start the backends. Reversing the order produces a + transient window where backend handlers can call into a v7 proxy API + and fail with `ClassNotFoundException`. +4. **Custom `messages.yml` overrides need to be re-encoded.** If you + maintain a customised `messages/*.yml` and skip the conversion to + MiniMessage, kick messages will display the literal MiniMessage tags + to players. Run a dry-run with the upgraded jar in a staging server + first. +5. **Keep the v7 jar handy.** v8 has no in-place downgrade path. See + [Rollback / downgrade](#rollback--downgrade-v8--v7) below for the + step-by-step procedure if you need to revert mid-window. + +#### Rollback / downgrade (v8 → v7) + +v8 ships forward-only schema migrations (the `bm_schema_version` table is +new in v8 and several existing tables get new columns / indexes). There +is **no downgrade migration** — restoring v7 means restoring the database +itself. + +**Before** the upgrade, do this on every host that runs BanManager: + +1. **Snapshot the BanManager database.** For MySQL/MariaDB: + ```sh + mysqldump --single-transaction --quick --routines --triggers \ + --databases banmanager > banmanager-pre-v8.sql + ``` + Adjust the database name to match your `config.yml`. For H2, + stop the server first and copy the entire + `plugins/BanManager/banmanager.mv.db` (and `.trace.db`, `.lock.db` if + present). +2. **Snapshot the `plugins/BanManager/` directory** — `config.yml`, + `messages/`, custom translations, exemptions, etc. A `tar -czf + banmanager-config-pre-v8.tgz plugins/BanManager` is enough. +3. **Keep the previous BanManager v7 jar** alongside the v8 jar so you + can swap it back without re-downloading. + +**To roll back** after a failed v8 startup: + +1. Stop the server (and every backend if running on a proxy fleet). +2. Replace the v8 jar with the saved v7 jar in `plugins/`. +3. Restore the SQL snapshot — this **drops and recreates** every table + the dump contains, undoing v8's schema additions in one step: + ```sh + mysql banmanager < banmanager-pre-v8.sql + ``` + For H2: stop the server, replace the database files with the saved + copies, then start. +4. Restore `plugins/BanManager/` from the directory snapshot if you + converted any `messages/*.yml` files to MiniMessage in-place. +5. Restart the proxy first (if applicable), then the backends. + +> **Why a SQL restore is required.** v8 may add columns mid-table +> (`ALTER TABLE ... ADD COLUMN`) and create new tables (`bm_schema_version`, +> `bm_player_pins`, etc.). v7 will start against a v8-shaped database, but +> writes to columns it doesn't know about will fail at insert time +> (`Field 'x' doesn't have a default value`) and the new tables will be +> ignored — leaving the database in an unsupported half-and-half state +> that's hard to recover from later. Restoring from the snapshot avoids +> the trap entirely. + +A new minor release of v8 (8.x → 8.y) does **not** require a snapshot +unless its release notes call for one — semver applies to internal schema +migrations as well as the public API. + +#### MiniMessage replaces section-symbol colour codes + +The `messages/` translations and any custom message overrides now use +[MiniMessage](https://docs.advntr.dev/minimessage/index.html) syntax +(``, ``, ``) instead of `&c` /`§c` codes. + +- Built-in translations are migrated automatically; nothing to do for + out-of-the-box installs. +- Operators with **customised** `messages/*.yml` files should convert legacy + codes manually. The official quick-reference is at + . +- Click/hover targets now go through MiniMessage tags + (e.g. `Appeal`), so old + `[Appeal](command://...)` markdown will not render. + +#### `messages.yml` symlink replaced by `messages/` directory symlink + +- The `messages.yml` symlink that earlier 7.x installs created has been + replaced by a `messages/` directory containing one file per locale. +- On first start, BanManager will detect the legacy single-file layout and + migrate it. Verify by checking that `plugins/BanManager/messages/` exists + and that the legacy `messages.yml` symlink has been removed. + +#### Sponge API 7 (Minecraft 1.12.2) is no longer supported + +- The `BanManagerSponge` artifact now targets Sponge API 8+. The legacy + `BanManagerSponge7` module has been dropped. +- Operators on 1.12.2 must either pin to the v7 release branch or upgrade + their Sponge install. + +#### Java 17 modernisation (carried over from v7.10) + +The following changes ship in v8 for installs that skipped v7.10. They are +fully transparent for most operators. + +- **HikariCP 6.x, ORMLite 6.x, MariaDB JDBC 3.x, MySQL Connector/J 8.x**. + BanManager generates the JDBC URL per-driver, so the legacy + `disableMariaDbDriver` workaround has been removed and `useSSL` / + `verifyServerCertificate` are translated into MariaDB's modern + `sslMode` (`disable` / `trust` / `verify-full`). +- If `storageType: mysql` is pointed at a MariaDB server, switch to + `storageType: mariadb` to avoid driver mismatch warnings. +- **SnakeYAML 2.x** with compatibility shims so duplicate keys still resolve + (last value wins) and the document-size limit is raised to 32 MB. +- **bStats 3.2** and **PlaceholderAPI 2.12** (Bukkit-only soft-dependency). +- **SLF4J 2.x** with a custom service-provider so HikariCP / ORMLite log + through BanManager's own logger; `disableDatabaseLogging()` is now a no-op + on Bukkit. + +--- + +### Plugin author upgrade (v7 → v8) + +If your plugin imports +`me.confuser.banmanager.common.api.BmAPI`, any +`me.confuser.banmanager..api.events.*` class, or any package under +`me.confuser.banmanager.common.*` (`PlayerData`, `Message`, the storage +classes, etc.), v8 is a **breaking change**. Switch to the +`BanManagerAPI` artifact described below — it is the only stable surface and +the only one with a semver guarantee. + +#### Switch to the new artifact + +The API now lives in its own module so consumer plugins do not pull in +shaded ORMLite / Hikari / Adventure / Caffeine. + +`build.gradle.kts`: + +```kotlin +repositories { + mavenCentral() +} + +dependencies { + compileOnly("me.confuser.banmanager:BanManagerAPI:8.0.0") +} +``` + +`pom.xml`: + +```xml + + me.confuser.banmanager + BanManagerAPI + 8.0.0 + provided + +``` + +The artifact has exactly **one** transitive dependency: +`com.github.seancfoley:ipaddress` (unshaded, so the `IPAddress` type the +API exposes is the canonical `inet.ipaddr.IPAddress`). + +If you only need to *publish* IP-related requests and don't want a direct +compile-time dependency on `inet.ipaddr`, use the `String`-accepting +overloads on `IpBanRequest`, `IpMuteRequest`, and `IpRangeBanRequest`. The +overloads parse via the bundled library internally and throw +`IllegalArgumentException` on invalid input. Pre-event handlers that read +`request.ip()` still need the dependency to introspect the parsed value. + +#### Resolve the service + +`BanManager.get()` works on every platform — Bukkit, Bungee, Velocity, +Sponge and Fabric: + +```java +import me.confuser.banmanager.api.BanManager; +import me.confuser.banmanager.api.BanManagerService; + +BanManagerService bm = BanManager.get(); +``` + +On Bukkit, the service is also published to the Bukkit `ServicesManager` +for plugins that prefer the platform-native lookup: + +```java +import org.bukkit.Bukkit; +import me.confuser.banmanager.api.BanManagerService; + +BanManagerService bm = Bukkit.getServicesManager().load(BanManagerService.class); +``` + +`BanManager.get()` throws `IllegalStateException` while BanManager is still +enabling. Use `BanManager.isAvailable()` if your plugin's +`onEnable` may run before BanManager's, or hook `PluginEnableEvent` / +`ProxyInitializedEvent` and resolve there. + +#### Async by default + `*Sync` siblings + +Every write path on the new services returns a +`CompletableFuture`. Cancelled pre-events resolve to a sentinel +(`Optional.empty()` for create, `false` for delete) on both async and sync +variants — they no longer throw. + +```java +import me.confuser.banmanager.api.BanManager; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.request.BanRequest; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +UUID playerUuid = ...; +UUID actorUuid = ...; + +CompletableFuture> future = BanManager.get().bans().ban( + new BanRequest(playerUuid, actorUuid, "griefing") + .expires(System.currentTimeMillis() / 1000L + 3600) + .silent(false) +); + +future.thenAccept(result -> result.ifPresent(ban -> + getLogger().info("Banned " + ban.player().name() + " until " + ban.expires()) +)).exceptionally(ex -> { + getLogger().log(java.util.logging.Level.WARNING, "Ban write failed", ex); + return null; +}); +``` + +`CompletableFuture` continuations swallow exceptions silently unless you +attach `.exceptionally(...)` (or `.whenComplete(...)`). Storage failures +(`StorageException` wrapping a `SQLException`) surface as the cause; the +default executor that runs continuations is BanManager's DB-I/O pool, so +do not call platform APIs that require the server tick thread inside an +`exceptionally` handler without scheduling. + +If you are already on a worker thread, the `*Sync` variants block: + +```java +Optional ban = BanManager.get().bans().banSync( + new BanRequest(playerUuid, actorUuid, "griefing")); +``` + +Persistence failures surface as `me.confuser.banmanager.api.exception.StorageException` +(an unchecked subclass of `BanManagerException`), wrapping the underlying +`SQLException`. Plugins may inspect `getCause()` to recover the driver +exception. **API methods no longer throw checked exceptions.** + +#### `BmAPI` mapping + +Every `BmAPI` static method has a 1:1 (sometimes nicer) replacement on the +new service tree. The full mapping: + +| v7 `BmAPI` static method | v8 replacement | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | +| `BmAPI.getPlayer(UUID)` | `bm.players().findByUuid(uuid)` / `findByUuidSync(uuid)` returning `Optional` | +| `BmAPI.getPlayer(String)` | `bm.players().findByName(name)` / `findByNameSync(name)` | +| `BmAPI.getPlayers(IPAddress)` | `bm.players().findByIp(ip)` / `findByIpSync(ip)` returning `List` | +| `BmAPI.getConsole()` | `bm.players().console()` | +| `BmAPI.toIp(String)` | Use `inet.ipaddr.IPAddressString("…").getAddress()` directly — no longer wrapped | +| `BmAPI.ban(player, actor, reason, silent)` | `bm.bans().ban(new BanRequest(player, actor, reason).silent(silent))` | +| `BmAPI.ban(player, actor, reason, silent, expires)` | …`.expires(epochSeconds)` on the same request | +| `BmAPI.unban(ban, actor[, silent])` | `bm.bans().unban(uuid, actor, reason, silent)` | +| `BmAPI.isBanned(uuid|name)` | `bm.bans().isBanned(uuid|name)` | +| `BmAPI.getCurrentBan(uuid|name)` | `bm.bans().findActive(uuid|name)` | +| `BmAPI.getBanRecords(player)` | `bm.bans().records(uuid, page, size)` returning `Page` | +| `BmAPI.mute(...)` (5 overloads) | `bm.mutes().mute(new MuteRequest(...).silent(...).soft(...).expires(...))` | +| `BmAPI.unmute(mute, actor[, silent])` | `bm.mutes().unmute(uuid, actor, reason, silent)` | +| `BmAPI.isMuted(uuid|name|IPAddress)` | `bm.mutes().isMuted(...)` for players, `bm.ipMutes().isMuted(ip)` for IPs | +| `BmAPI.getCurrentMute(uuid|name)` | `bm.mutes().findActive(uuid|name)` | +| `BmAPI.getMuteRecords(player)` | `bm.mutes().records(uuid, page, size)` returning `Page` | +| `BmAPI.ban(IpBanData)` / IP overloads | `bm.ipBans().ban(new IpBanRequest(ip, actor, reason).silent(...).expires(...))` | +| `BmAPI.unban(IpBanData, actor[, silent])` | `bm.ipBans().unban(ip, actor, reason, silent)` | +| `BmAPI.isBanned(IPAddress)` | `bm.ipBans().isBanned(ip)` | +| `BmAPI.getCurrentBan(IPAddress)` | `bm.ipBans().findActive(ip)` | +| `BmAPI.getBanRecords(IPAddress)` | `bm.ipBans().records(ip, page, size)` returning `Page` | +| `BmAPI.warn(player, actor, reason, read[, silent])` | `bm.warnings().warn(new WarnRequest(...).read(read).silent(silent))` | +| `BmAPI.warn(PlayerWarnData[, silent])` | Same as above (no longer takes the storage entity) | +| `BmAPI.getWarnings(player)` | `bm.warnings().warnings(uuid, page, size)` returning `Page` | +| `BmAPI.getPlayerNames(uuid)` | `bm.history().names(uuid)` / `namesSync(uuid)` returning `List` | +| `BmAPI.getPlayerHistory(uuid, since, page)` | `bm.history().sessions(uuid, since, page, size)` returning `Page` | +| `BmAPI.getPlayerNameAt(uuid, timestamp)` | `bm.history().nameAt(uuid, timestamp)` / `nameAtSync(uuid, timestamp)` | +| `BmAPI.getMessage(key)` | No replacement — message rendering is internal in v8. Build messages via the API record DTOs. | +| `BmAPI.getLocalConnection()` | `bm.database().localDataSource()` returning `javax.sql.DataSource` — **do not close it** | +| `BmAPI.toTimestamp(time, future)` | No replacement — use `java.time.Duration.parse` or your own helper | + +#### Pagination replaces `CloseableIterator` + +`CloseableIterator` is gone from the public API. Every paginated query +returns an immutable `Page`: + +```java +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerBanRecord; + +Page page = bm.bans().recordsSync(uuid, 0, 50); +page.items().forEach(record -> /* ... */); + +if (page.hasMore()) { + Page next = bm.bans().recordsSync(uuid, page.page() + 1, page.size()); +} +``` + +`Page.total()` is `-1` when the storage layer cannot compute it cheaply; +prefer `hasMore()` for pager UIs. + +#### Events: subscribe via `EventBus`, not platform listeners + +The 31 per-platform event classes (`me.confuser.banmanager.bukkit.api.events.*`, +plus equivalents for Bungee, Velocity, Sponge, Fabric) have all been deleted. +The single replacement is the cross-platform `EventBus`: + +| v7 (per-platform) | v8 (cross-platform on `EventBus`) | +| -------------------------------------------------------- | ---------------------------------------------------------------- | +| `PlayerBanEvent` (cancellable) | `me.confuser.banmanager.api.event.player.PlayerBanEvent` | +| `PlayerBannedEvent` (post) | `me.confuser.banmanager.api.event.player.PlayerBannedEvent` | +| `PlayerUnbanEvent` / `PlayerUnbannedEvent` | …`.PlayerUnbanEvent` / …`.PlayerUnbannedEvent` | +| `PlayerMuteEvent` / `PlayerMutedEvent` | …`.PlayerMuteEvent` / …`.PlayerMutedEvent` | +| `PlayerUnmuteEvent` / `PlayerUnmutedEvent` | …`.PlayerUnmuteEvent` / …`.PlayerUnmutedEvent` | +| `PlayerWarnEvent` (now cancellable on every platform) | …`.PlayerWarnEvent` | +| `PlayerWarnedEvent` | …`.PlayerWarnedEvent` | +| `PlayerNoteCreatedEvent` | …`.PlayerNoteEvent` (cancellable pre-event) + `PlayerNoteCreatedEvent` (post) | +| `PlayerReportEvent` / `PlayerReportedEvent` | …`.PlayerReportEvent` / `PlayerReportedEvent` | +| `PlayerReportDeletedEvent` | …`.PlayerReportDeletedEvent` | +| `PlayerKickedEvent` | …`.PlayerKickedEvent` | +| `PlayerDeniedEvent` (login denial — banned/IP/range/name) | …`.PlayerDeniedEvent` | +| `IpBanEvent` / `IpBannedEvent` | `me.confuser.banmanager.api.event.ip.IpBanEvent` / `IpBannedEvent` | +| `IpUnbanEvent` / `IpUnbannedEvent` | …`.IpUnbanEvent` / `IpUnbannedEvent` | +| `IpMuteEvent` / `IpMutedEvent` | …`.IpMuteEvent` / `IpMutedEvent` | +| `IpUnmuteEvent` / `IpUnmutedEvent` | …`.IpUnmuteEvent` / `IpUnmutedEvent` | +| `IpRangeBanEvent` / `IpRangeBannedEvent` | …`.IpRangeBanEvent` / `IpRangeBannedEvent` | +| `IpRangeUnbanEvent` / `IpRangeUnbannedEvent` | …`.IpRangeUnbanEvent` / `IpRangeUnbannedEvent` | +| `NameBanEvent` / `NameBannedEvent` | `me.confuser.banmanager.api.event.name.NameBanEvent` / `NameBannedEvent` | +| `NameUnbanEvent` / `NameUnbannedEvent` | …`.NameUnbanEvent` / `NameUnbannedEvent` | +| `PluginReloadedEvent` | `me.confuser.banmanager.api.event.player.PluginReloadedEvent` | +| `SilentEvent` / `SilentCancellableEvent` markers | Removed — every post-event has a `silent()` accessor | +| `CustomEvent` / `CustomCancellableEvent` base classes | Removed — implement `BanManagerEvent` / extend `AbstractCancellableEvent` | + +##### Subscription side-by-side + +**v7 (Bukkit):** + +```java +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import me.confuser.banmanager.bukkit.api.events.PlayerBannedEvent; + +public class BanListener implements Listener { + @EventHandler(priority = EventPriority.MONITOR) + public void onBanned(PlayerBannedEvent event) { + if (!event.isSilent()) { + getServer().broadcastMessage(event.getBan().getPlayer().getName() + " was banned"); + } + } +} + +// in onEnable(): +getServer().getPluginManager().registerEvents(new BanListener(), this); +``` + +**v8 (works on every platform):** + +```java +import me.confuser.banmanager.api.BanManager; +import me.confuser.banmanager.api.event.EventPriority; +import me.confuser.banmanager.api.event.Subscription; +import me.confuser.banmanager.api.event.player.PlayerBannedEvent; + +private Subscription bannedSub; + +public void onEnable() { + bannedSub = BanManager.get().events().subscribe( + PlayerBannedEvent.class, + EventPriority.MONITOR, + event -> { + if (!event.silent()) { + getServer().broadcastMessage(event.ban().player().name() + " was banned"); + } + }); +} + +public void onDisable() { + if (bannedSub != null) bannedSub.unsubscribe(); +} +``` + +Notes: + +- Event dispatch is synchronous on the publishing thread, preserving v7 + `callEvent(...)` semantics. Because BanManager's storage layer publishes + events from its own dedicated DB-I/O executor, your handlers run **off** + the main thread by default. Two consequences: + 1. **Subscribers must return promptly** — target budget under + ~10 ms, never any blocking I/O. A slow handler holds up *every* + subsequent ban / mute / warn write because it pins the DB-I/O + executor's worker. Offload heavy work to `bm.scheduler().runAsync(...)` + and return. + 2. **Do not call platform APIs that require the server tick thread** + (Bukkit `World`, etc.) without first hopping back via + `bm.scheduler().runSync(...)` — and only on platforms where + `scheduler.isMainThreadAware()` returns `true` (Bukkit/Sponge/Fabric). +- Always retain the `Subscription` and call `unsubscribe()` on plugin + disable / reload. BanManager unsubscribes its own listeners on + `/bmreload`; the post-reload `PluginReloadedEvent` is your cue to + re-register. +- Pre-events expose a mutable `request()` payload (e.g. + `PlayerBanEvent#request()` returns the `BanRequest` that will be + persisted). Modify it freely; cancel via `event.cancel()`. The + cancellation surfaces to async callers as + `CompletableFuture>` resolving empty. + +##### Mutating kick messages from a handler (PIN injection, etc.) + +Five post-events expose a mutable `placeholders()` map that BanManager +applies to the kick message template before disconnecting an online player: + +- `PlayerDeniedEvent` +- `PlayerBannedEvent` +- `IpBannedEvent` +- `IpRangeBannedEvent` +- `NameBannedEvent` + +This is the supported way to inject template variables (e.g. ``, +``) without touching BanManager internals — the same hook +WebEnhancer uses. + +```java +BanManager.get().events().subscribe(PlayerDeniedEvent.class, event -> + event.placeholders().put("appeal_url", "https://bans.example.com/appeal")); +``` + +Then in `messages/en.yml`: + +```yaml +ban: + player: + disallowed: "Banned: \nAppeal at '>" +``` + +#### Exception migration + +| v7 throws | v8 surface | +| -------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `java.sql.SQLException` (checked) | `me.confuser.banmanager.api.exception.StorageException` (unchecked, wraps the cause) | +| `RuntimeException` from cancelled events | Async **and** sync: returns `Optional.empty()` for create-style operations or `false` for delete-style operations. **Cancellation is not exceptional** — `OperationCancelledException` is reserved for future use and is not thrown by the v8 API. | +| `IllegalArgumentException` from invalid input | Same — preserved on the API where it makes sense | +| Misc internal exceptions | `BanManagerException` (root unchecked type — catch this for a wide net) | + +Existing `try { ... } catch (SQLException e) { ... }` blocks must drop the +checked exception or switch to `catch (StorageException e)`. + +#### Database access + +`BmAPI.getLocalConnection()` returned an internal ORMLite `ConnectionSource`. +v8 returns a standard `javax.sql.DataSource` instead so consumers do not +have to depend on shaded ORMLite: + +```java +import javax.sql.DataSource; +import me.confuser.banmanager.api.BanManager; + +DataSource ds = BanManager.get().database().localDataSource(); +try (var conn = ds.getConnection(); + var ps = conn.prepareStatement("SELECT count(*) FROM " + + BanManager.get().database().localTable("playerBans").orElseThrow())) { + // ... +} +``` + +`localTable("playerBans")` resolves the operator's configured table name +(operators may override individual tables in `config.yml`, e.g. for shared +WordPress prefixes). **Never close the returned `DataSource`** — it is owned +by BanManager and shared across the JVM. + +The pool is **not sandboxed** — it exposes the same DB privileges as +BanManager itself (typically full DDL/DML on the configured database). +Reads against any table are safe; writes against tables your plugin owns +are safe; writes against `bm_*` tables will silently desync the in-memory +cache, the event bus, and global-sync replication — use the service +sub-services for those instead. + +If your plugin needs the global (cross-server) database, use +`bm.database().globalDataSource()` which returns +`Optional` (empty when global sync isn't configured). + +#### Database migrations for companion plugins + +Plugins that ship their own SQL migrations can run them against a +BanManager-owned database by calling `MigrationService`: + +```java +import me.confuser.banmanager.api.BanManager; +import me.confuser.banmanager.api.database.DatabaseKind; +import me.confuser.banmanager.api.database.MigrationService.MigrationConfig; + +BanManager.get().migrations().run(new MigrationConfig( + DatabaseKind.LOCAL, + "myplugin", + "db/myplugin", + getClass().getClassLoader() +)); +``` + +Resources expected at `db/myplugin/` on the classpath: + +- `migrations.list` — newline-separated list of migration filenames +- `V1__initial_schema.sql`, `V2__add_index.sql`, … + +The runner is idempotent at the migration-file level and tracks applied +versions in a shared `bm_schema_version` table, namespaced by the `prefix` +argument. Statements within a file execute one at a time and are **not** +wrapped in a transaction (MySQL/MariaDB DDL implicitly commits anyway), so +a failure mid-file leaves earlier statements applied. The version row is +only inserted after every statement in the file succeeds, so re-running +restarts the failed migration from statement #1 — write idempotent +statements (`CREATE TABLE IF NOT EXISTS`, `ADD COLUMN IF NOT EXISTS`, etc.) +or split non-idempotent steps into their own `V*__*.sql` files so a retry +resumes from a clean state. + +Choose a `prefix` value that's unique to your plugin — BanManager rejects +two registrations of the same prefix from different plugin classloaders +to avoid silent `bm_schema_version` collisions. + +#### ORMLite shading guidance + +If you previously depended on `BanManagerCommon` and reflectively touched +storage classes (`PlayerBanStorage`, `PlayerData`, `Message`, etc.) you have +two options: + +1. **Recommended** — switch to the new `BanManagerAPI` artifact. Every read + path you needed has a stable replacement (see the mapping table above) + and you no longer have to deal with shaded packages or classloader + visibility issues. +2. **Companion-plugin escape hatch** — if you genuinely need to share + BanManager's ORMLite instance (e.g. you store entities related to + `PlayerData` and want them in the same database / connection pool): + - Continue depending on `BanManagerCommon` as `compileOnly` / + `provided` and resolve the `BanManagerPlugin` instance via the + platform's plugin manager (e.g. + `Bukkit.getPluginManager().getPlugin("BanManager")` → + `((BMBukkitPlugin) plugin).getPlugin()`). + - Pull `ConnectionSource` from `banManagerPlugin.getLocalConn()`. + - Be aware: `BanManagerCommon` shades ORMLite under + `me.confuser.banmanager.common.ormlite.*`. Do **not** also shade the + vanilla `com.j256.ormlite.*` package — your DAOs must use the shaded + types when sharing the connection. + - This is the path BanManager-WebEnhancer uses; see its source for a + worked example. + +#### Removed: `BanManagerPlugin.getInstance()` + +The static `getInstance()` accessor on `BanManagerPlugin` is gone. Resolve +the instance via the platform plugin manager as shown above. The new +`BanManager.get()` is the supported public path; `BanManagerPlugin` itself +remains an internal class with no semver guarantee. + +#### Schedulers + +Use `bm.scheduler()` (see +`me.confuser.banmanager.api.scheduler.BanManagerScheduler`) for +fire-and-forget async work. **Do not** use it for blocking JDBC — every +write path on the API already returns `CompletableFuture` and runs on a +dedicated DB-I/O executor; submitting blocking work via `runAsync` on +Sponge or Fabric runs on the platform's `ForkJoinPool` and can starve other +plugins. + +`scheduler.runSync` runs on the server tick thread on Bukkit, Sponge and +Fabric, and is documented to alias `runAsync` on Bungee / Velocity (which +are asynchronous proxies). Use `scheduler.isMainThreadAware()` when your +task requires single-threaded ordering. + +--- + +## v7.10 — Java 17 modernisation + +This release modernised the codebase from Java 8 to Java 17 and bumped a +number of bundled dependencies. v8 carries every change in this section +forward; v7 → v8 upgraders can read the v8 [Server upgrade](#server-upgrade-v7--v8) +section, which folds these notes in. + +### Required runtime: Java 17 or newer + +- BanManager now requires **Java 17+** at runtime (Java 21 to build from + source). +- This matches the supported runtimes for Spigot/Paper 1.20+, Velocity 3.3+, + and Sponge API 8+, so no action is normally required for modern servers. +- If you are still running Java 8/11 you must update your JRE before + installing this version. + +### MariaDB JDBC driver upgraded to 3.x + +- The bundled `mariadb-java-client` is now `3.5.x`. The 3.x driver no longer + hijacks `jdbc:mysql://` URLs, and it warns about legacy parameters that the + 2.x line silently accepted. +- BanManager now builds a per-driver JDBC URL automatically based on + `storageType` in `config.yml`, so you should not see `WARN` lines about + unknown options like `autoReconnect`, `serverTimezone`, or + `verifyServerCertificate` after upgrading. +- `useSSL` and `verifyServerCertificate` are translated to MariaDB's + `sslMode` (`disable`, `trust`, or `verify-full`). No `config.yml` changes + are required. +- If you previously set `storageType: mysql` but pointed at a MariaDB server, + consider switching to `storageType: mariadb` so the correct driver is used. + +### MySQL Connector/J upgraded to 8.4.x + +- The shaded `mysql-connector-j` is now `8.4.0`, replacing the legacy + `mysql-connector-java` artifact. +- The legacy `&disableMariaDbDriver` URL fragment has been removed because the + modern MariaDB driver no longer needs to be opted out. + +### SnakeYAML upgraded to 2.x + +- The bundled SnakeYAML jumped from `1.29` to `2.4`. SnakeYAML 2.x flips a + handful of defaults that could otherwise break existing user-edited + configs. BanManager pre-configures the loader to keep the old behaviour: + - `allowDuplicateKeys` is forced back to `true` so a duplicate key in + `messages.yml` won't refuse to load (the last value wins, as before). + - `codePointLimit` is raised to 32 MB so very large translation files keep + loading. + - `nestingDepthLimit` is raised to 100 for deeply nested webhook payloads. +- If you intentionally relied on duplicate-key detection, consider linting + your YAML separately. + +### Bundled bStats / PlaceholderAPI upgrades + +- bStats was bumped to `3.2.1` across all platforms. +- PlaceholderAPI was bumped to `2.12.2` (Bukkit only, soft-dependency). + +### SLF4J upgraded to 2.x on Bukkit + +- The Bukkit module now ships an SLF4J 2.x service-provider implementation so + that ORMLite and HikariCP log through BanManager's own logger rather than + the generic console. +- `disableDatabaseLogging()` is now a no-op on Bukkit (the new provider + filters log levels itself). + +### Tests: JUnit 5 + Mockito 5 + +- Internal change only — the test suite migrated from JUnit 4/Mockito 3 to + JUnit 5/Mockito 5. No effect on the runtime jar. diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 000000000..54ea98719 --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,52 @@ +import com.vanniktech.maven.publish.JavaLibrary +import com.vanniktech.maven.publish.JavadocJar + +plugins { + `java-library` + id("com.vanniktech.maven.publish") +} + +applyPlatformAndCoreConfiguration() + +mavenPublishing { + publishToMavenCentral() + if (project.hasProperty("signingInMemoryKey")) { + signAllPublications() + } + + configure(JavaLibrary( + javadocJar = JavadocJar.Javadoc(), + sourcesJar = true + )) + + pom { + name.set("BanManagerAPI") + description.set("Stable, dependency-light public API for BanManager v8+. Plugin authors integrate against this artifact.") + url.set("https://github.com/BanManagement/BanManager/") + licenses { + license { + name.set("Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales") + url.set("https://github.com/BanManagement/BanManager/blob/master/LICENCE") + } + } + developers { + developer { + id.set("confuser") + name.set("James Mortemore") + email.set("jamesmortemore@gmail.com") + } + } + scm { + connection.set("scm:git:git://github.com/BanManagement/BanManager.git") + developerConnection.set("scm:git:ssh://git@github.com/BanManagement/BanManager.git") + url.set("https://github.com/BanManagement/BanManager/") + } + } +} + +dependencies { + // The single non-JDK dependency. Unshaded so the IPAddress type the API + // exposes carries the canonical inet.ipaddr.* package, not the BM-shaded + // me.confuser.banmanager.common.ipaddr.* prefix. + api("com.github.seancfoley:ipaddress:5.5.1") +} diff --git a/api/src/main/java/me/confuser/banmanager/api/BanManager.java b/api/src/main/java/me/confuser/banmanager/api/BanManager.java new file mode 100644 index 000000000..66b0ffe17 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/BanManager.java @@ -0,0 +1,79 @@ +package me.confuser.banmanager.api; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * Static locator for the platform's {@link BanManagerService} instance. + * + *

This works on every platform (Bukkit, Bungee, Velocity, Sponge, Fabric) + * because the BanManager bootstrap calls {@link #set(BanManagerService)} + * during plugin enable, so {@code BanManager.get()} is the portable + * resolution path. On Bukkit you may alternatively use + * {@code Bukkit.getServicesManager().load(BanManagerService.class)} — the + * other platforms have no plugin-extensible service manager.

+ * + *

Resolution

+ *
    + *
  1. If {@link #set(BanManagerService)} has been called, the registered + * instance is returned. BanManager publishes itself this way on every + * platform during plugin enable.
  2. + *
  3. Otherwise {@link IllegalStateException} is thrown — there is no + * {@link java.util.ServiceLoader} fallback. {@code META-INF/services} + * discovery would only resolve consumer-side stubs, never the running + * plugin (whose classloader is invisible to consumer plugins), so it + * was a footgun and is intentionally absent.
  4. + *
+ * + *

Tests

+ * Unit tests should construct a service implementation (or test double) + * directly and call {@link #set(BanManagerService)} in {@code @BeforeEach}, + * then {@link #clear()} in {@code @AfterEach}. + */ +public final class BanManager { + + private static final AtomicReference INSTANCE = new AtomicReference<>(); + + private BanManager() {} + + /** + * @return the active service instance + * @throws IllegalStateException when BanManager has not finished enabling + * yet, or when running outside a BanManager + * environment + */ + public static BanManagerService get() { + BanManagerService current = INSTANCE.get(); + if (current != null) return current; + throw new IllegalStateException( + "BanManagerService has not been registered. Either BanManager hasn't enabled yet" + + " or you're running outside a server with the BanManager plugin installed."); + } + + /** + * Register the active service. Called once by the platform plugin on + * enable. {@code /bmreload} mutates the existing service in place rather + * than re-publishing — consumer plugins should subscribe to + * {@link me.confuser.banmanager.api.event.player.PluginReloadedEvent} to + * re-register listeners after a reload, not poll {@link #get()} for a + * fresh reference. Calls during a normal disable/enable cycle replace the + * previous instance. + */ + public static void set(BanManagerService service) { + INSTANCE.set(service); + } + + /** + * Clear the registered service. Called on plugin disable to avoid + * retaining classloaders. + */ + public static void clear() { + INSTANCE.set(null); + } + + /** + * @return {@code true} when {@link #get()} would succeed + */ + public static boolean isAvailable() { + return INSTANCE.get() != null; + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/BanManagerService.java b/api/src/main/java/me/confuser/banmanager/api/BanManagerService.java new file mode 100644 index 000000000..52976ff1f --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/BanManagerService.java @@ -0,0 +1,69 @@ +package me.confuser.banmanager.api; + +import me.confuser.banmanager.api.database.DatabaseAccess; +import me.confuser.banmanager.api.database.MigrationService; +import me.confuser.banmanager.api.event.EventBus; +import me.confuser.banmanager.api.scheduler.BanManagerScheduler; +import me.confuser.banmanager.api.service.BanService; +import me.confuser.banmanager.api.service.HistoryService; +import me.confuser.banmanager.api.service.IpBanService; +import me.confuser.banmanager.api.service.IpMuteService; +import me.confuser.banmanager.api.service.IpRangeBanService; +import me.confuser.banmanager.api.service.MuteService; +import me.confuser.banmanager.api.service.NameBanService; +import me.confuser.banmanager.api.service.NoteService; +import me.confuser.banmanager.api.service.PlayerService; +import me.confuser.banmanager.api.service.ReportService; +import me.confuser.banmanager.api.service.WarnService; + +/** + * Root entry point for the BanManager API. Resolve via either + * {@link BanManager#get()} (works everywhere) or, on Bukkit, the platform's + * native services manager: + * + *
{@code
+ * // Portable across Bukkit, Bungee, Velocity, Sponge, Fabric:
+ * BanManagerService bm = BanManager.get();
+ *
+ * // Bukkit also publishes the service via the Bukkit ServicesManager:
+ * BanManagerService bm = Bukkit.getServicesManager().load(BanManagerService.class);
+ * }
+ * + *

Velocity, Sponge, Bungee and Fabric have no plugin-extensible service + * manager that fits this use case, so {@link BanManager#get()} is the + * recommended path on those platforms.

+ * + *

Subservices are stable references — fetch them once at startup and reuse.

+ */ +public interface BanManagerService { + + PlayerService players(); + + BanService bans(); + + MuteService mutes(); + + WarnService warnings(); + + IpBanService ipBans(); + + IpMuteService ipMutes(); + + IpRangeBanService ipRangeBans(); + + NameBanService nameBans(); + + NoteService notes(); + + ReportService reports(); + + HistoryService history(); + + EventBus events(); + + DatabaseAccess database(); + + BanManagerScheduler scheduler(); + + MigrationService migrations(); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/Page.java b/api/src/main/java/me/confuser/banmanager/api/Page.java new file mode 100644 index 000000000..78800ac51 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/Page.java @@ -0,0 +1,52 @@ +package me.confuser.banmanager.api; + +import java.util.List; +import java.util.Objects; + +/** + * Immutable page of results. Replaces every {@code CloseableIterator} + * return on the public API surface so consumers never have to remember to + * {@code close()}. + * + * @param items the items in this page (never {@code null}) + * @param page the zero-indexed page number this {@code Page} represents + * (must be {@code >= 0}) + * @param size the requested page size (must be {@code > 0}); always reflects + * what the caller asked for, never the number of items actually + * returned + * @param total the total number of records matching the query, or {@code -1} + * when the storage layer cannot cheaply compute it + * @param element type + */ +public record Page(List items, int page, int size, long total) { + + public Page { + Objects.requireNonNull(items, "items"); + if (page < 0) throw new IllegalArgumentException("page must be >= 0"); + if (size <= 0) throw new IllegalArgumentException("size must be > 0"); + items = List.copyOf(items); + } + + /** + * @return {@code true} if a {@link #page() page+1} call may yield more results + */ + public boolean hasMore() { + if (total < 0) { + return items.size() == size; + } + return ((long) (page + 1) * size) < total; + } + + /** + * Empty page that preserves the caller's pagination request. Use when a + * lookup precondition is not satisfied (e.g. unknown player) — the + * {@code page}/{@code size} round-trip lets clients render the correct + * pager UI without re-asking. + * + * @param page zero-indexed page number that was requested + * @param size page size that was requested ({@code > 0}) + */ + public static Page empty(int page, int size) { + return new Page<>(List.of(), page, size, 0L); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/database/DatabaseAccess.java b/api/src/main/java/me/confuser/banmanager/api/database/DatabaseAccess.java new file mode 100644 index 000000000..d0be5faef --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/database/DatabaseAccess.java @@ -0,0 +1,67 @@ +package me.confuser.banmanager.api.database; + +import javax.sql.DataSource; + +import java.util.Optional; + +/** + * Direct access to the {@link DataSource}s that back BanManager. Useful for + * companion plugins that need to issue custom queries against BanManager's + * tables (e.g. BanManager-WebEnhancer for its forum-specific reporting + * queries) or that ship their own tables in the same database (typically + * created via {@link MigrationService}). + * + *

Privilege scope

+ *

The returned pools are not sandboxed. They expose the + * same database privileges as BanManager itself — typically full + * {@code SELECT/INSERT/UPDATE/DELETE/DDL} on the configured database — so a + * misbehaving consumer can mutate {@code bm_*} tables out from under the + * service layer. Treat them like raw JDBC:

+ * + *
    + *
  • Reads against any table — safe.
  • + *
  • Writes against tables your plugin owns (typically created via + * {@link MigrationService}) — safe.
  • + *
  • Writes against {@code bm_*} tablesdo not. Use the + * {@code BanManagerService} sub-services instead so the cache layer, + * event bus, and global-sync replication stay coherent.
  • + *
+ * + *

Do not close the returned {@link DataSource} instances; they are + * owned by BanManager and shared across the JVM. Closing one shuts the pool + * for every consumer (including BanManager itself).

+ * + *

Table name lookup

+ *

BanManager allows operators to rename individual tables in + * {@code config.yml} (e.g. for WordPress-style {@code wp_} prefixes); use + * {@link #localTable(String)} / {@link #globalTable(String)} to resolve a + * logical key like {@code "playerBans"} to the configured SQL table name. + * The full list of logical keys is documented under {@code config.yml -> + * databases.local.tables}.

+ */ +public interface DatabaseAccess { + + /** + * @return the local (single-server) database that BanManager always uses + */ + DataSource localDataSource(); + + /** + * @return the optional global database, when the operator has configured + * cross-server sync. Empty otherwise. + */ + Optional globalDataSource(); + + /** + * Resolve a logical table key (e.g. {@code "players"}, {@code "playerBans"}) + * to the configured SQL table name in the local database. + * + * @return the SQL table name, or empty when the key is unknown + */ + Optional localTable(String logicalName); + + /** + * Same as {@link #localTable(String)} but for the global database. + */ + Optional globalTable(String logicalName); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/database/DatabaseKind.java b/api/src/main/java/me/confuser/banmanager/api/database/DatabaseKind.java new file mode 100644 index 000000000..a137edd8e --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/database/DatabaseKind.java @@ -0,0 +1,25 @@ +package me.confuser.banmanager.api.database; + +/** + * Logical identifier for one of BanManager's two database connection + * pools. Used wherever the API needs the caller to name a database + * without exposing the underlying {@link javax.sql.DataSource} (which + * would invite reference-equality bugs when callers pass wrapped or + * proxied instances). + */ +public enum DatabaseKind { + + /** + * The local database. Always present — every BanManager install has a + * local database for player records and history. + */ + LOCAL, + + /** + * The optional global database. Only present when the operator has + * configured cross-server sharing; otherwise + * {@link DatabaseAccess#globalDataSource()} returns + * {@link java.util.Optional#empty()}. + */ + GLOBAL +} diff --git a/api/src/main/java/me/confuser/banmanager/api/database/MigrationService.java b/api/src/main/java/me/confuser/banmanager/api/database/MigrationService.java new file mode 100644 index 000000000..2a4618542 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/database/MigrationService.java @@ -0,0 +1,112 @@ +package me.confuser.banmanager.api.database; + +/** + * Runs SQL migration scripts bundled inside a plugin's JAR against an + * arbitrary database. Reuses BanManager's MariaDB-aware migration runner so + * companion plugins (WebEnhancer, etc.) do not have to ship their own. + */ +public interface MigrationService { + + /** + * Run the migrations described by {@code config}. Idempotent at the + * file level — applied migrations are tracked in the shared + * {@code bm_schema_version} table, scoped by the + * {@link MigrationConfig#prefix()} value, so re-running this method + * applies only the deltas that haven't been recorded yet. + * + *

Concurrency

+ *

The runner takes a database-level advisory lock + * ({@code GET_LOCK('bm_migration_', 30)} on MySQL/MariaDB) for + * the duration of the run, so a clustered deployment will not + * double-apply migrations even if two nodes restart simultaneously. The + * lock is skipped on H2 (single-process anyway).

+ * + *

Per-statement semantics — IMPORTANT

+ *

Statements within a single migration file execute + * one at a time and are not wrapped in a + * JDBC transaction. On MySQL/MariaDB this is a hard limitation: DDL + * (CREATE/ALTER/DROP) implicitly commits. The runner has the following + * partial-failure behaviour:

+ *
    + *
  • If a statement throws and the migration is not marked + * {@code lenient} in {@code migrations.list}, the exception + * propagates as {@code StorageException} and the version row is + * not inserted. Re-running this method will + * restart the failed migration from statement #1.
  • + *
  • If the migration is marked {@code lenient}, individual + * statement failures are logged and the run continues; the version + * row is still inserted at the end.
  • + *
+ * + *

The combination of advisory locking + late version-row insert means + * a partial failure cannot corrupt the + * {@code bm_schema_version} table — but the database itself can end up + * with half a migration applied (e.g. a new column exists but the + * follow-up index does not). To make re-runs safe, write idempotent + * statements:

+ *
{@code
+   * CREATE TABLE IF NOT EXISTS my_plugin_audit (...);
+   * ALTER TABLE my_plugin_audit ADD COLUMN IF NOT EXISTS ip VARBINARY(16);
+   * CREATE INDEX IF NOT EXISTS idx_audit_actor ON my_plugin_audit (actor);
+   * }
+ * + *

Where the SQL dialect lacks {@code IF NOT EXISTS} for a particular + * DDL form (older MariaDB indexes, for example), prefer splitting the + * non-idempotent step into its own {@code V*__*.sql} file so a retry + * resumes from a clean state.

+ * + *

Prefix scoping

+ *

Choose a distinct {@link MigrationConfig#prefix()} per plugin — + * BanManager rejects two registrations of the same prefix from different + * plugin classloaders to avoid silent {@code bm_schema_version} + * collisions.

+ * + * @throws me.confuser.banmanager.api.exception.StorageException + * when the migration fails (cause holds the SQL exception) + * @throws me.confuser.banmanager.api.exception.BanManagerException + * when the requested database is not configured (e.g. + * {@link DatabaseKind#GLOBAL} when no global DB is set up), or + * when the {@code prefix} collides with another plugin's + * registration + */ + void run(MigrationConfig config); + + /** + * Configuration for {@link MigrationService#run(MigrationConfig)}. + * + * @param database which BanManager-owned database to migrate. The + * {@link DatabaseKind} is resolved internally to the + * matching pool and ORMLite {@code ConnectionSource} so + * no {@link javax.sql.DataSource} round-tripping is + * required (and reference-equality bugs are impossible). + * @param prefix unique identifier for this set of migrations, used to + * namespace rows in the shared {@code bm_schema_version} + * table (e.g. {@code "webenhancer"}) + * @param resourcePath classpath directory containing + * {@code migrations.list} and the {@code V*__*.sql} + * files (e.g. {@code "db/webenhancer"}) + * @param classLoader classloader to load the resources from (typically + * the caller's plugin classloader) + * @param detectionTable optional logical-table name (matching a key in + * {@link DatabaseAccess#localTable(String)}) that + * must exist before any migrations are applied; when + * it is absent the runner marks the install as fresh + * and stamps the latest version without executing + * any SQL. Pass {@code null} to always apply + * migrations from V1. + */ + record MigrationConfig(DatabaseKind database, String prefix, String resourcePath, + ClassLoader classLoader, String detectionTable) { + + public MigrationConfig { + if (database == null) throw new IllegalArgumentException("database"); + if (prefix == null) throw new IllegalArgumentException("prefix"); + if (resourcePath == null) throw new IllegalArgumentException("resourcePath"); + if (classLoader == null) throw new IllegalArgumentException("classLoader"); + } + + public MigrationConfig(DatabaseKind database, String prefix, String resourcePath, ClassLoader classLoader) { + this(database, prefix, resourcePath, classLoader, null); + } + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/HistoryEntry.java b/api/src/main/java/me/confuser/banmanager/api/dto/HistoryEntry.java new file mode 100644 index 000000000..5b4359c59 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/HistoryEntry.java @@ -0,0 +1,26 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Cross-table history entry combining bans, mutes, warnings, kicks etc. + * Returned by {@link me.confuser.banmanager.api.service.HistoryService}. + * + * @param id row id within the originating table + * @param type one of {@code "ban"}, {@code "mute"}, {@code "warning"}, + * {@code "kick"}, {@code "note"}, etc. + * @param actor display name of the actor who took the action + * @param created unix timestamp seconds when the action was taken + * @param reason the reason or description + * @param meta storage-specific metadata as a JSON-serialised string (may be + * empty) + */ +public record HistoryEntry(int id, String type, String actor, long created, String reason, String meta) { + + public HistoryEntry { + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(meta, "meta"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/IpBan.java b/api/src/main/java/me/confuser/banmanager/api/dto/IpBan.java new file mode 100644 index 000000000..63b31e7d1 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/IpBan.java @@ -0,0 +1,44 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; + +/** + * Active ban on a single IP address. + * + * @param id storage row id + * @param ip the banned IP address + * @param actor who issued the ban + * @param reason ban reason + * @param created unix timestamp seconds the ban was created + * @param updated unix timestamp seconds the ban was last updated + * @param expires unix timestamp seconds the ban expires; {@code 0} means + * permanent + * @param silent whether the ban is silent (no broadcast) + */ +public record IpBan( + int id, + IPAddress ip, + Player actor, + String reason, + long created, + long updated, + long expires, + boolean silent +) { + + public IpBan { + Objects.requireNonNull(ip, "ip"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } + + public boolean isPermanent() { + return expires == 0; + } + + public boolean hasExpired() { + return expires != 0 && expires <= (System.currentTimeMillis() / 1000L); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/IpBanRecord.java b/api/src/main/java/me/confuser/banmanager/api/dto/IpBanRecord.java new file mode 100644 index 000000000..ea9015ff1 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/IpBanRecord.java @@ -0,0 +1,30 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; + +/** + * Historical IP ban record. + */ +public record IpBanRecord( + int id, + IPAddress ip, + Player actor, + Player pastActor, + String reason, + String createdReason, + long expired, + long pastCreated, + long created, + boolean silent +) { + + public IpBanRecord { + Objects.requireNonNull(ip, "ip"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(pastActor, "pastActor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(createdReason, "createdReason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/IpMute.java b/api/src/main/java/me/confuser/banmanager/api/dto/IpMute.java new file mode 100644 index 000000000..c70608dc2 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/IpMute.java @@ -0,0 +1,35 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; + +/** + * Active mute on a single IP address. + */ +public record IpMute( + int id, + IPAddress ip, + Player actor, + String reason, + long created, + long updated, + long expires, + boolean soft, + boolean silent +) { + + public IpMute { + Objects.requireNonNull(ip, "ip"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } + + public boolean isPermanent() { + return expires == 0; + } + + public boolean hasExpired() { + return expires != 0 && expires <= (System.currentTimeMillis() / 1000L); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/IpMuteRecord.java b/api/src/main/java/me/confuser/banmanager/api/dto/IpMuteRecord.java new file mode 100644 index 000000000..ac9e292b7 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/IpMuteRecord.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; + +/** + * Historical IP mute record. + */ +public record IpMuteRecord( + int id, + IPAddress ip, + Player actor, + Player pastActor, + String reason, + String createdReason, + long expired, + long pastCreated, + long created, + boolean soft, + boolean silent +) { + + public IpMuteRecord { + Objects.requireNonNull(ip, "ip"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(pastActor, "pastActor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(createdReason, "createdReason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/IpRangeBan.java b/api/src/main/java/me/confuser/banmanager/api/dto/IpRangeBan.java new file mode 100644 index 000000000..d70390f69 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/IpRangeBan.java @@ -0,0 +1,36 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; + +/** + * Active ban on a contiguous IP range (inclusive on both ends). + */ +public record IpRangeBan( + int id, + IPAddress fromIp, + IPAddress toIp, + Player actor, + String reason, + long created, + long updated, + long expires, + boolean silent +) { + + public IpRangeBan { + Objects.requireNonNull(fromIp, "fromIp"); + Objects.requireNonNull(toIp, "toIp"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } + + public boolean isPermanent() { + return expires == 0; + } + + public boolean hasExpired() { + return expires != 0 && expires <= (System.currentTimeMillis() / 1000L); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/IpRangeBanRecord.java b/api/src/main/java/me/confuser/banmanager/api/dto/IpRangeBanRecord.java new file mode 100644 index 000000000..e39f36bcc --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/IpRangeBanRecord.java @@ -0,0 +1,32 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; + +/** + * Historical IP range ban record. + */ +public record IpRangeBanRecord( + int id, + IPAddress fromIp, + IPAddress toIp, + Player actor, + Player pastActor, + String reason, + String createdReason, + long expired, + long pastCreated, + long created, + boolean silent +) { + + public IpRangeBanRecord { + Objects.requireNonNull(fromIp, "fromIp"); + Objects.requireNonNull(toIp, "toIp"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(pastActor, "pastActor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(createdReason, "createdReason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/NameBan.java b/api/src/main/java/me/confuser/banmanager/api/dto/NameBan.java new file mode 100644 index 000000000..cc253a56a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/NameBan.java @@ -0,0 +1,32 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Active ban on a player name. + */ +public record NameBan( + int id, + String name, + Player actor, + String reason, + long created, + long updated, + long expires, + boolean silent +) { + + public NameBan { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } + + public boolean isPermanent() { + return expires == 0; + } + + public boolean hasExpired() { + return expires != 0 && expires <= (System.currentTimeMillis() / 1000L); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/NameBanRecord.java b/api/src/main/java/me/confuser/banmanager/api/dto/NameBanRecord.java new file mode 100644 index 000000000..f130c9986 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/NameBanRecord.java @@ -0,0 +1,28 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Historical name ban record. + */ +public record NameBanRecord( + int id, + String name, + Player actor, + Player pastActor, + String reason, + String createdReason, + long expired, + long pastCreated, + long created, + boolean silent +) { + + public NameBanRecord { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(pastActor, "pastActor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(createdReason, "createdReason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/Player.java b/api/src/main/java/me/confuser/banmanager/api/dto/Player.java new file mode 100644 index 000000000..db196a576 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/Player.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * Immutable view of a known player. + * + * @param uuid the player's Mojang UUID + * @param name the player's last-known name + * @param ip the player's last-known IP address (may be {@code null} for the + * console actor or never-seen players) + * @param lastSeen unix timestamp in seconds of the last login or activity + * @param locale the player's selected locale, never {@code null} but possibly + * {@code Optional.empty()} + */ +public record Player(UUID uuid, String name, IPAddress ip, long lastSeen, Optional locale) { + + public Player { + Objects.requireNonNull(uuid, "uuid"); + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(locale, "locale"); + } + + public Player(UUID uuid, String name, IPAddress ip, long lastSeen) { + this(uuid, name, ip, lastSeen, Optional.empty()); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerBan.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerBan.java new file mode 100644 index 000000000..34bf8bf85 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerBan.java @@ -0,0 +1,43 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Active ban on a player. Use {@link me.confuser.banmanager.api.dto.PlayerBanRecord} + * to inspect previous (expired or removed) bans. + * + * @param id storage row id + * @param player the banned player + * @param actor who issued the ban (may be the console) + * @param reason ban reason + * @param created unix timestamp seconds the ban was created + * @param updated unix timestamp seconds the ban was last updated + * @param expires unix timestamp seconds the ban expires; {@code 0} means + * permanent + * @param silent whether the ban is silent (no broadcast) + */ +public record PlayerBan( + int id, + Player player, + Player actor, + String reason, + long created, + long updated, + long expires, + boolean silent +) { + + public PlayerBan { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } + + public boolean isPermanent() { + return expires == 0; + } + + public boolean hasExpired() { + return expires != 0 && expires <= (System.currentTimeMillis() / 1000L); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerBanRecord.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerBanRecord.java new file mode 100644 index 000000000..23961ebd8 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerBanRecord.java @@ -0,0 +1,40 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Historical ban record. Created when an active {@link PlayerBan} is + * unbanned without {@code delete=true}. + * + * @param id storage row id + * @param player who was banned + * @param actor who unbanned the player + * @param pastActor who originally issued the ban + * @param reason the original ban reason + * @param createdReason the unban reason (may be empty) + * @param expired the {@code expires} value of the original ban + * @param pastCreated when the original ban was created + * @param created when the unban happened + * @param silent whether the original ban was silent + */ +public record PlayerBanRecord( + int id, + Player player, + Player actor, + Player pastActor, + String reason, + String createdReason, + long expired, + long pastCreated, + long created, + boolean silent +) { + + public PlayerBanRecord { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(pastActor, "pastActor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(createdReason, "createdReason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerMute.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerMute.java new file mode 100644 index 000000000..3165109c8 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerMute.java @@ -0,0 +1,55 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Active mute on a player. + * + * @param id storage row id + * @param player the muted player + * @param actor who issued the mute + * @param reason mute reason + * @param created unix timestamp seconds the mute was created + * @param updated unix timestamp seconds the mute was last updated + * @param expires unix timestamp seconds the mute expires; {@code 0} means + * permanent or {@link #onlineOnly() online-only} + * @param soft whether the mute is soft (the player still sees their own + * messages) + * @param silent whether the mute is silent (no broadcast) + * @param onlineOnly whether the mute clock only ticks while the player is + * online + * @param pausedRemaining seconds remaining when the player last logged off, + * if {@link #onlineOnly()} is {@code true} + */ +public record PlayerMute( + int id, + Player player, + Player actor, + String reason, + long created, + long updated, + long expires, + boolean soft, + boolean silent, + boolean onlineOnly, + long pausedRemaining +) { + + public PlayerMute { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } + + public boolean isPermanent() { + return expires == 0 && !onlineOnly; + } + + public boolean hasExpired() { + return expires != 0 && expires <= (System.currentTimeMillis() / 1000L); + } + + public boolean isPaused() { + return onlineOnly && pausedRemaining > 0; + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerMuteRecord.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerMuteRecord.java new file mode 100644 index 000000000..9109fec92 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerMuteRecord.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Historical mute record. + */ +public record PlayerMuteRecord( + int id, + Player player, + Player actor, + Player pastActor, + String reason, + String createdReason, + long expired, + long pastCreated, + long created, + boolean soft, + boolean silent, + boolean onlineOnly, + long remainingOnlineTime +) { + + public PlayerMuteRecord { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(pastActor, "pastActor"); + Objects.requireNonNull(reason, "reason"); + Objects.requireNonNull(createdReason, "createdReason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerNameSummary.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerNameSummary.java new file mode 100644 index 000000000..38f35d484 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerNameSummary.java @@ -0,0 +1,18 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Aggregated summary of a player's usage of a particular name. + * + * @param name the name + * @param firstSeen earliest known {@code join} for this name (unix seconds) + * @param lastSeen latest known {@code leave} (or current time for an active + * session) for this name (unix seconds) + */ +public record PlayerNameSummary(String name, long firstSeen, long lastSeen) { + + public PlayerNameSummary { + Objects.requireNonNull(name, "name"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerNote.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerNote.java new file mode 100644 index 000000000..506d04a03 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerNote.java @@ -0,0 +1,27 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Internal note attached to a player. + * + * @param id storage row id + * @param player the note subject + * @param actor who wrote the note + * @param message note body + * @param created unix timestamp seconds the note was created + */ +public record PlayerNote( + int id, + Player player, + Player actor, + String message, + long created +) { + + public PlayerNote { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(message, "message"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerReport.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerReport.java new file mode 100644 index 000000000..6ff34f6d4 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerReport.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; +import java.util.Optional; + +/** + * Report filed against a player. + * + * @param assignee the staff member currently handling the report; empty + * when nobody has claimed it + * @param state the workflow state ({@code Open}, {@code Assigned} etc.) + */ +public record PlayerReport( + int id, + Player player, + Player actor, + Optional assignee, + ReportState state, + String reason, + long created, + long updated +) { + + public PlayerReport { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(assignee, "assignee"); + Objects.requireNonNull(state, "state"); + Objects.requireNonNull(reason, "reason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerSession.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerSession.java new file mode 100644 index 000000000..b7479fc45 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerSession.java @@ -0,0 +1,33 @@ +package me.confuser.banmanager.api.dto; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; +import java.util.Optional; + +/** + * Single login/logout session entry from the player history table. + * + * @param id storage row id + * @param player the player record (UUID + last-known name/ip) + * @param name the name in use during this session + * @param ip the IP in use during this session, when IP logging is enabled + * @param join unix timestamp seconds when the session began + * @param leave unix timestamp seconds when the session ended; {@code 0} for + * an open session + */ +public record PlayerSession( + int id, + Player player, + String name, + Optional ip, + long join, + long leave +) { + + public PlayerSession { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(ip, "ip"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/PlayerWarn.java b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerWarn.java new file mode 100644 index 000000000..dc41503c1 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/PlayerWarn.java @@ -0,0 +1,34 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Warning issued to a player. + * + * @param id storage row id + * @param player the warned player + * @param actor who issued the warning + * @param reason warning reason + * @param points warning point value (defaults to 1.0) + * @param read whether the warned player has acknowledged the warning + * @param created unix timestamp seconds the warning was created + * @param expires unix timestamp seconds the warning expires; {@code 0} means + * permanent + */ +public record PlayerWarn( + int id, + Player player, + Player actor, + String reason, + double points, + boolean read, + long created, + long expires +) { + + public PlayerWarn { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/dto/ReportState.java b/api/src/main/java/me/confuser/banmanager/api/dto/ReportState.java new file mode 100644 index 000000000..9675c51b2 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/dto/ReportState.java @@ -0,0 +1,15 @@ +package me.confuser.banmanager.api.dto; + +import java.util.Objects; + +/** + * Lookup row identifying a report's workflow state. Names are configurable + * per server but defaults are {@code Open}, {@code Assigned}, + * {@code Resolved}. + */ +public record ReportState(int id, String name) { + + public ReportState { + Objects.requireNonNull(name, "name"); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/AbstractCancellableEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/AbstractCancellableEvent.java new file mode 100644 index 000000000..4fa110d81 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/AbstractCancellableEvent.java @@ -0,0 +1,28 @@ +package me.confuser.banmanager.api.event; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Convenience base class for {@link CancellableEvent} implementations. + * Thread-safe so handlers running on different threads can read the + * cancellation state. + */ +public abstract class AbstractCancellableEvent implements CancellableEvent { + + private final AtomicBoolean cancelled = new AtomicBoolean(); + + @Override + public boolean isCancelled() { + return cancelled.get(); + } + + @Override + public void cancel() { + cancelled.set(true); + } + + @Override + public void uncancel() { + cancelled.set(false); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/BanManagerEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/BanManagerEvent.java new file mode 100644 index 000000000..2586b7d24 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/BanManagerEvent.java @@ -0,0 +1,10 @@ +package me.confuser.banmanager.api.event; + +/** + * Marker interface for every event published through {@link EventBus}. + * + *

Implementations are sealed to the BanManager API module. Plugin authors + * subscribe via {@link EventBus#subscribe(Class, java.util.function.Consumer)}.

+ */ +public interface BanManagerEvent { +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/CancellableEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/CancellableEvent.java new file mode 100644 index 000000000..b96a50bdf --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/CancellableEvent.java @@ -0,0 +1,24 @@ +package me.confuser.banmanager.api.event; + +/** + * An event that handlers can veto. When {@link #isCancelled()} returns + * {@code true} after dispatch, the originating action is aborted. + */ +public interface CancellableEvent extends BanManagerEvent { + + /** + * @return {@code true} if a handler has cancelled this event + */ + boolean isCancelled(); + + /** + * Mark this event as cancelled, preventing the action from completing. + */ + void cancel(); + + /** + * Restore the event to its uncancelled state. Useful if a later handler + * wants to override an earlier cancellation. + */ + void uncancel(); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/EventBus.java b/api/src/main/java/me/confuser/banmanager/api/event/EventBus.java new file mode 100644 index 000000000..6cb5d70b9 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/EventBus.java @@ -0,0 +1,75 @@ +package me.confuser.banmanager.api.event; + +import java.util.function.Consumer; + +/** + * Typed event bus. The single place where plugins subscribe to BanManager + * events; replaces the v7 platform-specific event classes. + * + *

Pre-events carry mutable {@code *Request} payloads that handlers may + * modify before the database write occurs; post-events carry immutable + * record DTOs.

+ * + *

Threading model — IMPORTANT

+ *

Dispatch is synchronous on the publishing thread, + * preserving v7 {@code callEvent(...)} semantics. Because BanManager's + * storage layer publishes events from its own dedicated DB-I/O executor, + * subscribers run off the server tick thread by default. + * Two consequences follow:

+ * + *
    + *
  1. Subscribers must return promptly. A slow handler (target + * budget: under ~10 ms steady-state, never any blocking I/O) + * holds up every subsequent ban / mute / warn write because + * it blocks the DB-I/O executor's worker. Offload heavy work to + * {@code BanManagerService#scheduler().runAsync(...)} and return.
  2. + *
  3. Subscribers may not call platform APIs that require the server + * tick thread (Bukkit {@code World}, etc.) without first + * hopping back via {@code scheduler().runSync(...)} — and only on + * {@link me.confuser.banmanager.api.scheduler.BanManagerScheduler#isMainThreadAware() + * main-thread-aware} platforms.
  4. + *
+ * + *

Errors thrown by a subscriber are caught, logged with the handler's + * class name, and do not abort dispatch; the next listener still fires.

+ * + *

Cancellation

+ *

For {@link CancellableEvent} subclasses, calling + * {@link CancellableEvent#cancel()} from a higher-priority handler stops + * the operation. Lower-priority handlers do not see cancelled events + * unless they registered with {@code ignoreCancelled = false} via + * {@link #subscribe(Class, EventPriority, boolean, Consumer)}. See + * {@link EventPriority} for ordering.

+ * + * @see me.confuser.banmanager.api.event.player.PlayerBanEvent + * @see me.confuser.banmanager.api.event.player.PlayerBannedEvent + * @see me.confuser.banmanager.api.scheduler.BanManagerScheduler + */ +public interface EventBus { + + /** + * Subscribe to {@code type} at {@link EventPriority#NORMAL} priority. + * + * @return a handle that can be used to {@link Subscription#unsubscribe()} + */ + Subscription subscribe(Class type, Consumer handler); + + /** + * Subscribe to {@code type} at the requested priority. + */ + Subscription subscribe(Class type, EventPriority priority, Consumer handler); + + /** + * Subscribe and additionally receive cancelled cancellable events. By + * default, cancellable events are not redelivered to lower-priority + * handlers once cancelled. + */ + Subscription subscribe(Class type, EventPriority priority, boolean ignoreCancelled, Consumer handler); + + /** + * Publish an event. Returns the same instance for inspection (useful for + * testing storage code paths that need to know whether the event was + * cancelled). + */ + E publish(E event); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/EventPriority.java b/api/src/main/java/me/confuser/banmanager/api/event/EventPriority.java new file mode 100644 index 000000000..06100b45a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/EventPriority.java @@ -0,0 +1,21 @@ +package me.confuser.banmanager.api.event; + +/** + * Subscription priority. Lower values run first. + * + *

Within a priority bucket, subscriptions run in registration order.

+ * + * @see EventBus#subscribe(Class, EventPriority, java.util.function.Consumer) + */ +public enum EventPriority { + LOWEST, + LOW, + NORMAL, + HIGH, + HIGHEST, + /** + * Runs after all other priorities. Use sparingly: handlers at this level + * see the final state of mutable pre-events and so cannot be overridden. + */ + MONITOR +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/Subscription.java b/api/src/main/java/me/confuser/banmanager/api/event/Subscription.java new file mode 100644 index 000000000..8dc6145bd --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/Subscription.java @@ -0,0 +1,21 @@ +package me.confuser.banmanager.api.event; + +/** + * Handle to a registered event subscription. Calling {@link #unsubscribe()} + * removes the listener; subsequent calls are no-ops. + * + *

Plugins should keep their subscriptions in a list and unsubscribe on + * shutdown / reload to avoid retaining classloaders.

+ */ +public interface Subscription { + + /** + * @return {@code true} once {@link #unsubscribe()} has been called + */ + boolean isCancelled(); + + /** + * Detach this listener from the event bus. + */ + void unsubscribe(); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpBanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpBanEvent.java new file mode 100644 index 000000000..ebf01f401 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpBanEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.IpBanRequest; + +import java.util.Objects; + +public final class IpBanEvent extends AbstractCancellableEvent { + + private final IpBanRequest request; + + public IpBanEvent(IpBanRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public IpBanRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpBannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpBannedEvent.java new file mode 100644 index 000000000..d224943a6 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpBannedEvent.java @@ -0,0 +1,37 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Post-event fired after an IP ban has been persisted. + * + *

Handlers may add entries to {@link #placeholders()}; when the IP ban was + * triggered against players who are currently connected from that IP, + * BanManager applies the resulting map to the kick-message template before + * disconnecting them.

+ */ +public final class IpBannedEvent implements BanManagerEvent { + + private final IpBan ban; + private final boolean silent; + private final Map placeholders = new HashMap<>(); + + public IpBannedEvent(IpBan ban, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.silent = silent; + } + + public IpBan ban() { return ban; } + public boolean silent() { return silent; } + + /** + * Mutable placeholder map applied to the kick message template when + * matching players are online. See {@link IpBannedEvent} javadoc. + */ + public Map placeholders() { return placeholders; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpMuteEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpMuteEvent.java new file mode 100644 index 000000000..e82b0e181 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpMuteEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.IpMuteRequest; + +import java.util.Objects; + +public final class IpMuteEvent extends AbstractCancellableEvent { + + private final IpMuteRequest request; + + public IpMuteEvent(IpMuteRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public IpMuteRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpMutedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpMutedEvent.java new file mode 100644 index 000000000..98ea95a23 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpMutedEvent.java @@ -0,0 +1,20 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class IpMutedEvent implements BanManagerEvent { + + private final IpMute mute; + private final boolean silent; + + public IpMutedEvent(IpMute mute, boolean silent) { + this.mute = Objects.requireNonNull(mute, "mute"); + this.silent = silent; + } + + public IpMute mute() { return mute; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeBanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeBanEvent.java new file mode 100644 index 000000000..9c2e35f39 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeBanEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.IpRangeBanRequest; + +import java.util.Objects; + +public final class IpRangeBanEvent extends AbstractCancellableEvent { + + private final IpRangeBanRequest request; + + public IpRangeBanEvent(IpRangeBanRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public IpRangeBanRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeBannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeBannedEvent.java new file mode 100644 index 000000000..404589e4c --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeBannedEvent.java @@ -0,0 +1,37 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Post-event fired after an IP-range ban has been persisted. + * + *

Handlers may add entries to {@link #placeholders()}; when the range ban + * was triggered against players who are currently connected from a matching + * IP, BanManager applies the resulting map to the kick-message template + * before disconnecting them.

+ */ +public final class IpRangeBannedEvent implements BanManagerEvent { + + private final IpRangeBan ban; + private final boolean silent; + private final Map placeholders = new HashMap<>(); + + public IpRangeBannedEvent(IpRangeBan ban, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.silent = silent; + } + + public IpRangeBan ban() { return ban; } + public boolean silent() { return silent; } + + /** + * Mutable placeholder map applied to the kick message template when + * matching players are online. See {@link IpRangeBannedEvent} javadoc. + */ + public Map placeholders() { return placeholders; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeUnbanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeUnbanEvent.java new file mode 100644 index 000000000..da1e5abc6 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeUnbanEvent.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.AbstractCancellableEvent; + +import java.util.Objects; + +public final class IpRangeUnbanEvent extends AbstractCancellableEvent { + + private final IpRangeBan ban; + private final Player actor; + private String reason; + private boolean silent; + + public IpRangeUnbanEvent(IpRangeBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public IpRangeBan ban() { return ban; } + public Player actor() { return actor; } + + public String reason() { return reason; } + public IpRangeUnbanEvent reason(String reason) { this.reason = reason; return this; } + + public boolean silent() { return silent; } + public IpRangeUnbanEvent silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeUnbannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeUnbannedEvent.java new file mode 100644 index 000000000..816a89ecf --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpRangeUnbannedEvent.java @@ -0,0 +1,27 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class IpRangeUnbannedEvent implements BanManagerEvent { + + private final IpRangeBan ban; + private final Player actor; + private final String reason; + private final boolean silent; + + public IpRangeUnbannedEvent(IpRangeBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public IpRangeBan ban() { return ban; } + public Player actor() { return actor; } + public String reason() { return reason; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnbanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnbanEvent.java new file mode 100644 index 000000000..20f3cba62 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnbanEvent.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.AbstractCancellableEvent; + +import java.util.Objects; + +public final class IpUnbanEvent extends AbstractCancellableEvent { + + private final IpBan ban; + private final Player actor; + private String reason; + private boolean silent; + + public IpUnbanEvent(IpBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public IpBan ban() { return ban; } + public Player actor() { return actor; } + + public String reason() { return reason; } + public IpUnbanEvent reason(String reason) { this.reason = reason; return this; } + + public boolean silent() { return silent; } + public IpUnbanEvent silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnbannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnbannedEvent.java new file mode 100644 index 000000000..0798bee66 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnbannedEvent.java @@ -0,0 +1,27 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class IpUnbannedEvent implements BanManagerEvent { + + private final IpBan ban; + private final Player actor; + private final String reason; + private final boolean silent; + + public IpUnbannedEvent(IpBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public IpBan ban() { return ban; } + public Player actor() { return actor; } + public String reason() { return reason; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnmuteEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnmuteEvent.java new file mode 100644 index 000000000..f395aee47 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnmuteEvent.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.AbstractCancellableEvent; + +import java.util.Objects; + +public final class IpUnmuteEvent extends AbstractCancellableEvent { + + private final IpMute mute; + private final Player actor; + private String reason; + private boolean silent; + + public IpUnmuteEvent(IpMute mute, Player actor, String reason, boolean silent) { + this.mute = Objects.requireNonNull(mute, "mute"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public IpMute mute() { return mute; } + public Player actor() { return actor; } + + public String reason() { return reason; } + public IpUnmuteEvent reason(String reason) { this.reason = reason; return this; } + + public boolean silent() { return silent; } + public IpUnmuteEvent silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnmutedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnmutedEvent.java new file mode 100644 index 000000000..2d12ce648 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/ip/IpUnmutedEvent.java @@ -0,0 +1,27 @@ +package me.confuser.banmanager.api.event.ip; + +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class IpUnmutedEvent implements BanManagerEvent { + + private final IpMute mute; + private final Player actor; + private final String reason; + private final boolean silent; + + public IpUnmutedEvent(IpMute mute, Player actor, String reason, boolean silent) { + this.mute = Objects.requireNonNull(mute, "mute"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public IpMute mute() { return mute; } + public Player actor() { return actor; } + public String reason() { return reason; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/name/NameBanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/name/NameBanEvent.java new file mode 100644 index 000000000..2dbd8aa9b --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/name/NameBanEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.name; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.NameBanRequest; + +import java.util.Objects; + +public final class NameBanEvent extends AbstractCancellableEvent { + + private final NameBanRequest request; + + public NameBanEvent(NameBanRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public NameBanRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/name/NameBannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/name/NameBannedEvent.java new file mode 100644 index 000000000..050d8818e --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/name/NameBannedEvent.java @@ -0,0 +1,37 @@ +package me.confuser.banmanager.api.event.name; + +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Post-event fired after a name ban has been persisted. + * + *

Handlers may add entries to {@link #placeholders()}; when the ban was + * triggered against a player who is currently online with the matching name, + * BanManager applies the resulting map to the kick-message template before + * disconnecting them.

+ */ +public final class NameBannedEvent implements BanManagerEvent { + + private final NameBan ban; + private final boolean silent; + private final Map placeholders = new HashMap<>(); + + public NameBannedEvent(NameBan ban, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.silent = silent; + } + + public NameBan ban() { return ban; } + public boolean silent() { return silent; } + + /** + * Mutable placeholder map applied to the kick message template when the + * named player is online. See {@link NameBannedEvent} javadoc. + */ + public Map placeholders() { return placeholders; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/name/NameUnbanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/name/NameUnbanEvent.java new file mode 100644 index 000000000..8f5abccc3 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/name/NameUnbanEvent.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.event.name; + +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.AbstractCancellableEvent; + +import java.util.Objects; + +public final class NameUnbanEvent extends AbstractCancellableEvent { + + private final NameBan ban; + private final Player actor; + private String reason; + private boolean silent; + + public NameUnbanEvent(NameBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public NameBan ban() { return ban; } + public Player actor() { return actor; } + + public String reason() { return reason; } + public NameUnbanEvent reason(String reason) { this.reason = reason; return this; } + + public boolean silent() { return silent; } + public NameUnbanEvent silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/name/NameUnbannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/name/NameUnbannedEvent.java new file mode 100644 index 000000000..3fea1825a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/name/NameUnbannedEvent.java @@ -0,0 +1,27 @@ +package me.confuser.banmanager.api.event.name; + +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class NameUnbannedEvent implements BanManagerEvent { + + private final NameBan ban; + private final Player actor; + private final String reason; + private final boolean silent; + + public NameUnbannedEvent(NameBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public NameBan ban() { return ban; } + public Player actor() { return actor; } + public String reason() { return reason; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerBanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerBanEvent.java new file mode 100644 index 000000000..35b9777c5 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerBanEvent.java @@ -0,0 +1,28 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.BanRequest; + +import java.util.Objects; + +/** + * Pre-event fired before a player ban is persisted. Handlers may mutate the + * {@link #request()} ({@code reason}, {@code expires}, {@code silent}) or + * {@link #cancel()} the ban entirely. + */ +public final class PlayerBanEvent extends AbstractCancellableEvent { + + private final BanRequest request; + + public PlayerBanEvent(BanRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + /** + * @return the mutable ban request that will be persisted if no handler + * calls {@link #cancel()} + */ + public BanRequest request() { + return request; + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerBannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerBannedEvent.java new file mode 100644 index 000000000..c94f626d2 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerBannedEvent.java @@ -0,0 +1,43 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Post-event fired after a player ban has been persisted. Carries the + * immutable {@link PlayerBan} record. Cannot be cancelled. + * + *

The {@link #silent()} flag may differ from {@code ban.silent()}: a ban + * that arrived from the global database sync is broadcast as silent when + * {@code broadcastOnSync = false} is set in config.

+ * + *

Handlers may add entries to {@link #placeholders()}; when the ban was + * triggered against a player who is currently online, BanManager applies the + * resulting map to the kick-message template before disconnecting them.

+ */ +public final class PlayerBannedEvent implements BanManagerEvent { + + private final PlayerBan ban; + private final boolean silent; + private final Map placeholders = new HashMap<>(); + + public PlayerBannedEvent(PlayerBan ban, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.silent = silent; + } + + public PlayerBan ban() { return ban; } + public boolean silent() { return silent; } + + /** + * Mutable placeholder map applied to the kick message template when the + * banned player is online. Add entries like + * {@code event.placeholders().put("pin", "123456")} to substitute the + * matching {@code } token in the rendered message. + */ + public Map placeholders() { return placeholders; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerDeniedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerDeniedEvent.java new file mode 100644 index 000000000..c86adc463 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerDeniedEvent.java @@ -0,0 +1,66 @@ +package me.confuser.banmanager.api.event.player; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * Fired when BanManager would deny a login or join attempt (banned UUID, + * banned IP, range ban, name ban). Allows administrative monitoring of + * blocked attempts. + * + *

Handlers may add entries to {@link #placeholders()}; BanManager applies + * them to the kick message template (e.g. {@code ban.player.disallowed}) + * before rendering and disconnecting the player. Companion plugins use this + * hook to inject custom placeholders such as {@code } into kick + * messages.

+ */ +public final class PlayerDeniedEvent implements BanManagerEvent { + + /** + * Type of denial. + */ + public enum Reason { + PLAYER_BAN, IP_BAN, IP_RANGE_BAN, NAME_BAN + } + + private final Optional uuid; + private final String name; + private final Optional ip; + private final Reason reason; + private final Map placeholders = new HashMap<>(); + + public PlayerDeniedEvent(Optional uuid, String name, Optional ip, Reason reason) { + this.uuid = Objects.requireNonNull(uuid, "uuid"); + this.name = Objects.requireNonNull(name, "name"); + this.ip = Objects.requireNonNull(ip, "ip"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + public Optional uuid() { return uuid; } + public String name() { return name; } + public Optional ip() { return ip; } + public Reason reason() { return reason; } + + /** + * Mutable placeholder map applied to the kick message template by + * BanManager after this event is published. Add entries like + * {@code event.placeholders().put("pin", "123456")} to substitute the + * matching {@code } token in the rendered message. + * + *

Concurrency: the map is the live event-shared + * instance — every subscriber observes a single shared map. When two + * subscribers write the same key, last-writer-wins; ordering follows the + * registered {@link me.confuser.banmanager.api.event.EventPriority}. + * Subscribers should namespace their keys (e.g. {@code "wenh.pin"}) to + * avoid stomping placeholders set by other plugins. Callers should + * not retain a reference to the map after the handler + * returns — BanManager re-uses it during render and may resize it.

+ */ + public Map placeholders() { return placeholders; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerKickedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerKickedEvent.java new file mode 100644 index 000000000..a8846bb14 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerKickedEvent.java @@ -0,0 +1,42 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +/** + * Post-event fired after a player has been kicked from the server via + * BanManager's kick handling. + */ +public final class PlayerKickedEvent implements BanManagerEvent { + + private final int id; + private final Player player; + private final Player actor; + private final String reason; + private final long created; + private final boolean silent; + + public PlayerKickedEvent(int id, Player player, Player actor, String reason, long created, boolean silent) { + this.id = id; + this.player = Objects.requireNonNull(player, "player"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.created = created; + this.silent = silent; + } + + /** + * Storage row id of the persisted kick record. + */ + public int id() { return id; } + public Player player() { return player; } + public Player actor() { return actor; } + public String reason() { return reason; } + /** + * Unix timestamp seconds the kick was recorded. + */ + public long created() { return created; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerMuteEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerMuteEvent.java new file mode 100644 index 000000000..4a339310a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerMuteEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.MuteRequest; + +import java.util.Objects; + +public final class PlayerMuteEvent extends AbstractCancellableEvent { + + private final MuteRequest request; + + public PlayerMuteEvent(MuteRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public MuteRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerMutedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerMutedEvent.java new file mode 100644 index 000000000..393def33d --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerMutedEvent.java @@ -0,0 +1,20 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class PlayerMutedEvent implements BanManagerEvent { + + private final PlayerMute mute; + private final boolean silent; + + public PlayerMutedEvent(PlayerMute mute, boolean silent) { + this.mute = Objects.requireNonNull(mute, "mute"); + this.silent = silent; + } + + public PlayerMute mute() { return mute; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerNoteCreatedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerNoteCreatedEvent.java new file mode 100644 index 000000000..cbfc51487 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerNoteCreatedEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class PlayerNoteCreatedEvent implements BanManagerEvent { + + private final PlayerNote note; + + public PlayerNoteCreatedEvent(PlayerNote note) { + this.note = Objects.requireNonNull(note, "note"); + } + + public PlayerNote note() { return note; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerNoteEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerNoteEvent.java new file mode 100644 index 000000000..5c2e95225 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerNoteEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.NoteRequest; + +import java.util.Objects; + +public final class PlayerNoteEvent extends AbstractCancellableEvent { + + private final NoteRequest request; + + public PlayerNoteEvent(NoteRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public NoteRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportDeletedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportDeletedEvent.java new file mode 100644 index 000000000..f4dc73365 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportDeletedEvent.java @@ -0,0 +1,21 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class PlayerReportDeletedEvent implements BanManagerEvent { + + private final PlayerReport report; + private final Player actor; + + public PlayerReportDeletedEvent(PlayerReport report, Player actor) { + this.report = Objects.requireNonNull(report, "report"); + this.actor = Objects.requireNonNull(actor, "actor"); + } + + public PlayerReport report() { return report; } + public Player actor() { return actor; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportEvent.java new file mode 100644 index 000000000..6e0ee58d5 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.ReportRequest; + +import java.util.Objects; + +public final class PlayerReportEvent extends AbstractCancellableEvent { + + private final ReportRequest request; + + public PlayerReportEvent(ReportRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public ReportRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportedEvent.java new file mode 100644 index 000000000..5f575d05f --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerReportedEvent.java @@ -0,0 +1,17 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class PlayerReportedEvent implements BanManagerEvent { + + private final PlayerReport report; + + public PlayerReportedEvent(PlayerReport report) { + this.report = Objects.requireNonNull(report, "report"); + } + + public PlayerReport report() { return report; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnbanEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnbanEvent.java new file mode 100644 index 000000000..342931f9f --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnbanEvent.java @@ -0,0 +1,34 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.AbstractCancellableEvent; + +import java.util.Objects; + +/** + * Pre-event fired before a player ban is removed. Cancellable. + */ +public final class PlayerUnbanEvent extends AbstractCancellableEvent { + + private final PlayerBan ban; + private final Player actor; + private String reason; + private boolean silent; + + public PlayerUnbanEvent(PlayerBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public PlayerBan ban() { return ban; } + public Player actor() { return actor; } + + public String reason() { return reason; } + public PlayerUnbanEvent reason(String reason) { this.reason = reason; return this; } + + public boolean silent() { return silent; } + public PlayerUnbanEvent silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnbannedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnbannedEvent.java new file mode 100644 index 000000000..67a4abe0c --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnbannedEvent.java @@ -0,0 +1,30 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +/** + * Post-event fired after a player ban has been removed. + */ +public final class PlayerUnbannedEvent implements BanManagerEvent { + + private final PlayerBan ban; + private final Player actor; + private final String reason; + private final boolean silent; + + public PlayerUnbannedEvent(PlayerBan ban, Player actor, String reason, boolean silent) { + this.ban = Objects.requireNonNull(ban, "ban"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public PlayerBan ban() { return ban; } + public Player actor() { return actor; } + public String reason() { return reason; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnmuteEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnmuteEvent.java new file mode 100644 index 000000000..251b23b9c --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnmuteEvent.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.event.AbstractCancellableEvent; + +import java.util.Objects; + +public final class PlayerUnmuteEvent extends AbstractCancellableEvent { + + private final PlayerMute mute; + private final Player actor; + private String reason; + private boolean silent; + + public PlayerUnmuteEvent(PlayerMute mute, Player actor, String reason, boolean silent) { + this.mute = Objects.requireNonNull(mute, "mute"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public PlayerMute mute() { return mute; } + public Player actor() { return actor; } + + public String reason() { return reason; } + public PlayerUnmuteEvent reason(String reason) { this.reason = reason; return this; } + + public boolean silent() { return silent; } + public PlayerUnmuteEvent silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnmutedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnmutedEvent.java new file mode 100644 index 000000000..1c228e38b --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerUnmutedEvent.java @@ -0,0 +1,27 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class PlayerUnmutedEvent implements BanManagerEvent { + + private final PlayerMute mute; + private final Player actor; + private final String reason; + private final boolean silent; + + public PlayerUnmutedEvent(PlayerMute mute, Player actor, String reason, boolean silent) { + this.mute = Objects.requireNonNull(mute, "mute"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + this.silent = silent; + } + + public PlayerMute mute() { return mute; } + public Player actor() { return actor; } + public String reason() { return reason; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerWarnEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerWarnEvent.java new file mode 100644 index 000000000..96af78ba9 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerWarnEvent.java @@ -0,0 +1,21 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.request.WarnRequest; + +import java.util.Objects; + +/** + * Pre-event for a player warning. Cancellable on every platform (in v7 this + * was inconsistent: cancellable on Sponge, non-cancellable elsewhere). + */ +public final class PlayerWarnEvent extends AbstractCancellableEvent { + + private final WarnRequest request; + + public PlayerWarnEvent(WarnRequest request) { + this.request = Objects.requireNonNull(request, "request"); + } + + public WarnRequest request() { return request; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerWarnedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerWarnedEvent.java new file mode 100644 index 000000000..cfaeed86e --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PlayerWarnedEvent.java @@ -0,0 +1,20 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.event.BanManagerEvent; + +import java.util.Objects; + +public final class PlayerWarnedEvent implements BanManagerEvent { + + private final PlayerWarn warn; + private final boolean silent; + + public PlayerWarnedEvent(PlayerWarn warn, boolean silent) { + this.warn = Objects.requireNonNull(warn, "warn"); + this.silent = silent; + } + + public PlayerWarn warn() { return warn; } + public boolean silent() { return silent; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/event/player/PluginReloadedEvent.java b/api/src/main/java/me/confuser/banmanager/api/event/player/PluginReloadedEvent.java new file mode 100644 index 000000000..24477aa9b --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/event/player/PluginReloadedEvent.java @@ -0,0 +1,9 @@ +package me.confuser.banmanager.api.event.player; + +import me.confuser.banmanager.api.event.BanManagerEvent; + +/** + * Fired after BanManager has reloaded its configuration via {@code /bmreload}. + */ +public final class PluginReloadedEvent implements BanManagerEvent { +} diff --git a/api/src/main/java/me/confuser/banmanager/api/exception/BanManagerException.java b/api/src/main/java/me/confuser/banmanager/api/exception/BanManagerException.java new file mode 100644 index 000000000..e91c68c5a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/exception/BanManagerException.java @@ -0,0 +1,25 @@ +package me.confuser.banmanager.api.exception; + +/** + * Root unchecked exception for all BanManager API failures. + * + *

Wraps {@link java.sql.SQLException} and other internal storage failures + * so the public API never declares a checked exception. Callers may inspect + * {@link #getCause()} to recover the underlying error.

+ */ +public class BanManagerException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public BanManagerException(String message) { + super(message); + } + + public BanManagerException(String message, Throwable cause) { + super(message, cause); + } + + public BanManagerException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/exception/EntityNotFoundException.java b/api/src/main/java/me/confuser/banmanager/api/exception/EntityNotFoundException.java new file mode 100644 index 000000000..77c2f1681 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/exception/EntityNotFoundException.java @@ -0,0 +1,19 @@ +package me.confuser.banmanager.api.exception; + +/** + * Thrown when a lookup expects an entity to exist (e.g. {@code unbanSync(playerId)}) + * but no row is found. Callers expecting a possibly-empty result should use + * the {@code Optional}-returning variants instead. + */ +public class EntityNotFoundException extends BanManagerException { + + private static final long serialVersionUID = 1L; + + public EntityNotFoundException(String message) { + super(message); + } + + public EntityNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/exception/OperationCancelledException.java b/api/src/main/java/me/confuser/banmanager/api/exception/OperationCancelledException.java new file mode 100644 index 000000000..5ca3baccf --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/exception/OperationCancelledException.java @@ -0,0 +1,21 @@ +package me.confuser.banmanager.api.exception; + +/** + * Reserved exception type for explicit cancellation reporting. The current + * v8 API does not throw this exception — both async and + * {@code *Sync} variants signal cancellation by returning + * {@link java.util.Optional#empty()} (for create operations) or + * {@code false} (for delete operations). Plugin authors should check the + * returned value rather than wrap the call in a {@code try / catch} for + * this type. Reserved here so future overloads that need to distinguish + * cancellation from a routine empty result can promote it without breaking + * the existing surface. + */ +public class OperationCancelledException extends BanManagerException { + + private static final long serialVersionUID = 1L; + + public OperationCancelledException(String message) { + super(message); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/exception/StorageException.java b/api/src/main/java/me/confuser/banmanager/api/exception/StorageException.java new file mode 100644 index 000000000..da1492ed7 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/exception/StorageException.java @@ -0,0 +1,18 @@ +package me.confuser.banmanager.api.exception; + +/** + * Thrown when the underlying storage layer fails. Always carries the original + * {@link java.sql.SQLException} (or driver exception) as its {@link #getCause()}. + */ +public class StorageException extends BanManagerException { + + private static final long serialVersionUID = 1L; + + public StorageException(String message, Throwable cause) { + super(message, cause); + } + + public StorageException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/package-info.java b/api/src/main/java/me/confuser/banmanager/api/package-info.java new file mode 100644 index 000000000..97422229a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/package-info.java @@ -0,0 +1,37 @@ +/** + * BanManager v8 public API. + * + *

This module ships the only contract plugin authors should integrate + * against. Internals — the {@code me.confuser.banmanager.common.*} + * implementation, the ORMLite entities, the platform plugins — make no + * stability guarantees and may change at any minor release.

+ * + *

Resolution

+ *
{@code
+ * BanManagerService bm = me.confuser.banmanager.api.BanManager.get();
+ * }
+ * or via the platform's native service manager. See + * {@link me.confuser.banmanager.api.BanManagerService} for examples. + * + *

Mutation model

+ *

All write paths use mutable {@link me.confuser.banmanager.api.request + * Request} objects. Pre-events carry the same {@code Request} instance so + * handlers can modify reason, expires, silent flags, etc. before the + * database write. Post-events carry immutable record DTOs.

+ * + *

Async

+ *

Every method that touches the database returns + * {@link java.util.concurrent.CompletableFuture}; a {@code *Sync} sibling is + * provided for callers that are already off the main thread.

+ * + *

For storage failures, sync methods throw + * {@link me.confuser.banmanager.api.exception.BanManagerException} + * (unchecked) — typically the + * {@link me.confuser.banmanager.api.exception.StorageException} subtype — + * and async methods complete the future exceptionally with the same type. + * Cancellation is not an exception: when a pre-event + * handler cancels an operation, both surfaces signal it by returning + * {@link java.util.Optional#empty()} (create-style operations) or + * {@code false} (delete-style operations) rather than throwing.

+ */ +package me.confuser.banmanager.api; diff --git a/api/src/main/java/me/confuser/banmanager/api/request/BanRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/BanRequest.java new file mode 100644 index 000000000..6868302f5 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/BanRequest.java @@ -0,0 +1,53 @@ +package me.confuser.banmanager.api.request; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing a player ban to create. Pre-event handlers may + * mutate any field on this object before persistence. + * + *

Time fields are unix timestamps in seconds. {@code expires == 0} means + * the ban is permanent.

+ * + *

Use the fluent setters for chaining:

+ * + *
{@code
+ * service.bans().ban(new BanRequest()
+ *     .player(playerUuid)
+ *     .actor(actorUuid)
+ *     .reason("griefing")
+ *     .expires(System.currentTimeMillis() / 1000L + 3600));
+ * }
+ */ +public final class BanRequest { + + private UUID player; + private UUID actor; + private String reason = ""; + private long expires; + private boolean silent; + + public BanRequest() {} + + public BanRequest(UUID player, UUID actor, String reason) { + this.player = Objects.requireNonNull(player, "player"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + public UUID player() { return player; } + public BanRequest player(UUID player) { this.player = player; return this; } + + public UUID actor() { return actor; } + public BanRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public BanRequest reason(String reason) { this.reason = reason; return this; } + + public long expires() { return expires; } + public BanRequest expires(long expires) { this.expires = expires; return this; } + + public boolean silent() { return silent; } + public BanRequest silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/IpAddresses.java b/api/src/main/java/me/confuser/banmanager/api/request/IpAddresses.java new file mode 100644 index 000000000..1443c094e --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/IpAddresses.java @@ -0,0 +1,32 @@ +package me.confuser.banmanager.api.request; + +import inet.ipaddr.IPAddress; +import inet.ipaddr.IPAddressString; + +/** + * Internal helper for the {@code String}-accepting IP request constructors. + * Kept package-private so it is not part of the public API surface — callers + * that want to parse an IP themselves should use + * {@link inet.ipaddr.IPAddressString} directly. + */ +final class IpAddresses { + + private IpAddresses() {} + + /** + * Parse a textual IPv4 / IPv6 address. + * + * @throws IllegalArgumentException when {@code text} is null, blank, or + * not a valid IPv4 / IPv6 literal + */ + static IPAddress parse(String text) { + if (text == null || text.isBlank()) { + throw new IllegalArgumentException("ip must not be null or blank"); + } + IPAddress address = new IPAddressString(text.trim()).getAddress(); + if (address == null) { + throw new IllegalArgumentException("Not a valid IP address: " + text); + } + return address; + } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/IpBanRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/IpBanRequest.java new file mode 100644 index 000000000..af8b37800 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/IpBanRequest.java @@ -0,0 +1,70 @@ +package me.confuser.banmanager.api.request; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing an IP ban to create. + * + *

The {@link String}-accepting constructor and {@link #ip(String)} + * setter let callers avoid a direct compile-time dependency on + * {@code com.github.seancfoley:ipaddress} for the common create-and-publish + * flow. Reading {@link #ip()} still returns {@link IPAddress} — pre-event + * handlers that mutate the request based on the parsed IP need the + * dependency, but the request-building site does not.

+ */ +public final class IpBanRequest { + + private IPAddress ip; + private UUID actor; + private String reason = ""; + private long expires; + private boolean silent; + + public IpBanRequest() {} + + public IpBanRequest(IPAddress ip, UUID actor, String reason) { + this.ip = Objects.requireNonNull(ip, "ip"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + /** + * Convenience constructor that parses {@code ip} via the bundled + * {@code com.github.seancfoley:ipaddress} library so callers don't have + * to import it directly. + * + *

Pass a single host literal. The parser also accepts subnet / CIDR + * notation (e.g. {@code 10.0.0.0/8}) and abbreviated forms, which is + * rarely intended for a single-host ban — use {@link IpRangeBanRequest} + * for ranges.

+ * + * @throws IllegalArgumentException when {@code ip} is null, blank, or not + * a valid IPv4 / IPv6 literal + */ + public IpBanRequest(String ip, UUID actor, String reason) { + this(IpAddresses.parse(ip), actor, reason); + } + + public IPAddress ip() { return ip; } + public IpBanRequest ip(IPAddress ip) { this.ip = ip; return this; } + + /** + * Set the IP from its textual form. See {@link #IpBanRequest(String, UUID, String)}. + */ + public IpBanRequest ip(String ip) { return ip(IpAddresses.parse(ip)); } + + public UUID actor() { return actor; } + public IpBanRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public IpBanRequest reason(String reason) { this.reason = reason; return this; } + + public long expires() { return expires; } + public IpBanRequest expires(long expires) { this.expires = expires; return this; } + + public boolean silent() { return silent; } + public IpBanRequest silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/IpMuteRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/IpMuteRequest.java new file mode 100644 index 000000000..a51757d3a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/IpMuteRequest.java @@ -0,0 +1,67 @@ +package me.confuser.banmanager.api.request; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing an IP mute to create. + * + *

See {@link IpBanRequest} for the {@link String}-accepting overloads + * pattern — same shape applies here.

+ */ +public final class IpMuteRequest { + + private IPAddress ip; + private UUID actor; + private String reason = ""; + private long expires; + private boolean soft; + private boolean silent; + + public IpMuteRequest() {} + + public IpMuteRequest(IPAddress ip, UUID actor, String reason) { + this.ip = Objects.requireNonNull(ip, "ip"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + /** + * Convenience constructor that parses {@code ip} via the bundled + * {@code com.github.seancfoley:ipaddress} library so callers don't have + * to import it directly. + * + *

Pass a single host literal. The parser also accepts subnet / CIDR + * notation and abbreviated forms, which is rarely intended for a + * single-host mute.

+ * + * @throws IllegalArgumentException when {@code ip} is null, blank, or not + * a valid IPv4 / IPv6 literal + */ + public IpMuteRequest(String ip, UUID actor, String reason) { + this(IpAddresses.parse(ip), actor, reason); + } + + public IPAddress ip() { return ip; } + public IpMuteRequest ip(IPAddress ip) { this.ip = ip; return this; } + + /** Set the IP from its textual form. See {@link #IpMuteRequest(String, UUID, String)}. */ + public IpMuteRequest ip(String ip) { return ip(IpAddresses.parse(ip)); } + + public UUID actor() { return actor; } + public IpMuteRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public IpMuteRequest reason(String reason) { this.reason = reason; return this; } + + public long expires() { return expires; } + public IpMuteRequest expires(long expires) { this.expires = expires; return this; } + + public boolean soft() { return soft; } + public IpMuteRequest soft(boolean soft) { this.soft = soft; return this; } + + public boolean silent() { return silent; } + public IpMuteRequest silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/IpRangeBanRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/IpRangeBanRequest.java new file mode 100644 index 000000000..98350b8e0 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/IpRangeBanRequest.java @@ -0,0 +1,72 @@ +package me.confuser.banmanager.api.request; + +import inet.ipaddr.IPAddress; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing an IP range ban to create. The range is + * inclusive on both ends. + * + *

See {@link IpBanRequest} for the {@link String}-accepting overloads + * pattern — same shape applies here, with one overload per endpoint.

+ */ +public final class IpRangeBanRequest { + + private IPAddress fromIp; + private IPAddress toIp; + private UUID actor; + private String reason = ""; + private long expires; + private boolean silent; + + public IpRangeBanRequest() {} + + public IpRangeBanRequest(IPAddress fromIp, IPAddress toIp, UUID actor, String reason) { + this.fromIp = Objects.requireNonNull(fromIp, "fromIp"); + this.toIp = Objects.requireNonNull(toIp, "toIp"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + /** + * Convenience constructor that parses {@code fromIp} / {@code toIp} via + * the bundled {@code com.github.seancfoley:ipaddress} library so callers + * don't have to import it directly. + * + *

Pass single host literals for each endpoint. The parser also accepts + * subnet / CIDR notation and abbreviated forms, which is rarely intended + * here — the range is defined by the two endpoints, not by CIDR.

+ * + * @throws IllegalArgumentException when either endpoint is null, blank, + * or not a valid IPv4 / IPv6 literal + */ + public IpRangeBanRequest(String fromIp, String toIp, UUID actor, String reason) { + this(IpAddresses.parse(fromIp), IpAddresses.parse(toIp), actor, reason); + } + + public IPAddress fromIp() { return fromIp; } + public IpRangeBanRequest fromIp(IPAddress fromIp) { this.fromIp = fromIp; return this; } + + /** Set the lower bound from its textual form. */ + public IpRangeBanRequest fromIp(String fromIp) { return fromIp(IpAddresses.parse(fromIp)); } + + public IPAddress toIp() { return toIp; } + public IpRangeBanRequest toIp(IPAddress toIp) { this.toIp = toIp; return this; } + + /** Set the upper bound from its textual form. */ + public IpRangeBanRequest toIp(String toIp) { return toIp(IpAddresses.parse(toIp)); } + + public UUID actor() { return actor; } + public IpRangeBanRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public IpRangeBanRequest reason(String reason) { this.reason = reason; return this; } + + public long expires() { return expires; } + public IpRangeBanRequest expires(long expires) { this.expires = expires; return this; } + + public boolean silent() { return silent; } + public IpRangeBanRequest silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/MuteRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/MuteRequest.java new file mode 100644 index 000000000..d89bf82eb --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/MuteRequest.java @@ -0,0 +1,47 @@ +package me.confuser.banmanager.api.request; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing a player mute to create. + */ +public final class MuteRequest { + + private UUID player; + private UUID actor; + private String reason = ""; + private long expires; + private boolean soft; + private boolean silent; + private boolean onlineOnly; + + public MuteRequest() {} + + public MuteRequest(UUID player, UUID actor, String reason) { + this.player = Objects.requireNonNull(player, "player"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + public UUID player() { return player; } + public MuteRequest player(UUID player) { this.player = player; return this; } + + public UUID actor() { return actor; } + public MuteRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public MuteRequest reason(String reason) { this.reason = reason; return this; } + + public long expires() { return expires; } + public MuteRequest expires(long expires) { this.expires = expires; return this; } + + public boolean soft() { return soft; } + public MuteRequest soft(boolean soft) { this.soft = soft; return this; } + + public boolean silent() { return silent; } + public MuteRequest silent(boolean silent) { this.silent = silent; return this; } + + public boolean onlineOnly() { return onlineOnly; } + public MuteRequest onlineOnly(boolean onlineOnly) { this.onlineOnly = onlineOnly; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/NameBanRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/NameBanRequest.java new file mode 100644 index 000000000..5af5da4a5 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/NameBanRequest.java @@ -0,0 +1,39 @@ +package me.confuser.banmanager.api.request; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing a name ban to create. + */ +public final class NameBanRequest { + + private String name; + private UUID actor; + private String reason = ""; + private long expires; + private boolean silent; + + public NameBanRequest() {} + + public NameBanRequest(String name, UUID actor, String reason) { + this.name = Objects.requireNonNull(name, "name"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + public String name() { return name; } + public NameBanRequest name(String name) { this.name = name; return this; } + + public UUID actor() { return actor; } + public NameBanRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public NameBanRequest reason(String reason) { this.reason = reason; return this; } + + public long expires() { return expires; } + public NameBanRequest expires(long expires) { this.expires = expires; return this; } + + public boolean silent() { return silent; } + public NameBanRequest silent(boolean silent) { this.silent = silent; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/NoteRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/NoteRequest.java new file mode 100644 index 000000000..f2d38a0d4 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/NoteRequest.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.request; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing a player note to create. + */ +public final class NoteRequest { + + private UUID player; + private UUID actor; + private String message = ""; + + public NoteRequest() {} + + public NoteRequest(UUID player, UUID actor, String message) { + this.player = Objects.requireNonNull(player, "player"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.message = Objects.requireNonNull(message, "message"); + } + + public UUID player() { return player; } + public NoteRequest player(UUID player) { this.player = player; return this; } + + public UUID actor() { return actor; } + public NoteRequest actor(UUID actor) { this.actor = actor; return this; } + + public String message() { return message; } + public NoteRequest message(String message) { this.message = message; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/ReportRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/ReportRequest.java new file mode 100644 index 000000000..9f8289c2a --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/ReportRequest.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.request; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing a player report to create. + */ +public final class ReportRequest { + + private UUID player; + private UUID actor; + private String reason = ""; + + public ReportRequest() {} + + public ReportRequest(UUID player, UUID actor, String reason) { + this.player = Objects.requireNonNull(player, "player"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + public UUID player() { return player; } + public ReportRequest player(UUID player) { this.player = player; return this; } + + public UUID actor() { return actor; } + public ReportRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public ReportRequest reason(String reason) { this.reason = reason; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/request/WarnRequest.java b/api/src/main/java/me/confuser/banmanager/api/request/WarnRequest.java new file mode 100644 index 000000000..ead3a5688 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/request/WarnRequest.java @@ -0,0 +1,47 @@ +package me.confuser.banmanager.api.request; + +import java.util.Objects; +import java.util.UUID; + +/** + * Mutable request describing a player warning to create. + */ +public final class WarnRequest { + + private UUID player; + private UUID actor; + private String reason = ""; + private double points = 1.0; + private boolean read = true; + private boolean silent; + private long expires; + + public WarnRequest() {} + + public WarnRequest(UUID player, UUID actor, String reason) { + this.player = Objects.requireNonNull(player, "player"); + this.actor = Objects.requireNonNull(actor, "actor"); + this.reason = Objects.requireNonNull(reason, "reason"); + } + + public UUID player() { return player; } + public WarnRequest player(UUID player) { this.player = player; return this; } + + public UUID actor() { return actor; } + public WarnRequest actor(UUID actor) { this.actor = actor; return this; } + + public String reason() { return reason; } + public WarnRequest reason(String reason) { this.reason = reason; return this; } + + public double points() { return points; } + public WarnRequest points(double points) { this.points = points; return this; } + + public boolean read() { return read; } + public WarnRequest read(boolean read) { this.read = read; return this; } + + public boolean silent() { return silent; } + public WarnRequest silent(boolean silent) { this.silent = silent; return this; } + + public long expires() { return expires; } + public WarnRequest expires(long expires) { this.expires = expires; return this; } +} diff --git a/api/src/main/java/me/confuser/banmanager/api/scheduler/BanManagerScheduler.java b/api/src/main/java/me/confuser/banmanager/api/scheduler/BanManagerScheduler.java new file mode 100644 index 000000000..5c521d301 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/scheduler/BanManagerScheduler.java @@ -0,0 +1,96 @@ +package me.confuser.banmanager.api.scheduler; + +import java.time.Duration; + +/** + * Platform-agnostic scheduler facade for fire-and-forget tasks (e.g. UI + * updates, periodic cleanups, webhook fan-out). + * + *

Do not use for blocking JDBC. All BanManager database operations + * already return {@link java.util.concurrent.CompletableFuture} and run on a + * dedicated DB-I/O executor. Submitting blocking work via {@link #runAsync} + * on Sponge or Fabric runs on the platform's CPU-bound {@code ForkJoinPool} + * and can starve other plugins.

+ * + *

Per-platform thread targets

+ *

Every method in this interface is asynchronous submission + * — the call returns immediately and the task runs at some later point on + * the target executor. None of these methods ever execute the runnable + * inline on the calling thread.

+ * + * + * + * + * + * + * + * + * + *
Target thread per method, per platform
Platform{@code runAsync*}{@code runSync*}{@link #isMainThreadAware()}
BukkitBanManager async poolBukkit main tick thread{@code true}
SpongeBanManager async poolSponge main game-tick thread (via {@code Sponge.server().scheduler()}){@code true}
FabricBanManager async pool{@code MinecraftServer.execute(...)} — server tick thread{@code true}
BungeeCordBungee scheduler async poolsame as {@code runAsync} (no main thread exists){@code false}
VelocityVelocity scheduler async poolsame as {@code runAsync} (no main thread exists){@code false}
+ * + *

Picking the right method

+ *

If your task touches a platform API that requires the server tick + * thread (e.g. Bukkit {@code World}, entities, inventories), gate on + * {@link #isMainThreadAware()} so it doesn't silently misbehave on a + * proxy where {@code runSync} is an alias for {@code runAsync}:

+ * + *
{@code
+ * if (scheduler.isMainThreadAware()) {
+ *     scheduler.runSync(() -> world.spawnEntity(loc, EntityType.PIG));
+ * } else {
+ *     // proxy environment — there is no World here at all
+ * }
+ * }
+ * + *

For everything else (HTTP webhooks, cache warmups, periodic cleanups), + * use {@link #runAsync} unconditionally — it works the same on every + * platform.

+ */ +public interface BanManagerScheduler { + + /** + * Submit {@code task} to BanManager's async pool (server platforms) or + * the platform scheduler's async pool (proxies). Returns immediately; + * {@code task} runs at some later point on a worker thread. + */ + void runAsync(Runnable task); + + /** + * As {@link #runAsync(Runnable)} but submission is delayed by + * {@code delay} before the task is dispatched to the async pool. + */ + void runAsyncLater(Runnable task, Duration delay); + + /** + * Schedule {@code task} on the platform's main thread when one exists + * (Bukkit/Sponge/Fabric), or the worker pool otherwise (Bungee/Velocity). + * See {@link #isMainThreadAware()} to discriminate at runtime. + * + *

This is always asynchronous submission — even when called from the + * main thread, {@code task} is queued and runs at the next opportunity + * rather than executing inline.

+ */ + void runSync(Runnable task); + + /** + * Schedule {@code task} on the platform's main thread after {@code delay}, + * subject to the same main-thread caveats as {@link #runSync(Runnable)}. + */ + void runSyncLater(Runnable task, Duration delay); + + /** + * Submit {@code task} to the async pool, run after {@code initialDelay}, + * then re-run every {@code period} thereafter. Always async; never on the + * main thread. + */ + void runAsyncRepeating(Runnable task, Duration initialDelay, Duration period); + + /** + * @return {@code true} if {@link #runSync(Runnable)} actually pins the + * task to a server tick thread (Bukkit, Sponge, Fabric); + * {@code false} on proxies (BungeeCord, Velocity) where there is + * no main thread and {@link #runSync(Runnable)} is an alias for + * {@link #runAsync(Runnable)}. + */ + boolean isMainThreadAware(); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/BanService.java b/api/src/main/java/me/confuser/banmanager/api/service/BanService.java new file mode 100644 index 000000000..41a37be3d --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/BanService.java @@ -0,0 +1,73 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.PlayerBanRecord; +import me.confuser.banmanager.api.request.BanRequest; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Player ban operations. All mutating methods fire pre-events through the + * {@link me.confuser.banmanager.api.event.EventBus}; cancelled events + * resolve to the documented sentinel value ({@link Optional#empty()} for + * create, {@code false} for delete) on both async and sync variants. + */ +public interface BanService { + + /** + * Issue a new ban. Fires {@link me.confuser.banmanager.api.event.player.PlayerBanEvent} + * (cancellable) then {@link me.confuser.banmanager.api.event.player.PlayerBannedEvent} + * after persistence. + * + * @return future resolving to the persisted ban, or empty when cancelled + */ + CompletableFuture> ban(BanRequest request); + + /** + * Synchronous variant. Returns {@link Optional#empty()} when a pre-event + * handler cancels the ban or persistence fails. + */ + Optional banSync(BanRequest request); + + /** + * Remove the active ban on a player. + * + * @return future resolving to {@code true} when an active ban existed and + * was removed; {@code false} when no ban existed or a pre-event + * handler cancelled the unban + */ + CompletableFuture unban(UUID player, Player actor, String reason, boolean silent); + + /** + * Synchronous variant. Returns {@code false} when no ban existed or a + * pre-event handler cancelled the unban. + */ + boolean unbanSync(UUID player, Player actor, String reason, boolean silent); + + /** + * Look up the current ban for a UUID. Backed by an in-memory cache so + * this is fast and side-effect free. + */ + Optional findActive(UUID player); + + Optional findActive(String name); + + boolean isBanned(UUID player); + + boolean isBanned(String name); + + /** + * Page through historical bans for a player. + * + * @param player the player UUID + * @param page zero-indexed page number + * @param size page size; 1..200 + */ + CompletableFuture> records(UUID player, int page, int size); + + Page recordsSync(UUID player, int page, int size); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/HistoryService.java b/api/src/main/java/me/confuser/banmanager/api/service/HistoryService.java new file mode 100644 index 000000000..3bf847135 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/HistoryService.java @@ -0,0 +1,48 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.HistoryEntry; +import me.confuser.banmanager.api.dto.PlayerNameSummary; +import me.confuser.banmanager.api.dto.PlayerSession; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Read-only access to player session and combined punishment history. + */ +public interface HistoryService { + + /** + * @return cross-table history (bans, mutes, warnings, kicks, notes) for a + * player, newest first + */ + CompletableFuture> history(UUID player, int page, int size); + + Page historySync(UUID player, int page, int size); + + /** + * @return all known names this player has used, with first/last seen + * timestamps; ordered most-recent first + */ + CompletableFuture> names(UUID player); + + List namesSync(UUID player); + + /** + * @return paginated list of login sessions since {@code since} unix + * seconds, newest first + */ + CompletableFuture> sessions(UUID player, long since, int page, int size); + + Page sessionsSync(UUID player, long since, int page, int size); + + /** + * @return the name in use by the player at the given timestamp + */ + CompletableFuture> nameAt(UUID player, long timestamp); + + Optional nameAtSync(UUID player, long timestamp); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/IpBanService.java b/api/src/main/java/me/confuser/banmanager/api/service/IpBanService.java new file mode 100644 index 000000000..73ba67d36 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/IpBanService.java @@ -0,0 +1,38 @@ +package me.confuser.banmanager.api.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.IpBanRecord; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.request.IpBanRequest; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Single-IP ban operations. Cancellation contract matches {@link BanService}: + * cancelled events resolve to the documented sentinel value + * ({@link Optional#empty()} for ban, {@code false} for unban) on both + * async and sync variants. + */ +public interface IpBanService { + + CompletableFuture> ban(IpBanRequest request); + + /** Returns empty when the pre-event was cancelled or the ban could not be persisted. */ + Optional banSync(IpBanRequest request); + + CompletableFuture unban(IPAddress ip, Player actor, String reason, boolean silent); + + /** Returns {@code false} when no ban existed or the pre-event was cancelled. */ + boolean unbanSync(IPAddress ip, Player actor, String reason, boolean silent); + + Optional findActive(IPAddress ip); + + boolean isBanned(IPAddress ip); + + CompletableFuture> records(IPAddress ip, int page, int size); + + Page recordsSync(IPAddress ip, int page, int size); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/IpMuteService.java b/api/src/main/java/me/confuser/banmanager/api/service/IpMuteService.java new file mode 100644 index 000000000..47af7325c --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/IpMuteService.java @@ -0,0 +1,38 @@ +package me.confuser.banmanager.api.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.IpMuteRecord; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.request.IpMuteRequest; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Single-IP mute operations. Cancellation contract matches {@link BanService}: + * cancelled events resolve to the documented sentinel value + * ({@link Optional#empty()} for mute, {@code false} for unmute) on both + * async and sync variants. + */ +public interface IpMuteService { + + CompletableFuture> mute(IpMuteRequest request); + + /** Returns empty when the pre-event was cancelled or the mute could not be persisted. */ + Optional muteSync(IpMuteRequest request); + + CompletableFuture unmute(IPAddress ip, Player actor, String reason, boolean silent); + + /** Returns {@code false} when no mute existed or the pre-event was cancelled. */ + boolean unmuteSync(IPAddress ip, Player actor, String reason, boolean silent); + + Optional findActive(IPAddress ip); + + boolean isMuted(IPAddress ip); + + CompletableFuture> records(IPAddress ip, int page, int size); + + Page recordsSync(IPAddress ip, int page, int size); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/IpRangeBanService.java b/api/src/main/java/me/confuser/banmanager/api/service/IpRangeBanService.java new file mode 100644 index 000000000..6ebf8ce86 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/IpRangeBanService.java @@ -0,0 +1,36 @@ +package me.confuser.banmanager.api.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.request.IpRangeBanRequest; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * IP-range ban operations. Cancellation contract matches {@link BanService}: + * cancelled events resolve to the documented sentinel value + * ({@link Optional#empty()} for ban, {@code false} for unban) on both + * async and sync variants. + */ +public interface IpRangeBanService { + + CompletableFuture> ban(IpRangeBanRequest request); + + /** Returns empty when the pre-event was cancelled or the ban could not be persisted. */ + Optional banSync(IpRangeBanRequest request); + + CompletableFuture unban(IpRangeBan ban, Player actor, String reason, boolean silent); + + /** Returns {@code false} when no range ban existed or the pre-event was cancelled. */ + boolean unbanSync(IpRangeBan ban, Player actor, String reason, boolean silent); + + /** + * @return the most-specific active range ban that contains {@code ip}, + * or empty when no range ban applies + */ + Optional findActive(IPAddress ip); + + boolean isBanned(IPAddress ip); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/MuteService.java b/api/src/main/java/me/confuser/banmanager/api/service/MuteService.java new file mode 100644 index 000000000..129872629 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/MuteService.java @@ -0,0 +1,42 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.dto.PlayerMuteRecord; +import me.confuser.banmanager.api.request.MuteRequest; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Player mute operations. Cancellation contract matches {@link BanService}: + * cancelled events resolve to the documented sentinel value + * ({@link Optional#empty()} for mute, {@code false} for unmute) on both + * async and sync variants. + */ +public interface MuteService { + + CompletableFuture> mute(MuteRequest request); + + /** Returns empty when the pre-event was cancelled or the mute could not be persisted. */ + Optional muteSync(MuteRequest request); + + CompletableFuture unmute(UUID player, Player actor, String reason, boolean silent); + + /** Returns {@code false} when no mute existed or the pre-event was cancelled. */ + boolean unmuteSync(UUID player, Player actor, String reason, boolean silent); + + Optional findActive(UUID player); + + Optional findActive(String name); + + boolean isMuted(UUID player); + + boolean isMuted(String name); + + CompletableFuture> records(UUID player, int page, int size); + + Page recordsSync(UUID player, int page, int size); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/NameBanService.java b/api/src/main/java/me/confuser/banmanager/api/service/NameBanService.java new file mode 100644 index 000000000..ffa672b89 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/NameBanService.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.request.NameBanRequest; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Name ban operations. Cancellation contract matches {@link BanService}: + * cancelled events resolve to the documented sentinel value + * ({@link Optional#empty()} for ban, {@code false} for unban) on both + * async and sync variants. + */ +public interface NameBanService { + + CompletableFuture> ban(NameBanRequest request); + + /** Returns empty when the pre-event was cancelled or the ban could not be persisted. */ + Optional banSync(NameBanRequest request); + + CompletableFuture unban(String name, Player actor, String reason, boolean silent); + + /** Returns {@code false} when no name ban existed or the pre-event was cancelled. */ + boolean unbanSync(String name, Player actor, String reason, boolean silent); + + Optional findActive(String name); + + boolean isBanned(String name); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/NoteService.java b/api/src/main/java/me/confuser/banmanager/api/service/NoteService.java new file mode 100644 index 000000000..8289de025 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/NoteService.java @@ -0,0 +1,31 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.request.NoteRequest; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Player note operations. {@code create} fires a cancellable + * {@link me.confuser.banmanager.api.event.player.PlayerNoteEvent}; cancel + * surfaces as {@link Optional#empty()} on both async and sync paths. + * {@code delete} does not fire a pre-event. + */ +public interface NoteService { + + CompletableFuture> create(NoteRequest request); + + /** Returns empty when the pre-event was cancelled or the note could not be persisted. */ + Optional createSync(NoteRequest request); + + CompletableFuture delete(int noteId); + + boolean deleteSync(int noteId); + + CompletableFuture> notes(UUID player, int page, int size); + + Page notesSync(UUID player, int page, int size); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/PlayerService.java b/api/src/main/java/me/confuser/banmanager/api/service/PlayerService.java new file mode 100644 index 000000000..cb0309016 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/PlayerService.java @@ -0,0 +1,49 @@ +package me.confuser.banmanager.api.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.dto.Player; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Lookup and bookkeeping for known players. All write paths happen via the + * other services (banning, muting, etc.); this one is read-only plus the + * {@link #console()} accessor. + */ +public interface PlayerService { + + /** + * Look up a player by UUID. Future completes empty when the player has + * never connected. + */ + CompletableFuture> findByUuid(UUID uuid); + + /** + * Synchronous variant; throws on storage errors. + */ + Optional findByUuidSync(UUID uuid); + + /** + * Look up by name (case-insensitive). Future completes empty when no + * record exists. + */ + CompletableFuture> findByName(String name); + + Optional findByNameSync(String name); + + /** + * @return all players that have logged in from {@code ip} within the + * configured "associated alts" window + */ + CompletableFuture> findByIp(IPAddress ip); + + List findByIpSync(IPAddress ip); + + /** + * @return the synthetic "console" actor used for system-issued punishments + */ + Player console(); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/ReportService.java b/api/src/main/java/me/confuser/banmanager/api/service/ReportService.java new file mode 100644 index 000000000..3c3fba0d3 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/ReportService.java @@ -0,0 +1,60 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.dto.ReportState; +import me.confuser.banmanager.api.request.ReportRequest; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Player report operations. {@code create} fires a cancellable + * {@link me.confuser.banmanager.api.event.player.PlayerReportEvent}; cancel + * surfaces as {@link Optional#empty()} on both async and sync paths. + */ +public interface ReportService { + + CompletableFuture> create(ReportRequest request); + + /** Returns empty when the pre-event was cancelled or the report could not be persisted. */ + Optional createSync(ReportRequest request); + + CompletableFuture delete(int reportId, Player actor); + + boolean deleteSync(int reportId, Player actor); + + CompletableFuture> findById(int reportId); + + Optional findByIdSync(int reportId); + + /** + * Page through reports filed against {@code player}. + */ + CompletableFuture> againstPlayer(UUID player, int page, int size); + + Page againstPlayerSync(UUID player, int page, int size); + + /** + * Update the workflow state of a report. + */ + CompletableFuture updateState(int reportId, ReportState state); + + boolean updateStateSync(int reportId, ReportState state); + + /** + * @return all configured workflow states (e.g. {@code Open}, {@code Assigned}, + * {@code Resolved}) + */ + CompletableFuture> states(); + + /** + * Synchronous variant of {@link #states()} for callers already on a worker + * thread. Throws {@link me.confuser.banmanager.api.exception.StorageException} + * if the lookup fails. + */ + List statesSync(); +} diff --git a/api/src/main/java/me/confuser/banmanager/api/service/WarnService.java b/api/src/main/java/me/confuser/banmanager/api/service/WarnService.java new file mode 100644 index 000000000..d55e8c786 --- /dev/null +++ b/api/src/main/java/me/confuser/banmanager/api/service/WarnService.java @@ -0,0 +1,40 @@ +package me.confuser.banmanager.api.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.request.WarnRequest; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface WarnService { + + /** + * Issue a warning. Fires {@link me.confuser.banmanager.api.event.player.PlayerWarnEvent} + * (now cancellable on every platform) and + * {@link me.confuser.banmanager.api.event.player.PlayerWarnedEvent}. + * + * @return future resolving to the persisted warning, or empty when + * cancelled by a pre-event handler + */ + CompletableFuture> warn(WarnRequest request); + + /** Returns empty when the pre-event was cancelled or the warning could not be persisted. */ + Optional warnSync(WarnRequest request); + + /** + * @return list of unread warnings for the player (warnings displayed at + * next login) + */ + CompletableFuture> warnings(UUID player, int page, int size); + + Page warningsSync(UUID player, int page, int size); + + /** + * Mark a warning as read. + */ + CompletableFuture markRead(int warnId); + + boolean markReadSync(int warnId); +} diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index ad83bdd25..e171260d6 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -114,9 +114,11 @@ tasks.named("shadowJar") { archiveVersion.set("") dependencies { + include(dependency(":BanManagerAPI")) include(dependency(":BanManagerCommon")) include(dependency(":BanManagerLibs")) + include(dependency("com.github.seancfoley:ipaddress:.*")) include(dependency("org.bstats:.*:.*")) include(dependency("org.slf4j:.*:.*")) } @@ -145,6 +147,12 @@ tasks.named("shadowJar") { minimize { exclude(dependency("org.bstats:.*:.*")) exclude(dependency("org.slf4j:.*:.*")) + // BanManagerAPI is the public surface plugin consumers compile against; preserve + // every class even if the BM core itself doesn't reference it. + exclude(dependency(":BanManagerAPI")) + // ipaddress is exposed unshaded through the API and consumers may call any of its + // methods, so don't let static analysis prune unreferenced classes. + exclude(dependency("com.github.seancfoley:ipaddress:.*")) // JDBC drivers, HikariCP and ORMLite load codecs / dialects / drivers // via ServiceLoader. Static analysis can't see those references, so // exclude them from minimisation to avoid stripping classes that are diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BMBukkitPlugin.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BMBukkitPlugin.java index 735710449..7203551c0 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BMBukkitPlugin.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BMBukkitPlugin.java @@ -1,6 +1,8 @@ package me.confuser.banmanager.bukkit; import lombok.Getter; +import me.confuser.banmanager.api.BanManagerService; +import me.confuser.banmanager.api.event.player.PluginReloadedEvent; import me.confuser.banmanager.bukkit.listeners.*; import me.confuser.banmanager.bukkit.placeholders.PAPIPlaceholderResolver; import me.confuser.banmanager.bukkit.placeholders.PAPIPlaceholders; @@ -9,12 +11,19 @@ import me.confuser.banmanager.common.configs.PluginInfo; import me.confuser.banmanager.common.configuration.ConfigurationSection; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; +import me.confuser.banmanager.common.listeners.CommonBanListener; +import me.confuser.banmanager.common.listeners.CommonHooksListener; +import me.confuser.banmanager.common.listeners.CommonMuteListener; +import me.confuser.banmanager.common.listeners.CommonNoteListener; +import me.confuser.banmanager.common.listeners.CommonReportListener; +import me.confuser.banmanager.common.listeners.CommonWebhookListener; import me.confuser.banmanager.common.runnables.*; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -52,13 +61,15 @@ public void onEnable() { pluginInfo = setupConfigs(); } catch (IOException e) { getPluginLoader().disablePlugin(this); - BanManagerPlugin.getInstance().getLogger().warning("Failed to set up plugin configuration", e); + getLogger().log(java.util.logging.Level.WARNING, "Failed to set up plugin configuration", e); return; } metrics = new Metrics(this, 6455); plugin = new BanManagerPlugin(pluginInfo, new PluginLogger(getLogger()), getDataFolder(), new BukkitScheduler(this), server, new BukkitMetrics(metrics)); + org.slf4j.impl.BanManagerSlf4jLogger.setPlugin(plugin); + server.enable(plugin); try { @@ -69,6 +80,11 @@ public void onEnable() { return; } + // Publish on Bukkit's native ServicesManager too. BanManagerPlugin.enable() already + // populated the portable BanManager.get() locator; this gives Vault-style consumers + // the idiomatic Bukkit lookup as well. + Bukkit.getServicesManager().register(BanManagerService.class, plugin.getApiService(), this, ServicePriority.Normal); + setupListeners(); setupCommands(); setupRunnables(); @@ -78,7 +94,11 @@ public void onEnable() { public void onDisable() { getServer().getScheduler().cancelTasks(this); + Bukkit.getServicesManager().unregisterAll(this); + if (plugin != null) plugin.disable(); + + org.slf4j.impl.BanManagerSlf4jLogger.setPlugin(null); } private PluginInfo setupConfigs() throws IOException { @@ -132,21 +152,23 @@ public void setupListeners() { registerEvent(new JoinListener(plugin)); registerEvent(new LeaveListener(plugin)); registerEvent(new CommandListener(plugin)); - registerEvent(new HookListener(plugin)); + new CommonHooksListener(plugin); registerChatListener(); - registerEvent(new ReloadListener(this)); + plugin.getEventBus().subscribe(PluginReloadedEvent.class, e -> registerChatListener()); if (plugin.getConfig().isDisplayNotificationsEnabled()) { - registerEvent(new BanListener(plugin)); + new CommonBanListener(plugin); + new CommonMuteListener(plugin); registerEvent(new MuteListener(plugin)); - registerEvent(new NoteListener(plugin)); - registerEvent(new ReportListener(plugin)); + new CommonNoteListener(plugin); + new CommonReportListener(plugin); + new ReportListener(plugin); } if (plugin.getWebhookConfig().isHooksEnabled()) { - registerEvent(new WebhookListener(plugin)); + new CommonWebhookListener(plugin); } if(Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { @@ -189,7 +211,7 @@ private void registerEvent(Listener listener) { public void setupCommands() { for (CommonCommand cmd : plugin.getCommands()) { try { - getCommand(cmd.getCommandName()).setExecutor(new BukkitCommand(cmd)); + getCommand(cmd.getCommandName()).setExecutor(new BukkitCommand(plugin, cmd)); } catch (NullPointerException e) { plugin.getLogger().severe("Failed to register /" + cmd.getCommandName() + " command"); } @@ -198,7 +220,7 @@ public void setupCommands() { if (plugin.getGlobalConn() != null) { for (CommonCommand cmd : plugin.getGlobalCommands()) { try { - getCommand(cmd.getCommandName()).setExecutor(new BukkitCommand(cmd)); + getCommand(cmd.getCommandName()).setExecutor(new BukkitCommand(plugin, cmd)); } catch (NullPointerException e) { plugin.getLogger().severe("Failed to register /" + cmd.getCommandName() + " command"); } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitCommand.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitCommand.java index f111ddaa5..0ab368310 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitCommand.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitCommand.java @@ -15,9 +15,11 @@ public class BukkitCommand implements CommandExecutor, TabCompleter { + private final BanManagerPlugin plugin; private CommonCommand command; - public BukkitCommand(CommonCommand command) { + public BukkitCommand(BanManagerPlugin plugin, CommonCommand command) { + this.plugin = plugin; this.command = command; } @@ -33,7 +35,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St return true; } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to execute command", e); + plugin.getLogger().warning("Failed to execute command", e); } return false; @@ -41,9 +43,9 @@ public boolean onCommand(CommandSender sender, Command command, String label, St private CommonSender getSender(CommandSender source) { if (source instanceof Player) { - return new BukkitPlayer((Player) source, BanManagerPlugin.getInstance().getConfig().isOnlineMode()); + return new BukkitPlayer(plugin, (Player) source, plugin.getConfig().isOnlineMode()); } else { - return new BukkitSender(BanManagerPlugin.getInstance(), source); + return new BukkitSender(plugin, source); } } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java index b0557e077..2268962e3 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java @@ -35,24 +35,26 @@ public class BukkitPlayer implements CommonPlayer { PAPER_ADVENTURE = paper; } + private final BanManagerPlugin plugin; private Player player; private final UUID uuid; private InetAddress address; private final boolean onlineMode; - public BukkitPlayer(UUID uuid, String name, boolean onlineMode) { + public BukkitPlayer(BanManagerPlugin plugin, UUID uuid, String name, boolean onlineMode) { + this.plugin = plugin; this.uuid = uuid; this.onlineMode = onlineMode; } - public BukkitPlayer(Player player, boolean onlineMode) { - this(player.getUniqueId(), player.getName(), onlineMode); + public BukkitPlayer(BanManagerPlugin plugin, Player player, boolean onlineMode) { + this(plugin, player.getUniqueId(), player.getName(), onlineMode); this.player = player; } - public BukkitPlayer(Player player, boolean onlineMode, InetAddress address) { - this(player, onlineMode); + public BukkitPlayer(BanManagerPlugin plugin, Player player, boolean onlineMode, InetAddress address) { + this(plugin, player, onlineMode); this.address = address; } @@ -64,9 +66,9 @@ public void kick(String message) { @Override public void kick(Component component) { if (PAPER_ADVENTURE) { - PaperAdventureHelper.kick(getPlayer(), component); + PaperAdventureHelper.kick(getPlayer(), component, plugin.getMessageRenderer()); } else { - kick(MessageRenderer.getInstance().toLegacy(component)); + kick(plugin.getMessageRenderer().toLegacy(component)); } } @@ -83,9 +85,9 @@ public void sendMessage(String message) { @Override public void sendMessage(Component component) { if (PAPER_ADVENTURE) { - PaperAdventureHelper.sendMessage(getPlayer(), component); + PaperAdventureHelper.sendMessage(getPlayer(), component, plugin.getMessageRenderer()); } else { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); getPlayer().spigot().sendMessage(ComponentSerializer.parse(json)); } } @@ -93,9 +95,9 @@ public void sendMessage(Component component) { @Override public void sendActionBar(Component component) { if (PAPER_ADVENTURE) { - PaperAdventureHelper.sendActionBar(getPlayer(), component); + PaperAdventureHelper.sendActionBar(getPlayer(), component, plugin.getMessageRenderer()); } else { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); getPlayer().spigot().sendMessage(ChatMessageType.ACTION_BAR, ComponentSerializer.parse(json)); } } @@ -105,7 +107,7 @@ public void showTitle(Component title, Component subtitle, int fadeIn, int stay, if (PAPER_ADVENTURE) { PaperAdventureHelper.showTitle(getPlayer(), title, subtitle, fadeIn, stay, fadeOut); } else { - MessageRenderer renderer = MessageRenderer.getInstance(); + MessageRenderer renderer = plugin.getMessageRenderer(); String legacyTitle = title != null ? renderer.toLegacy(title) : ""; String legacySubtitle = subtitle != null ? renderer.toLegacy(subtitle) : ""; getPlayer().sendTitle(legacyTitle, legacySubtitle, fadeIn, stay, fadeOut); @@ -139,9 +141,9 @@ public boolean isConsole() { public PlayerData getData() { try { - return BanManagerPlugin.getInstance().getPlayerStorage().queryForId(UUIDUtils.toBytes(getUniqueId())); + return plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(getUniqueId())); } catch (SQLException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to load player data", e); + plugin.getLogger().warning("Failed to load player data", e); sendMessage(Message.get("sender.error.exception").toString()); return null; } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitScheduler.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitScheduler.java index 351d8d51e..c34d70d2c 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitScheduler.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitScheduler.java @@ -43,4 +43,15 @@ public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration per long periodTicks = SchedulerTime.durationToTicksCeil(period); Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, initialTicks, periodTicks); } + + /** + * Bukkit pins {@link #runSync(Runnable)} to the server tick thread via + * {@code BukkitScheduler#runTask(Plugin, Runnable)}, so main-thread-only + * APIs (Bukkit {@code World} mutations, inventory changes, etc.) are safe + * to call from inside the submitted task. + */ + @Override + public boolean isMainThreadAware() { + return true; + } } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitServer.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitServer.java index a2cabe782..511b6aa7a 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitServer.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitServer.java @@ -1,12 +1,8 @@ package me.confuser.banmanager.bukkit; -import me.confuser.banmanager.bukkit.api.events.*; import me.confuser.banmanager.common.*; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; -import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.util.ColorUtils; -import me.confuser.banmanager.common.util.Message; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.World; @@ -37,7 +33,7 @@ public CommonPlayer getPlayer(UUID uniqueId) { if (player == null) return null; - return new BukkitPlayer(player, plugin.getConfig().isOnlineMode()); + return new BukkitPlayer(plugin, player, plugin.getConfig().isOnlineMode()); } @Override @@ -46,7 +42,7 @@ public CommonPlayer getPlayer(String name) { if (player == null) return null; - return new BukkitPlayer(player, plugin.getConfig().isOnlineMode()); + return new BukkitPlayer(plugin, player, plugin.getConfig().isOnlineMode()); } @Override @@ -55,13 +51,13 @@ public CommonPlayer getPlayerExact(String name) { if (player == null) return null; - return new BukkitPlayer(player, plugin.getConfig().isOnlineMode()); + return new BukkitPlayer(plugin, player, plugin.getConfig().isOnlineMode()); } @Override public CommonPlayer[] getOnlinePlayers() { return Bukkit.getOnlinePlayers().stream() - .map(player -> new BukkitPlayer(player, plugin.getConfig().isOnlineMode())) + .map(player -> new BukkitPlayer(plugin, player, plugin.getConfig().isOnlineMode())) .collect(Collectors.toList()).toArray(new CommonPlayer[0]); } @@ -118,130 +114,6 @@ public CommonWorld getWorld(String name) { return new CommonWorld(name); } - @Override - public CommonEvent callEvent(String name, Object... args) { - // @TODO replace with a cleaner implementation - CustomEvent event = null; - CommonEvent commonEvent = new CommonEvent(false, true); - - switch (name) { - case "PlayerBanEvent": - event = new PlayerBanEvent((PlayerBanData) args[0], (boolean) args[1]); - break; - case "PlayerBannedEvent": - PlayerBannedEvent bannedEvent = new PlayerBannedEvent((PlayerBanData) args[0], (boolean) args[1]); - if (args.length > 2 && args[2] instanceof Message) { - bannedEvent.setKickMessage((Message) args[2]); - } - event = bannedEvent; - break; - case "PlayerUnbanEvent": - event = new PlayerUnbanEvent((PlayerBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpBanEvent": - event = new IpBanEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpBannedEvent": - event = new IpBannedEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpUnbanEvent": - event = new IpUnbanEvent((IpBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpMuteEvent": - event = new IpMuteEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpMutedEvent": - event = new IpMutedEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpUnmutedEvent": - event = new IpUnmutedEvent((IpMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerKickedEvent": - event = new PlayerKickedEvent((PlayerKickData) args[0], (boolean) args[1]); - break; - - case "PlayerNoteCreatedEvent": - event = new PlayerNoteCreatedEvent((PlayerNoteData) args[0], args.length > 1 && (boolean) args[1]); - break; - - case "PlayerReportEvent": - event = new PlayerReportEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportedEvent": - event = new PlayerReportedEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportDeletedEvent": - event = new PlayerReportDeletedEvent((PlayerReportData) args[0]); - break; - - case "NameBanEvent": - event = new NameBanEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameBannedEvent": - event = new NameBannedEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameUnbanEvent": - event = new NameUnbanEvent((NameBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerWarnEvent": - event = new PlayerWarnEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - case "PlayerWarnedEvent": - event = new PlayerWarnedEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - - case "IpRangeBanEvent": - event = new IpRangeBanEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeBannedEvent": - event = new IpRangeBannedEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeUnbanEvent": - event = new IpRangeUnbanEvent((IpRangeBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerMuteEvent": - event = new PlayerMuteEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerMutedEvent": - event = new PlayerMutedEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerUnmuteEvent": - event = new PlayerUnmuteEvent((PlayerMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PluginReloadedEvent": - event = new PluginReloadedEvent((PlayerData) args[0]); - break; - - case "PlayerDeniedEvent": - event = new PlayerDeniedEvent((PlayerData) args[0], (Message) args[1]); - break; - } - - if (event == null) { - plugin.getLogger().warning("Unable to call missing event " + name); - - return commonEvent; - } - - Bukkit.getServer().getPluginManager().callEvent(event); - - if (event instanceof SilentCancellableEvent) { - commonEvent = new CommonEvent(((SilentCancellableEvent) event).isCancelled(), ((SilentCancellableEvent) event).isSilent()); - } else if (event instanceof SilentEvent) { - commonEvent = new CommonEvent(false, ((SilentEvent) event).isSilent()); - } else if (event instanceof CustomCancellableEvent) { - commonEvent = new CommonEvent(((CustomCancellableEvent) event).isCancelled(), true); - } - - return commonEvent; - } - @Override public CommonExternalCommand getPluginCommand(String commandName) { PluginCommand pluginCommand = Bukkit.getServer().getPluginCommand(commandName); diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/PaperAdventureHelper.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/PaperAdventureHelper.java index 632b58d35..18f964b84 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/PaperAdventureHelper.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/PaperAdventureHelper.java @@ -30,20 +30,25 @@ class PaperAdventureHelper { private PaperAdventureHelper() {} - static void sendMessage(Player player, Component component) { - ((Audience) player).sendMessage(convertToNative(component)); + static void sendMessage(Player player, Component component, MessageRenderer renderer) { + ((Audience) player).sendMessage(convertToNative(component, renderer)); } - static void sendActionBar(Player player, Component component) { - ((Audience) player).sendActionBar(convertToNative(component)); + static void sendActionBar(Player player, Component component, MessageRenderer renderer) { + ((Audience) player).sendActionBar(convertToNative(component, renderer)); } static void showTitle(Player player, Component title, Component subtitle, int fadeIn, int stay, int fadeOut) { + showTitle(player, title, subtitle, fadeIn, stay, fadeOut, null); + } + + static void showTitle(Player player, Component title, Component subtitle, + int fadeIn, int stay, int fadeOut, MessageRenderer renderer) { net.kyori.adventure.text.Component nativeTitle = title != null - ? convertToNative(title) : net.kyori.adventure.text.Component.empty(); + ? convertToNative(title, renderer) : net.kyori.adventure.text.Component.empty(); net.kyori.adventure.text.Component nativeSubtitle = subtitle != null - ? convertToNative(subtitle) : net.kyori.adventure.text.Component.empty(); + ? convertToNative(subtitle, renderer) : net.kyori.adventure.text.Component.empty(); Title.Times times = Title.Times.times( Duration.ofMillis(fadeIn * 50L), @@ -53,10 +58,10 @@ static void showTitle(Player player, Component title, Component subtitle, ((Audience) player).showTitle(Title.title(nativeTitle, nativeSubtitle, times)); } - static void kick(Player player, Component component) { + static void kick(Player player, Component component, MessageRenderer renderer) { if (KICK_METHOD != null) { try { - KICK_METHOD.invoke(player, convertToNative(component)); + KICK_METHOD.invoke(player, convertToNative(component, renderer)); return; } catch (IllegalAccessException e) { java.util.logging.Logger.getLogger("BanManager").warning("Failed to invoke Paper kick method, falling back to legacy: " + e.getMessage()); @@ -64,11 +69,12 @@ static void kick(Player player, Component component) { throw new IllegalStateException("Failed to kick player", e.getCause()); } } - player.kickPlayer(MessageRenderer.getInstance().toLegacy(component)); + player.kickPlayer(renderer != null ? renderer.toLegacy(component) : component.toString()); } - private static net.kyori.adventure.text.Component convertToNative(Component component) { - String json = MessageRenderer.getInstance().toJson(component); + private static net.kyori.adventure.text.Component convertToNative(Component component, MessageRenderer renderer) { + if (renderer == null) return net.kyori.adventure.text.Component.empty(); + String json = renderer.toJson(component); return GsonComponentSerializer.gson().deserialize(json); } } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/CustomCancellableEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/CustomCancellableEvent.java deleted file mode 100644 index d4eb54066..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/CustomCancellableEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import lombok.Setter; -import org.bukkit.event.Cancellable; - -public abstract class CustomCancellableEvent extends CustomEvent implements Cancellable { - - @Getter - @Setter - private boolean cancelled = false; - - public CustomCancellableEvent(boolean isAsync) { - super(isAsync); - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/CustomEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/CustomEvent.java deleted file mode 100644 index 06ee20d10..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/CustomEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; - -public abstract class CustomEvent extends Event { - - private static final HandlerList handlers = new HandlerList(); - - public CustomEvent(boolean isAsync) { - super(isAsync); - } - - public HandlerList getHandlers() { - return handlers; - } - - public static HandlerList getHandlerList() { - return handlers; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpBanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpBanEvent.java deleted file mode 100644 index 97ce9cbb7..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpBanEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBanEvent extends SilentCancellableEvent { - - @Getter - private IpBanData ban; - - public IpBanEvent(IpBanData ban, boolean silent) { - super(silent, true); - this.ban = ban; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpBannedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpBannedEvent.java deleted file mode 100644 index 04833dba1..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpBannedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBannedEvent extends SilentEvent { - - @Getter - private IpBanData ban; - - public IpBannedEvent(IpBanData ban, boolean silent) { - super(silent, true); - this.ban = ban; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpMuteEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpMuteEvent.java deleted file mode 100644 index 18e441074..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpMuteEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMuteEvent extends SilentCancellableEvent { - - @Getter - private IpMuteData mute; - - public IpMuteEvent(IpMuteData mute, boolean silent) { - super(silent, true); - this.mute = mute; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpMutedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpMutedEvent.java deleted file mode 100644 index 54c094952..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpMutedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMutedEvent extends SilentEvent { - - @Getter - private IpMuteData mute; - - public IpMutedEvent(IpMuteData mute, boolean silent) { - super(silent, true); - this.mute = mute; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeBanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeBanEvent.java deleted file mode 100644 index 06170bf22..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeBanEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - -public class IpRangeBanEvent extends SilentCancellableEvent { - - @Getter - private IpRangeBanData ban; - - public IpRangeBanEvent(IpRangeBanData ban, boolean silent) { - super(silent, true); - this.ban = ban; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeBannedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeBannedEvent.java deleted file mode 100644 index 0b4e224dd..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeBannedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - - -public class IpRangeBannedEvent extends SilentEvent { - - @Getter - private IpRangeBanData ban; - - public IpRangeBannedEvent(IpRangeBanData ban, boolean silent) { - super(silent, true); - this.ban = ban; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeUnbanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeUnbanEvent.java deleted file mode 100644 index e673913a2..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpRangeUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpRangeUnbanEvent extends SilentCancellableEvent { - - @Getter - private IpRangeBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent, true); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpUnbanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpUnbanEvent.java deleted file mode 100644 index 7ccff770f..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpUnbanEvent extends SilentCancellableEvent { - - @Getter - private IpBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent, true); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpUnmutedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpUnmutedEvent.java deleted file mode 100644 index 91d68e1e5..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/IpUnmutedEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpUnmutedEvent extends SilentCancellableEvent { - - @Getter - private IpMuteData mute; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent, true); - - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameBanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameBanEvent.java deleted file mode 100644 index 7a22eb967..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameBanEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - - -public class NameBanEvent extends SilentCancellableEvent { - - @Getter - private NameBanData ban; - - public NameBanEvent(NameBanData ban, boolean isSilent) { - super(isSilent, true); - this.ban = ban; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameBannedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameBannedEvent.java deleted file mode 100644 index e14978c1c..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameBannedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - -public class NameBannedEvent extends SilentEvent { - - @Getter - private NameBanData ban; - - public NameBannedEvent(NameBanData ban, boolean isSilent) { - super(isSilent, true); - this.ban = ban; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameUnbanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameUnbanEvent.java deleted file mode 100644 index 4f7195a79..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/NameUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class NameUnbanEvent extends SilentCancellableEvent { - - @Getter - private NameBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent, true); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerBanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerBanEvent.java deleted file mode 100644 index 4ab731760..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerBanEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; - - -public class PlayerBanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - - public PlayerBanEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent, true); - this.ban = ban; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerBannedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerBannedEvent.java deleted file mode 100644 index 0dcebbfe6..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerBannedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.util.Message; - - -public class PlayerBannedEvent extends SilentEvent { - - @Getter - private PlayerBanData ban; - - @Getter - @Setter - private Message kickMessage; - - public PlayerBannedEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent, true); - this.ban = ban; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerDeniedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerDeniedEvent.java deleted file mode 100644 index 35cbf6ff1..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerDeniedEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.util.Message; - - -public class PlayerDeniedEvent extends CustomCancellableEvent { - - @Getter - @Setter - private Message message; - - @Getter - private PlayerData player; - - public PlayerDeniedEvent(PlayerData player, Message message) { - super(true); - - this.player = player; - this.message = message; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerKickedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerKickedEvent.java deleted file mode 100644 index 702848922..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerKickedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerKickData; - -public class PlayerKickedEvent extends SilentEvent { - - @Getter - private PlayerKickData kick; - - public PlayerKickedEvent(PlayerKickData kick, boolean isSilent) { - super(isSilent, true); - this.kick = kick; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerMuteEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerMuteEvent.java deleted file mode 100644 index 184188ad3..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerMuteEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerMuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMuteEvent(PlayerMuteData mute, boolean silent) { - super(silent, true); - this.mute = mute; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerMutedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerMutedEvent.java deleted file mode 100644 index 974287afd..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerMutedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerMutedEvent extends SilentEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMutedEvent(PlayerMuteData mute, boolean silent) { - super(silent, true); - this.mute = mute; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerNoteCreatedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerNoteCreatedEvent.java deleted file mode 100644 index 8c63a75d1..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerNoteCreatedEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerNoteData; - - -public class PlayerNoteCreatedEvent extends SilentCancellableEvent { - - @Getter - private PlayerNoteData note; - - public PlayerNoteCreatedEvent(PlayerNoteData note) { - this(note, false); - } - - public PlayerNoteCreatedEvent(PlayerNoteData note, boolean silent) { - super(silent, true); - this.note = note; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportDeletedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportDeletedEvent.java deleted file mode 100644 index 0ac1cfb79..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportDeletedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - -public class PlayerReportDeletedEvent extends CustomEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportDeletedEvent(PlayerReportData report) { - super(true); - this.report = report; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportEvent.java deleted file mode 100644 index 553d2637c..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - - -public class PlayerReportEvent extends SilentCancellableEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportEvent(PlayerReportData report, boolean isSilent) { - super(isSilent, true); - this.report = report; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportedEvent.java deleted file mode 100644 index 9dc7415fd..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerReportedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - - -public class PlayerReportedEvent extends SilentEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportedEvent(PlayerReportData report, boolean isSilent) { - super(isSilent, true); - this.report = report; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerUnbanEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerUnbanEvent.java deleted file mode 100644 index 2fe0f84c6..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class PlayerUnbanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent, true); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerUnmuteEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerUnmuteEvent.java deleted file mode 100644 index 790cf67d0..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerUnmuteEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerUnmuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - @Getter - private PlayerData actor; - @Getter - private String reason; - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent, true); - - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerWarnEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerWarnEvent.java deleted file mode 100644 index d9875db4f..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerWarnEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - - -public class PlayerWarnEvent extends SilentEvent { - - @Getter - private PlayerWarnData warning; - - public PlayerWarnEvent(PlayerWarnData warning, boolean silent) { - super(silent, true); - this.warning = warning; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerWarnedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerWarnedEvent.java deleted file mode 100644 index ac12f3ffa..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PlayerWarnedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - - -public class PlayerWarnedEvent extends SilentEvent { - - @Getter - private PlayerWarnData warning; - - public PlayerWarnedEvent(PlayerWarnData warning, boolean silent) { - super(silent, true); - this.warning = warning; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PluginReloadedEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PluginReloadedEvent.java deleted file mode 100644 index ee735321a..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/PluginReloadedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; - - -public class PluginReloadedEvent extends CustomCancellableEvent { - - @Getter - private PlayerData actor; - - public PluginReloadedEvent(PlayerData actor) { - super(false); - - this.actor = actor; - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/SilentCancellableEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/SilentCancellableEvent.java deleted file mode 100644 index 8bb258b73..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/SilentCancellableEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentCancellableEvent extends CustomCancellableEvent { - - @Getter - @Setter - private boolean silent = false; - - public SilentCancellableEvent(boolean silent, boolean isAsync) { - super(isAsync); - - this.silent = silent; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/SilentEvent.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/SilentEvent.java deleted file mode 100644 index 8581addce..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/api/events/SilentEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bukkit.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentEvent extends CustomEvent { - @Getter - @Setter - private boolean silent = false; - - public SilentEvent(boolean silent, boolean isAsync) { - super(isAsync); - this.silent = silent; - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/BanListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/BanListener.java deleted file mode 100644 index 29eb0f933..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/BanListener.java +++ /dev/null @@ -1,49 +0,0 @@ -package me.confuser.banmanager.bukkit.listeners; - - -import me.confuser.banmanager.bukkit.api.events.IpBannedEvent; -import me.confuser.banmanager.bukkit.api.events.IpRangeBannedEvent; -import me.confuser.banmanager.bukkit.api.events.NameBannedEvent; -import me.confuser.banmanager.bukkit.api.events.PlayerBannedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.CommonPlayer; -import me.confuser.banmanager.common.commands.CommonSender; -import me.confuser.banmanager.common.data.*; -import me.confuser.banmanager.common.listeners.CommonBanListener; -import me.confuser.banmanager.common.util.DateUtils; -import me.confuser.banmanager.common.util.IPUtils; -import me.confuser.banmanager.common.util.Message; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -import java.util.List; - -public class BanListener implements Listener { - - private final CommonBanListener listener; - - public BanListener(BanManagerPlugin plugin) { - this.listener = new CommonBanListener(plugin); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnBan(PlayerBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnIpBan(IpBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnIpRangeBan(IpRangeBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnNameBan(NameBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/HookListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/HookListener.java deleted file mode 100644 index 21de4e8f3..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/HookListener.java +++ /dev/null @@ -1,101 +0,0 @@ -package me.confuser.banmanager.bukkit.listeners; - -import me.confuser.banmanager.bukkit.api.events.*; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonHooksListener; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -public class HookListener implements Listener { - private final CommonHooksListener listener; - - public HookListener(BanManagerPlugin plugin) { - this.listener = new CommonHooksListener(plugin); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onBan(final PlayerBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onBan(final PlayerBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onUnban(final PlayerUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onMute(final PlayerMuteEvent event) { - listener.onMute(event.getMute(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onMute(final PlayerMutedEvent event) { - listener.onMute(event.getMute(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onUnmute(final PlayerUnmuteEvent event) { - listener.onUnmute(event.getMute(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onBan(final IpBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onBan(final IpBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onUnban(final IpUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onBan(final IpRangeBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onBan(final IpRangeBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onUnban(final IpRangeUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onWarn(final PlayerWarnEvent event) { - listener.onWarn(event.getWarning(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onWarn(final PlayerWarnedEvent event) { - listener.onWarn(event.getWarning(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onNote(final PlayerNoteCreatedEvent event) { - listener.onNote(event.getNote(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onReport(final PlayerReportEvent event) { - listener.onReport(event.getReport(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onReport(final PlayerReportedEvent event) { - listener.onReport(event.getReport(), false, event.isSilent()); - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java index 4c7fbe8f3..d2352f7a5 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java @@ -40,7 +40,7 @@ public void onJoin(AsyncPlayerPreLoginEvent event) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onJoin(final PlayerJoinEvent event) { - listener.onJoin(new BukkitPlayer(event.getPlayer(), plugin.getConfig().isOnlineMode())); + listener.onJoin(new BukkitPlayer(plugin, event.getPlayer(), plugin.getConfig().isOnlineMode())); } @EventHandler(priority = EventPriority.MONITOR) @@ -49,7 +49,7 @@ public void onPlayerLogin(final PlayerLoginEvent event) { return; } - listener.onPlayerLogin(new BukkitPlayer(event.getPlayer(), plugin.getConfig().isOnlineMode(), event.getAddress()), new LoginHandler(event)); + listener.onPlayerLogin(new BukkitPlayer(plugin, event.getPlayer(), plugin.getConfig().isOnlineMode(), event.getAddress()), new LoginHandler(event)); } @RequiredArgsConstructor @@ -59,16 +59,15 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { - plugin.getServer().callEvent("PlayerDeniedEvent", player, message); String locale = player.getLocale() != null ? player.getLocale() : "en"; event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_BANNED); - event.setKickMessage(BukkitServer.formatMessage(MessageRenderer.getInstance().toLegacy(message.resolveComponent(locale)))); + event.setKickMessage(BukkitServer.formatMessage(plugin.getMessageRenderer().toLegacy(message.resolveComponent(locale)))); } @Override public void handleDeny(Message message) { event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_BANNED); - event.setKickMessage(BukkitServer.formatMessage(MessageRenderer.getInstance().toLegacy(message.resolveComponent()))); + event.setKickMessage(BukkitServer.formatMessage(plugin.getMessageRenderer().toLegacy(message.resolveComponent()))); } } @@ -77,14 +76,14 @@ private class LoginHandler implements CommonJoinHandler { private final PlayerLoginEvent event; @Override - public void handlePlayerDeny(PlayerData player, Message message) { - handleDeny(message); + public void handleDeny(Message message) { + event.disallow(PlayerLoginEvent.Result.KICK_BANNED, + BukkitServer.formatMessage(plugin.getMessageRenderer().toLegacy(message.resolveComponent()))); } @Override - public void handleDeny(Message message) { - event.disallow(PlayerLoginEvent.Result.KICK_BANNED, - BukkitServer.formatMessage(MessageRenderer.getInstance().toLegacy(message.resolveComponent()))); + public void handlePlayerDeny(PlayerData player, Message message) { + handleDeny(message); } } } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/MuteListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/MuteListener.java index 8cc6fc1b1..fef5abad5 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/MuteListener.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/MuteListener.java @@ -1,35 +1,21 @@ package me.confuser.banmanager.bukkit.listeners; - -import me.confuser.banmanager.bukkit.api.events.IpMutedEvent; -import me.confuser.banmanager.bukkit.api.events.PlayerMutedEvent; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonMuteListener; import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.player.PlayerEditBookEvent; -import java.util.UUID; - +/** + * Handles Bukkit-specific events that should be blocked while a player is + * muted (sign changes, book edits). Notification dispatch lives in the + * cross-platform {@link me.confuser.banmanager.common.listeners.CommonMuteListener}. + */ public class MuteListener implements Listener { private final BanManagerPlugin plugin; - private final CommonMuteListener listener; public MuteListener(BanManagerPlugin plugin) { this.plugin = plugin; - this.listener = new CommonMuteListener(plugin); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void notifyOnMute(PlayerMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void notifyOnMute(IpMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); } @EventHandler diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/NoteListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/NoteListener.java deleted file mode 100644 index 0bca23cc3..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/NoteListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.bukkit.listeners; - - -import me.confuser.banmanager.bukkit.api.events.PlayerNoteCreatedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonNoteListener; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -public class NoteListener implements Listener { - private final CommonNoteListener listener; - - public NoteListener(BanManagerPlugin plugin) { - this.listener = new CommonNoteListener(plugin); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void notifyOnNote(PlayerNoteCreatedEvent event) { - listener.notifyOnNote(event.getNote(), event.isSilent()); - } - -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReloadListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReloadListener.java deleted file mode 100644 index 67c2161d7..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReloadListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.bukkit.listeners; - -import lombok.RequiredArgsConstructor; -import me.confuser.banmanager.bukkit.BMBukkitPlugin; -import me.confuser.banmanager.bukkit.api.events.PluginReloadedEvent; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -@RequiredArgsConstructor -public class ReloadListener implements Listener { - private final BMBukkitPlugin bukkitPlugin; - - @EventHandler(priority = EventPriority.MONITOR) - public void onReload(final PluginReloadedEvent event) { - bukkitPlugin.registerChatListener(); - } -} diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReportListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReportListener.java index 7eefb06b4..73d0cf78f 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReportListener.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/ReportListener.java @@ -1,51 +1,53 @@ package me.confuser.banmanager.bukkit.listeners; -import me.confuser.banmanager.bukkit.api.events.PlayerReportDeletedEvent; -import me.confuser.banmanager.bukkit.api.events.PlayerReportedEvent; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.common.data.PlayerReportLocationData; -import me.confuser.banmanager.common.listeners.CommonReportListener; import me.confuser.banmanager.common.util.UUIDUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import java.sql.SQLException; -public class ReportListener implements Listener { - private final CommonReportListener listener; - private BanManagerPlugin plugin; +/** + * Stores the Bukkit-specific player and actor location at the time a report + * is filed. Notification dispatch and reference cleanup live in the + * cross-platform {@link me.confuser.banmanager.common.listeners.CommonReportListener}. + */ +public class ReportListener { + private final BanManagerPlugin plugin; public ReportListener(BanManagerPlugin plugin) { this.plugin = plugin; - this.listener = new CommonReportListener(plugin); + plugin.getEventBus().subscribe(PlayerReportedEvent.class, this::storeLocation); } - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void notifyOnReport(PlayerReportedEvent event) { - listener.notifyOnReport(event.getReport()); - } - - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) - public void storeLocation(PlayerReportedEvent event) { - PlayerReportData report = event.getReport(); + private void storeLocation(PlayerReportedEvent event) { + PlayerReport report = event.report(); + PlayerReportData entity; + try { + entity = plugin.getPlayerReportStorage().queryForId(report.id()); + } catch (SQLException e) { + plugin.getLogger().warning("Failed to load report entity for location storage", e); + return; + } + if (entity == null) return; - Player player = Bukkit.getServer().getPlayer(report.getPlayer().getUUID()); - Player actor = Bukkit.getServer().getPlayer(report.getActor().getUUID()); + Player player = Bukkit.getServer().getPlayer(report.player().uuid()); + Player actor = Bukkit.getServer().getPlayer(report.actor().uuid()); try { - createLocation(report, player); + createLocation(entity, player); } catch (SQLException e) { plugin.getLogger().warning("Failed to store report location for reported player", e); } try { - createLocation(report, actor); + createLocation(entity, actor); } catch (SQLException e) { plugin.getLogger().warning("Failed to store report location for actor", e); } @@ -62,9 +64,4 @@ private void createLocation(PlayerReportData report, Player player) throws SQLEx .getZ() , loc.getPitch(), loc.getYaw())); } - - @EventHandler - public void deleteReferences(PlayerReportDeletedEvent event) { - listener.deleteReferences(event.getReport()); - } } diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/WebhookListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/WebhookListener.java deleted file mode 100644 index 7263ba555..000000000 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/WebhookListener.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.confuser.banmanager.bukkit.listeners; - -import me.confuser.banmanager.bukkit.api.events.*; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonWebhookListener; -import me.confuser.banmanager.common.data.Webhook; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; - -import java.util.List; - -public class WebhookListener implements Listener { - private CommonWebhookListener listener; - - public WebhookListener(BanManagerPlugin plugin) { - this.listener = new CommonWebhookListener(plugin); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnBan(PlayerBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnMute(PlayerMutedEvent event) { - List webhooks = listener.notifyOnMute(event.getMute()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnBan(IpBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnKick(PlayerKickedEvent event) { - List webhooks = listener.notifyOnKick(event.getKick()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnWarn(PlayerWarnedEvent event) { - List webhooks = listener.notifyOnWarn(event.getWarning()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnUnban(PlayerUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnUnban(IpUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnUnmute(PlayerUnmuteEvent event) { - List webhooks = listener.notifyOnUnmute(event.getMute(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void notifyOnReport(PlayerReportedEvent event) { - List webhooks = listener.notifyOnReport(event.getReport(), event.getReport().getActor(), event.getReport().getReason()); - sendAll(webhooks, event.isSilent()); - } - - private void sendAll(List webhooks, boolean isSilent) { - for (Webhook data : webhooks) { - if (isSilent && data.ignoreSilent()) continue; - if (data.url() == null || data.payload() == null || data.url().isEmpty() || data.payload().isEmpty()) continue; - listener.sendAsync(data); - } - } -} diff --git a/bukkit/src/main/java/org/slf4j/impl/BanManagerSlf4jLogger.java b/bukkit/src/main/java/org/slf4j/impl/BanManagerSlf4jLogger.java index 371487b49..f2b270e87 100644 --- a/bukkit/src/main/java/org/slf4j/impl/BanManagerSlf4jLogger.java +++ b/bukkit/src/main/java/org/slf4j/impl/BanManagerSlf4jLogger.java @@ -13,23 +13,38 @@ *

Level filtering: when debug mode is off, TRACE / DEBUG / INFO are * suppressed. This replaces the old {@code disableDatabaseLogging()} system * property which has no effect when ORMLite uses SLF4J instead of LocalLog.

+ * + *

SLF4J binds loggers statically via the service-provider mechanism, so + * this class cannot receive its plugin reference via constructor injection. + * Instead {@link #setPlugin(BanManagerPlugin)} must be called once the plugin + * has been instantiated, and {@link #setPlugin(BanManagerPlugin) cleared} in + * the platform's disable hook to avoid classloader leaks during reloads.

*/ public class BanManagerSlf4jLogger extends LegacyAbstractLogger { private static final long serialVersionUID = 1L; + private static volatile BanManagerPlugin plugin; + + /** + * Bind the active plugin instance so SLF4J log calls can route through + * {@link CommonLogger}. Pass {@code null} during shutdown. + */ + public static void setPlugin(BanManagerPlugin instance) { + plugin = instance; + } BanManagerSlf4jLogger(String name) { this.name = name; } private CommonLogger logger() { - BanManagerPlugin plugin = BanManagerPlugin.getInstance(); - return plugin != null ? plugin.getLogger() : null; + BanManagerPlugin p = plugin; + return p != null ? p.getLogger() : null; } private boolean isDebugMode() { - BanManagerPlugin plugin = BanManagerPlugin.getInstance(); - return plugin != null && plugin.getConfig() != null && plugin.getConfig().isDebugEnabled(); + BanManagerPlugin p = plugin; + return p != null && p.getConfig() != null && p.getConfig().isDebugEnabled(); } @Override diff --git a/bungee/build.gradle.kts b/bungee/build.gradle.kts index 22c174db2..761b33b71 100644 --- a/bungee/build.gradle.kts +++ b/bungee/build.gradle.kts @@ -93,8 +93,10 @@ tasks.named("shadowJar") { archiveVersion.set("") dependencies { + include(dependency(":BanManagerAPI")) include(dependency(":BanManagerCommon")) include(dependency(":BanManagerLibs")) + include(dependency("com.github.seancfoley:ipaddress:.*")) include(dependency("org.bstats:.*:.*")) relocate("org.bstats", "me.confuser.banmanager.common.bstats") @@ -117,6 +119,8 @@ tasks.named("shadowJar") { minimize { exclude(dependency("org.bstats:.*:.*")) exclude(dependency(":BanManagerLibs")) + exclude(dependency(":BanManagerAPI")) + exclude(dependency("com.github.seancfoley:ipaddress:.*")) } } diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BMBungeePlugin.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BMBungeePlugin.java index ea4b96d18..7cf7fe974 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BMBungeePlugin.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BMBungeePlugin.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.bungee; import lombok.Getter; +import me.confuser.banmanager.api.event.player.PluginReloadedEvent; import me.confuser.banmanager.bungee.configs.BungeeConfig; import me.confuser.banmanager.bungee.listeners.*; import me.confuser.banmanager.common.BanManagerPlugin; @@ -8,6 +9,11 @@ import me.confuser.banmanager.common.configs.PluginInfo; import me.confuser.banmanager.common.configuration.ConfigurationSection; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; +import me.confuser.banmanager.common.listeners.CommonBanListener; +import me.confuser.banmanager.common.listeners.CommonHooksListener; +import me.confuser.banmanager.common.listeners.CommonMuteListener; +import me.confuser.banmanager.common.listeners.CommonNoteListener; +import me.confuser.banmanager.common.listeners.CommonWebhookListener; import me.confuser.banmanager.common.runnables.*; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; @@ -47,7 +53,7 @@ public void onEnable() { try { pluginInfo = setupConfigs(); } catch (IOException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to set up plugin configuration", e); + getLogger().log(java.util.logging.Level.WARNING, "Failed to set up plugin configuration", e); return; } @@ -115,7 +121,7 @@ private PluginInfo setupConfigs() throws IOException { try (InputStream in = getResourceAsStream(name)) { Files.copy(in, file.toPath()); } catch (IOException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to copy default config file", e); + getLogger().log(java.util.logging.Level.WARNING, "Failed to copy default config file", e); } } else { try (InputStream in = getResourceAsStream(file.getName()); @@ -154,20 +160,20 @@ private PluginInfo setupConfigs() throws IOException { public void setupListeners() { registerEvent(new JoinListener(this)); registerEvent(new LeaveListener(plugin)); - registerEvent(new HookListener(plugin)); + new CommonHooksListener(plugin); registerChatListener(); - registerEvent(new ReloadListener(this)); + plugin.getEventBus().subscribe(PluginReloadedEvent.class, e -> registerChatListener()); if (plugin.getConfig().isDisplayNotificationsEnabled()) { - registerEvent(new BanListener(plugin)); - registerEvent(new MuteListener(plugin)); - registerEvent(new NoteListener(plugin)); + new CommonBanListener(plugin); + new CommonMuteListener(plugin); + new CommonNoteListener(plugin); } if (plugin.getWebhookConfig().isHooksEnabled()) { - registerEvent(new WebhookListener(plugin)); + new CommonWebhookListener(plugin); } } diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeCommand.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeCommand.java index 97b6cc6e8..aa3061cab 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeCommand.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeCommand.java @@ -42,10 +42,11 @@ public void execute(CommandSender sender, String[] args) { } private CommonSender getSender(CommandSender source) { + BanManagerPlugin core = plugin.getPlugin(); if (source instanceof ProxiedPlayer) { - return new BungeePlayer((ProxiedPlayer) source, BanManagerPlugin.getInstance().getConfig().isOnlineMode()); + return new BungeePlayer(core, (ProxiedPlayer) source, core.getConfig().isOnlineMode()); } else { - return new BungeeSender(BanManagerPlugin.getInstance(), source); + return new BungeeSender(core, source); } } diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java index 2a573ce03..04a1693e2 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java @@ -1,5 +1,6 @@ package me.confuser.banmanager.bungee; +import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.CommonWorld; import me.confuser.banmanager.common.commands.CommonCommand; @@ -7,7 +8,6 @@ import me.confuser.banmanager.common.kyori.text.Component; import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.common.util.MessageRenderer; import me.confuser.banmanager.common.util.MessageRegistry; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ProxyServer; @@ -18,11 +18,13 @@ import java.util.UUID; public class BungeePlayer implements CommonPlayer { + private final BanManagerPlugin plugin; private final UUID uuid; private final boolean onlineMode; private final ProxiedPlayer player; - public BungeePlayer(ProxiedPlayer player, boolean onlineMode) { + public BungeePlayer(BanManagerPlugin plugin, ProxiedPlayer player, boolean onlineMode) { + this.plugin = plugin; this.player = player; this.uuid = player.getUniqueId(); this.onlineMode = onlineMode; @@ -35,7 +37,7 @@ public void kick(String message) { @Override public void kick(Component component) { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); player.disconnect(ComponentSerializer.parse(json)); } @@ -52,13 +54,13 @@ public void sendMessage(String message) { @Override public void sendMessage(Component component) { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); player.sendMessage(ComponentSerializer.parse(json)); } @Override public void sendActionBar(Component component) { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); player.sendMessage(ChatMessageType.ACTION_BAR, ComponentSerializer.parse(json)); } @@ -79,7 +81,7 @@ public boolean isConsole() { @Override public PlayerData getData() { - return CommonCommand.getPlayer(this, getName(), false); + return CommonCommand.resolvePlayer(plugin, this, getName(), false); } @Override diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeScheduler.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeScheduler.java index 99da59ccd..57cc98392 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeScheduler.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeScheduler.java @@ -38,4 +38,16 @@ public void runSyncLater(Runnable task, Duration delay) { public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration period) { ProxyServer.getInstance().getScheduler().schedule(plugin, task, initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS); } + + /** + * BungeeCord is a fully-asynchronous proxy with no main-thread tick. + * {@link #runSync(Runnable)} is therefore an alias for + * {@link #runAsync(Runnable)} — exposed via this flag so callers can + * detect platforms where main-thread-only APIs (Bukkit {@code World} etc.) + * are unavailable. + */ + @Override + public boolean isMainThreadAware() { + return false; + } } diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeSender.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeSender.java index 7c5c46992..b82a996ce 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeSender.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeSender.java @@ -41,6 +41,6 @@ public boolean isConsole() { public PlayerData getData() { if (isConsole()) return plugin.getPlayerStorage().getConsole(); - return CommonCommand.getPlayer(this, getName(), false); + return CommonCommand.resolvePlayer(plugin, this, getName(), false); } } diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeServer.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeServer.java index 496a47786..9b470f582 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeServer.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeeServer.java @@ -1,14 +1,10 @@ package me.confuser.banmanager.bungee; -import me.confuser.banmanager.bungee.api.events.*; import me.confuser.banmanager.common.*; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; -import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.kyori.text.Component; import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer; import me.confuser.banmanager.common.util.ColorUtils; -import me.confuser.banmanager.common.util.Message; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.chat.BaseComponent; @@ -34,7 +30,7 @@ public CommonPlayer getPlayer(UUID uniqueId) { if (player == null) return null; - return new BungeePlayer(player, plugin.getConfig().isOnlineMode()); + return new BungeePlayer(plugin, player, plugin.getConfig().isOnlineMode()); } @Override @@ -43,7 +39,7 @@ public CommonPlayer getPlayer(String name) { if (player == null) return null; - return new BungeePlayer(player, plugin.getConfig().isOnlineMode()); + return new BungeePlayer(plugin, player, plugin.getConfig().isOnlineMode()); } @Override @@ -54,7 +50,7 @@ public CommonPlayer getPlayerExact(String name) { @Override public CommonPlayer[] getOnlinePlayers() { return ProxyServer.getInstance().getPlayers().stream() - .map(player -> new BungeePlayer(player, plugin.getConfig().isOnlineMode())) + .map(player -> new BungeePlayer(plugin, player, plugin.getConfig().isOnlineMode())) .collect(Collectors.toList()).toArray(new CommonPlayer[0]); } @@ -99,130 +95,6 @@ public CommonWorld getWorld(String name) { return null; } - @Override - public CommonEvent callEvent(String name, Object... args) { - // @TODO replace with a cleaner implementation - CustomEvent event = null; - CommonEvent commonEvent = new CommonEvent(false, true); - - switch (name) { - case "PlayerBanEvent": - event = new PlayerBanEvent((PlayerBanData) args[0], (boolean) args[1]); - break; - case "PlayerBannedEvent": - PlayerBannedEvent bannedEvent = new PlayerBannedEvent((PlayerBanData) args[0], (boolean) args[1]); - if (args.length > 2 && args[2] instanceof Message) { - bannedEvent.setKickMessage((Message) args[2]); - } - event = bannedEvent; - break; - case "PlayerUnbanEvent": - event = new PlayerUnbanEvent((PlayerBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpBanEvent": - event = new IpBanEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpBannedEvent": - event = new IpBannedEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpUnbanEvent": - event = new IpUnbanEvent((IpBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpMuteEvent": - event = new IpMuteEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpMutedEvent": - event = new IpMutedEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpUnmutedEvent": - event = new IpUnmutedEvent((IpMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerKickedEvent": - event = new PlayerKickedEvent((PlayerKickData) args[0], (boolean) args[1]); - break; - - case "PlayerNoteCreatedEvent": - event = new PlayerNoteCreatedEvent((PlayerNoteData) args[0], args.length > 1 && (boolean) args[1]); - break; - - case "PlayerReportEvent": - event = new PlayerReportEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportedEvent": - event = new PlayerReportedEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportDeletedEvent": - event = new PlayerReportDeletedEvent((PlayerReportData) args[0]); - break; - - case "NameBanEvent": - event = new NameBanEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameBannedEvent": - event = new NameBannedEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameUnbanEvent": - event = new NameUnbanEvent((NameBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerWarnEvent": - event = new PlayerWarnEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - case "PlayerWarnedEvent": - event = new PlayerWarnedEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - - case "IpRangeBanEvent": - event = new IpRangeBanEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeBannedEvent": - event = new IpRangeBannedEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeUnbanEvent": - event = new IpRangeUnbanEvent((IpRangeBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerMuteEvent": - event = new PlayerMuteEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerMutedEvent": - event = new PlayerMutedEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerUnmuteEvent": - event = new PlayerUnmuteEvent((PlayerMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PluginReloadedEvent": - event = new PluginReloadedEvent((PlayerData) args[0]); - break; - - case "PlayerDeniedEvent": - event = new PlayerDeniedEvent((PlayerData) args[0], (Message) args[1]); - break; - } - - if (event == null) { - plugin.getLogger().warning("Unable to call missing event " + name); - - return commonEvent; - } - - ProxyServer.getInstance().getPluginManager().callEvent(event); - - if (event instanceof SilentCancellableEvent) { - commonEvent = new CommonEvent(((SilentCancellableEvent) event).isCancelled(), ((SilentCancellableEvent) event).isSilent()); - } else if (event instanceof SilentEvent) { - commonEvent = new CommonEvent(false, ((SilentEvent) event).isSilent()); - } else if (event instanceof CustomCancellableEvent) { - commonEvent = new CommonEvent(((CustomCancellableEvent) event).isCancelled(), true); - } - - return commonEvent; - } - public static BaseComponent[] formatMessage(String message) { String json = ColorUtils.toDownsampledJson(message); return ComponentSerializer.parse(json); diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/CustomCancellableEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/CustomCancellableEvent.java deleted file mode 100755 index 37596d608..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/CustomCancellableEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import lombok.Setter; -import net.md_5.bungee.api.plugin.Cancellable; - -public abstract class CustomCancellableEvent extends CustomEvent implements Cancellable { - - @Getter - @Setter - private boolean cancelled = false; - - public CustomCancellableEvent() { - super(); - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/CustomEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/CustomEvent.java deleted file mode 100755 index 4de2a8a46..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/CustomEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import net.md_5.bungee.api.plugin.Event; - -public abstract class CustomEvent extends Event { -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpBanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpBanEvent.java deleted file mode 100755 index b0dcf8bc5..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpBanEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBanEvent extends SilentCancellableEvent { - - @Getter - private IpBanData ban; - - public IpBanEvent(IpBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpBannedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpBannedEvent.java deleted file mode 100755 index 1e78287e2..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpBannedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBannedEvent extends SilentEvent { - - @Getter - private IpBanData ban; - - public IpBannedEvent(IpBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpMuteEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpMuteEvent.java deleted file mode 100755 index d712ee46b..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpMuteEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMuteEvent extends SilentCancellableEvent { - - @Getter - private IpMuteData mute; - - public IpMuteEvent(IpMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpMutedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpMutedEvent.java deleted file mode 100755 index 818c3ba04..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpMutedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMutedEvent extends SilentEvent { - - @Getter - private IpMuteData mute; - - public IpMutedEvent(IpMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeBanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeBanEvent.java deleted file mode 100755 index 134d5aec8..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeBanEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - -public class IpRangeBanEvent extends SilentCancellableEvent { - - @Getter - private IpRangeBanData ban; - - public IpRangeBanEvent(IpRangeBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeBannedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeBannedEvent.java deleted file mode 100755 index df23aab5d..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeBannedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - - -public class IpRangeBannedEvent extends SilentEvent { - - @Getter - private IpRangeBanData ban; - - public IpRangeBannedEvent(IpRangeBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeUnbanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeUnbanEvent.java deleted file mode 100755 index cb5626529..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpRangeUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpRangeUnbanEvent extends SilentCancellableEvent { - - @Getter - private IpRangeBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpUnbanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpUnbanEvent.java deleted file mode 100755 index 5ab9b81c1..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpUnbanEvent extends SilentCancellableEvent { - - @Getter - private IpBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpUnmutedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpUnmutedEvent.java deleted file mode 100755 index 446e50eb5..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/IpUnmutedEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpUnmutedEvent extends SilentCancellableEvent { - - @Getter - private IpMuteData mute; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameBanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameBanEvent.java deleted file mode 100755 index c33781f08..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameBanEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - - -public class NameBanEvent extends SilentCancellableEvent { - - @Getter - private NameBanData ban; - - public NameBanEvent(NameBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameBannedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameBannedEvent.java deleted file mode 100755 index b50e72435..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameBannedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - -public class NameBannedEvent extends SilentEvent { - - @Getter - private NameBanData ban; - - public NameBannedEvent(NameBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameUnbanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameUnbanEvent.java deleted file mode 100755 index 0aa9b53c9..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/NameUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class NameUnbanEvent extends SilentCancellableEvent { - - @Getter - private NameBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerBanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerBanEvent.java deleted file mode 100755 index 855785272..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerBanEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; - - -public class PlayerBanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - - public PlayerBanEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerBannedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerBannedEvent.java deleted file mode 100755 index 472cdcf77..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerBannedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.util.Message; - - -public class PlayerBannedEvent extends SilentEvent { - - @Getter - private PlayerBanData ban; - - @Getter - @Setter - private Message kickMessage; - - public PlayerBannedEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerDeniedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerDeniedEvent.java deleted file mode 100644 index e0287d1d6..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerDeniedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.util.Message; - -public class PlayerDeniedEvent extends CustomCancellableEvent { - - @Getter - @Setter - private Message message; - - @Getter - private PlayerData player; - - public PlayerDeniedEvent(PlayerData player, Message message) { - super(); - - this.player = player; - this.message = message; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerKickedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerKickedEvent.java deleted file mode 100755 index a6915df51..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerKickedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerKickData; - -public class PlayerKickedEvent extends SilentEvent { - - @Getter - private PlayerKickData kick; - - public PlayerKickedEvent(PlayerKickData kick, boolean isSilent) { - super(isSilent); - this.kick = kick; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerMuteEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerMuteEvent.java deleted file mode 100755 index e228f303e..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerMuteEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerMuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMuteEvent(PlayerMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerMutedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerMutedEvent.java deleted file mode 100755 index 5e488073e..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerMutedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerMutedEvent extends SilentEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMutedEvent(PlayerMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerNoteCreatedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerNoteCreatedEvent.java deleted file mode 100755 index ee001ad25..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerNoteCreatedEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerNoteData; - - -public class PlayerNoteCreatedEvent extends SilentCancellableEvent { - - @Getter - private PlayerNoteData note; - - public PlayerNoteCreatedEvent(PlayerNoteData note) { - this(note, false); - } - - public PlayerNoteCreatedEvent(PlayerNoteData note, boolean silent) { - super(silent); - this.note = note; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportDeletedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportDeletedEvent.java deleted file mode 100755 index 43c585110..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportDeletedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - -public class PlayerReportDeletedEvent extends CustomEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportDeletedEvent(PlayerReportData report) { - super(); - this.report = report; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportEvent.java deleted file mode 100755 index 86f7492b0..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - - -public class PlayerReportEvent extends SilentCancellableEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportEvent(PlayerReportData report, boolean isSilent) { - super(isSilent); - this.report = report; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportedEvent.java deleted file mode 100755 index 3c8c59190..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerReportedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - - -public class PlayerReportedEvent extends SilentEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportedEvent(PlayerReportData report, boolean isSilent) { - super(isSilent); - this.report = report; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerUnbanEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerUnbanEvent.java deleted file mode 100755 index 66f2c0a73..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class PlayerUnbanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerUnmuteEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerUnmuteEvent.java deleted file mode 100755 index f36a27a1d..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerUnmuteEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerUnmuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - @Getter - private PlayerData actor; - @Getter - private String reason; - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerWarnEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerWarnEvent.java deleted file mode 100755 index 6ceedf5ce..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerWarnEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - - -public class PlayerWarnEvent extends SilentEvent { - - @Getter - private PlayerWarnData warning; - - public PlayerWarnEvent(PlayerWarnData warning, boolean silent) { - super(silent); - this.warning = warning; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerWarnedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerWarnedEvent.java deleted file mode 100755 index fb9e88010..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PlayerWarnedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - - -public class PlayerWarnedEvent extends SilentEvent { - - @Getter - private PlayerWarnData warning; - - public PlayerWarnedEvent(PlayerWarnData warning, boolean silent) { - super(silent); - this.warning = warning; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PluginReloadedEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PluginReloadedEvent.java deleted file mode 100644 index ba2ca92e0..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/PluginReloadedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; - - -public class PluginReloadedEvent extends CustomCancellableEvent { - - @Getter - private PlayerData actor; - - public PluginReloadedEvent(PlayerData actor) { - super(); - - this.actor = actor; - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/SilentCancellableEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/SilentCancellableEvent.java deleted file mode 100755 index 34234d8e9..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/SilentCancellableEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentCancellableEvent extends CustomCancellableEvent { - - @Getter - @Setter - private boolean silent; - - public SilentCancellableEvent(boolean silent) { - super(); - - this.silent = silent; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/SilentEvent.java b/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/SilentEvent.java deleted file mode 100755 index c834216e3..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/api/events/SilentEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.bungee.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentEvent extends CustomEvent { - @Getter - @Setter - private boolean silent = false; - - public SilentEvent(boolean silent) { - super(); - - this.silent = silent; - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/BanListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/BanListener.java deleted file mode 100755 index d7059c7a4..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/BanListener.java +++ /dev/null @@ -1,41 +0,0 @@ -package me.confuser.banmanager.bungee.listeners; - - -import me.confuser.banmanager.bungee.api.events.IpBannedEvent; -import me.confuser.banmanager.bungee.api.events.IpRangeBannedEvent; -import me.confuser.banmanager.bungee.api.events.NameBannedEvent; -import me.confuser.banmanager.bungee.api.events.PlayerBannedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonBanListener; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -public class BanListener implements Listener { - - private final CommonBanListener listener; - - public BanListener(BanManagerPlugin plugin) { - this.listener = new CommonBanListener(plugin); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnBan(PlayerBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnIpBan(IpBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnIpRangeBan(IpRangeBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnNameBan(NameBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/HookListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/HookListener.java deleted file mode 100755 index 1fcf72786..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/HookListener.java +++ /dev/null @@ -1,101 +0,0 @@ -package me.confuser.banmanager.bungee.listeners; - -import me.confuser.banmanager.bungee.api.events.*; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonHooksListener; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -public class HookListener implements Listener { - private final CommonHooksListener listener; - - public HookListener(BanManagerPlugin plugin) { - this.listener = new CommonHooksListener(plugin); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBan(final PlayerBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBan(final PlayerBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onUnban(final PlayerUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onMute(final PlayerMuteEvent event) { - listener.onMute(event.getMute(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onMute(final PlayerMutedEvent event) { - listener.onMute(event.getMute(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onUnmute(final PlayerUnmuteEvent event) { - listener.onUnmute(event.getMute(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBan(final IpBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBan(final IpBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onUnban(final IpUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBan(final IpRangeBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onBan(final IpRangeBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onUnban(final IpRangeUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onWarn(final PlayerWarnEvent event) { - listener.onWarn(event.getWarning(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onWarn(final PlayerWarnedEvent event) { - listener.onWarn(event.getWarning(), false, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onNote(final PlayerNoteCreatedEvent event) { - listener.onNote(event.getNote(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onReport(final PlayerReportEvent event) { - listener.onReport(event.getReport(), true, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onReport(final PlayerReportedEvent event) { - listener.onReport(event.getReport(), false, event.isSilent()); - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java index 43f3f861d..ec7ae79d4 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java @@ -42,12 +42,12 @@ public void banCheck(LoginEvent event) { @EventHandler public void onJoin(PostLoginEvent event) { - listener.onJoin(new BungeePlayer(event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode())); + listener.onJoin(new BungeePlayer(plugin.getPlugin(), event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode())); } @EventHandler(priority = EventPriority.LOWEST) public void onPlayerLogin(PostLoginEvent event) { - listener.onPlayerLogin(new BungeePlayer(event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode()), new LoginHandler(event)); + listener.onPlayerLogin(new BungeePlayer(plugin.getPlugin(), event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode()), new LoginHandler(event)); } @RequiredArgsConstructor @@ -57,7 +57,6 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { - plugin.getServer().callEvent("PlayerDeniedEvent", player, message); String locale = player.getLocale() != null ? player.getLocale() : "en"; event.setCancelled(true); event.setCancelReason(BungeeServer.formatMessage(message.resolveComponent(locale))); diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/MuteListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/MuteListener.java deleted file mode 100755 index acf252be3..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/MuteListener.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.bungee.listeners; - - -import me.confuser.banmanager.bungee.api.events.IpMutedEvent; -import me.confuser.banmanager.bungee.api.events.PlayerMutedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonMuteListener; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -public class MuteListener implements Listener { - private final CommonMuteListener listener; - - public MuteListener(BanManagerPlugin plugin) { - this.listener = new CommonMuteListener(plugin); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnMute(PlayerMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnMute(IpMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/NoteListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/NoteListener.java deleted file mode 100755 index e5007f24e..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/NoteListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.bungee.listeners; - - -import me.confuser.banmanager.bungee.api.events.PlayerNoteCreatedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonNoteListener; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -public class NoteListener implements Listener { - private final CommonNoteListener listener; - - public NoteListener(BanManagerPlugin plugin) { - this.listener = new CommonNoteListener(plugin); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnNote(PlayerNoteCreatedEvent event) { - listener.notifyOnNote(event.getNote(), event.isSilent()); - } - -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/ReloadListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/ReloadListener.java deleted file mode 100644 index beb1438a3..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/ReloadListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.bungee.listeners; - -import lombok.RequiredArgsConstructor; -import me.confuser.banmanager.bungee.BMBungeePlugin; -import me.confuser.banmanager.bungee.api.events.PluginReloadedEvent; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -@RequiredArgsConstructor -public class ReloadListener implements Listener { - private final BMBungeePlugin bungeePlugin; - - @EventHandler(priority = EventPriority.HIGHEST) - public void onReload(final PluginReloadedEvent event) { - bungeePlugin.registerChatListener(); - } -} diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/WebhookListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/WebhookListener.java deleted file mode 100644 index a58c3fee8..000000000 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/WebhookListener.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.confuser.banmanager.bungee.listeners; - -import me.confuser.banmanager.bungee.api.events.*; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonWebhookListener; -import me.confuser.banmanager.common.data.Webhook; -import net.md_5.bungee.api.plugin.Listener; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; - -import java.util.List; - -public class WebhookListener implements Listener { - private CommonWebhookListener listener; - - public WebhookListener(BanManagerPlugin plugin) { - this.listener = new CommonWebhookListener(plugin); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnBan(PlayerBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnMute(PlayerMutedEvent event) { - List webhooks = listener.notifyOnMute(event.getMute()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnBan(IpBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnKick(PlayerKickedEvent event) { - List webhooks = listener.notifyOnKick(event.getKick()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnWarn(PlayerWarnedEvent event) { - List webhooks = listener.notifyOnWarn(event.getWarning()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnUnban(PlayerUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnUnban(IpUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnUnmute(PlayerUnmuteEvent event) { - List webhooks = listener.notifyOnUnmute(event.getMute(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void notifyOnReport(PlayerReportedEvent event) { - List webhooks = listener.notifyOnReport(event.getReport(), event.getReport().getActor(), event.getReport().getReason()); - sendAll(webhooks, event.isSilent()); - } - - private void sendAll(List webhooks, boolean isSilent) { - for (Webhook data : webhooks) { - if (isSilent && data.ignoreSilent()) continue; - if (data.url() == null || data.payload() == null || data.url().isEmpty() || data.payload().isEmpty()) continue; - listener.sendAsync(data); - } - } -} diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 6e2944a7b..9f0e262c2 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -46,6 +46,7 @@ mavenPublishing { } dependencies { + api(project(":BanManagerAPI")) api(project(":BanManagerLibs")) testImplementation(platform("org.junit:junit-bom:5.11.4")) diff --git a/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java b/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java index 03dfdc778..3acd74ee4 100644 --- a/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java +++ b/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java @@ -2,11 +2,16 @@ import lombok.Getter; import lombok.Setter; -import me.confuser.banmanager.common.api.BmAPI; import me.confuser.banmanager.common.commands.*; import me.confuser.banmanager.common.commands.global.*; import me.confuser.banmanager.common.configs.*; import me.confuser.banmanager.common.hikari.HikariDataSource; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.BanManagerServiceImpl; +import me.confuser.banmanager.common.impl.DatabaseAccessImpl; +import me.confuser.banmanager.common.impl.SchedulerAdapter; +import me.confuser.banmanager.api.event.EventBus; +import me.confuser.banmanager.common.impl.event.EventBusImpl; import me.confuser.banmanager.common.ormlite.dao.GenericRawResults; import me.confuser.banmanager.common.ormlite.db.DatabaseType; import me.confuser.banmanager.common.ormlite.jdbc.DataSourceConnectionSource; @@ -41,7 +46,6 @@ import static java.lang.Long.parseLong; public class BanManagerPlugin { - private static BanManagerPlugin self; // Token names used by commands/listeners via Message.set(). Hardcoded because these are // defined across many source files and not available at runtime as a registry. Used only @@ -65,15 +69,6 @@ public class BanManagerPlugin { BUILTIN_TOKENS = Collections.unmodifiableSet(normalised); } - /* - * This block prevents the Maven Shade plugin to remove the specified classes - */ - static { - @SuppressWarnings("unused") Class[] classes = new Class[]{ - BmAPI.class, - }; - } - @Getter private PluginInfo pluginInfo; @Getter @@ -106,6 +101,12 @@ public class BanManagerPlugin { private ConnectionSource localConn; @Getter private ConnectionSource globalConn; + // Underlying Hikari pools, exposed via DatabaseAccess to API consumers. + // Owned by this plugin; do not close from outside. + @Getter + private HikariDataSource localDataSource; + @Getter + private HikariDataSource globalDataSource; // Storage @Getter @@ -192,6 +193,12 @@ public class BanManagerPlugin { @Getter private MessageRegistry messageRegistry; + // MiniMessage / serializer pipeline. Single instance owned by the plugin so static + // tokens loaded from messages.yml are scoped to this server instead of leaking + // across reloads or between plugins on the same JVM. + @Getter + private final MessageRenderer messageRenderer = new MessageRenderer(); + // Shared HTTP infrastructure for webhooks, UUID lookups and GeoIP downloads. // Lazily initialised on first use so unit tests that don't touch HTTP avoid // spinning up the executor. volatile ensures the double-checked locking in @@ -200,6 +207,20 @@ public class BanManagerPlugin { private volatile ExecutorService httpExecutor; private final Object httpLock = new Object(); + // Public API surface — created in enable() once storage is initialised so all + // sub-services have working DAOs, torn down in disable() to close the dedicated + // DB-I/O executor and detach the SPI registration. + @Getter + private BanManagerServiceImpl apiService; + private ExecutorService dbExecutor; + + // EventBus is created up-front in enable() — *before* storage — so the storage + // layer can publish typed events from its mutating methods. The same instance + // is later wrapped by BanManagerServiceImpl so external subscribers and internal + // publishers share one dispatch graph. + @Getter + private EventBus eventBus; + public BanManagerPlugin(PluginInfo pluginInfo, CommonLogger logger, File dataFolder, CommonScheduler scheduler, CommonServer server, CommonMetrics metrics) { this.pluginInfo = pluginInfo; this.logger = logger; @@ -207,12 +228,16 @@ public BanManagerPlugin(PluginInfo pluginInfo, CommonLogger logger, File dataFol this.server = server; this.scheduler = scheduler; this.metrics = metrics; - self = this; } public final void enable() throws Exception { setupConfigs(); + // Build the event bus first so storage can publish typed events from + // its mutating methods. apiService later wraps the same instance so + // external subscribers and internal publishers share one dispatch graph. + eventBus = new EventBusImpl(logger); + try { if (!config.isDebugEnabled()) { disableDatabaseLogging(); @@ -290,9 +315,50 @@ public final void enable() throws Exception { logger.warning("Failed to submit stats, ignoring"); } } + + apiService = buildApiService(); + me.confuser.banmanager.api.BanManager.set(apiService); + } + + /** + * Builds the public {@link me.confuser.banmanager.api.BanManagerService} composite root. + * + *

The dedicated DB-I/O executor sizes itself to {@code maxConnections + 2} + * threads (a hair over the Hikari pool to leave headroom for transient + * spikes); going larger would just queue inside Hikari. Daemon threads stop + * the JVM from hanging on shutdown if {@link #disable()} doesn't run + * (e.g. SIGKILL).

+ */ + private BanManagerServiceImpl buildApiService() { + int poolSize = Math.max(2, config.getLocalDb().getMaxConnections() + 2); + AtomicInteger counter = new AtomicInteger(); + dbExecutor = Executors.newFixedThreadPool(poolSize, r -> { + Thread t = new Thread(r, "BanManager-DB-" + counter.incrementAndGet()); + t.setDaemon(true); + return t; + }); + + AsyncSupport async = new AsyncSupport(dbExecutor); + DatabaseAccessImpl database = new DatabaseAccessImpl(this); + SchedulerAdapter sched = new SchedulerAdapter(scheduler); + + return new BanManagerServiceImpl(this, eventBus, database, sched, async); } public final void disable() { + // Shutdown order matters: + // 1. Drain the DB executor first so all in-flight async work that may + // publish post-events completes against a still-live event bus. + // 2. Only then detach the static SPI handle. Callers of BanManager.get() + // after this point see the documented IllegalStateException instead + // of a half-torn-down service whose async submissions silently fail. + // 3. Save in-memory state (schedules) before closing connections. + // 4. Close ORMLite ConnectionSources, then the Hikari pools they wrap. + // 5. HTTP executor last (webhook bursts queued by post-events should + // flush before the pool dies). + shutdownDbExecutor(); + me.confuser.banmanager.api.BanManager.clear(); + if (getSchedulesConfig() != null) { getSchedulesConfig().save(); } @@ -313,9 +379,43 @@ public final void disable() { globalConn.closeQuietly(); } + // ORMLite's DataSourceConnectionSource doesn't close the underlying DataSource on + // closeQuietly(), so the Hikari pools must be closed explicitly to release threads. + if (localDataSource != null) { + localDataSource.close(); + localDataSource = null; + } + if (globalDataSource != null) { + globalDataSource.close(); + globalDataSource = null; + } + shutdownHttpExecutor(); } + /** + * Shuts down the dedicated DB-I/O executor backing the public API. Mirrors + * {@link #shutdownHttpExecutor()} — give in-flight queries a few seconds to + * finish, then force-cancel anything still running so the JVM can exit. + */ + private void shutdownDbExecutor() { + ExecutorService executor = dbExecutor; + dbExecutor = null; + apiService = null; + eventBus = null; + if (executor == null) return; + + executor.shutdown(); + try { + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + /** * Returns the shared {@link HttpClient}, creating it on first use. Callers * (webhooks, UUID lookups, GeoIP downloads) share a single dedicated thread @@ -370,7 +470,7 @@ public void setupConfigs() { schedulesConfig = reloadConfig(new SchedulesConfig(dataFolder, logger), schedulesConfig, "schedules.yml"); exemptionsConfig = reloadConfig(new ExemptionsConfig(dataFolder, logger), exemptionsConfig, "exemptions.yml"); reasonsConfig = reloadConfig(new ReasonsConfig(dataFolder, logger), reasonsConfig, "reasons.yml"); - geoIpConfig = reloadConfig(new GeoIpConfig(dataFolder, logger), geoIpConfig, "geoip.yml"); + geoIpConfig = reloadConfig(new GeoIpConfig(dataFolder, logger, this::getHttpClient), geoIpConfig, "geoip.yml"); webhookConfig = reloadConfig(new WebhookConfig(dataFolder, logger), webhookConfig, "webhooks.yml"); notificationsConfig = reloadConfig(new NotificationsConfig(dataFolder, logger), notificationsConfig, "notifications.yml"); @@ -382,8 +482,7 @@ private void loadMessages() { MessageRegistry newRegistry = new MessageRegistry(defaultLocale); // Clear static tokens before loading so removed tokens don't persist across reloads - MessageRenderer renderer = MessageRenderer.getInstance(); - renderer.loadStaticTokens(Collections.emptyMap()); + messageRenderer.loadStaticTokens(Collections.emptyMap()); copyMessagesDirectory(); @@ -420,10 +519,12 @@ private void loadMessages() { messageRegistry = newRegistry; } - Message.init(messageRegistry, logger); + Message.init(messageRegistry, logger, messageRenderer, + () -> placeholderResolver, + () -> config != null && config.isPerPlayerLocale()); - if (renderer.getStaticTokens() != null) { - for (String tokenName : renderer.getStaticTokens().keySet()) { + if (messageRenderer.getStaticTokens() != null) { + for (String tokenName : messageRenderer.getStaticTokens().keySet()) { if (BUILTIN_TOKENS.contains(tokenName)) { logger.warning("Static token '" + tokenName + "' collides with a built-in token and will be overridden at runtime"); } @@ -460,7 +561,7 @@ private void loadLocaleFile(MessageRegistry registry, File file, String locale) } Map messages = new HashMap<>(); - MessageRenderer renderer = MessageRenderer.getInstance(); + MessageRenderer renderer = messageRenderer; int rejectedCount = 0; for (String key : conf.getConfigurationSection("messages").getKeys(true)) { @@ -565,10 +666,12 @@ private void disableDatabaseLogging() { } public boolean setupConnections() throws SQLException { - localConn = createConnection(config.getLocalDb(), "bm-local"); + localDataSource = createDataSource(config.getLocalDb(), "bm-local"); + localConn = createConnection(config.getLocalDb(), localDataSource); if (config.getGlobalDb().isEnabled()) { - globalConn = createConnection(config.getGlobalDb(), "bm-global"); + globalDataSource = createDataSource(config.getGlobalDb(), "bm-global"); + globalConn = createConnection(config.getGlobalDb(), globalDataSource); } // Deregister BanManager's relocated drivers from DriverManager to prevent @@ -579,7 +682,26 @@ public boolean setupConnections() throws SQLException { return true; } + /** + * Backwards-compatible single-arg helper that builds a fresh DataSource + * and wraps it. Prefer the (DatabaseConfig, HikariDataSource) overload so + * the caller can also expose the pool via {@link #getLocalDataSource()}. + */ public ConnectionSource createConnection(DatabaseConfig dbConfig, String type) throws SQLException { + return createConnection(dbConfig, createDataSource(dbConfig, type)); + } + + public ConnectionSource createConnection(DatabaseConfig dbConfig, HikariDataSource ds) throws SQLException { + DatabaseType databaseType = resolveDatabaseType(dbConfig); + return new DataSourceConnectionSource(ds, databaseType); + } + + /** + * Build the Hikari pool for the supplied database config. Split out from + * {@link #createConnection(DatabaseConfig, String)} so the public API can + * surface the {@link javax.sql.DataSource} without going through ORMLite. + */ + public HikariDataSource createDataSource(DatabaseConfig dbConfig, String type) { HikariDataSource ds = new HikariDataSource(); if (!dbConfig.getStorageType().equals("h2")) { @@ -600,17 +722,12 @@ public ConnectionSource createConnection(DatabaseConfig dbConfig, String type) t if (dbConfig.getLeakDetection() != 0) ds.setLeakDetectionThreshold(dbConfig.getLeakDetection()); - DatabaseType databaseType; - if (dbConfig.getStorageType().equals("mariadb")) { ds.setDriverClassName("me.confuser.banmanager.common.mariadb.Driver"); - databaseType = new MariaDBDatabase(); } else if (dbConfig.getStorageType().equals("mysql")) { // Forcefully specify the newer driver ds.setDriverClassName(me.confuser.banmanager.common.mysql.cj.jdbc.Driver.class.getName()); - databaseType = new MySQLDatabase(); - ds.addDataSourceProperty("cachePrepStmts", "true"); ds.addDataSourceProperty("prepStmtCacheSize", "250"); ds.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); @@ -630,11 +747,24 @@ public ConnectionSource createConnection(DatabaseConfig dbConfig, String type) t // Required for integration tests ds.setDriverClassName("org.h2.Driver"); } - - databaseType = new H2DatabaseType(); } - return new DataSourceConnectionSource(ds, databaseType); + return ds; + } + + /** + * Resolves the ORMLite {@link DatabaseType} for the configured storage backend. + * Mirrors the dispatch in {@link #createDataSource(DatabaseConfig, String)} so + * the two stay aligned without duplicating Hikari setup. + */ + public DatabaseType resolveDatabaseType(DatabaseConfig dbConfig) { + if (dbConfig.getStorageType().equals("mariadb")) { + return new MariaDBDatabase(); + } + if (dbConfig.getStorageType().equals("mysql")) { + return new MySQLDatabase(); + } + return new H2DatabaseType(); } public void setupStorage() throws SQLException { @@ -752,8 +882,4 @@ public CommonCommand[] getGlobalCommands() { new UnmuteAllCommand(this) }; } - - public static BanManagerPlugin getInstance() { - return self; - } } diff --git a/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java b/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java index 769b7ba74..61b1eca14 100644 --- a/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java +++ b/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java @@ -19,7 +19,8 @@ default void kick(Message message) { } default void kick(Component component) { - kick(MessageRenderer.getInstance().toLegacy(component)); + MessageRenderer renderer = Message.renderer(); + kick(renderer != null ? renderer.toLegacy(component) : component.toString()); } void sendMessage(String message); @@ -31,7 +32,8 @@ default void sendMessage(Message message) { @Override default void sendMessage(Component component) { - sendMessage(MessageRenderer.getInstance().toLegacy(component)); + MessageRenderer renderer = Message.renderer(); + sendMessage(renderer != null ? renderer.toLegacy(component) : component.toString()); } default void sendActionBar(Component component) { diff --git a/common/src/main/java/me/confuser/banmanager/common/CommonScheduler.java b/common/src/main/java/me/confuser/banmanager/common/CommonScheduler.java index 88d32d7c9..0dd8df2b3 100644 --- a/common/src/main/java/me/confuser/banmanager/common/CommonScheduler.java +++ b/common/src/main/java/me/confuser/banmanager/common/CommonScheduler.java @@ -9,4 +9,15 @@ public interface CommonScheduler { void runSyncLater(Runnable task, Duration delay); void runAsyncRepeating(Runnable task, Duration initialDelay, Duration period); default void cancelAll() {} + + /** + * @return {@code true} when {@link #runSync(Runnable)} pins to the server + * tick thread (Bukkit/Sponge/Fabric); {@code false} on proxies + * where it aliases to {@link #runAsync(Runnable)} (Bungee/Velocity). + * Mirrors + * {@link me.confuser.banmanager.api.scheduler.BanManagerScheduler#isMainThreadAware()}. + */ + default boolean isMainThreadAware() { + return true; + } } diff --git a/common/src/main/java/me/confuser/banmanager/common/CommonServer.java b/common/src/main/java/me/confuser/banmanager/common/CommonServer.java index 4fc39b669..6b817a2bc 100644 --- a/common/src/main/java/me/confuser/banmanager/common/CommonServer.java +++ b/common/src/main/java/me/confuser/banmanager/common/CommonServer.java @@ -1,6 +1,5 @@ package me.confuser.banmanager.common; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; import me.confuser.banmanager.common.kyori.text.Component; import me.confuser.banmanager.common.util.Message; @@ -25,7 +24,10 @@ default void broadcast(Message message, String permission) { player.sendMessage(message.resolveComponentFor(player)); } } - getConsoleSender().sendMessage(MessageRenderer.getInstance().toPlainText(message.resolveComponent())); + MessageRenderer renderer = Message.renderer(); + if (renderer != null) { + getConsoleSender().sendMessage(renderer.toPlainText(message.resolveComponent())); + } } // Console receives plain text since it cannot render Components with hover/click events @@ -35,7 +37,10 @@ default void broadcast(Component message, String permission) { player.sendMessage(message); } } - getConsoleSender().sendMessage(MessageRenderer.getInstance().toPlainText(message)); + MessageRenderer renderer = Message.renderer(); + if (renderer != null) { + getConsoleSender().sendMessage(renderer.toPlainText(message)); + } } void broadcast(String message, String permission, CommonSender sender); @@ -46,7 +51,5 @@ default void broadcast(Component message, String permission) { CommonWorld getWorld(String name); - CommonEvent callEvent(String name, Object... args); - CommonExternalCommand getPluginCommand(String commandName); } diff --git a/common/src/main/java/me/confuser/banmanager/common/api/BmAPI.java b/common/src/main/java/me/confuser/banmanager/common/api/BmAPI.java deleted file mode 100644 index 6808bd251..000000000 --- a/common/src/main/java/me/confuser/banmanager/common/api/BmAPI.java +++ /dev/null @@ -1,631 +0,0 @@ -package me.confuser.banmanager.common.api; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.data.*; -import me.confuser.banmanager.common.ipaddr.IPAddress; -import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; -import me.confuser.banmanager.common.ormlite.support.ConnectionSource; -import me.confuser.banmanager.common.util.DateUtils; -import me.confuser.banmanager.common.util.IPUtils; -import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.common.util.UUIDUtils; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * This is a static API for BanManager - * No methods are thread safe unless stated otherwise - * Note: The API does not handle permission checks for exemptions - * - * @author James Mortemore - */ -public class BmAPI { - - /** - * @param uuid Player UUID - * @return PlayerData - * @throws SQLException - */ - public static PlayerData getPlayer(UUID uuid) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); - } - - /** - * @param name Player name - * @return PlayerData - * @throws SQLException - */ - public static PlayerData getPlayer(String name) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerStorage().retrieve(name, false); - } - - /** - * @param ip Player IP - * @return a list of PlayerData - * @throws SQLException - */ - public static List getPlayers(IPAddress ip) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerStorage().getDuplicatesInTime(ip, BanManagerPlugin.getInstance().getConfig().getTimeAssociatedAlts()); - } - - /** - * Get the console for use as an actor - * - * @return PlayerData - */ - public static PlayerData getConsole() { - return BanManagerPlugin.getInstance().getPlayerStorage().getConsole(); - } - - /** - * Convert an ip string of x.x.x.x to IPAddress - * - * @param ip IPv4 in x.x.x.x format - * @return IPv4 in number format - */ - public static IPAddress toIp(String ip) { - return IPUtils.toIPAddress(ip); - } - - /** - * Permanently ban a player. - * You must handle kicking the player if they are online. - * - * @param ban PlayerBanData - * @return Returns true if ban successful - * @throws SQLException - */ - public static boolean ban(PlayerBanData ban) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerBanStorage().ban(ban); - } - - /** - * Permanently ban a player. - * You must handle kicking the player if they are online. - * - * @param player Player to ban - * @param actor Who the ban is by - * @param reason Why they are banned - * @param silent Whether the ban should be broadcast - * @return Returns true if ban successful - * @throws SQLException - */ - public static boolean ban(PlayerData player, PlayerData actor, String reason, boolean silent) throws SQLException { - return ban(new PlayerBanData(player, actor, reason, silent)); - } - - /** - * Temporarily ban a player - * You must handle kicking the player if they are online. - * - * @param player Player to ban - * @param actor Who the ban is by - * @param reason Why they are banned - * @param expires Unix Timestamp in seconds stating the time of when the ban ends - * @return Returns true if ban successful - * @throws SQLException - */ - public static boolean ban(PlayerData player, PlayerData actor, String reason, boolean silent, long expires) throws SQLException { - return ban(new PlayerBanData(player, actor, reason, silent, expires)); - } - - /** - * @param ban The ban (can be retrieved via getBan) - * @param actor Who is unbanning the player - * @return Returns true if the unban is successful - * @throws SQLException - */ - public static boolean unban(PlayerBanData ban, PlayerData actor) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerBanStorage().unban(ban, actor); - } - - /** - * @param ban The ban (can be retrieved via getBan) - * @param actor Who is unbanning the player - * @param silent Whether the unban should be treated as silent - * @return Returns true if the unban is successful - * @throws SQLException - */ - public static boolean unban(PlayerBanData ban, PlayerData actor, boolean silent) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerBanStorage().unban(ban, actor, "", false, silent); - } - - /** - * Thread safe - * - * @param uuid Player UUID - * @return Returns true if player is banned - */ - public static boolean isBanned(UUID uuid) { - return BanManagerPlugin.getInstance().getPlayerBanStorage().isBanned(uuid); - } - - /** - * Thread safe - * - * @param name Player name - * @return Returns true if player is banned - */ - public static boolean isBanned(String name) { - return BanManagerPlugin.getInstance().getPlayerBanStorage().isBanned(name); - } - - - /** - * Thread safe - * - * @param name Player name - * @return Returns the active ban of a player; if the player is not banned this returns null - */ - public static PlayerBanData getCurrentBan(String name) { - return BanManagerPlugin.getInstance().getPlayerBanStorage().getBan(name); - } - - /** - * Thread safe - * - * @param uuid Player UUID - * @return Returns the active ban of a player; if the player is not banned this returns null - */ - public static PlayerBanData getCurrentBan(UUID uuid) { - return BanManagerPlugin.getInstance().getPlayerBanStorage().getBan(uuid); - } - - /** - * Retrieve previous ban records for a player. - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param player BanManager's player record - * @return An iterator of ban records - * @throws SQLException - */ - public static CloseableIterator getBanRecords(PlayerData player) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerBanRecordStorage().getRecords(player); - } - - /** - * Permanently mute a player. - * You must handle kicking the player if they are online. - * - * @param mute PlayerMuteData - * @return Returns true if the mute is successful - * @throws SQLException - */ - public static boolean mute(PlayerMuteData mute) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().mute(mute); - } - - /** - * Permanently mute a player. - * You must handle kicking the player if they are online. - * - * @param player Player to mute - * @param actor Who the mute is by - * @param reason Why they are muted - * @return Returns true if the mute is successful - * @throws SQLException - */ - public static boolean mute(PlayerData player, PlayerData actor, String reason) throws SQLException { - return mute(new PlayerMuteData(player, actor, reason, false, false)); - } - - /** - * Permanently mute a player. - * You must handle kicking the player if they are online. - * - * @param player Player to mute - * @param actor Who the mute is by - * @param reason Why they are muted - * @param silent Whether the mute should be broadcast - * @return Returns true if mute is successful - * @throws SQLException - */ - public static boolean mute(PlayerData player, PlayerData actor, String reason, boolean silent) throws SQLException { - return mute(new PlayerMuteData(player, actor, reason, silent, false)); - } - - /** - * Permanently mute a player. - * You must handle kicking the player if they are online. - * - * @param player Player to mute - * @param actor Who the mute is by - * @param reason Why they are muted - * @param silent Whether the mute should be broadcast - * @param isSoft Whether the player should be aware they are muted; they will still see their own messages but nobody else will - * @return Returns true if the mute is successful - * @throws SQLException - */ - public static boolean mute(PlayerData player, PlayerData actor, String reason, boolean silent, boolean isSoft) throws SQLException { - return mute(new PlayerMuteData(player, actor, reason, silent, isSoft)); - } - - - /** - * Temporarily mute a player - * You must handle kicking the player if they are online. - * - * @param player Player to mute - * @param actor Who the mute is by - * @param reason Why they are muted - * @param silent Whether the mute should be broadcast - * @param isSoft Whether the player should be aware they are muted; they will still see their own messages but nobody else will - * @param expires Unix Timestamp in seconds stating the time of when the mute ends - * @return Returns true if mute successful - * @throws SQLException - */ - public static boolean mute(PlayerData player, PlayerData actor, String reason, boolean silent, boolean isSoft, long expires) throws SQLException { - return mute(new PlayerMuteData(player, actor, reason, silent, isSoft, expires)); - } - - /** - * Mute an IP. - * - * @param mute IpMuteData - * @return Returns true if the mute is successful - * @throws SQLException - */ - public static boolean mute(IpMuteData mute) throws SQLException { - return BanManagerPlugin.getInstance().getIpMuteStorage().mute(mute); - } - - /** - * @param mute PlayerMuteData - * @param actor Who unmuted the player - * @return Returns true if unmute successful - * @throws SQLException - */ - public static boolean unmute(PlayerMuteData mute, PlayerData actor) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().unmute(mute, actor); - } - - /** - * @param mute PlayerMuteData - * @param actor Who unmuted the player - * @param silent Whether the unmute should be treated as silent - * @return Returns true if unmute successful - * @throws SQLException - */ - public static boolean unmute(PlayerMuteData mute, PlayerData actor, boolean silent) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().unmute(mute, actor, "", false, silent); - } - - /** - * @param mute IP Mute record - * @param actor Who unmuted the ip - * @return Returns true if unmute is successful - * @throws SQLException - */ - public static boolean unmute(IpMuteData mute, PlayerData actor) throws SQLException { - return BanManagerPlugin.getInstance().getIpMuteStorage().unmute(mute, actor); - } - - /** - * @param mute IP Mute record - * @param actor Who unmuted the ip - * @param silent Whether the unmute should be treated as silent - * @return Returns true if unmute is successful - * @throws SQLException - */ - public static boolean unmute(IpMuteData mute, PlayerData actor, boolean silent) throws SQLException { - return BanManagerPlugin.getInstance().getIpMuteStorage().unmute(mute, actor, "", silent); - } - - /** - * Thread safe - * - * @param uuid Player UUID - * @return Returns true if player muted - */ - public static boolean isMuted(UUID uuid) { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().isMuted(uuid); - } - - /** - * Thread safe - * - * @param name Player Name - * @return Returns true if player muted - */ - public static boolean isMuted(String name) { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().isMuted(name); - } - - /** - * Thread safe - * - * @param ip IP address - * @return Returns true if IP address muted - */ - public static boolean isMuted(IPAddress ip) { - return BanManagerPlugin.getInstance().getIpMuteStorage().isMuted(ip); - } - - /** - * Thread safe - * - * @param name - * @return Returns the active mute of a player; if the player is not muted this returns null - */ - public static PlayerMuteData getCurrentMute(String name) { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().getMute(name); - } - - /** - * Thread safe - * - * @param uuid - * @return Returns the active mute of a player; if the player is not muted this returns null - */ - public static PlayerMuteData getCurrentMute(UUID uuid) { - return BanManagerPlugin.getInstance().getPlayerMuteStorage().getMute(uuid); - } - - /** - * Retrieve previous mute records for a player. - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param player Player record - * @return Iterator of previous mutes - * @throws SQLException - */ - public static CloseableIterator getMuteRecords(PlayerData player) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerMuteRecordStorage().getRecords(player); - } - - /** - * Permanently ban an ip. - * You must handle kicking the player if they are online. - * - * @param ban IpBanData - * @return Returns true if ban is successful - * @throws SQLException - */ - public static boolean ban(IpBanData ban) throws SQLException { - return BanManagerPlugin.getInstance().getIpBanStorage().ban(ban); - } - - /** - * Permanently ban an ip. - * You must handle kicking the player if they are online. - * - * @param ip IP to ban, use toIp to convert x.x.x.x to IPAddress - * @param actor Who the ban is by - * @param reason Why they are banned - * @param silent Whether the ban should be broadcast - * @return Returns true if ban is successful - * @throws SQLException - */ - public static boolean ban(IPAddress ip, PlayerData actor, String reason, boolean silent) throws SQLException { - return ban(new IpBanData(ip, actor, reason, silent)); - } - - /** - * Temporarily ban an ip - * You must handle kicking the player if they are online. - * - * @param ip IP to ban, use toIp to convert x.x.x.x to IPAddress - * @param actor Who the ban is by - * @param reason Why they are banned - * @param silent Whether the ban should be broadcast - * @param expires Unix Timestamp in seconds stating the time of when the ban ends - * @return Returns true if ban is successful - * @throws SQLException - */ - public static boolean ban(IPAddress ip, PlayerData actor, String reason, boolean silent, long expires) throws SQLException { - return ban(new IpBanData(ip, actor, reason, silent, expires)); - } - - /** - * @param ban IP Ban record - * @param actor Who unbanned the ip - * @return Returns true if unban is successful - * @throws SQLException - */ - public static boolean unban(IpBanData ban, PlayerData actor) throws SQLException { - return BanManagerPlugin.getInstance().getIpBanStorage().unban(ban, actor); - } - - /** - * @param ban IP Ban record - * @param actor Who unbanned the ip - * @param silent Whether the unban should be treated as silent - * @return Returns true if unban is successful - * @throws SQLException - */ - public static boolean unban(IpBanData ban, PlayerData actor, boolean silent) throws SQLException { - return BanManagerPlugin.getInstance().getIpBanStorage().unban(ban, actor, "", false, silent); - } - - /** - * Thread safe - * - * @param ip IP address to check, use toIp to convert x.x.x.x to IPAddress - * @return Returns true if ip is banned - */ - public static boolean isBanned(IPAddress ip) { - return BanManagerPlugin.getInstance().getIpBanStorage().isBanned(ip); - } - - /** - * Thread safe - * - * @param ip IP address to check, use toIp to convert x.x.x.x to IPAddress - * @return Returns the active ban of an ip; if the ip is not banned this returns null - */ - public static IpBanData getCurrentBan(IPAddress ip) { - return BanManagerPlugin.getInstance().getIpBanStorage().getBan(ip); - } - - /** - * Retrieve previous bans of an IP address. - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param ip IP address to look up, use toIp to convert x.x.x.x to IPAddress - * @return Iterator of previous bans for the ip - * @throws SQLException - */ - public static CloseableIterator getBanRecords(IPAddress ip) throws SQLException { - return BanManagerPlugin.getInstance().getIpBanRecordStorage().getRecords(ip); - } - - /** - * Warn a player. - * You must handle the notification to the warned player yourself. - * - * @param player Player record - * @param actor Player record of who warned the player - * @param reason What the player was warned for - * @param read Whether the player has already viewed the warning - * @return Returns true if warning is successful - * @throws SQLException - */ - public static boolean warn(PlayerData player, PlayerData actor, String reason, boolean read) throws SQLException { - return warn(new PlayerWarnData(player, actor, reason, 1, read)); - } - - /** - * Warn a player. - * You must handle the notification to the warned player yourself. - * - * @param player Player record - * @param actor Player record of who warned the player - * @param reason What the player was warned for - * @param read Whether the player has already viewed the warning - * @param silent Whether the warning should be broadcast - * @return Returns true if warning is successful - * @throws SQLException - */ - public static boolean warn(PlayerData player, PlayerData actor, String reason, boolean read, boolean silent) throws - SQLException { - return warn(new PlayerWarnData(player, actor, reason, 1, read), silent); - } - - /** - * Warn a player. - * You must handle the notification to the warned player yourself. - * - * @param data PlayerWarnData - * @return Returns true if warning is successful - * @throws SQLException - */ - public static boolean warn(PlayerWarnData data) throws SQLException { - return warn(data, false); - } - - /** - * Warn a player. - * You must handle the notification to the warned player yourself. - * - * @param data PlayerWarnData - * @param silent Whether the warning should be broadcast - * @return Returns true if warning is successful - * @throws SQLException - */ - public static boolean warn(PlayerWarnData data, boolean silent) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerWarnStorage().addWarning(data, silent); - } - - /** - * Retrieve past warnings of a player. - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param player Player record - * @return Iterator containing previous player warnings - * @throws SQLException - */ - public static CloseableIterator getWarnings(PlayerData player) throws SQLException { - return BanManagerPlugin.getInstance().getPlayerWarnStorage().getWarnings(player); - } - - /** - * Get all known names for a player (summary view with first/last seen). - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param uuid Player UUID - * @return List of PlayerNameSummary ordered by lastSeen descending (most recent first) - * @throws SQLException - */ - public static List getPlayerNames(UUID uuid) throws SQLException { - PlayerData player = getPlayer(uuid); - - if (player == null) return new ArrayList<>(); - - return BanManagerPlugin.getInstance().getPlayerHistoryStorage().getNamesSummary(player); - } - - /** - * Get the full session history for a player. - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param uuid Player UUID - * @param since Unix timestamp in seconds to get sessions since - * @param page Page number (0-indexed, 10 results per page) - * @return Iterator of PlayerHistoryData ordered by join descending (most recent first), null if player not found - * @throws SQLException - */ - public static CloseableIterator getPlayerHistory(UUID uuid, long since, int page) throws SQLException { - PlayerData player = getPlayer(uuid); - - if (player == null) return null; - - return BanManagerPlugin.getInstance().getPlayerHistoryStorage().getSince(player, since, page); - } - - /** - * Get the name a player was using at a specific timestamp. - *

This method performs blocking database I/O and should not be called - * from the main server thread. Use an asynchronous task instead.

- *

This method is not internally synchronized. If called concurrently - * from multiple threads, the caller is responsible for synchronization.

- * - * @param uuid Player UUID - * @param timestamp Unix timestamp in seconds - * @return The name at that time, or null if not found - * @throws SQLException - */ - public static String getPlayerNameAt(UUID uuid, long timestamp) throws SQLException { - PlayerData player = getPlayer(uuid); - - if (player == null) return null; - - return BanManagerPlugin.getInstance().getPlayerHistoryStorage().getNameAt(player, timestamp); - } - - /** - * @param key The message config node within messages.yml, e.g. "ban.notify" - * @return A Message instance that can be resolved to a Component via resolveComponent() - */ - public static Message getMessage(String key) { - return Message.get(key); - } - - public static ConnectionSource getLocalConnection() { - return BanManagerPlugin.getInstance().getLocalConn(); - } - - public static long toTimestamp(String time, boolean future) throws Exception { - return DateUtils.parseDateDiff(time, future); - } -} diff --git a/common/src/main/java/me/confuser/banmanager/common/api/events/CommonEvent.java b/common/src/main/java/me/confuser/banmanager/common/api/events/CommonEvent.java deleted file mode 100644 index 5acf9b260..000000000 --- a/common/src/main/java/me/confuser/banmanager/common/api/events/CommonEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.common.api.events; - - -import lombok.Getter; - -public class CommonEvent { - - @Getter - private boolean cancelled; - @Getter - private boolean silent; - - public CommonEvent(boolean cancelled, boolean silent) { - this.cancelled = cancelled; - this.silent = silent; - } -} diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java index 2bf32dcfd..89601a6db 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java @@ -38,7 +38,7 @@ public boolean onCommand(CommonSender sender, CommandParser parser) { final String playerName = parser.args[0]; final boolean isUUID = isUUID(playerName); - final TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + final TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java index f4669779d..efe5b05a9 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java @@ -110,8 +110,12 @@ public boolean onCommand(CommonSender sender, CommandParser parser) { final IpBanData ban = new IpBanData(ip, actor, parser.getReason().getMessage(), isSilent); boolean created; + Message kickMessage = Message.get("banip.ip.kick") + .set("reason", ban.getReason()) + .set("actor", actor.getName()); + try { - created = getPlugin().getIpBanStorage().ban(ban); + created = getPlugin().getIpBanStorage().ban(ban, false, kickMessage); } catch (SQLException e) { handlePunishmentCreateException(e, sender, Message.get("banip.error.exists").set("ip", ipStr)); @@ -123,11 +127,6 @@ public boolean onCommand(CommonSender sender, CommandParser parser) { } getPlugin().getScheduler().runSync(() -> { - Message kickMessage = Message.get("banip.ip.kick") - .set("reason", ban.getReason()) - .set("id", ban.getId()) - .set("actor", actor.getName()); - for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (IPUtils.toIPAddress(onlinePlayer.getAddress()).equals(ip)) { onlinePlayer.kick(kickMessage); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java index 4725fb019..e631dc5ae 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java @@ -62,8 +62,12 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final IpRangeBanData ban = new IpRangeBanData(fromIp, toIp, actor, reason.getMessage(), isSilent); boolean created; + Message kickMessage = Message.get("baniprange.ip.kick") + .set("reason", ban.getReason()) + .set("actor", actor.getName()); + try { - created = getPlugin().getIpRangeBanStorage().ban(ban); + created = getPlugin().getIpRangeBanStorage().ban(ban, kickMessage); } catch (SQLException e) { handlePunishmentCreateException(e, sender, Message.get("baniprange.error.exists")); return; @@ -75,11 +79,6 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { // Find online players getPlugin().getScheduler().runSync(() -> { - Message kickMessage = Message.get("baniprange.ip.kick") - .set("id", ban.getId()) - .set("reason", ban.getReason()) - .set("actor", actor.getName()); - for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (ban.inRange(IPUtils.toIPAddress(onlinePlayer.getAddress()))) { onlinePlayer.kick(kickMessage); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java index 2b03357db..9e402aee5 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java @@ -69,8 +69,13 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final NameBanData ban = new NameBanData(name, actor, reason.getMessage(), isSilent); boolean created; + Message kickMessage = Message.get("banname.name.kick") + .set("reason", ban.getReason()) + .set("actor", actor.getName()) + .set("name", name); + try { - created = getPlugin().getNameBanStorage().ban(ban); + created = getPlugin().getNameBanStorage().ban(ban, kickMessage); } catch (SQLException e) { handlePunishmentCreateException(e, sender, Message.get("banname.error.exists").set("name", name)); @@ -83,12 +88,6 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { // Find online players getPlugin().getScheduler().runSync(() -> { - Message kickMessage = Message.get("banname.name.kick") - .set("reason", ban.getReason()) - .set("actor", actor.getName()) - .set("id", ban.getId()) - .set("name", name); - for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(name)) { onlinePlayer.kick(kickMessage); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/ClearCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/ClearCommand.java index e2439d7b8..0fc08ca77 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/ClearCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/ClearCommand.java @@ -88,7 +88,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { break; case "reports": - getPlugin().getPlayerReportStorage().deleteAll(player); + getPlugin().getPlayerReportStorage().deleteAll(player, sender.getData()); break; case "warnings": diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/CommonCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/CommonCommand.java index e927e48ea..a76e939f9 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/CommonCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/CommonCommand.java @@ -81,25 +81,34 @@ public static boolean isUUID(String player) { return player.length() > 16; } - public static PlayerData getPlayer(CommonSender sender, String playerName, boolean mojangLookup) { + public PlayerData getPlayer(CommonSender sender, String playerName, boolean mojangLookup) { + return resolvePlayer(plugin, sender, playerName, mojangLookup); + } + + /** + * Static-friendly variant of {@link #getPlayer(CommonSender, String, boolean)} + * for callers that don't have a {@code CommonCommand} instance to hand + * (for example, the platform-specific {@code Sender} adapters). + */ + public static PlayerData resolvePlayer(BanManagerPlugin plugin, CommonSender sender, String playerName, boolean mojangLookup) { boolean isUUID = isUUID(playerName); PlayerData player = null; if (isUUID) { try { - player = BanManagerPlugin.getInstance().getPlayerStorage().queryForId(UUIDUtils.toBytes(UUID.fromString(playerName))); + player = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(UUID.fromString(playerName))); } catch (SQLException e) { Message.get("sender.error.exception").sendTo(sender); - BanManagerPlugin.getInstance().getLogger().warning("Failed to execute command", e); + plugin.getLogger().warning("Failed to execute command", e); } } else { - player = BanManagerPlugin.getInstance().getPlayerStorage().retrieve(playerName, mojangLookup); + player = plugin.getPlayerStorage().retrieve(playerName, mojangLookup); } return player; } - public static void handlePunishmentCreateException(SQLException e, CommonSender sender, Message duplicateMessage) { + public void handlePunishmentCreateException(SQLException e, CommonSender sender, Message duplicateMessage) { // For some reason ORMLite hides the error code (returns 0 instead of 1062) if (e.getCause().getMessage().startsWith("Duplicate entry")) { duplicateMessage.sendTo(sender); @@ -107,29 +116,29 @@ public static void handlePunishmentCreateException(SQLException e, CommonSender } Message.get("sender.error.exception").sendTo(sender); - BanManagerPlugin.getInstance().getLogger().warning("Failed to execute command", e); + plugin.getLogger().warning("Failed to execute command", e); } - public static void handlePrivateNotes(PlayerData player, PlayerData actor, Reason reason) { - if (BanManagerPlugin.getInstance().getConfig().isCreateNoteReasons()) + public void handlePrivateNotes(PlayerData player, PlayerData actor, Reason reason) { + if (plugin.getConfig().isCreateNoteReasons()) if (reason.getNotes().size() == 0) return; for (String note : reason.getNotes()) { try { - BanManagerPlugin.getInstance().getPlayerNoteStorage().create(new PlayerNoteData(player, actor, note)); + plugin.getPlayerNoteStorage().create(new PlayerNoteData(player, actor, note)); } catch (SQLException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to execute command", e); + plugin.getLogger().warning("Failed to execute command", e); } } } - public static IPAddress getIp(String ipStr) { + public IPAddress getIp(String ipStr) { final boolean isName = !IPUtils.isValid(ipStr); IPAddress ip = null; if (isName) { - PlayerData player = BanManagerPlugin.getInstance().getPlayerStorage().retrieve(ipStr, false); + PlayerData player = plugin.getPlayerStorage().retrieve(ipStr, false); if (player == null) return null; ip = player.getIp(); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/CommonSender.java b/common/src/main/java/me/confuser/banmanager/common/commands/CommonSender.java index 305c0862d..a43589c6e 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/CommonSender.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/CommonSender.java @@ -17,6 +17,7 @@ default void sendMessage(Message message) { } default void sendMessage(Component component) { - sendMessage(MessageRenderer.getInstance().toLegacy(component)); + MessageRenderer renderer = Message.renderer(); + sendMessage(renderer != null ? renderer.toLegacy(component) : component.toString()); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/DeleteCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/DeleteCommand.java index 7d47ff5d9..4e5b83f8f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/DeleteCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/DeleteCommand.java @@ -82,7 +82,7 @@ public void run() { break; case "reports": - rows = getPlugin().getPlayerReportStorage().deleteIds(ids); + rows = getPlugin().getPlayerReportStorage().deleteIds(ids, sender.getData()); break; case "warnings": diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/FindAltsCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/FindAltsCommand.java index e7f5940d3..ef5fbe3a5 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/FindAltsCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/FindAltsCommand.java @@ -61,7 +61,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { return; } - ((CommonPlayer) sender).sendJSONMessage(alts(players)); + ((CommonPlayer) sender).sendJSONMessage(alts(getPlugin(), players)); } else { ArrayList names = new ArrayList<>(players.size()); @@ -83,14 +83,14 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { return true; } - public static TextComponent alts(List players) { + public static TextComponent alts(BanManagerPlugin plugin, List players) { TextComponent.Builder message = Component.text(); List unbanned = new ArrayList<>(); Map colours = new HashMap<>(); for (PlayerData player : players) { - PlayerBanData ban = BanManagerPlugin.getInstance().getPlayerBanStorage().getBan(player.getUUID()); + PlayerBanData ban = plugin.getPlayerBanStorage().getBan(player.getUUID()); if (ban != null) { colours.put(player.getUUID(), ban.getExpires() == 0 ? NamedTextColor.RED : NamedTextColor.GOLD); @@ -101,7 +101,7 @@ public static TextComponent alts(List players) { if (!unbanned.isEmpty()) { try { - Set withRecords = BanManagerPlugin.getInstance().getPlayerBanRecordStorage() + Set withRecords = plugin.getPlayerBanRecordStorage() .queryBuilder() .selectColumns("player_id") .where().in("player_id", unbanned.stream().map(PlayerData::getId).collect(Collectors.toList())) @@ -114,7 +114,7 @@ public static TextComponent alts(List players) { colours.put(player.getUUID(), withRecords.contains(player.getUUID()) ? NamedTextColor.YELLOW : NamedTextColor.GREEN); } } catch (SQLException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to execute findalts command", e); + plugin.getLogger().warning("Failed to execute findalts command", e); for (PlayerData player : unbanned) { colours.put(player.getUUID(), NamedTextColor.GREEN); } @@ -123,7 +123,7 @@ public static TextComponent alts(List players) { boolean hasEntryTemplate = Message.getRawTemplate("alts.entry") != null; String separatorRaw = Message.getRawTemplate("alts.separator"); - MessageRenderer renderer = MessageRenderer.getInstance(); + MessageRenderer renderer = plugin.getMessageRenderer(); Component separator = separatorRaw != null ? renderer.render(separatorRaw) : Component.text(", "); int index = 0; diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/InfoCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/InfoCommand.java index e911924f9..ea302f957 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/InfoCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/InfoCommand.java @@ -186,7 +186,7 @@ public void ipInfo(CommonSender sender, IPAddress ip, InfoCommandParser parser) List duplicatePlayers = getPlugin().getPlayerStorage().getDuplicatesInTime(ip, getPlugin().getConfig().getTimeAssociatedAlts()); if (!sender.isConsole()) { - messages.add(FindAltsCommand.alts(duplicatePlayers)); + messages.add(FindAltsCommand.alts(getPlugin(), duplicatePlayers)); } else { StringBuilder duplicates = new StringBuilder(); @@ -510,7 +510,7 @@ public void playerInfo(CommonSender sender, String name, Integer index, InfoComm List duplicatePlayers = getPlugin().getPlayerStorage().getDuplicatesInTime(player.getIp(), getPlugin().getConfig().getTimeAssociatedAlts()); if (!sender.isConsole()) { - messages.add(FindAltsCommand.alts(duplicatePlayers)); + messages.add(FindAltsCommand.alts(getPlugin(), duplicatePlayers)); } else { StringBuilder duplicates = new StringBuilder(); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/MuteCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/MuteCommand.java index e12a389a8..26f52f3c8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/MuteCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/MuteCommand.java @@ -44,7 +44,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final String playerName = parser.args[0]; final boolean isUUID = playerName.length() > 16; - TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/NamesCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/NamesCommand.java index e8a229bc7..a13bfe8bb 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/NamesCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/NamesCommand.java @@ -82,8 +82,8 @@ static TextComponent buildNamesComponent(List names, String d TextComponent.Builder message = Component.text(); boolean hasInteractiveTemplate = Message.getRawTemplate("names.interactive") != null; String separatorRaw = Message.getRawTemplate("names.separator"); - MessageRenderer renderer = MessageRenderer.getInstance(); - Component separator = separatorRaw != null ? renderer.render(separatorRaw) : Component.text(", ").color(NamedTextColor.GRAY); + MessageRenderer renderer = Message.renderer(); + Component separator = separatorRaw != null && renderer != null ? renderer.render(separatorRaw) : Component.text(", ").color(NamedTextColor.GRAY); int index = 0; diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/ReloadCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/ReloadCommand.java index 278a98dd9..32a1fd038 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/ReloadCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/ReloadCommand.java @@ -1,5 +1,6 @@ package me.confuser.banmanager.common.commands; +import me.confuser.banmanager.api.event.player.PluginReloadedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.util.Message; @@ -13,7 +14,7 @@ public ReloadCommand(BanManagerPlugin plugin) { public boolean onCommand(CommonSender sender, CommandParser parser) { getPlugin().setupConfigs(); - getPlugin().getServer().callEvent("PluginReloadedEvent", sender.getData()); + getPlugin().getEventBus().publish(new PluginReloadedEvent()); Message.get("configReloaded").sendTo(sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/ReportCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/ReportCommand.java index bc2f63b9d..10a0c37b5 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/ReportCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/ReportCommand.java @@ -35,7 +35,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { } final String playerName = parser.args[0]; - final TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + final TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/RollbackCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/RollbackCommand.java index 2ee473314..66a90fd13 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/RollbackCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/RollbackCommand.java @@ -238,14 +238,12 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { reportQb.where().eq("actor_id", player.getId()).and().le("created", now).and().ge("created", expires); List matchedReports = reportQb.query(); - for (PlayerReportData report : matchedReports) { - getPlugin().getServer().callEvent("PlayerReportDeletedEvent", report); - } - if (!matchedReports.isEmpty()) { - DeleteBuilder reports = getPlugin().getPlayerReportStorage().deleteBuilder(); - reports.where().eq("actor_id", player.getId()).and().le("created", now).and().ge("created", expires); - reports.delete(); + java.util.List reportIds = new java.util.ArrayList<>(matchedReports.size()); + for (PlayerReportData report : matchedReports) { + reportIds.add(report.getId()); + } + getPlugin().getPlayerReportStorage().deleteIds(reportIds, sender.getData()); } break; diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TargetResolver.java b/common/src/main/java/me/confuser/banmanager/common/commands/TargetResolver.java index 751d51be1..c3c7b4b38 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TargetResolver.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TargetResolver.java @@ -43,7 +43,9 @@ public String getResolvedName() { private TargetResolver() { } - public static TargetResult resolveTarget(CommonServer server, String input) { + public static TargetResult resolveTarget(BanManagerPlugin plugin, String input) { + CommonServer server = plugin.getServer(); + if (CommonCommand.isUUID(input)) { try { CommonPlayer onlinePlayer = server.getPlayer(UUID.fromString(input)); @@ -65,7 +67,7 @@ public static TargetResult resolveTarget(CommonServer server, String input) { CommonPlayer partialPlayer = server.getPlayer(input); if (partialPlayer != null) { - PlayerData exactStored = BanManagerPlugin.getInstance().getPlayerStorage().retrieve(input, false); + PlayerData exactStored = plugin.getPlayerStorage().retrieve(input, false); if (exactStored != null && !exactStored.getName().equalsIgnoreCase(partialPlayer.getName())) { return new TargetResult(TargetStatus.AMBIGUOUS, partialPlayer, partialPlayer.getName()); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java index 2cc313d9b..4c9670044 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java @@ -39,7 +39,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final String playerName = parser.args[0]; final boolean isUUID = playerName.length() > 16; - final TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + final TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java index adab6626a..8c270af20 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java @@ -126,8 +126,13 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final IpBanData ban = new IpBanData(ip, actor, reason, isSilent, expires); boolean created; + Message kickMessage = Message.get("tempbanip.ip.kick") + .set("reason", ban.getReason()) + .set("actor", actor.getName()) + .set("expires", DateUtils.getDifferenceFormat(ban.getExpires())); + try { - created = getPlugin().getIpBanStorage().ban(ban); + created = getPlugin().getIpBanStorage().ban(ban, false, kickMessage); } catch (SQLException e) { handlePunishmentCreateException(e, sender, Message.get("banip.error.exists").set("ip", ipStr)); @@ -140,12 +145,6 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { // Find online players getPlugin().getScheduler().runSync(() -> { - Message kickMessage = Message.get("tempbanip.ip.kick") - .set("reason", ban.getReason()) - .set("actor", actor.getName()) - .set("id", ban.getId()) - .set("expires", DateUtils.getDifferenceFormat(ban.getExpires())); - for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (IPUtils.toIPAddress(onlinePlayer.getAddress()).equals(ip)) { onlinePlayer.kick(kickMessage); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java index 9da0fcd19..b3794cfe6 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java @@ -80,8 +80,13 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final IpRangeBanData ban = new IpRangeBanData(fromIp, toIp, actor, reason, isSilent, expires); boolean created; + Message kickMessage = Message.get("tempbaniprange.ip.kick") + .set("reason", ban.getReason()) + .set("actor", actor.getName()) + .set("expires", DateUtils.getDifferenceFormat(ban.getExpires())); + try { - created = getPlugin().getIpRangeBanStorage().ban(ban); + created = getPlugin().getIpRangeBanStorage().ban(ban, kickMessage); } catch (SQLException e) { handlePunishmentCreateException(e, sender, Message.get("baniprange.error.exists")); return; @@ -93,12 +98,6 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { // Find online players getPlugin().getScheduler().runSync(() -> { - Message kickMessage = Message.get("tempbaniprange.ip.kick") - .set("reason", ban.getReason()) - .set("actor", actor.getName()) - .set("id", ban.getId()) - .set("expires", DateUtils.getDifferenceFormat(ban.getExpires())); - for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (ban.inRange(IPUtils.toIPAddress((onlinePlayer.getAddress())))) { onlinePlayer.kick(kickMessage); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempMuteCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempMuteCommand.java index 440ae02b0..d16193ef1 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempMuteCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempMuteCommand.java @@ -54,7 +54,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { final String playerName = parser.args[0]; final boolean isUUID = playerName.length() > 16; - TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java index 6e3e1011f..e1edbf909 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java @@ -90,8 +90,13 @@ public void run() { final NameBanData ban = new NameBanData(name, actor, reason, isSilent, expires); boolean created; + Message kickMessage = Message.get("tempbanname.name.kick") + .set("reason", ban.getReason()) + .set("name", name) + .set("actor", actor.getName()); + try { - created = getPlugin().getNameBanStorage().ban(ban); + created = getPlugin().getNameBanStorage().ban(ban, kickMessage); } catch (SQLException e) { handlePunishmentCreateException(e, sender, Message.get("banname.error.exists").set("player", name)); @@ -104,12 +109,6 @@ public void run() { // Find online players getPlugin().getScheduler().runSync(() -> { - Message kickMessage = Message.get("tempbanname.name.kick") - .set("reason", ban.getReason()) - .set("name", name) - .set("id", ban.getId()) - .set("actor", actor.getName()); - for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(name)) { onlinePlayer.kick(kickMessage); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java index 6d3b2cf71..e24b374de 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java @@ -49,7 +49,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser } final String playerName = parsedArgs[0]; - TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); @@ -177,7 +177,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser final List actionCommands; try { - actionCommands = getPlugin().getConfig().getWarningActions().getCommands(player, getPlugin().getPlayerWarnStorage().getPointsCount(player)); + actionCommands = getPlugin().getConfig().getWarningActions().getCommands(getPlugin(), player, getPlugin().getPlayerWarnStorage().getPointsCount(player)); } catch (SQLException e) { getPlugin().getLogger().warning("Failed to execute tempwarn command", e); return; diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java index 074b336c6..d9fb396c8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java @@ -48,7 +48,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser final String playerName = parser.args[0]; final Reason reason = parser.getReason(); - TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin().getServer(), playerName); + TargetResolver.TargetResult target = TargetResolver.resolveTarget(getPlugin(), playerName); if (target.getStatus() == TargetResolver.TargetStatus.NOT_FOUND) { Message.get("sender.error.notFound").set("player", playerName).sendTo(sender); @@ -156,7 +156,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser final List actionCommands; try { - actionCommands = getPlugin().getConfig().getWarningActions().getCommands(player, getPlugin().getPlayerWarnStorage().getPointsCount(player)); + actionCommands = getPlugin().getConfig().getWarningActions().getCommands(getPlugin(), player, getPlugin().getPlayerWarnStorage().getPointsCount(player)); } catch (SQLException e) { getPlugin().getLogger().warning("Failed to execute warn command", e); return; diff --git a/common/src/main/java/me/confuser/banmanager/common/configs/GeoIpConfig.java b/common/src/main/java/me/confuser/banmanager/common/configs/GeoIpConfig.java index cedb29992..8a45006a2 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configs/GeoIpConfig.java +++ b/common/src/main/java/me/confuser/banmanager/common/configs/GeoIpConfig.java @@ -1,7 +1,6 @@ package me.confuser.banmanager.common.configs; import lombok.Getter; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonLogger; import me.confuser.banmanager.common.apachecommons.compress.archivers.ArchiveEntry; import me.confuser.banmanager.common.apachecommons.compress.archivers.tar.TarArchiveInputStream; @@ -11,6 +10,8 @@ import me.confuser.banmanager.common.maxmind.db.cache.CHMCache; import me.confuser.banmanager.common.maxmind.db.model.CountryResponse; +import java.util.function.Supplier; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -35,8 +36,14 @@ public class GeoIpConfig extends Config { @Getter private String type; - public GeoIpConfig(File dataFolder, CommonLogger logger) { + // Lazy supplier so the GeoIP config can be constructed before the plugin's + // shared HttpClient has been initialised; the supplier is only invoked when + // we actually need to download a database. + private final Supplier httpClientSupplier; + + public GeoIpConfig(File dataFolder, CommonLogger logger, Supplier httpClientSupplier) { super(dataFolder, "geoip.yml", logger); + this.httpClientSupplier = httpClientSupplier; } @Override @@ -104,7 +111,7 @@ public void afterLoad() { if (countryFile.exists()) { logger.info("Loading country database"); try { - countryDatabase = new Reader(cityFile, Reader.FileMode.MEMORY, new CHMCache()); + countryDatabase = new Reader(countryFile, Reader.FileMode.MEMORY, new CHMCache()); } catch (IOException e) { logger.severe("Failed loading country database", e); enabled = false; @@ -145,7 +152,7 @@ private void downloadDatabase(String downloadUrl, File location) throws IOExcept location.delete(); } - HttpClient client = BanManagerPlugin.getInstance().getHttpClient(); + HttpClient client = httpClientSupplier.get(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(downloadUrl)) .timeout(Duration.ofMinutes(5)) diff --git a/common/src/main/java/me/confuser/banmanager/common/configs/TimeLimitsConfig.java b/common/src/main/java/me/confuser/banmanager/common/configs/TimeLimitsConfig.java index ff422c215..9e6356887 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configs/TimeLimitsConfig.java +++ b/common/src/main/java/me/confuser/banmanager/common/configs/TimeLimitsConfig.java @@ -1,6 +1,5 @@ package me.confuser.banmanager.common.configs; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonLogger; import me.confuser.banmanager.common.commands.CommonSender; import me.confuser.banmanager.common.configuration.ConfigurationSection; @@ -11,8 +10,10 @@ public class TimeLimitsConfig { private HashMap> limits; + private final CommonLogger logger; public TimeLimitsConfig(ConfigurationSection config, CommonLogger logger) { + this.logger = logger; limits = new HashMap<>(); for (TimeLimitType type : TimeLimitType.values()) { @@ -55,7 +56,7 @@ public boolean isPastLimit(CommonSender sender, TimeLimitType type, long expires return true; } } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to parse time limit", e); + logger.warning("Failed to parse time limit", e); } } } diff --git a/common/src/main/java/me/confuser/banmanager/common/configs/WarningActionsConfig.java b/common/src/main/java/me/confuser/banmanager/common/configs/WarningActionsConfig.java index 440ab61d8..64e26bd41 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configs/WarningActionsConfig.java +++ b/common/src/main/java/me/confuser/banmanager/common/configs/WarningActionsConfig.java @@ -96,7 +96,7 @@ public WarningActionsConfig(ConfigurationSection config, CommonLogger logger) { } } - public List getCommands(PlayerData player, double overallPoints) { + public List getCommands(BanManagerPlugin plugin, PlayerData player, double overallPoints) { List commands = new ArrayList<>(); for (Map.Entry> entry : actions.entrySet()) { @@ -105,9 +105,9 @@ public List getCommands(PlayerData player, double overallPoints) if (!actionCommand.getPointsTimeframe().isEmpty()) { try { - totalPoints = BanManagerPlugin.getInstance().getPlayerWarnStorage().getPointsCount(player, DateUtils.parseDateDiff(actionCommand.getPointsTimeframe(), false)); + totalPoints = plugin.getPlayerWarnStorage().getPointsCount(player, DateUtils.parseDateDiff(actionCommand.getPointsTimeframe(), false)); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to calculate warning points", e); + plugin.getLogger().warning("Failed to calculate warning points", e); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/configuration/file/YamlConfiguration.java b/common/src/main/java/me/confuser/banmanager/common/configuration/file/YamlConfiguration.java index d808d2dd1..f3ada5fcb 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configuration/file/YamlConfiguration.java +++ b/common/src/main/java/me/confuser/banmanager/common/configuration/file/YamlConfiguration.java @@ -1,6 +1,5 @@ package me.confuser.banmanager.common.configuration.file; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.configuration.Configuration; import me.confuser.banmanager.common.configuration.ConfigurationSection; import me.confuser.banmanager.common.configuration.InvalidConfigurationException; @@ -14,6 +13,8 @@ import java.io.IOException; import java.io.Reader; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * An implementation of {@link Configuration} which saves all files in Yaml. @@ -21,6 +22,7 @@ */ public class YamlConfiguration extends FileConfiguration { + private static final Logger LOGGER = Logger.getLogger("BanManager"); private static final String COMMENT_PREFIX = "# "; private static final String BLANK_CONFIG = "{}\n"; private final DumperOptions yamlOptions = new DumperOptions(); @@ -70,7 +72,7 @@ public static YamlConfiguration loadConfiguration(File file) { try { config.load(file); } catch ( IOException |InvalidConfigurationException e){ - BanManagerPlugin.getInstance().getLogger().warning("Failed to load YAML configuration", e); + LOGGER.log(Level.WARNING, "Failed to load YAML configuration", e); } return config; @@ -93,7 +95,7 @@ public static YamlConfiguration loadConfiguration(Reader reader) { try { config.load(reader); } catch (InvalidConfigurationException | IOException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to load YAML configuration", e); + LOGGER.log(Level.WARNING, "Failed to load YAML configuration", e); } return config; diff --git a/common/src/main/java/me/confuser/banmanager/common/data/IpBanData.java b/common/src/main/java/me/confuser/banmanager/common/data/IpBanData.java index 19245be4c..475cb6c09 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/IpBanData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/IpBanData.java @@ -6,6 +6,7 @@ import me.confuser.banmanager.common.storage.mysql.ByteArray; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.storage.mysql.IpAddress; @DatabaseTable @@ -18,6 +19,7 @@ public class IpBanData { @DatabaseField(canBeNull = false, persisterClass = IpAddress.class, columnDefinition = "VARBINARY(16) NOT NULL") private IPAddress ip; @Getter + @Setter @DatabaseField(canBeNull = false) private String reason; @Getter @@ -32,11 +34,13 @@ public class IpBanData { @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") private long updated = System.currentTimeMillis() / 1000L; @Getter + @Setter @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") private long expires = 0; @DatabaseField @Getter + @Setter private boolean silent = false; IpBanData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/data/IpMuteData.java b/common/src/main/java/me/confuser/banmanager/common/data/IpMuteData.java index 86e01f498..dc816eaca 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/IpMuteData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/IpMuteData.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.common.data; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.ormlite.field.DatabaseField; import me.confuser.banmanager.common.ormlite.table.DatabaseTable; @@ -17,6 +18,7 @@ public class IpMuteData { @DatabaseField(canBeNull = false, persisterClass = IpAddress.class, columnDefinition = "VARBINARY(16) NOT NULL") private IPAddress ip; @Getter + @Setter @DatabaseField(canBeNull = false) private String reason; @Getter @@ -31,15 +33,18 @@ public class IpMuteData { @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") private long updated = System.currentTimeMillis() / 1000L; @Getter + @Setter @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") private long expires = 0; @DatabaseField(index = true) @Getter + @Setter private boolean soft = false; @DatabaseField @Getter + @Setter private boolean silent = false; IpMuteData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/data/IpRangeBanData.java b/common/src/main/java/me/confuser/banmanager/common/data/IpRangeBanData.java index 9dcfebba9..33ae80775 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/IpRangeBanData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/IpRangeBanData.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.common.data; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.google.guava.collect.Range; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.ormlite.field.DatabaseField; @@ -25,6 +26,7 @@ public class IpRangeBanData { private IPAddress toIp; @Getter + @Setter @DatabaseField(canBeNull = false) private String reason; @@ -40,11 +42,13 @@ public class IpRangeBanData { @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") private long updated = System.currentTimeMillis() / 1000L; @Getter + @Setter @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") private long expires = 0; @DatabaseField @Getter + @Setter private boolean silent = false; IpRangeBanData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/data/NameBanData.java b/common/src/main/java/me/confuser/banmanager/common/data/NameBanData.java index b0ba6ff6f..d88eae1bd 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/NameBanData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/NameBanData.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.common.data; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.ormlite.field.DatabaseField; import me.confuser.banmanager.common.ormlite.table.DatabaseTable; import me.confuser.banmanager.common.storage.mysql.ByteArray; @@ -14,10 +15,12 @@ public class NameBanData { @DatabaseField(unique = true, canBeNull = false, columnDefinition = "VARCHAR(16) NOT NULL") @Getter + @Setter private String name; @DatabaseField(canBeNull = false) @Getter + @Setter private String reason; @Getter @@ -35,10 +38,12 @@ public class NameBanData { @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") @Getter + @Setter private long expires = 0; @DatabaseField @Getter + @Setter private boolean silent = false; NameBanData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerBanData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerBanData.java index 88ffd34f7..e247e2af1 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerBanData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerBanData.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.common.data; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.ormlite.field.DatabaseField; import me.confuser.banmanager.common.ormlite.table.DatabaseTable; import me.confuser.banmanager.common.storage.mysql.ByteArray; @@ -16,6 +17,7 @@ public class PlayerBanData { private PlayerData player; @DatabaseField(canBeNull = false) @Getter + @Setter private String reason; @Getter @DatabaseField(canBeNull = false, foreign = true, foreignAutoRefresh = true, persisterClass = ByteArray.class, columnDefinition = "BINARY(16) NOT NULL") @@ -30,10 +32,12 @@ public class PlayerBanData { private long updated = System.currentTimeMillis() / 1000L; @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") @Getter + @Setter private long expires = 0; @DatabaseField @Getter + @Setter private boolean silent = false; PlayerBanData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java index b3c37f7b9..cbe5d53ea 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java @@ -2,7 +2,7 @@ import lombok.Getter; import lombok.Setter; -import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.exception.BanManagerInternalException; import me.confuser.banmanager.common.ipaddr.AddressStringException; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.ipaddr.IPAddressString; @@ -18,6 +18,20 @@ @DatabaseTable(tableName = "players", daoClass = PlayerStorage.class) public class PlayerData { + /** + * Cached loopback address used when constructing a synthetic player without + * an IP. Computed once because the input string is a constant — failure here + * means the bundled ipaddr library is broken. + */ + private static final IPAddress LOCALHOST_IP; + static { + try { + LOCALHOST_IP = new IPAddressString("127.0.0.1").toAddress(); + } catch (AddressStringException e) { + throw new BanManagerInternalException("Bundled ipaddr library failed to parse '127.0.0.1'", e); + } + } + @DatabaseField(id = true, persisterClass = ByteArray.class, columnDefinition = "BINARY(16) NOT NULL") @Getter private byte[] id; @@ -47,13 +61,7 @@ public PlayerData(UUID uuid, String name) { this.uuid = uuid; this.id = UUIDUtils.toBytes(uuid); this.name = name; - - try { - this.ip = new IPAddressString("127.0.0.1").toAddress(); - } catch (AddressStringException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to process player data", e); - } - + this.ip = LOCALHOST_IP; this.lastSeen = System.currentTimeMillis() / 1000L; } diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerMuteData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerMuteData.java index 1a7a547ef..99148749e 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerMuteData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerMuteData.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.common.data; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.ormlite.field.DatabaseField; import me.confuser.banmanager.common.ormlite.table.DatabaseTable; import me.confuser.banmanager.common.storage.mysql.ByteArray; @@ -16,6 +17,7 @@ public class PlayerMuteData { private PlayerData player; @DatabaseField(canBeNull = false) @Getter + @Setter private String reason; @DatabaseField(canBeNull = false, foreign = true, foreignAutoRefresh = true, persisterClass = ByteArray.class, columnDefinition = "BINARY(16) NOT NULL") @Getter @@ -28,6 +30,9 @@ public class PlayerMuteData { @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") @Getter private long updated = System.currentTimeMillis() / 1000L; + // setExpires() is defined manually below so other code can extend the + // mute (e.g. when continuing a paused mute on rejoin) without a Lombok + // @Setter clashing with the existing override. @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") @Getter private long expires = 0; @@ -35,14 +40,17 @@ public class PlayerMuteData { @DatabaseField(index = true) @Getter + @Setter private boolean soft = false; @DatabaseField @Getter + @Setter private boolean silent = false; @DatabaseField @Getter + @Setter private boolean onlineOnly = false; @DatabaseField(columnDefinition = "BIGINT UNSIGNED NOT NULL DEFAULT 0") diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerNoteData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerNoteData.java index f35687038..4b5dd95cc 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerNoteData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerNoteData.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.common.data; import lombok.Getter; +import lombok.Setter; import me.confuser.banmanager.common.ormlite.field.DatabaseField; import me.confuser.banmanager.common.ormlite.table.DatabaseTable; import me.confuser.banmanager.common.storage.mysql.ByteArray; @@ -16,6 +17,7 @@ public class PlayerNoteData { private PlayerData player; @DatabaseField(canBeNull = false) @Getter + @Setter private String message; @DatabaseField(index = true, canBeNull = false, foreign = true, foreignAutoRefresh = true, persisterClass = ByteArray.class, columnDefinition = "BINARY(16) NOT NULL") @Getter diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerReportData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerReportData.java index ff8b955b7..5b6f710c1 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerReportData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerReportData.java @@ -20,6 +20,7 @@ public class PlayerReportData { @DatabaseField(canBeNull = false) @Getter + @Setter private String reason; @Getter diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerWarnData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerWarnData.java index 0f84fb017..f4b195d6c 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerWarnData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerWarnData.java @@ -19,6 +19,7 @@ public class PlayerWarnData { @DatabaseField(canBeNull = false) @Getter + @Setter private String reason; @DatabaseField(canBeNull = false, foreign = true, foreignAutoRefresh = true, persisterClass = ByteArray.class, columnDefinition = "BINARY(16) NOT NULL") @@ -32,6 +33,7 @@ public class PlayerWarnData { @DatabaseField(index = true, columnDefinition = "BIGINT UNSIGNED NOT NULL") @Getter + @Setter private long expires = 0; @DatabaseField(index = true) @@ -41,6 +43,7 @@ public class PlayerWarnData { @DatabaseField(index = true) @Getter + @Setter private double points = 1; PlayerWarnData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/exception/BanManagerInternalException.java b/common/src/main/java/me/confuser/banmanager/common/exception/BanManagerInternalException.java new file mode 100644 index 000000000..0a377dafd --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/exception/BanManagerInternalException.java @@ -0,0 +1,24 @@ +package me.confuser.banmanager.common.exception; + +import me.confuser.banmanager.api.exception.BanManagerException; + +/** + * Internal-only equivalent of {@link BanManagerException}. Used inside the + * common module for runtime errors that have nothing to do with the API + * surface but still need to bubble up. + * + *

Extends {@code BanManagerException} so a single {@code catch + * (BanManagerException)} clause inside the common module reliably traps + * everything BanManager throws, regardless of whether the failure + * originated in the public API layer or in internal plumbing.

+ */ +public class BanManagerInternalException extends BanManagerException { + + public BanManagerInternalException(String message) { + super(message); + } + + public BanManagerInternalException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/AsyncSupport.java b/common/src/main/java/me/confuser/banmanager/common/impl/AsyncSupport.java new file mode 100644 index 000000000..f185dbeb3 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/AsyncSupport.java @@ -0,0 +1,139 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.exception.BanManagerException; +import me.confuser.banmanager.api.exception.OperationCancelledException; +import me.confuser.banmanager.api.exception.StorageException; + +import java.sql.SQLException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +/** + * Bridge between the legacy storage layer (which throws {@link SQLException}) + * and the public API surface (which returns {@link CompletableFuture} or + * throws {@link BanManagerException}). + * + *

The dedicated DB-I/O executor passed in is sized to the Hikari pool by + * {@link me.confuser.banmanager.common.impl.BanManagerServiceImpl}; that keeps + * blocking JDBC off the platform's main scheduler and prevents the common + * ForkJoinPool from being starved by webhook bursts.

+ */ +public final class AsyncSupport { + + private final Executor executor; + + public AsyncSupport(Executor executor) { + this.executor = executor; + } + + /** + * Run {@code callable} on the DB executor, returning its result via + * {@link CompletableFuture}. {@link SQLException} is wrapped in + * {@link StorageException}; other throwables propagate as-is so callers can + * catch them or surface them via {@link CompletableFuture#exceptionally}. + */ + public CompletableFuture async(SqlCallable callable) { + return CompletableFuture.supplyAsync(() -> sync(callable), executor); + } + + /** + * Run {@code callable} on the DB executor, returning {@link Void}. + */ + public CompletableFuture asyncVoid(SqlRunnable runnable) { + return CompletableFuture.runAsync(() -> syncVoid(runnable), executor); + } + + /** + * Bridge for create-style operations whose {@code *Sync()} variant throws + * {@link OperationCancelledException} when a pre-event handler vetoes the + * action. Async callers expect a sentinel value (typically + * {@link java.util.Optional#empty()} or {@code Boolean.FALSE}) rather than + * a failed future. + * + *

Any other thrown exception (including {@link SQLException} via + * {@link #sync}) propagates through the future as usual.

+ * + * @param callable the synchronous operation + * @param onCancel sentinel returned when the operation is cancelled + */ + public CompletableFuture asyncCancellable(SqlCallable callable, T onCancel) { + return CompletableFuture.supplyAsync(() -> { + try { + return sync(callable); + } catch (OperationCancelledException ignored) { + return onCancel; + } + }, executor); + } + + /** + * Synchronous execution helper. Use from {@code *Sync()} variants on the + * service interfaces — callers are expected to have already moved to a + * non-blocking thread. + */ + public static T sync(SqlCallable callable) { + return sync(callable, "BanManager storage operation failed"); + } + + /** + * Context-rich sync variant. The supplied {@code contextMessage} is used + * when wrapping {@link SQLException} into {@link StorageException} and any + * other checked exception into {@link BanManagerException}, so debug logs + * and crash reports name the operation that actually failed (e.g. + * {@code "Failed to delete report 12"}). Use this from {@code *Sync()} + * service variants where the generic default message would lose useful + * context. + */ + public static T sync(SqlCallable callable, String contextMessage) { + try { + return callable.call(); + } catch (SQLException e) { + throw new StorageException(contextMessage, e); + } catch (BanManagerException e) { + throw e; + } catch (Exception e) { + throw new BanManagerException(contextMessage, e); + } + } + + public static void syncVoid(SqlRunnable runnable) { + syncVoid(runnable, "BanManager storage operation failed"); + } + + /** + * Context-rich {@link #syncVoid(SqlRunnable)} variant. See + * {@link #sync(SqlCallable, String)} for rationale. + */ + public static void syncVoid(SqlRunnable runnable, String contextMessage) { + try { + runnable.run(); + } catch (SQLException e) { + throw new StorageException(contextMessage, e); + } catch (BanManagerException e) { + throw e; + } catch (Exception e) { + throw new BanManagerException(contextMessage, e); + } + } + + /** + * @return the underlying DB-I/O {@link Executor}. Exposed for + * service implementations that need to chain additional + * {@code thenApplyAsync}-style stages onto the same pool rather + * than the {@link java.util.concurrent.ForkJoinPool#commonPool() + * common pool}. + */ + public Executor executor() { + return executor; + } + + @FunctionalInterface + public interface SqlCallable { + T call() throws Exception; + } + + @FunctionalInterface + public interface SqlRunnable { + void run() throws Exception; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/BanManagerServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/BanManagerServiceImpl.java new file mode 100644 index 000000000..2fba6cf57 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/BanManagerServiceImpl.java @@ -0,0 +1,106 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.BanManagerService; +import me.confuser.banmanager.api.database.DatabaseAccess; +import me.confuser.banmanager.api.database.MigrationService; +import me.confuser.banmanager.api.event.EventBus; +import me.confuser.banmanager.api.scheduler.BanManagerScheduler; +import me.confuser.banmanager.api.service.BanService; +import me.confuser.banmanager.api.service.HistoryService; +import me.confuser.banmanager.api.service.IpBanService; +import me.confuser.banmanager.api.service.IpMuteService; +import me.confuser.banmanager.api.service.IpRangeBanService; +import me.confuser.banmanager.api.service.MuteService; +import me.confuser.banmanager.api.service.NameBanService; +import me.confuser.banmanager.api.service.NoteService; +import me.confuser.banmanager.api.service.PlayerService; +import me.confuser.banmanager.api.service.ReportService; +import me.confuser.banmanager.api.service.WarnService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.service.BanServiceImpl; +import me.confuser.banmanager.common.impl.service.HistoryServiceImpl; +import me.confuser.banmanager.common.impl.service.IpBanServiceImpl; +import me.confuser.banmanager.common.impl.service.IpMuteServiceImpl; +import me.confuser.banmanager.common.impl.service.IpRangeBanServiceImpl; +import me.confuser.banmanager.common.impl.service.MuteServiceImpl; +import me.confuser.banmanager.common.impl.service.NameBanServiceImpl; +import me.confuser.banmanager.common.impl.service.NoteServiceImpl; +import me.confuser.banmanager.common.impl.service.PlayerServiceImpl; +import me.confuser.banmanager.common.impl.service.ReportServiceImpl; +import me.confuser.banmanager.common.impl.service.WarnServiceImpl; + +import java.util.Objects; + +/** + * Composite root of the public {@link BanManagerService} API. Holds the + * concrete sub-service implementations and the shared infrastructure + * ({@link EventBus}, {@link DatabaseAccess}, {@link BanManagerScheduler}) + * so platform bootstrap code only needs to register a single object. The + * {@link AsyncSupport} executor is injected through the constructor and + * threaded into each sub-service; the composite does not retain a + * reference itself. + * + *

Lifecycle: created in + * {@code BanManagerPlugin.enable()} after storage initialises, registered + * with the platform service manager (or {@link java.util.ServiceLoader}), + * shut down in {@code BanManagerPlugin.disable()}.

+ */ +public final class BanManagerServiceImpl implements BanManagerService { + + private final PlayerService players; + private final BanService bans; + private final MuteService mutes; + private final WarnService warnings; + private final IpBanService ipBans; + private final IpMuteService ipMutes; + private final IpRangeBanService ipRangeBans; + private final NameBanService nameBans; + private final NoteService notes; + private final ReportService reports; + private final HistoryService history; + private final EventBus events; + private final DatabaseAccess database; + private final BanManagerScheduler scheduler; + private final MigrationService migrations; + + public BanManagerServiceImpl(BanManagerPlugin plugin, + EventBus events, + DatabaseAccess database, + BanManagerScheduler scheduler, + AsyncSupport async) { + Objects.requireNonNull(plugin, "plugin"); + this.events = Objects.requireNonNull(events, "events"); + this.database = Objects.requireNonNull(database, "database"); + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + Objects.requireNonNull(async, "async"); + + this.players = new PlayerServiceImpl(plugin, async); + this.bans = new BanServiceImpl(plugin, async); + this.mutes = new MuteServiceImpl(plugin, async); + this.warnings = new WarnServiceImpl(plugin, async); + this.ipBans = new IpBanServiceImpl(plugin, async); + this.ipMutes = new IpMuteServiceImpl(plugin, async); + this.ipRangeBans = new IpRangeBanServiceImpl(plugin, async); + this.nameBans = new NameBanServiceImpl(plugin, async); + this.notes = new NoteServiceImpl(plugin, async); + this.reports = new ReportServiceImpl(plugin, async); + this.history = new HistoryServiceImpl(plugin, async); + this.migrations = new MigrationServiceImpl(plugin); + } + + @Override public PlayerService players() { return players; } + @Override public BanService bans() { return bans; } + @Override public MuteService mutes() { return mutes; } + @Override public WarnService warnings() { return warnings; } + @Override public IpBanService ipBans() { return ipBans; } + @Override public IpMuteService ipMutes() { return ipMutes; } + @Override public IpRangeBanService ipRangeBans() { return ipRangeBans; } + @Override public NameBanService nameBans() { return nameBans; } + @Override public NoteService notes() { return notes; } + @Override public ReportService reports() { return reports; } + @Override public HistoryService history() { return history; } + @Override public EventBus events() { return events; } + @Override public DatabaseAccess database() { return database; } + @Override public BanManagerScheduler scheduler() { return scheduler; } + @Override public MigrationService migrations() { return migrations; } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/DatabaseAccessImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/DatabaseAccessImpl.java new file mode 100644 index 000000000..1f2f8e1c1 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/DatabaseAccessImpl.java @@ -0,0 +1,58 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.database.DatabaseAccess; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.configs.DatabaseConfig; +import me.confuser.banmanager.common.hikari.HikariDataSource; +import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; + +import javax.sql.DataSource; + +import java.util.Optional; + +/** + * {@link DatabaseAccess} backed by BanManager's internal Hikari pools. The + * {@code HikariDataSource} instances are owned by {@link BanManagerPlugin} + * and live for the duration of the plugin — never close them. + */ +public final class DatabaseAccessImpl implements DatabaseAccess { + + private final BanManagerPlugin plugin; + + public DatabaseAccessImpl(BanManagerPlugin plugin) { + this.plugin = plugin; + } + + @Override + public DataSource localDataSource() { + HikariDataSource ds = plugin.getLocalDataSource(); + if (ds == null) { + throw new IllegalStateException( + "Local datasource not yet initialised. Call DatabaseAccess after BanManager has finished enabling."); + } + return ds; + } + + @Override + public Optional globalDataSource() { + return Optional.ofNullable(plugin.getGlobalDataSource()); + } + + @Override + public Optional localTable(String logicalName) { + return tableName(plugin.getConfig().getLocalDb(), logicalName); + } + + @Override + public Optional globalTable(String logicalName) { + DatabaseConfig global = plugin.getConfig().getGlobalDb(); + if (!global.isEnabled()) return Optional.empty(); + return tableName(global, logicalName); + } + + private static Optional tableName(DatabaseConfig dbConfig, String logicalName) { + DatabaseTableConfig table = dbConfig.getTable(logicalName); + if (table == null) return Optional.empty(); + return Optional.ofNullable(table.getTableName()); + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/EntityMappers.java b/common/src/main/java/me/confuser/banmanager/common/impl/EntityMappers.java new file mode 100644 index 000000000..3660547a6 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/EntityMappers.java @@ -0,0 +1,495 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.dto.HistoryEntry; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.IpBanRecord; +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.IpMuteRecord; +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.dto.IpRangeBanRecord; +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.dto.NameBanRecord; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.PlayerBanRecord; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.dto.PlayerMuteRecord; +import me.confuser.banmanager.api.dto.PlayerNameSummary; +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.dto.PlayerSession; +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.dto.ReportState; +import me.confuser.banmanager.api.request.BanRequest; +import me.confuser.banmanager.api.request.IpBanRequest; +import me.confuser.banmanager.api.request.IpMuteRequest; +import me.confuser.banmanager.api.request.IpRangeBanRequest; +import me.confuser.banmanager.api.request.MuteRequest; +import me.confuser.banmanager.api.request.NameBanRequest; +import me.confuser.banmanager.api.request.NoteRequest; +import me.confuser.banmanager.api.request.ReportRequest; +import me.confuser.banmanager.api.request.WarnRequest; +import me.confuser.banmanager.common.data.IpBanData; +import me.confuser.banmanager.common.data.IpMuteData; +import me.confuser.banmanager.common.data.IpRangeBanData; +import me.confuser.banmanager.common.data.NameBanData; +import me.confuser.banmanager.common.data.PlayerBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerHistoryData; +import me.confuser.banmanager.common.data.PlayerMuteData; +import me.confuser.banmanager.common.data.PlayerNoteData; +import me.confuser.banmanager.common.data.PlayerReportData; +import me.confuser.banmanager.common.data.PlayerWarnData; + +import java.util.Optional; + +/** + * Static mapping helpers between internal ORMLite-annotated entities and the + * public API surface (immutable record DTOs and mutable {@code Request} POJOs). + * + *

Three groups of mappers:

+ *
    + *
  • {@code entity → DTO} for post-event payloads and read APIs.
  • + *
  • {@code entity → Request} for pre-event payloads — handlers receive a + * request snapshot they can mutate (reason/expires/silent/etc.) before + * persistence.
  • + *
  • {@link #applyTo(BanRequest, PlayerBanData) applyTo(...)} families that + * copy the (possibly mutated) Request fields back onto the entity right + * before {@code create(...)} runs, so handler edits actually take effect.
  • + *
+ * + *

{@code Player}/IP/timestamp identity fields are not copied back — + * they are part of the entity's identity and are validated at the service + * boundary, not by the storage layer.

+ * + *

All entity → DTO/Request mappers tolerate {@code null} input, returning + * {@code null} so callers can chain {@code map(x)} on optional fields without + * explicit null checks.

+ */ +public final class EntityMappers { + + private EntityMappers() {} + + public static Player player(PlayerData data) { + if (data == null) return null; + return new Player( + data.getUUID(), + data.getName(), + IpAddressMapper.toApi(data.getIp()), + data.getLastSeen(), + Optional.ofNullable(data.getLocale()).filter(s -> !s.isEmpty())); + } + + public static PlayerBan playerBan(PlayerBanData data) { + if (data == null) return null; + return new PlayerBan( + data.getId(), + player(data.getPlayer()), + player(data.getActor()), + data.getReason(), + data.getCreated(), + data.getUpdated(), + data.getExpires(), + data.isSilent()); + } + + public static PlayerBanRecord playerBanRecord(me.confuser.banmanager.common.data.PlayerBanRecord internal) { + if (internal == null) return null; + return new PlayerBanRecord( + internal.getId(), + player(internal.getPlayer()), + player(internal.getActor()), + player(internal.getPastActor()), + internal.getReason(), + emptyIfNull(internal.getCreatedReason()), + internal.getExpired(), + internal.getPastCreated(), + internal.getCreated(), + internal.isSilent()); + } + + public static PlayerMute playerMute(PlayerMuteData data) { + if (data == null) return null; + return new PlayerMute( + data.getId(), + player(data.getPlayer()), + player(data.getActor()), + data.getReason(), + data.getCreated(), + data.getUpdated(), + data.getExpires(), + data.isSoft(), + data.isSilent(), + data.isOnlineOnly(), + data.getPausedRemaining()); + } + + public static PlayerMuteRecord playerMuteRecord(me.confuser.banmanager.common.data.PlayerMuteRecord internal) { + if (internal == null) return null; + return new PlayerMuteRecord( + internal.getId(), + player(internal.getPlayer()), + player(internal.getActor()), + player(internal.getPastActor()), + internal.getReason(), + emptyIfNull(internal.getCreatedReason()), + internal.getExpired(), + internal.getPastCreated(), + internal.getCreated(), + internal.isSoft(), + internal.isSilent(), + internal.isOnlineOnly(), + internal.getRemainingOnlineTime()); + } + + public static PlayerWarn playerWarn(PlayerWarnData data) { + if (data == null) return null; + return new PlayerWarn( + data.getId(), + player(data.getPlayer()), + player(data.getActor()), + data.getReason(), + data.getPoints(), + data.isRead(), + data.getCreated(), + data.getExpires()); + } + + public static IpBan ipBan(IpBanData data) { + if (data == null) return null; + return new IpBan( + data.getId(), + IpAddressMapper.toApi(data.getIp()), + player(data.getActor()), + data.getReason(), + data.getCreated(), + data.getUpdated(), + data.getExpires(), + data.isSilent()); + } + + public static IpBanRecord ipBanRecord(me.confuser.banmanager.common.data.IpBanRecord internal) { + if (internal == null) return null; + return new IpBanRecord( + internal.getId(), + IpAddressMapper.toApi(internal.getIp()), + player(internal.getActor()), + player(internal.getPastActor()), + internal.getReason(), + emptyIfNull(internal.getCreatedReason()), + internal.getExpired(), + internal.getPastCreated(), + internal.getCreated(), + internal.isSilent()); + } + + public static IpMute ipMute(IpMuteData data) { + if (data == null) return null; + return new IpMute( + data.getId(), + IpAddressMapper.toApi(data.getIp()), + player(data.getActor()), + data.getReason(), + data.getCreated(), + data.getUpdated(), + data.getExpires(), + data.isSoft(), + data.isSilent()); + } + + public static IpMuteRecord ipMuteRecord(me.confuser.banmanager.common.data.IpMuteRecord internal) { + if (internal == null) return null; + return new IpMuteRecord( + internal.getId(), + IpAddressMapper.toApi(internal.getIp()), + player(internal.getActor()), + player(internal.getPastActor()), + internal.getReason(), + emptyIfNull(internal.getCreatedReason()), + internal.getExpired(), + internal.getPastCreated(), + internal.getCreated(), + internal.isSoft(), + internal.isSilent()); + } + + public static IpRangeBan ipRangeBan(IpRangeBanData data) { + if (data == null) return null; + return new IpRangeBan( + data.getId(), + IpAddressMapper.toApi(data.getFromIp()), + IpAddressMapper.toApi(data.getToIp()), + player(data.getActor()), + data.getReason(), + data.getCreated(), + data.getUpdated(), + data.getExpires(), + data.isSilent()); + } + + public static IpRangeBanRecord ipRangeBanRecord(me.confuser.banmanager.common.data.IpRangeBanRecord internal) { + if (internal == null) return null; + return new IpRangeBanRecord( + internal.getId(), + IpAddressMapper.toApi(internal.getFromIp()), + IpAddressMapper.toApi(internal.getToIp()), + player(internal.getActor()), + player(internal.getPastActor()), + internal.getReason(), + emptyIfNull(internal.getCreatedReason()), + internal.getExpired(), + internal.getPastCreated(), + internal.getCreated(), + internal.isSilent()); + } + + public static NameBan nameBan(NameBanData data) { + if (data == null) return null; + return new NameBan( + data.getId(), + data.getName(), + player(data.getActor()), + data.getReason(), + data.getCreated(), + data.getUpdated(), + data.getExpires(), + data.isSilent()); + } + + public static NameBanRecord nameBanRecord(me.confuser.banmanager.common.data.NameBanRecord internal) { + if (internal == null) return null; + return new NameBanRecord( + internal.getId(), + internal.getName(), + player(internal.getActor()), + player(internal.getPastActor()), + internal.getReason(), + emptyIfNull(internal.getCreatedReason()), + internal.getExpired(), + internal.getPastCreated(), + internal.getCreated(), + internal.isSilent()); + } + + public static PlayerNote playerNote(PlayerNoteData data) { + if (data == null) return null; + return new PlayerNote( + data.getId(), + player(data.getPlayer()), + player(data.getActor()), + data.getMessage(), + data.getCreated()); + } + + public static PlayerReport playerReport(PlayerReportData data) { + if (data == null) return null; + return new PlayerReport( + data.getId(), + player(data.getPlayer()), + player(data.getActor()), + Optional.ofNullable(player(data.getAssignee())), + reportState(data.getState()), + data.getReason(), + data.getCreated(), + data.getUpdated()); + } + + public static ReportState reportState(me.confuser.banmanager.common.data.ReportState data) { + if (data == null) return null; + return new ReportState(data.getId(), data.getName()); + } + + public static PlayerSession playerSession(PlayerHistoryData data) { + if (data == null) return null; + return new PlayerSession( + data.getId(), + player(data.getPlayer()), + data.getName(), + Optional.ofNullable(IpAddressMapper.toApi(data.getIp())), + data.getJoin(), + data.getLeave()); + } + + public static HistoryEntry historyEntry(me.confuser.banmanager.common.data.HistoryEntry internal) { + if (internal == null) return null; + return new HistoryEntry( + internal.id(), + internal.type(), + internal.actor(), + internal.created(), + emptyIfNull(internal.reason()), + emptyIfNull(internal.meta())); + } + + public static PlayerNameSummary playerNameSummary(me.confuser.banmanager.common.data.PlayerNameSummary internal) { + if (internal == null) return null; + return new PlayerNameSummary(internal.name(), internal.firstSeen(), internal.lastSeen()); + } + + // --------------------------------------------------------------------- + // Entity → Request mappers (snapshot of the about-to-persist entity, used + // as the pre-event payload so handlers see the entity's current state and + // can mutate fields like reason/expires/silent before persistence). + // --------------------------------------------------------------------- + + public static BanRequest banRequest(PlayerBanData data) { + if (data == null) return null; + BanRequest req = new BanRequest() + .player(data.getPlayer().getUUID()) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .expires(data.getExpires()) + .silent(data.isSilent()); + return req; + } + + public static MuteRequest muteRequest(PlayerMuteData data) { + if (data == null) return null; + return new MuteRequest() + .player(data.getPlayer().getUUID()) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .expires(data.getExpires()) + .soft(data.isSoft()) + .silent(data.isSilent()) + .onlineOnly(data.isOnlineOnly()); + } + + public static WarnRequest warnRequest(PlayerWarnData data, boolean silent) { + if (data == null) return null; + return new WarnRequest() + .player(data.getPlayer().getUUID()) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .points(data.getPoints()) + .read(data.isRead()) + .expires(data.getExpires()) + .silent(silent); + } + + public static NoteRequest noteRequest(PlayerNoteData data) { + if (data == null) return null; + return new NoteRequest() + .player(data.getPlayer().getUUID()) + .actor(data.getActor().getUUID()) + .message(data.getMessage()); + } + + public static ReportRequest reportRequest(PlayerReportData data) { + if (data == null) return null; + return new ReportRequest() + .player(data.getPlayer().getUUID()) + .actor(data.getActor().getUUID()) + .reason(data.getReason()); + } + + public static IpBanRequest ipBanRequest(IpBanData data) { + if (data == null) return null; + return new IpBanRequest() + .ip(IpAddressMapper.toApi(data.getIp())) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .expires(data.getExpires()) + .silent(data.isSilent()); + } + + public static IpMuteRequest ipMuteRequest(IpMuteData data) { + if (data == null) return null; + return new IpMuteRequest() + .ip(IpAddressMapper.toApi(data.getIp())) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .expires(data.getExpires()) + .soft(data.isSoft()) + .silent(data.isSilent()); + } + + public static IpRangeBanRequest ipRangeBanRequest(IpRangeBanData data) { + if (data == null) return null; + return new IpRangeBanRequest() + .fromIp(IpAddressMapper.toApi(data.getFromIp())) + .toIp(IpAddressMapper.toApi(data.getToIp())) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .expires(data.getExpires()) + .silent(data.isSilent()); + } + + public static NameBanRequest nameBanRequest(NameBanData data) { + if (data == null) return null; + return new NameBanRequest() + .name(data.getName()) + .actor(data.getActor().getUUID()) + .reason(data.getReason()) + .expires(data.getExpires()) + .silent(data.isSilent()); + } + + // --------------------------------------------------------------------- + // Request → Entity mutators. Called *after* the pre-event has fired so the + // entity's reason/expires/silent (etc.) reflect any handler edits before we + // persist. Identity fields (player, actor, ip, timestamps) are validated at + // the service boundary and never copied back here. + // --------------------------------------------------------------------- + + public static void applyTo(BanRequest req, PlayerBanData target) { + target.setReason(req.reason()); + target.setExpires(req.expires()); + target.setSilent(req.silent()); + } + + public static void applyTo(MuteRequest req, PlayerMuteData target) { + target.setReason(req.reason()); + target.setExpires(req.expires()); + target.setSoft(req.soft()); + target.setSilent(req.silent()); + target.setOnlineOnly(req.onlineOnly()); + } + + public static void applyTo(WarnRequest req, PlayerWarnData target) { + target.setReason(req.reason()); + target.setPoints(req.points()); + target.setRead(req.read()); + target.setExpires(req.expires()); + // PlayerWarnData has no silent column; silent is a transient broadcast + // flag tracked separately by storage and surfaced via PlayerWarnedEvent. + } + + public static void applyTo(NoteRequest req, PlayerNoteData target) { + target.setMessage(req.message()); + } + + public static void applyTo(ReportRequest req, PlayerReportData target) { + target.setReason(req.reason()); + } + + public static void applyTo(IpBanRequest req, IpBanData target) { + target.setReason(req.reason()); + target.setExpires(req.expires()); + target.setSilent(req.silent()); + } + + public static void applyTo(IpMuteRequest req, IpMuteData target) { + target.setReason(req.reason()); + target.setExpires(req.expires()); + target.setSoft(req.soft()); + target.setSilent(req.silent()); + } + + public static void applyTo(IpRangeBanRequest req, IpRangeBanData target) { + target.setReason(req.reason()); + target.setExpires(req.expires()); + target.setSilent(req.silent()); + } + + public static void applyTo(NameBanRequest req, NameBanData target) { + target.setName(req.name()); + target.setReason(req.reason()); + target.setExpires(req.expires()); + target.setSilent(req.silent()); + } + + private static String emptyIfNull(String s) { + return s == null ? "" : s; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/IpAddressMapper.java b/common/src/main/java/me/confuser/banmanager/common/impl/IpAddressMapper.java new file mode 100644 index 000000000..4e3376afb --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/IpAddressMapper.java @@ -0,0 +1,43 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.common.exception.BanManagerInternalException; +import me.confuser.banmanager.common.ipaddr.AddressStringException; +import me.confuser.banmanager.common.ipaddr.IPAddressString; + +/** + * Converts between BanManager's shaded {@code me.confuser.banmanager.common.ipaddr.IPAddress} + * (used internally by ORMLite entities) and the unshaded {@code inet.ipaddr.IPAddress} + * exposed on the public API. The two classes are bytecode-identical but live in + * different packages, so direct casting is impossible. + * + *

Conversion is via canonical string form: cheap, allocation-light, and + * round-trips for both IPv4 and IPv6 including subnets.

+ */ +public final class IpAddressMapper { + + private IpAddressMapper() {} + + /** + * Convert from internal (shaded) to public API (unshaded). + */ + public static inet.ipaddr.IPAddress toApi(me.confuser.banmanager.common.ipaddr.IPAddress internal) { + if (internal == null) return null; + try { + return new inet.ipaddr.IPAddressString(internal.toCanonicalString()).toAddress(); + } catch (inet.ipaddr.AddressStringException e) { + throw new BanManagerInternalException("Failed to convert internal IPAddress '" + internal + "' to API form", e); + } + } + + /** + * Convert from public API (unshaded) to internal (shaded). + */ + public static me.confuser.banmanager.common.ipaddr.IPAddress toInternal(inet.ipaddr.IPAddress api) { + if (api == null) return null; + try { + return new IPAddressString(api.toCanonicalString()).toAddress(); + } catch (AddressStringException e) { + throw new BanManagerInternalException("Failed to convert API IPAddress '" + api + "' to internal form", e); + } + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/MigrationPrefixRegistry.java b/common/src/main/java/me/confuser/banmanager/common/impl/MigrationPrefixRegistry.java new file mode 100644 index 000000000..5de13e5b9 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/MigrationPrefixRegistry.java @@ -0,0 +1,54 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.exception.BanManagerException; + +import java.lang.ref.WeakReference; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Tracks which {@link ClassLoader} most recently registered each migration + * {@code prefix}. Used by {@link MigrationServiceImpl} to reject silent + * cross-plugin {@code bm_schema_version} collisions. + * + *

Holds a {@link WeakReference} to each classloader so a plugin reload + * (Bukkit {@code /reload}, Velocity hot-reload) that drops the previous + * loader frees the slot for the redeploy. A re-run from the same instance + * is a no-op (idempotent migrations).

+ * + *

Extracted from the service so the collision logic can be unit-tested + * in isolation, without bringing up a {@code BanManagerPlugin}.

+ */ +final class MigrationPrefixRegistry { + + private final ConcurrentHashMap> entries = new ConcurrentHashMap<>(); + + /** + * Claim {@code prefix} for {@code caller}. Idempotent for the same + * caller; replaces a stale entry whose classloader has been GC'd. + * + * @throws BanManagerException when {@code prefix} is held by a different, + * still-live classloader + */ + void claim(String prefix, ClassLoader caller) { + while (true) { + WeakReference existing = entries.get(prefix); + ClassLoader prior = existing != null ? existing.get() : null; + + if (prior == caller) return; + + if (prior != null) { + throw new BanManagerException( + "Migration prefix '" + prefix + "' is already registered by another plugin" + + " (classloader " + prior + "). Choose a unique prefix to avoid" + + " bm_schema_version row collisions."); + } + + WeakReference next = new WeakReference<>(caller); + if (existing == null) { + if (entries.putIfAbsent(prefix, next) == null) return; + } else if (entries.replace(prefix, existing, next)) { + return; + } + } + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/MigrationServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/MigrationServiceImpl.java new file mode 100644 index 000000000..ee678b425 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/MigrationServiceImpl.java @@ -0,0 +1,98 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.database.DatabaseKind; +import me.confuser.banmanager.api.database.MigrationService; +import me.confuser.banmanager.api.exception.BanManagerException; +import me.confuser.banmanager.api.exception.StorageException; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.configs.DatabaseConfig; +import me.confuser.banmanager.common.ormlite.support.ConnectionSource; +import me.confuser.banmanager.common.storage.migration.MigrationRunner; + +import java.sql.SQLException; +import java.util.Objects; + +/** + * Default {@link MigrationService} implementation that delegates to the + * internal {@link MigrationRunner}. + * + *

Companion plugins (WebEnhancer, etc.) ship their migrations on their + * own classpath but reuse BanManager's runner — including the advisory + * locking that prevents two servers from migrating simultaneously and the + * placeholder substitution that resolves table-name variables.

+ * + *

The caller selects the target database by {@link DatabaseKind} rather + * than by passing a {@link javax.sql.DataSource}, eliminating an entire + * class of reference-equality bugs.

+ * + *

Prefix collision detection

+ *

The {@link MigrationConfig#prefix()} value scopes rows in the shared + * {@code bm_schema_version} table. Two plugins picking the same prefix + * (for example, both shipping {@code "myplugin"}) would silently corrupt + * each other's version state — every plugin reload would see "current + * version = whatever the other plugin wrote last" and either skip + * migrations entirely or replay them from scratch. The + * {@link MigrationPrefixRegistry} delegate makes that loud rather than + * silent: a prefix already claimed by a different (still-live) + * classloader rejects the second registration with a + * {@link BanManagerException}.

+ */ +public final class MigrationServiceImpl implements MigrationService { + + private final BanManagerPlugin plugin; + private final MigrationPrefixRegistry prefixRegistry = new MigrationPrefixRegistry(); + + public MigrationServiceImpl(BanManagerPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run(MigrationConfig config) { + Objects.requireNonNull(config, "config"); + + prefixRegistry.claim(config.prefix(), config.classLoader()); + + ConnectionSource cs; + DatabaseConfig dbConfig; + switch (config.database()) { + case LOCAL -> { + cs = plugin.getLocalConn(); + dbConfig = plugin.getConfig().getLocalDb(); + } + case GLOBAL -> { + if (plugin.getGlobalDataSource() == null) { + throw new BanManagerException( + "Cannot run migrations against the global database — no global database is configured."); + } + cs = plugin.getGlobalConn(); + dbConfig = plugin.getConfig().getGlobalDb(); + } + default -> throw new IllegalStateException("Unhandled DatabaseKind: " + config.database()); + } + + String detectionTableName = config.detectionTable(); + String detectionTableKey = null; + if (detectionTableName != null && dbConfig.getTables().containsKey(detectionTableName)) { + // The caller passed a logical key — resolve through dbConfig so the + // configured (possibly remapped) table name is used. + detectionTableKey = detectionTableName; + detectionTableName = null; + } + + MigrationRunner runner = new MigrationRunner( + plugin, + cs, + dbConfig, + config.prefix(), + detectionTableKey, + detectionTableName, + config.classLoader(), + config.resourcePath()); + + try { + runner.migrate(); + } catch (SQLException e) { + throw new StorageException("Migration failed for prefix '" + config.prefix() + "'", e); + } + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/SchedulerAdapter.java b/common/src/main/java/me/confuser/banmanager/common/impl/SchedulerAdapter.java new file mode 100644 index 000000000..94ffa4d50 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/SchedulerAdapter.java @@ -0,0 +1,52 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.scheduler.BanManagerScheduler; +import me.confuser.banmanager.common.CommonScheduler; + +import java.time.Duration; + +/** + * Thin adapter from the platform-specific {@link CommonScheduler} to the + * public {@link BanManagerScheduler} API. The two interfaces are + * intentionally identical at the moment so the adapter is a straight-through + * delegate; it exists so platform schedulers stay free to add internal + * methods without leaking them into the API surface. + */ +public final class SchedulerAdapter implements BanManagerScheduler { + + private final CommonScheduler delegate; + + public SchedulerAdapter(CommonScheduler delegate) { + this.delegate = delegate; + } + + @Override + public void runAsync(Runnable task) { + delegate.runAsync(task); + } + + @Override + public void runAsyncLater(Runnable task, Duration delay) { + delegate.runAsyncLater(task, delay); + } + + @Override + public void runSync(Runnable task) { + delegate.runSync(task); + } + + @Override + public void runSyncLater(Runnable task, Duration delay) { + delegate.runSyncLater(task, delay); + } + + @Override + public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration period) { + delegate.runAsyncRepeating(task, initialDelay, period); + } + + @Override + public boolean isMainThreadAware() { + return delegate.isMainThreadAware(); + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/event/EventBusImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/event/EventBusImpl.java new file mode 100644 index 000000000..7665dff32 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/event/EventBusImpl.java @@ -0,0 +1,223 @@ +package me.confuser.banmanager.common.impl.event; + +import me.confuser.banmanager.api.event.BanManagerEvent; +import me.confuser.banmanager.api.event.CancellableEvent; +import me.confuser.banmanager.api.event.EventBus; +import me.confuser.banmanager.api.event.EventPriority; +import me.confuser.banmanager.api.event.Subscription; +import me.confuser.banmanager.common.CommonLogger; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * Default {@link EventBus} implementation. + * + *

Per-event-type listener lists are kept sorted by {@link EventPriority} + * (LOWEST first, MONITOR last) so dispatch never re-sorts; subscriptions are + * comparatively rare while publishes are hot. Lists are guarded by a + * per-list lock — small enough to be uncontended in practice and small + * enough that {@code copy-on-write} would be wasteful.

+ * + *

Dispatch walks the entire {@link BanManagerEvent} super-type graph + * (classes and interfaces, transitively) so a listener registered + * against {@link CancellableEvent} or {@link BanManagerEvent} fires for + * every concrete event in the hierarchy. Errors thrown by listeners are + * caught and logged with the listener's class name so one bad plugin can't + * stop the rest of the chain.

+ */ +public final class EventBusImpl implements EventBus { + + private static final Comparator> ORDERING = + Comparator.comparingInt(reg -> reg.priority.ordinal()); + + private final Map, SortedRegistrationList> registrations = + new ConcurrentHashMap<>(); + private final CommonLogger logger; + + public EventBusImpl(CommonLogger logger) { + this.logger = logger; + } + + @Override + public Subscription subscribe(Class type, Consumer handler) { + return subscribe(type, EventPriority.NORMAL, false, handler); + } + + @Override + public Subscription subscribe(Class type, EventPriority priority, Consumer handler) { + return subscribe(type, priority, false, handler); + } + + @Override + public Subscription subscribe(Class type, EventPriority priority, boolean ignoreCancelled, Consumer handler) { + Registration reg = new Registration<>(this, type, priority, ignoreCancelled, handler); + registrations.computeIfAbsent(type, k -> new SortedRegistrationList()).insert(reg); + return reg; + } + + @Override + public E publish(E event) { + Set> visited = new HashSet<>(); + dispatchClass(event.getClass(), event, visited); + return event; + } + + /** + * Walk the class hierarchy starting at {@code type}, firing matching + * listeners and recursing into every interface implemented at each level + * so subscriptions to {@link CancellableEvent} and {@link BanManagerEvent} + * receive events whose direct supertype is an abstract class. + */ + private void dispatchClass(Class type, E event, Set> visited) { + if (type == null || !visited.add(type)) return; + if (!BanManagerEvent.class.isAssignableFrom(type)) return; + + fire(type, event); + + for (Class iface : type.getInterfaces()) { + dispatchInterface(iface, event, visited); + } + dispatchClass(type.getSuperclass(), event, visited); + } + + private void dispatchInterface(Class iface, E event, Set> visited) { + if (iface == null || !visited.add(iface)) return; + if (!BanManagerEvent.class.isAssignableFrom(iface)) return; + + fire(iface, event); + + for (Class parent : iface.getInterfaces()) { + dispatchInterface(parent, event, visited); + } + } + + @SuppressWarnings("unchecked") + private void fire(Class type, E event) { + SortedRegistrationList list = registrations.get(type); + if (list == null) return; + + Registration[] snapshot = list.snapshot(); + if (snapshot.length == 0) return; + + boolean cancellable = event instanceof CancellableEvent; + for (Registration reg : snapshot) { + if (reg.cancelled) continue; + if (cancellable && ((CancellableEvent) event).isCancelled() && !reg.ignoreCancelled) continue; + + try { + ((Consumer) reg.handler).accept(event); + } catch (Throwable t) { + logger.warning("Event handler " + describe(reg) + " for " + type.getName() + " threw an exception", t); + } + } + } + + /** + * Best-effort identity for a handler. Lambdas are synthetic classes whose + * own {@code getName()} is opaque ({@code Foo$$Lambda/0x...}); look for + * the host class either via {@link Class#getEnclosingClass()} (works for + * inner-class lambdas on some JVMs) or by stripping the synthetic suffix + * from the runtime name (always works) so the log entry still points + * operators at the right plugin. + */ + static String describe(Registration reg) { + Class handlerCls = reg.handler.getClass(); + String name = handlerCls.getName(); + int lambdaIdx = name.indexOf("$$Lambda"); + if (lambdaIdx < 0) return name; + + Class enclosing = handlerCls.getEnclosingClass(); + if (enclosing != null) return enclosing.getName() + " (lambda)"; + return name.substring(0, lambdaIdx) + " (lambda)"; + } + + void remove(Registration reg) { + SortedRegistrationList list = registrations.get(reg.type); + if (list != null) list.remove(reg); + } + + /** + * Single subscription record. Implements {@link Subscription} so callers can + * detach via the returned handle. + */ + static final class Registration implements Subscription { + + private final EventBusImpl bus; + private final Class type; + private final EventPriority priority; + private final boolean ignoreCancelled; + private final Consumer handler; + private volatile boolean cancelled; + + Registration(EventBusImpl bus, Class type, EventPriority priority, boolean ignoreCancelled, Consumer handler) { + this.bus = bus; + this.type = type; + this.priority = priority; + this.ignoreCancelled = ignoreCancelled; + this.handler = handler; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void unsubscribe() { + if (cancelled) return; + cancelled = true; + bus.remove(this); + } + } + + /** + * Listener list that maintains priority order at insertion time and + * publishes a fresh snapshot for each dispatch so concurrent subscriptions + * are safe but already-running iterations keep their original view. + */ + private static final class SortedRegistrationList { + + private static final Registration[] EMPTY = new Registration[0]; + + private volatile Registration[] sorted = EMPTY; + + synchronized void insert(Registration reg) { + Registration[] current = sorted; + Registration[] next = new Registration[current.length + 1]; + int idx = 0; + while (idx < current.length && ORDERING.compare(current[idx], reg) <= 0) { + next[idx] = current[idx]; + idx++; + } + next[idx] = reg; + System.arraycopy(current, idx, next, idx + 1, current.length - idx); + sorted = next; + } + + synchronized void remove(Registration reg) { + Registration[] current = sorted; + int found = -1; + for (int i = 0; i < current.length; i++) { + if (current[i] == reg) { + found = i; + break; + } + } + if (found < 0) return; + + Registration[] next = new Registration[current.length - 1]; + System.arraycopy(current, 0, next, 0, found); + System.arraycopy(current, found + 1, next, found, current.length - found - 1); + sorted = next; + } + + Registration[] snapshot() { + return sorted; + } + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/BanServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/BanServiceImpl.java new file mode 100644 index 000000000..86c1faae9 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/BanServiceImpl.java @@ -0,0 +1,133 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.PlayerBanRecord; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.BanRequest; +import me.confuser.banmanager.api.service.BanService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Default {@link BanService}. Thin wrapper over {@code PlayerBanStorage} which + * owns the canonical event publishing for ban operations. Pre-event cancellation + * surfaces here as {@link Optional#empty()} on {@link #ban(BanRequest)} and + * {@link Boolean#FALSE} on {@link #unban}. + */ +public final class BanServiceImpl implements BanService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public BanServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> ban(BanRequest request) { + return async.async(() -> banSync(request)); + } + + @Override + public Optional banSync(BanRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.player(), "request.player"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData playerEntity = requirePlayer(request.player(), "player"); + PlayerData actorEntity = requirePlayer(request.actor(), "actor"); + + PlayerBanData ban = new PlayerBanData( + playerEntity, + actorEntity, + request.reason(), + request.silent(), + request.expires()); + + boolean created = plugin.getPlayerBanStorage().ban(ban); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.playerBan(ban)); + }); + } + + @Override + public CompletableFuture unban(UUID player, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + return async.async(() -> unbanSync(player, actor, reason, silent)); + } + + @Override + public boolean unbanSync(UUID player, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + + return AsyncSupport.sync(() -> { + PlayerBanData ban = plugin.getPlayerBanStorage().getBan(player); + if (ban == null) { + return false; + } + PlayerData actorEntity = requirePlayer(actor.uuid(), "actor"); + return plugin.getPlayerBanStorage().unban(ban, actorEntity, reason, false, silent); + }); + } + + @Override + public Optional findActive(UUID player) { + return Optional.ofNullable(EntityMappers.playerBan(plugin.getPlayerBanStorage().getBan(player))); + } + + @Override + public Optional findActive(String name) { + return Optional.ofNullable(EntityMappers.playerBan(plugin.getPlayerBanStorage().getBan(name))); + } + + @Override + public boolean isBanned(UUID player) { + return plugin.getPlayerBanStorage().isBanned(player); + } + + @Override + public boolean isBanned(String name) { + return plugin.getPlayerBanStorage().isBanned(name); + } + + @Override + public CompletableFuture> records(UUID player, int page, int size) { + return async.async(() -> recordsSync(player, page, size)); + } + + @Override + public Page recordsSync(UUID player, int page, int size) { + return Pagination.recordsByPlayer( + plugin.getPlayerBanRecordStorage(), + plugin.getPlayerStorage(), + player, + page, + size, + EntityMappers::playerBanRecord); + } + + private PlayerData requirePlayer(UUID uuid, String label) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No " + label + " player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/HistoryServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/HistoryServiceImpl.java new file mode 100644 index 000000000..d455dc0fa --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/HistoryServiceImpl.java @@ -0,0 +1,142 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.HistoryEntry; +import me.confuser.banmanager.api.dto.PlayerNameSummary; +import me.confuser.banmanager.api.dto.PlayerSession; +import me.confuser.banmanager.api.service.HistoryService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerHistoryData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public final class HistoryServiceImpl implements HistoryService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public HistoryServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> history(UUID player, int page, int size) { + return async.async(() -> historySync(player, page, size)); + } + + @Override + public Page historySync(UUID player, int page, int size) { + if (page < 0) throw new IllegalArgumentException("page must be >= 0"); + if (size <= 0 || size > Pagination.MAX_PAGE_SIZE) { + throw new IllegalArgumentException("size must be in 1.." + Pagination.MAX_PAGE_SIZE); + } + + return AsyncSupport.sync(() -> { + PlayerData target = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(player)); + if (target == null) { + return Page.empty(page, size); + } + + // LIMIT/OFFSET is pushed into the underlying UNION so memory stays + // bounded regardless of how big the player's history is. The COUNT + // is a second round-trip, but it's the cost of a real `total` value. + List rows = + plugin.getHistoryStorage().getPaged(target, true, true, true, true, true, true, page, size); + if (rows == null) { + // HistoryStorage#getPaged contract: never null; defensive guard + // turns a silent NPE downstream into a clear contract violation. + throw new IllegalStateException( + "HistoryStorage.getPaged returned null for player " + player); + } + + long total = plugin.getHistoryStorage().count(target, true, true, true, true, true, true); + + List slice = new ArrayList<>(rows.size()); + for (me.confuser.banmanager.common.data.HistoryEntry row : rows) { + slice.add(EntityMappers.historyEntry(row)); + } + return new Page<>(slice, page, size, total); + }, "Failed to query history for player " + player); + } + + @Override + public CompletableFuture> names(UUID player) { + return async.async(() -> namesSync(player)); + } + + @Override + public List namesSync(UUID player) { + return AsyncSupport.sync(() -> { + PlayerData target = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(player)); + if (target == null) return Collections.emptyList(); + + List rows = + plugin.getPlayerHistoryStorage().getNamesSummary(target); + List out = new ArrayList<>(rows.size()); + for (me.confuser.banmanager.common.data.PlayerNameSummary row : rows) { + out.add(new PlayerNameSummary(row.name(), row.firstSeen(), row.lastSeen())); + } + return out; + }, "Failed to query name history for player " + player); + } + + @Override + public CompletableFuture> sessions(UUID player, long since, int page, int size) { + return async.async(() -> sessionsSync(player, since, page, size)); + } + + @Override + public Page sessionsSync(UUID player, long since, int page, int size) { + if (page < 0) throw new IllegalArgumentException("page must be >= 0"); + if (size <= 0 || size > Pagination.MAX_PAGE_SIZE) { + throw new IllegalArgumentException("size must be in 1.." + Pagination.MAX_PAGE_SIZE); + } + + return AsyncSupport.sync(() -> { + PlayerData target = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(player)); + if (target == null) return Page.empty(page, size); + + List sessions = new ArrayList<>(); + /* + * try-with-resources is safe here because we're inside SqlCallable.call(), + * which already declares `throws Exception`; that lets close() + * propagate without forcing the outer service method to declare + * any checked exception. + */ + try (CloseableIterator it = + plugin.getPlayerHistoryStorage().getSince(target, since, page, size)) { + while (it.hasNext()) { + sessions.add(EntityMappers.playerSession(it.next())); + } + } + long total = plugin.getPlayerHistoryStorage().countSince(target, since); + return new Page<>(sessions, page, size, total); + }, "Failed to query sessions for player " + player); + } + + @Override + public CompletableFuture> nameAt(UUID player, long timestamp) { + return async.async(() -> nameAtSync(player, timestamp)); + } + + @Override + public Optional nameAtSync(UUID player, long timestamp) { + return AsyncSupport.sync(() -> { + PlayerData target = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(player)); + if (target == null) return Optional.empty(); + return Optional.ofNullable(plugin.getPlayerHistoryStorage().getNameAt(target, timestamp)); + }, "Failed to query name at " + timestamp + " for player " + player); + } + +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/IpBanServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/IpBanServiceImpl.java new file mode 100644 index 000000000..7293c9b1d --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/IpBanServiceImpl.java @@ -0,0 +1,117 @@ +package me.confuser.banmanager.common.impl.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.IpBanRecord; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.IpBanRequest; +import me.confuser.banmanager.api.service.IpBanService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.IpBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.impl.IpAddressMapper; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public final class IpBanServiceImpl implements IpBanService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public IpBanServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> ban(IpBanRequest request) { + return async.async(() -> banSync(request)); + } + + @Override + public Optional banSync(IpBanRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.ip(), "request.ip"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData actorEntity = requirePlayer(request.actor()); + + IpBanData ban = new IpBanData( + IpAddressMapper.toInternal(request.ip()), + actorEntity, + request.reason(), + request.silent(), + request.expires()); + + boolean created = plugin.getIpBanStorage().ban(ban); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.ipBan(ban)); + }); + } + + @Override + public CompletableFuture unban(IPAddress ip, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + return async.async(() -> unbanSync(ip, actor, reason, silent)); + } + + @Override + public boolean unbanSync(IPAddress ip, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + Objects.requireNonNull(ip, "ip"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + + return AsyncSupport.sync(() -> { + IpBanData ban = plugin.getIpBanStorage().getBan(IpAddressMapper.toInternal(ip)); + if (ban == null) { + return false; + } + PlayerData actorEntity = requirePlayer(actor.uuid()); + return plugin.getIpBanStorage().unban(ban, actorEntity, reason, false, silent); + }); + } + + @Override + public Optional findActive(IPAddress ip) { + return Optional.ofNullable(EntityMappers.ipBan(plugin.getIpBanStorage().getBan(IpAddressMapper.toInternal(ip)))); + } + + @Override + public boolean isBanned(IPAddress ip) { + return plugin.getIpBanStorage().isBanned(IpAddressMapper.toInternal(ip)); + } + + @Override + public CompletableFuture> records(IPAddress ip, int page, int size) { + return async.async(() -> recordsSync(ip, page, size)); + } + + @Override + public Page recordsSync(IPAddress ip, int page, int size) { + return Pagination.recordsByColumn( + plugin.getIpBanRecordStorage(), + "ip", + IpAddressMapper.toInternal(ip), + page, + size, + EntityMappers::ipBanRecord); + } + + private PlayerData requirePlayer(java.util.UUID uuid) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No actor player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/IpMuteServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/IpMuteServiceImpl.java new file mode 100644 index 000000000..ee06bfd30 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/IpMuteServiceImpl.java @@ -0,0 +1,118 @@ +package me.confuser.banmanager.common.impl.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.IpMuteRecord; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.IpMuteRequest; +import me.confuser.banmanager.api.service.IpMuteService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.IpMuteData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.impl.IpAddressMapper; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public final class IpMuteServiceImpl implements IpMuteService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public IpMuteServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> mute(IpMuteRequest request) { + return async.async(() -> muteSync(request)); + } + + @Override + public Optional muteSync(IpMuteRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.ip(), "request.ip"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData actorEntity = requirePlayer(request.actor()); + + IpMuteData mute = new IpMuteData( + IpAddressMapper.toInternal(request.ip()), + actorEntity, + request.reason(), + request.silent(), + request.soft(), + request.expires()); + + boolean created = plugin.getIpMuteStorage().mute(mute); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.ipMute(mute)); + }); + } + + @Override + public CompletableFuture unmute(IPAddress ip, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + return async.async(() -> unmuteSync(ip, actor, reason, silent)); + } + + @Override + public boolean unmuteSync(IPAddress ip, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + Objects.requireNonNull(ip, "ip"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + + return AsyncSupport.sync(() -> { + IpMuteData mute = plugin.getIpMuteStorage().getMute(IpAddressMapper.toInternal(ip)); + if (mute == null) { + return false; + } + PlayerData actorEntity = requirePlayer(actor.uuid()); + return plugin.getIpMuteStorage().unmute(mute, actorEntity, reason, silent); + }); + } + + @Override + public Optional findActive(IPAddress ip) { + return Optional.ofNullable(EntityMappers.ipMute(plugin.getIpMuteStorage().getMute(IpAddressMapper.toInternal(ip)))); + } + + @Override + public boolean isMuted(IPAddress ip) { + return plugin.getIpMuteStorage().isMuted(IpAddressMapper.toInternal(ip)); + } + + @Override + public CompletableFuture> records(IPAddress ip, int page, int size) { + return async.async(() -> recordsSync(ip, page, size)); + } + + @Override + public Page recordsSync(IPAddress ip, int page, int size) { + return Pagination.recordsByColumn( + plugin.getIpMuteRecordStorage(), + "ip", + IpAddressMapper.toInternal(ip), + page, + size, + EntityMappers::ipMuteRecord); + } + + private PlayerData requirePlayer(java.util.UUID uuid) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No actor player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/IpRangeBanServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/IpRangeBanServiceImpl.java new file mode 100644 index 000000000..784ef4278 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/IpRangeBanServiceImpl.java @@ -0,0 +1,101 @@ +package me.confuser.banmanager.common.impl.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.IpRangeBanRequest; +import me.confuser.banmanager.api.service.IpRangeBanService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.IpRangeBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.impl.IpAddressMapper; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public final class IpRangeBanServiceImpl implements IpRangeBanService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public IpRangeBanServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> ban(IpRangeBanRequest request) { + return async.async(() -> banSync(request)); + } + + @Override + public Optional banSync(IpRangeBanRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.fromIp(), "request.fromIp"); + Objects.requireNonNull(request.toIp(), "request.toIp"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData actorEntity = requirePlayer(request.actor()); + + IpRangeBanData ban = new IpRangeBanData( + IpAddressMapper.toInternal(request.fromIp()), + IpAddressMapper.toInternal(request.toIp()), + actorEntity, + request.reason(), + request.silent(), + request.expires()); + + boolean created = plugin.getIpRangeBanStorage().ban(ban); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.ipRangeBan(ban)); + }); + } + + @Override + public CompletableFuture unban(IpRangeBan ban, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + return async.async(() -> unbanSync(ban, actor, reason, silent)); + } + + @Override + public boolean unbanSync(IpRangeBan ban, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + Objects.requireNonNull(ban, "ban"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + + return AsyncSupport.sync(() -> { + IpRangeBanData internalBan = plugin.getIpRangeBanStorage().getBan(IpAddressMapper.toInternal(ban.fromIp())); + if (internalBan == null) { + return false; + } + PlayerData actorEntity = requirePlayer(actor.uuid()); + return plugin.getIpRangeBanStorage().unban(internalBan, actorEntity, reason, silent); + }); + } + + @Override + public Optional findActive(IPAddress ip) { + return Optional.ofNullable(EntityMappers.ipRangeBan(plugin.getIpRangeBanStorage().getBan(IpAddressMapper.toInternal(ip)))); + } + + @Override + public boolean isBanned(IPAddress ip) { + return plugin.getIpRangeBanStorage().isBanned(IpAddressMapper.toInternal(ip)); + } + + private PlayerData requirePlayer(java.util.UUID uuid) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No actor player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/MuteServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/MuteServiceImpl.java new file mode 100644 index 000000000..a98cdda8e --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/MuteServiceImpl.java @@ -0,0 +1,129 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.dto.PlayerMuteRecord; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.MuteRequest; +import me.confuser.banmanager.api.service.MuteService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerMuteData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public final class MuteServiceImpl implements MuteService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public MuteServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> mute(MuteRequest request) { + return async.async(() -> muteSync(request)); + } + + @Override + public Optional muteSync(MuteRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.player(), "request.player"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData playerEntity = requirePlayer(request.player(), "player"); + PlayerData actorEntity = requirePlayer(request.actor(), "actor"); + + PlayerMuteData mute = new PlayerMuteData( + playerEntity, + actorEntity, + request.reason(), + request.silent(), + request.soft(), + request.expires(), + request.onlineOnly()); + + boolean created = plugin.getPlayerMuteStorage().mute(mute); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.playerMute(mute)); + }); + } + + @Override + public CompletableFuture unmute(UUID player, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + return async.async(() -> unmuteSync(player, actor, reason, silent)); + } + + @Override + public boolean unmuteSync(UUID player, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + Objects.requireNonNull(player, "player"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + + return AsyncSupport.sync(() -> { + PlayerMuteData mute = plugin.getPlayerMuteStorage().getMute(player); + if (mute == null) { + return false; + } + PlayerData actorEntity = requirePlayer(actor.uuid(), "actor"); + return plugin.getPlayerMuteStorage().unmute(mute, actorEntity, reason, false, silent); + }); + } + + @Override + public Optional findActive(UUID player) { + return Optional.ofNullable(EntityMappers.playerMute(plugin.getPlayerMuteStorage().getMute(player))); + } + + @Override + public Optional findActive(String name) { + return Optional.ofNullable(EntityMappers.playerMute(plugin.getPlayerMuteStorage().getMute(name))); + } + + @Override + public boolean isMuted(UUID player) { + return plugin.getPlayerMuteStorage().isMuted(player); + } + + @Override + public boolean isMuted(String name) { + return plugin.getPlayerMuteStorage().isMuted(name); + } + + @Override + public CompletableFuture> records(UUID player, int page, int size) { + return async.async(() -> recordsSync(player, page, size)); + } + + @Override + public Page recordsSync(UUID player, int page, int size) { + return Pagination.recordsByPlayer( + plugin.getPlayerMuteRecordStorage(), + plugin.getPlayerStorage(), + player, + page, + size, + EntityMappers::playerMuteRecord); + } + + private PlayerData requirePlayer(UUID uuid, String label) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No " + label + " player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/NameBanServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/NameBanServiceImpl.java new file mode 100644 index 000000000..2ea8b09fd --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/NameBanServiceImpl.java @@ -0,0 +1,97 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.NameBanRequest; +import me.confuser.banmanager.api.service.NameBanService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.NameBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public final class NameBanServiceImpl implements NameBanService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public NameBanServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> ban(NameBanRequest request) { + return async.async(() -> banSync(request)); + } + + @Override + public Optional banSync(NameBanRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.name(), "request.name"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData actorEntity = requirePlayer(request.actor()); + + NameBanData ban = new NameBanData( + request.name(), + actorEntity, + request.reason(), + request.silent(), + request.expires()); + + boolean created = plugin.getNameBanStorage().ban(ban); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.nameBan(ban)); + }); + } + + @Override + public CompletableFuture unban(String name, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + return async.async(() -> unbanSync(name, actor, reason, silent)); + } + + @Override + public boolean unbanSync(String name, me.confuser.banmanager.api.dto.Player actor, String reason, boolean silent) { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(actor, "actor"); + Objects.requireNonNull(reason, "reason"); + + return AsyncSupport.sync(() -> { + NameBanData ban = plugin.getNameBanStorage().getBan(name); + if (ban == null) { + return false; + } + PlayerData actorEntity = requirePlayer(actor.uuid()); + return plugin.getNameBanStorage().unban(ban, actorEntity, reason, false, silent); + }); + } + + @Override + public Optional findActive(String name) { + return Optional.ofNullable(EntityMappers.nameBan(plugin.getNameBanStorage().getBan(name))); + } + + @Override + public boolean isBanned(String name) { + return plugin.getNameBanStorage().isBanned(name); + } + + private PlayerData requirePlayer(java.util.UUID uuid) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No actor player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/NoteServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/NoteServiceImpl.java new file mode 100644 index 000000000..e3fc4b969 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/NoteServiceImpl.java @@ -0,0 +1,92 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.NoteRequest; +import me.confuser.banmanager.api.service.NoteService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerNoteData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public final class NoteServiceImpl implements NoteService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public NoteServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> create(NoteRequest request) { + return async.async(() -> createSync(request)); + } + + @Override + public Optional createSync(NoteRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.player(), "request.player"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.message(), "request.message"); + + return AsyncSupport.sync(() -> { + PlayerData playerEntity = requirePlayer(request.player(), "player"); + PlayerData actorEntity = requirePlayer(request.actor(), "actor"); + + PlayerNoteData note = new PlayerNoteData(playerEntity, actorEntity, request.message()); + + boolean created = plugin.getPlayerNoteStorage().addNote(note); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.playerNote(note)); + }); + } + + @Override + public CompletableFuture delete(int noteId) { + return async.async(() -> deleteSync(noteId)); + } + + @Override + public boolean deleteSync(int noteId) { + return AsyncSupport.sync( + () -> plugin.getPlayerNoteStorage().deleteById(noteId) > 0, + "Failed to delete note " + noteId); + } + + @Override + public CompletableFuture> notes(UUID player, int page, int size) { + return async.async(() -> notesSync(player, page, size)); + } + + @Override + public Page notesSync(UUID player, int page, int size) { + return Pagination.recordsByPlayer( + plugin.getPlayerNoteStorage(), + plugin.getPlayerStorage(), + player, + page, + size, + EntityMappers::playerNote); + } + + private PlayerData requirePlayer(UUID uuid, String label) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No " + label + " player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/Pagination.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/Pagination.java new file mode 100644 index 000000000..ad3b8801c --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/Pagination.java @@ -0,0 +1,99 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.ormlite.dao.BaseDaoImpl; +import me.confuser.banmanager.common.ormlite.stmt.QueryBuilder; +import me.confuser.banmanager.common.storage.PlayerStorage; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +/** + * Shared pagination helpers for service implementations. The historical + * record DAOs all key on a {@code player_id} byte[] column, so the same + * countOf + offset/limit pattern works for ban records, mute records, etc. + * + *

Page indices are zero-based on the API surface (matches Spring/JPA + * conventions); the underlying ORMLite layer takes raw {@code offset/limit} + * pairs.

+ */ +final class Pagination { + + static final int MAX_PAGE_SIZE = 200; + + private Pagination() {} + + static Page recordsByPlayer(BaseDaoImpl dao, + PlayerStorage playerStorage, + UUID player, + int page, + int size, + Function mapper) { + if (page < 0) throw new IllegalArgumentException("page must be >= 0"); + if (size <= 0 || size > MAX_PAGE_SIZE) { + throw new IllegalArgumentException("size must be in 1.." + MAX_PAGE_SIZE); + } + + return AsyncSupport.sync(() -> { + PlayerData target = playerStorage.queryForId(UUIDUtils.toBytes(player)); + if (target == null) { + return Page.empty(page, size); + } + + QueryBuilder count = dao.queryBuilder(); + count.where().eq("player_id", target); + long total = count.countOf(); + + QueryBuilder q = dao.queryBuilder(); + q.where().eq("player_id", target); + q.orderBy("created", false); + q.offset((long) page * size); + q.limit((long) size); + + List rows = q.query(); + List mapped = new ArrayList<>(rows.size()); + for (I row : rows) { + mapped.add(mapper.apply(row)); + } + + return new Page<>(mapped, page, size, total); + }, "Failed to page records for player " + player); + } + + static Page recordsByColumn(BaseDaoImpl dao, + String column, + Object value, + int page, + int size, + Function mapper) { + if (page < 0) throw new IllegalArgumentException("page must be >= 0"); + if (size <= 0 || size > MAX_PAGE_SIZE) { + throw new IllegalArgumentException("size must be in 1.." + MAX_PAGE_SIZE); + } + + return AsyncSupport.sync(() -> { + QueryBuilder count = dao.queryBuilder(); + count.where().eq(column, value); + long total = count.countOf(); + + QueryBuilder q = dao.queryBuilder(); + q.where().eq(column, value); + q.orderBy("created", false); + q.offset((long) page * size); + q.limit((long) size); + + List rows = q.query(); + List mapped = new ArrayList<>(rows.size()); + for (I row : rows) { + mapped.add(mapper.apply(row)); + } + + return new Page<>(mapped, page, size, total); + }, "Failed to page records by " + column); + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/PlayerServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/PlayerServiceImpl.java new file mode 100644 index 000000000..84d4440c5 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/PlayerServiceImpl.java @@ -0,0 +1,87 @@ +package me.confuser.banmanager.common.impl.service; + +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.service.PlayerService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.impl.IpAddressMapper; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Default {@link PlayerService} implementation. Delegates to + * {@link me.confuser.banmanager.common.storage.PlayerStorage} and translates + * results into API DTOs. + * + *

Lookups are read-only — write paths happen through the punishment + * services which create {@code PlayerData} on the fly via + * {@code createIfNotExists}. Exposing those primitives here would tempt + * callers into bypassing the event bus.

+ */ +public final class PlayerServiceImpl implements PlayerService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public PlayerServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> findByUuid(UUID uuid) { + return async.async(() -> findByUuidSync(uuid)); + } + + @Override + public Optional findByUuidSync(UUID uuid) { + return AsyncSupport.sync(() -> { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + return Optional.ofNullable(EntityMappers.player(data)); + }); + } + + @Override + public CompletableFuture> findByName(String name) { + return async.async(() -> findByNameSync(name)); + } + + @Override + public Optional findByNameSync(String name) { + return AsyncSupport.sync(() -> { + PlayerData data = plugin.getPlayerStorage().findByExactName(name); + return Optional.ofNullable(EntityMappers.player(data)); + }, "Failed to look up player by name " + name); + } + + @Override + public CompletableFuture> findByIp(IPAddress ip) { + return async.async(() -> findByIpSync(ip)); + } + + @Override + public List findByIpSync(IPAddress ip) { + me.confuser.banmanager.common.ipaddr.IPAddress internal = IpAddressMapper.toInternal(ip); + return AsyncSupport.sync(() -> { + List matches = plugin.getPlayerStorage().findDuplicatesInTime(internal, 0L); + List result = new ArrayList<>(matches.size()); + for (PlayerData data : matches) { + result.add(EntityMappers.player(data)); + } + return result; + }, "Failed to look up players by IP " + ip); + } + + @Override + public Player console() { + return EntityMappers.player(plugin.getPlayerStorage().getConsole()); + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/ReportServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/ReportServiceImpl.java new file mode 100644 index 000000000..8889a43fc --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/ReportServiceImpl.java @@ -0,0 +1,151 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.dto.ReportState; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.ReportRequest; +import me.confuser.banmanager.api.service.ReportService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerReportData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public final class ReportServiceImpl implements ReportService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public ReportServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> create(ReportRequest request) { + return async.async(() -> createSync(request)); + } + + @Override + public Optional createSync(ReportRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.player(), "request.player"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData playerEntity = requirePlayer(request.player(), "player"); + PlayerData actorEntity = requirePlayer(request.actor(), "actor"); + + me.confuser.banmanager.common.data.ReportState defaultState = + plugin.getReportStateStorage().queryForId(1); + + PlayerReportData report = new PlayerReportData(playerEntity, actorEntity, request.reason(), defaultState); + + boolean created = plugin.getPlayerReportStorage().report(report, false); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.playerReport(report)); + }); + } + + @Override + public CompletableFuture delete(int reportId, Player actor) { + return async.async(() -> deleteSync(reportId, actor)); + } + + @Override + public boolean deleteSync(int reportId, Player actor) { + Objects.requireNonNull(actor, "actor"); + return AsyncSupport.sync(() -> { + PlayerData actorEntity = requirePlayer(actor.uuid(), "actor"); + return plugin.getPlayerReportStorage().deleteById(reportId, actorEntity) == 1; + }, "Failed to delete report " + reportId); + } + + @Override + public CompletableFuture> findById(int reportId) { + return async.async(() -> findByIdSync(reportId)); + } + + @Override + public Optional findByIdSync(int reportId) { + return AsyncSupport.sync( + () -> Optional.ofNullable(EntityMappers.playerReport(plugin.getPlayerReportStorage().queryForId(reportId))), + "Failed to load report " + reportId); + } + + @Override + public CompletableFuture> againstPlayer(UUID player, int page, int size) { + return async.async(() -> againstPlayerSync(player, page, size)); + } + + @Override + public Page againstPlayerSync(UUID player, int page, int size) { + return Pagination.recordsByPlayer( + plugin.getPlayerReportStorage(), + plugin.getPlayerStorage(), + player, + page, + size, + EntityMappers::playerReport); + } + + @Override + public CompletableFuture updateState(int reportId, ReportState state) { + return async.async(() -> updateStateSync(reportId, state)); + } + + @Override + public boolean updateStateSync(int reportId, ReportState state) { + Objects.requireNonNull(state, "state"); + return AsyncSupport.sync(() -> { + PlayerReportData report = plugin.getPlayerReportStorage().queryForId(reportId); + if (report == null) return false; + + me.confuser.banmanager.common.data.ReportState internal = + plugin.getReportStateStorage().queryForId(state.id()); + if (internal == null) return false; + + report.setState(internal); + return plugin.getPlayerReportStorage().update(report) == 1; + }, "Failed to update report " + reportId + " state"); + } + + @Override + public CompletableFuture> states() { + return async.async(this::statesSync); + } + + @Override + public List statesSync() { + return AsyncSupport.sync(() -> { + List rows = plugin.getReportStateStorage().queryForAll(); + List mapped = new ArrayList<>(rows.size()); + for (me.confuser.banmanager.common.data.ReportState row : rows) { + mapped.add(new ReportState(row.getId(), row.getName())); + } + return mapped; + }, "Failed to load report states"); + } + + private PlayerData requirePlayer(UUID uuid, String label) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No " + label + " player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/impl/service/WarnServiceImpl.java b/common/src/main/java/me/confuser/banmanager/common/impl/service/WarnServiceImpl.java new file mode 100644 index 000000000..ecda04a44 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/impl/service/WarnServiceImpl.java @@ -0,0 +1,102 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.exception.EntityNotFoundException; +import me.confuser.banmanager.api.request.WarnRequest; +import me.confuser.banmanager.api.service.WarnService; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerWarnData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.impl.EntityMappers; +import me.confuser.banmanager.common.ormlite.stmt.UpdateBuilder; +import me.confuser.banmanager.common.util.UUIDUtils; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public final class WarnServiceImpl implements WarnService { + + private final BanManagerPlugin plugin; + private final AsyncSupport async; + + public WarnServiceImpl(BanManagerPlugin plugin, AsyncSupport async) { + this.plugin = plugin; + this.async = async; + } + + @Override + public CompletableFuture> warn(WarnRequest request) { + return async.async(() -> warnSync(request)); + } + + @Override + public Optional warnSync(WarnRequest request) { + Objects.requireNonNull(request, "request"); + Objects.requireNonNull(request.player(), "request.player"); + Objects.requireNonNull(request.actor(), "request.actor"); + Objects.requireNonNull(request.reason(), "request.reason"); + + return AsyncSupport.sync(() -> { + PlayerData playerEntity = requirePlayer(request.player(), "player"); + PlayerData actorEntity = requirePlayer(request.actor(), "actor"); + + PlayerWarnData warn = new PlayerWarnData( + playerEntity, + actorEntity, + request.reason(), + request.points(), + request.read(), + request.expires()); + + boolean created = plugin.getPlayerWarnStorage().addWarning(warn, request.silent()); + if (!created) { + return Optional.empty(); + } + + return Optional.of(EntityMappers.playerWarn(warn)); + }); + } + + @Override + public CompletableFuture> warnings(UUID player, int page, int size) { + return async.async(() -> warningsSync(player, page, size)); + } + + @Override + public Page warningsSync(UUID player, int page, int size) { + return Pagination.recordsByPlayer( + plugin.getPlayerWarnStorage(), + plugin.getPlayerStorage(), + player, + page, + size, + EntityMappers::playerWarn); + } + + @Override + public CompletableFuture markRead(int warnId) { + return async.async(() -> markReadSync(warnId)); + } + + @Override + public boolean markReadSync(int warnId) { + return AsyncSupport.sync(() -> { + UpdateBuilder builder = plugin.getPlayerWarnStorage().updateBuilder(); + builder.updateColumnValue("read", true); + builder.where().eq("id", warnId); + return builder.update() > 0; + }, "Failed to mark warning " + warnId + " read"); + } + + private PlayerData requirePlayer(UUID uuid, String label) throws Exception { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + if (data == null) { + throw new EntityNotFoundException("No " + label + " player exists with UUID " + uuid); + } + return data; + } +} diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java index c764bfc84..87e1d1a95 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java @@ -1,27 +1,46 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.event.ip.IpBannedEvent; +import me.confuser.banmanager.api.event.ip.IpRangeBannedEvent; +import me.confuser.banmanager.api.event.name.NameBannedEvent; +import me.confuser.banmanager.api.event.player.PlayerBannedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; -import me.confuser.banmanager.common.data.*; +import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.util.DateUtils; import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.util.NotificationUtils; import java.util.List; +/** + * Subscribes to ban post-events and broadcasts notifications to staff. + * + *

Constructed once during plugin enable; the constructor wires up + * subscriptions through the {@link me.confuser.banmanager.api.event.EventBus} + * so platform listener wrappers are no longer required.

+ */ public class CommonBanListener { - private BanManagerPlugin plugin; + private final BanManagerPlugin plugin; public CommonBanListener(BanManagerPlugin plugin) { this.plugin = plugin; + plugin.getEventBus().subscribe(PlayerBannedEvent.class, e -> notifyOnBan(e.ban(), e.silent())); + plugin.getEventBus().subscribe(IpBannedEvent.class, e -> notifyOnBan(e.ban(), e.silent())); + plugin.getEventBus().subscribe(IpRangeBannedEvent.class, e -> notifyOnBan(e.ban(), e.silent())); + plugin.getEventBus().subscribe(NameBannedEvent.class, e -> notifyOnBan(e.ban(), e.silent())); } - public void notifyOnBan(PlayerBanData data, boolean silent) { + public void notifyOnBan(PlayerBan data, boolean silent) { String broadcastPermission; String event; Message message; - if (data.getExpires() == 0) { + if (data.expires() == 0) { broadcastPermission = "bm.notify.ban"; event = "ban"; message = Message.get("ban.notify"); @@ -29,24 +48,24 @@ public void notifyOnBan(PlayerBanData data, boolean silent) { broadcastPermission = "bm.notify.tempban"; event = "tempban"; message = Message.get("tempban.notify"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } message - .set("id", data.getId()) - .set("player", data.getPlayer().getName()) - .set("playerId", data.getPlayer().getUUID().toString()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()); + .set("id", data.id()) + .set("player", data.player().name()) + .set("playerId", data.player().uuid().toString()) + .set("actor", data.actor().name()) + .set("reason", data.reason()); if (!silent) { NotificationUtils.notifyStaff(plugin, event, message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; @@ -57,12 +76,12 @@ public void notifyOnBan(PlayerBanData data, boolean silent) { } } - public void notifyOnBan(IpBanData data, boolean silent) { + public void notifyOnBan(IpBan data, boolean silent) { String broadcastPermission; String event; Message message; - if (data.getExpires() == 0) { + if (data.expires() == 0) { broadcastPermission = "bm.notify.banip"; event = "ban"; message = Message.get("banip.notify"); @@ -70,10 +89,13 @@ public void notifyOnBan(IpBanData data, boolean silent) { broadcastPermission = "bm.notify.tempbanip"; event = "tempban"; message = Message.get("tempbanip.notify"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } - List players = plugin.getPlayerStorage().getDuplicatesInTime(data.getIp(), plugin.getConfig().getTimeAssociatedAlts()); + me.confuser.banmanager.common.ipaddr.IPAddress internalIp = + me.confuser.banmanager.common.impl.IpAddressMapper.toInternal(data.ip()); + List players = plugin.getPlayerStorage().getDuplicatesInTime(internalIp, + plugin.getConfig().getTimeAssociatedAlts()); StringBuilder playerNames = new StringBuilder(); for (PlayerData player : players) { @@ -84,20 +106,20 @@ public void notifyOnBan(IpBanData data, boolean silent) { if (playerNames.length() >= 2) playerNames.setLength(playerNames.length() - 2); message - .set("id", data.getId()) - .set("ip", data.getIp().toString()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()) + .set("id", data.id()) + .set("ip", data.ip().toString()) + .set("actor", data.actor().name()) + .set("reason", data.reason()) .set("players", playerNames.toString()); if (!silent) { NotificationUtils.notifyStaff(plugin, event, message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; @@ -108,12 +130,12 @@ public void notifyOnBan(IpBanData data, boolean silent) { } } - public void notifyOnBan(IpRangeBanData data, boolean silent) { + public void notifyOnBan(IpRangeBan data, boolean silent) { String broadcastPermission; String event; Message message; - if (data.getExpires() == 0) { + if (data.expires() == 0) { broadcastPermission = "bm.notify.baniprange"; event = "ban"; message = Message.get("baniprange.notify"); @@ -121,24 +143,24 @@ public void notifyOnBan(IpRangeBanData data, boolean silent) { broadcastPermission = "bm.notify.tempbaniprange"; event = "tempban"; message = Message.get("tempbaniprange.notify"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } message - .set("id", data.getId()) - .set("from", data.getFromIp().toString()) - .set("to", data.getToIp().toString()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()); + .set("id", data.id()) + .set("from", data.fromIp().toString()) + .set("to", data.toIp().toString()) + .set("actor", data.actor().name()) + .set("reason", data.reason()); if (!silent) { NotificationUtils.notifyStaff(plugin, event, message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; @@ -149,12 +171,12 @@ public void notifyOnBan(IpRangeBanData data, boolean silent) { } } - public void notifyOnBan(NameBanData data, boolean silent) { + public void notifyOnBan(NameBan data, boolean silent) { String broadcastPermission; String event; Message message; - if (data.getExpires() == 0) { + if (data.expires() == 0) { broadcastPermission = "bm.notify.banname"; event = "ban"; message = Message.get("banname.notify"); @@ -162,23 +184,23 @@ public void notifyOnBan(NameBanData data, boolean silent) { broadcastPermission = "bm.notify.tempbanname"; event = "tempban"; message = Message.get("tempbanname.notify"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } message - .set("id", data.getId()) - .set("name", data.getName()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()); + .set("id", data.id()) + .set("name", data.name()) + .set("actor", data.actor().name()) + .set("reason", data.reason()); if (!silent) { NotificationUtils.notifyStaff(plugin, event, message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonHooksListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonHooksListener.java index bf91db048..85f9a6bf4 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonHooksListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonHooksListener.java @@ -1,276 +1,375 @@ package me.confuser.banmanager.common.listeners; +import inet.ipaddr.IPAddress; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.event.ip.IpBanEvent; +import me.confuser.banmanager.api.event.ip.IpBannedEvent; +import me.confuser.banmanager.api.event.ip.IpRangeBanEvent; +import me.confuser.banmanager.api.event.ip.IpRangeBannedEvent; +import me.confuser.banmanager.api.event.ip.IpRangeUnbannedEvent; +import me.confuser.banmanager.api.event.ip.IpUnbannedEvent; +import me.confuser.banmanager.api.event.player.PlayerBanEvent; +import me.confuser.banmanager.api.event.player.PlayerBannedEvent; +import me.confuser.banmanager.api.event.player.PlayerMuteEvent; +import me.confuser.banmanager.api.event.player.PlayerMutedEvent; +import me.confuser.banmanager.api.event.player.PlayerNoteCreatedEvent; +import me.confuser.banmanager.api.event.player.PlayerReportEvent; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; +import me.confuser.banmanager.api.event.player.PlayerUnbannedEvent; +import me.confuser.banmanager.api.event.player.PlayerUnmutedEvent; +import me.confuser.banmanager.api.event.player.PlayerWarnEvent; +import me.confuser.banmanager.api.event.player.PlayerWarnedEvent; +import me.confuser.banmanager.api.request.BanRequest; +import me.confuser.banmanager.api.request.IpBanRequest; +import me.confuser.banmanager.api.request.IpRangeBanRequest; +import me.confuser.banmanager.api.request.MuteRequest; +import me.confuser.banmanager.api.request.ReportRequest; +import me.confuser.banmanager.api.request.WarnRequest; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.configs.ActionCommand; import me.confuser.banmanager.common.configs.Hook; import me.confuser.banmanager.common.configs.HooksConfig; -import me.confuser.banmanager.common.data.*; +import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.google.guava.collect.ImmutableMap; +import me.confuser.banmanager.common.util.UUIDUtils; +import java.sql.SQLException; import java.time.Duration; import java.util.List; import java.util.Map; - +import java.util.UUID; + +/** + * Subscribes to ban/mute/warn/etc. events and runs configured hook commands + * (pre and post). Pre-event commands run on cancellable events at + * {@link me.confuser.banmanager.api.event.EventPriority#MONITOR} priority so + * they observe the final post-cancellation state. + */ public class CommonHooksListener { - private BanManagerPlugin plugin; + + private final BanManagerPlugin plugin; public CommonHooksListener(BanManagerPlugin plugin) { this.plugin = plugin; + subscribe(); } - private boolean shouldSkip(Hook hook, boolean silent) { - return silent && hook.ignoreSilent(); - } + private void subscribe() { + plugin.getEventBus().subscribe(PlayerBanEvent.class, this::onPlayerBanPre); + plugin.getEventBus().subscribe(PlayerBannedEvent.class, e -> onBan(e.ban(), false, e.silent())); + plugin.getEventBus().subscribe(PlayerUnbannedEvent.class, + e -> onUnban(e.ban(), e.actor(), e.reason(), e.silent())); - public void onBan(PlayerBanData data, boolean pre) { - onBan(data, pre, false); - } + plugin.getEventBus().subscribe(PlayerMuteEvent.class, this::onPlayerMutePre); + plugin.getEventBus().subscribe(PlayerMutedEvent.class, e -> onMute(e.mute(), false, e.silent())); + plugin.getEventBus().subscribe(PlayerUnmutedEvent.class, + e -> onUnmute(e.mute(), e.actor(), e.reason(), e.silent())); - public void onBan(PlayerBanData data, boolean pre, boolean silent) { - HooksConfig config = plugin.getConfig().getHooksConfig(); - final Hook hook = data.getExpires() == 0 ? config.getHook("ban") : config.getHook("tempban"); + plugin.getEventBus().subscribe(IpBanEvent.class, this::onIpBanPre); + plugin.getEventBus().subscribe(IpBannedEvent.class, e -> onBan(e.ban(), false, e.silent())); + plugin.getEventBus().subscribe(IpUnbannedEvent.class, + e -> onUnban(e.ban(), e.actor(), e.reason(), e.silent())); - if (hook == null) return; - if (shouldSkip(hook, silent)) return; + plugin.getEventBus().subscribe(IpRangeBanEvent.class, this::onIpRangeBanPre); + plugin.getEventBus().subscribe(IpRangeBannedEvent.class, e -> onBan(e.ban(), false, e.silent())); + plugin.getEventBus().subscribe(IpRangeUnbannedEvent.class, + e -> onUnban(e.ban(), e.actor(), e.reason(), e.silent())); - List commands = pre ? hook.pre() : hook.post(); + plugin.getEventBus().subscribe(PlayerWarnEvent.class, this::onPlayerWarnPre); + plugin.getEventBus().subscribe(PlayerWarnedEvent.class, e -> onWarn(e.warn(), false, e.silent())); - if (commands.size() != 0) { - executeCommands(commands, ImmutableMap.of( - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", data.getActor().getName() - , "reason", data.getReason() - , "expires", Long.toString(data.getExpires()) - )); - } + plugin.getEventBus().subscribe(PlayerNoteCreatedEvent.class, e -> onNote(e.note(), false)); + + plugin.getEventBus().subscribe(PlayerReportEvent.class, this::onPlayerReportPre); + plugin.getEventBus().subscribe(PlayerReportedEvent.class, e -> onReport(e.report(), false, false)); } - public void onUnban(PlayerBanData data, PlayerData actor, String reason) { - onUnban(data, actor, reason, false); + private void onPlayerBanPre(PlayerBanEvent event) { + if (event.isCancelled()) return; + BanRequest request = event.request(); + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = request.expires() == 0 ? config.getHook("ban") : config.getHook("tempban"); + if (hook == null || shouldSkip(hook, request.silent()) || hook.pre().isEmpty()) return; + + String playerName = nameOf(request.player()); + String actorName = nameOf(request.actor()); + executeCommands(hook.pre(), ImmutableMap.of( + "player", playerName, + "playerId", String.valueOf(request.player()), + "actor", actorName, + "reason", request.reason(), + "expires", Long.toString(request.expires()) + )); } - public void onUnban(PlayerBanData data, PlayerData actor, String reason, boolean silent) { + public void onBan(PlayerBan data, boolean pre, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); - final Hook hook = config.getHook("unban"); + final Hook hook = data.expires() == 0 ? config.getHook("ban") : config.getHook("tempban"); + if (hook == null || shouldSkip(hook, silent)) return; - if (hook == null) return; - if (shouldSkip(hook, silent)) return; - - if (hook.post().size() != 0) { - executeCommands(hook.post(), ImmutableMap.of( - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", actor.getName() - , "reason", reason - , "expires", Long.toString(data.getExpires()) - )); - } + List commands = pre ? hook.pre() : hook.post(); + if (commands.isEmpty()) return; + + executeCommands(commands, ImmutableMap.of( + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", data.actor().name(), + "reason", data.reason(), + "expires", Long.toString(data.expires()) + )); } - public void onMute(PlayerMuteData data, boolean pre) { - onMute(data, pre, false); + public void onUnban(PlayerBan data, Player actor, String reason, boolean silent) { + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = config.getHook("unban"); + if (hook == null || shouldSkip(hook, silent) || hook.post().isEmpty()) return; + + executeCommands(hook.post(), ImmutableMap.of( + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", actor.name(), + "reason", reason, + "expires", Long.toString(data.expires()) + )); } - public void onMute(PlayerMuteData data, boolean pre, boolean silent) { + private void onPlayerMutePre(PlayerMuteEvent event) { + if (event.isCancelled()) return; + MuteRequest request = event.request(); HooksConfig config = plugin.getConfig().getHooksConfig(); - final Hook hook = data.getExpires() == 0 ? config.getHook("mute") : config.getHook("tempmute"); + final Hook hook = request.expires() == 0 ? config.getHook("mute") : config.getHook("tempmute"); + if (hook == null || shouldSkip(hook, request.silent()) || hook.pre().isEmpty()) return; + + executeCommands(hook.pre(), ImmutableMap.of( + "player", nameOf(request.player()), + "playerId", String.valueOf(request.player()), + "actor", nameOf(request.actor()), + "reason", request.reason(), + "expires", Long.toString(request.expires()) + )); + } - if (hook == null) return; - if (shouldSkip(hook, silent)) return; + public void onMute(PlayerMute data, boolean pre, boolean silent) { + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = data.expires() == 0 ? config.getHook("mute") : config.getHook("tempmute"); + if (hook == null || shouldSkip(hook, silent)) return; List commands = pre ? hook.pre() : hook.post(); - - if (commands.size() != 0) { - executeCommands(commands, ImmutableMap.of( - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", data.getActor().getName() - , "reason", data.getReason() - , "expires", Long.toString(data.getExpires()) - )); - } - } - - public void onUnmute(PlayerMuteData data, PlayerData actor, String reason) { - onUnmute(data, actor, reason, false); + if (commands.isEmpty()) return; + + executeCommands(commands, ImmutableMap.of( + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", data.actor().name(), + "reason", data.reason(), + "expires", Long.toString(data.expires()) + )); } - public void onUnmute(PlayerMuteData data, PlayerData actor, String reason, boolean silent) { + public void onUnmute(PlayerMute data, Player actor, String reason, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); final Hook hook = config.getHook("unmute"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; - - if (hook.post().size() != 0) { - executeCommands(hook.post(), ImmutableMap.of( - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", actor.getName() - , "reason", reason - , "expires", Long.toString(data.getExpires()) - )); - } + if (hook == null || shouldSkip(hook, silent) || hook.post().isEmpty()) return; + + executeCommands(hook.post(), ImmutableMap.of( + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", actor.name(), + "reason", reason, + "expires", Long.toString(data.expires()) + )); } - public void onBan(IpBanData data, boolean pre) { - onBan(data, pre, false); + private void onIpBanPre(IpBanEvent event) { + if (event.isCancelled()) return; + IpBanRequest request = event.request(); + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = request.expires() == 0 ? config.getHook("ipban") : config.getHook("tempipban"); + if (hook == null || shouldSkip(hook, request.silent()) || hook.pre().isEmpty()) return; + + executeCommands(hook.pre(), ImmutableMap.of( + "ip", String.valueOf(request.ip()), + "actor", nameOf(request.actor()), + "reason", request.reason(), + "expires", Long.toString(request.expires()) + )); } - public void onBan(IpBanData data, boolean pre, boolean silent) { + public void onBan(IpBan data, boolean pre, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); - final Hook hook = data.getExpires() == 0 ? config.getHook("ipban") : config.getHook("tempipban"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; + final Hook hook = data.expires() == 0 ? config.getHook("ipban") : config.getHook("tempipban"); + if (hook == null || shouldSkip(hook, silent)) return; List commands = pre ? hook.pre() : hook.post(); - - if (commands.size() != 0) { - executeCommands(commands, ImmutableMap.of( - "ip", data.getIp().toString() - , "actor", data.getActor().getName() - , "reason", data.getReason() - , "expires", Long.toString(data.getExpires()) - )); - } - } - - public void onUnban(IpBanData data, PlayerData actor, String reason) { - onUnban(data, actor, reason, false); + if (commands.isEmpty()) return; + + executeCommands(commands, ImmutableMap.of( + "ip", data.ip().toString(), + "actor", data.actor().name(), + "reason", data.reason(), + "expires", Long.toString(data.expires()) + )); } - public void onUnban(IpBanData data, PlayerData actor, String reason, boolean silent) { + public void onUnban(IpBan data, Player actor, String reason, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); final Hook hook = config.getHook("unbanip"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; - - if (hook.post().size() != 0) { - executeCommands(hook.post(), ImmutableMap.of( - "ip", data.getIp().toString() - , "actor", actor.getName() - , "reason", reason - , "expires", Long.toString(data.getExpires()) - )); - } + if (hook == null || shouldSkip(hook, silent) || hook.post().isEmpty()) return; + + executeCommands(hook.post(), ImmutableMap.of( + "ip", data.ip().toString(), + "actor", actor.name(), + "reason", reason, + "expires", Long.toString(data.expires()) + )); } - public void onBan(IpRangeBanData data, boolean pre) { - onBan(data, pre, false); + private void onIpRangeBanPre(IpRangeBanEvent event) { + if (event.isCancelled()) return; + IpRangeBanRequest request = event.request(); + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = request.expires() == 0 ? config.getHook("iprangeban") : config.getHook("temprangeipban"); + if (hook == null || shouldSkip(hook, request.silent()) || hook.pre().isEmpty()) return; + + IPAddress fromIp = request.fromIp(); + IPAddress toIp = request.toIp(); + executeCommands(hook.pre(), ImmutableMap.of( + "from", fromIp == null ? "" : fromIp.toString(), + "to", toIp == null ? "" : toIp.toString(), + "actor", nameOf(request.actor()), + "reason", request.reason(), + "expires", Long.toString(request.expires()) + )); } - public void onBan(IpRangeBanData data, boolean pre, boolean silent) { + public void onBan(IpRangeBan data, boolean pre, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); - final Hook hook = data.getExpires() == 0 ? config.getHook("iprangeban") : config - .getHook("temprangeipban"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; + final Hook hook = data.expires() == 0 ? config.getHook("iprangeban") : config.getHook("temprangeipban"); + if (hook == null || shouldSkip(hook, silent)) return; List commands = pre ? hook.pre() : hook.post(); - - if (commands.size() != 0) { - executeCommands(commands, ImmutableMap.of( - "from", data.getFromIp().toString() - , "to", data.getToIp().toString() - , "actor", data.getActor().getName() - , "reason", data.getReason() - , "expires", Long.toString(data.getExpires()) - )); - } + if (commands.isEmpty()) return; + + executeCommands(commands, ImmutableMap.of( + "from", data.fromIp().toString(), + "to", data.toIp().toString(), + "actor", data.actor().name(), + "reason", data.reason(), + "expires", Long.toString(data.expires()) + )); } - public void onUnban(IpRangeBanData data, PlayerData actor, String reason) { - onUnban(data, actor, reason, false); - } - - public void onUnban(IpRangeBanData data, PlayerData actor, String reason, boolean silent) { + public void onUnban(IpRangeBan data, Player actor, String reason, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); final Hook hook = config.getHook("unbaniprange"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; - - if (hook.post().size() != 0) { - executeCommands(hook.post(), ImmutableMap.of( - "from", data.getFromIp().toString() - , "to", data.getToIp().toString() - , "actor", actor.getName() - , "reason", reason - , "expires", Long.toString(data.getExpires()) - )); - } + if (hook == null || shouldSkip(hook, silent) || hook.post().isEmpty()) return; + + executeCommands(hook.post(), ImmutableMap.of( + "from", data.fromIp().toString(), + "to", data.toIp().toString(), + "actor", actor.name(), + "reason", reason, + "expires", Long.toString(data.expires()) + )); } - public void onWarn(PlayerWarnData data, boolean pre) { - onWarn(data, pre, false); + private void onPlayerWarnPre(PlayerWarnEvent event) { + if (event.isCancelled()) return; + WarnRequest request = event.request(); + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = config.getHook("warn"); + if (hook == null || shouldSkip(hook, request.silent()) || hook.pre().isEmpty()) return; + + executeCommands(hook.pre(), ImmutableMap.of( + "player", nameOf(request.player()), + "playerId", String.valueOf(request.player()), + "actor", nameOf(request.actor()), + "reason", request.reason(), + "expires", Long.toString(request.expires()) + )); } - public void onWarn(PlayerWarnData data, boolean pre, boolean silent) { + public void onWarn(PlayerWarn data, boolean pre, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); final Hook hook = config.getHook("warn"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; + if (hook == null || shouldSkip(hook, silent)) return; List commands = pre ? hook.pre() : hook.post(); - - if (commands.size() != 0) { - executeCommands(commands, ImmutableMap.of( - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", data.getActor().getName() - , "reason", data.getReason() - , "expires", Long.toString(data.getExpires()) - )); - } - } - - public void onNote(PlayerNoteData data) { - onNote(data, false); + if (commands.isEmpty()) return; + + executeCommands(commands, ImmutableMap.of( + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", data.actor().name(), + "reason", data.reason(), + "expires", Long.toString(data.expires()) + )); } - public void onNote(PlayerNoteData data, boolean silent) { + public void onNote(PlayerNote data, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); final Hook hook = config.getHook("note"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; - - if (hook.post().size() != 0) { - executeCommands(hook.post(), ImmutableMap.of( - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", data.getActor().getName() - , "message", data.getMessage() - )); - } + if (hook == null || shouldSkip(hook, silent) || hook.post().isEmpty()) return; + + executeCommands(hook.post(), ImmutableMap.of( + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", data.actor().name(), + "message", data.message() + )); } - public void onReport(PlayerReportData data, boolean pre) { - onReport(data, pre, false); + private void onPlayerReportPre(PlayerReportEvent event) { + if (event.isCancelled()) return; + ReportRequest request = event.request(); + HooksConfig config = plugin.getConfig().getHooksConfig(); + final Hook hook = config.getHook("report"); + if (hook == null || hook.pre().isEmpty()) return; + + executeCommands(hook.pre(), ImmutableMap.of( + "id", "0", + "player", nameOf(request.player()), + "playerId", String.valueOf(request.player()), + "actor", nameOf(request.actor()), + "message", request.reason() + )); } - public void onReport(PlayerReportData data, boolean pre, boolean silent) { + public void onReport(PlayerReport data, boolean pre, boolean silent) { HooksConfig config = plugin.getConfig().getHooksConfig(); final Hook hook = config.getHook("report"); - - if (hook == null) return; - if (shouldSkip(hook, silent)) return; + if (hook == null || shouldSkip(hook, silent)) return; List commands = pre ? hook.pre() : hook.post(); + if (commands.isEmpty()) return; + + executeCommands(commands, ImmutableMap.of( + "id", String.valueOf(data.id()), + "player", data.player().name(), + "playerId", data.player().uuid().toString(), + "actor", data.actor().name(), + "message", data.reason() + )); + } + + private boolean shouldSkip(Hook hook, boolean silent) { + return silent && hook.ignoreSilent(); + } - if (commands.size() != 0) { - executeCommands(commands, ImmutableMap.of( - "id", String.valueOf(data.getId()), - "player", data.getPlayer().getName() - , "playerId", data.getPlayer().getUUID().toString() - , "actor", data.getActor().getName() - , "message", data.getReason() - )); + private String nameOf(UUID uuid) { + if (uuid == null) return ""; + try { + PlayerData data = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(uuid)); + return data != null ? data.getName() : uuid.toString(); + } catch (SQLException e) { + return uuid.toString(); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java index 376130c05..26fa58868 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java @@ -1,11 +1,13 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.event.player.PlayerDeniedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.commands.NotesCommand; import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.google.guava.cache.Cache; import me.confuser.banmanager.common.google.guava.cache.CacheBuilder; +import me.confuser.banmanager.common.impl.IpAddressMapper; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.maxmind.db.model.CountryResponse; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; @@ -125,6 +127,7 @@ public void banCheck(UUID id, String name, IPAddress address, CommonJoinHandler message.set("actor", ipRangeBan.getActor().getName()); message.set("created", DateUtils.format(dateTimeFormat, ipRangeBan.getCreated())); + applyDeniedPlaceholders(publishDeniedEvent(id, name, address, PlayerDeniedEvent.Reason.IP_RANGE_BAN), message); handler.handleDeny(message); return; } @@ -160,6 +163,7 @@ public void banCheck(UUID id, String name, IPAddress address, CommonJoinHandler message.set("actor", ipBan.getActor().getName()); message.set("created", DateUtils.format(dateTimeFormat, ipBan.getCreated())); + applyDeniedPlaceholders(publishDeniedEvent(id, name, address, PlayerDeniedEvent.Reason.IP_BAN), message); handler.handleDeny(message); handleJoinDeny(address.toString(), ipBan.getActor(), ipBan.getReason()); return; @@ -196,6 +200,7 @@ public void banCheck(UUID id, String name, IPAddress address, CommonJoinHandler message.set("actor", nameBan.getActor().getName()); message.set("created", DateUtils.format(dateTimeFormat, nameBan.getCreated())); + applyDeniedPlaceholders(publishDeniedEvent(id, name, address, PlayerDeniedEvent.Reason.NAME_BAN), message); handler.handleDeny(message); return; } @@ -236,10 +241,22 @@ public void banCheck(UUID id, String name, IPAddress address, CommonJoinHandler message.set("actor", data.getActor().getName()); message.set("created", DateUtils.format(dateTimeFormat != null ? dateTimeFormat : "dd-MM-yyyy kk:mm:ss", data.getCreated())); + applyDeniedPlaceholders(publishDeniedEvent(id, name, address, PlayerDeniedEvent.Reason.PLAYER_BAN), message); handler.handlePlayerDeny(data.getPlayer(), message); handleJoinDeny(data.getPlayer(), data.getActor(), data.getReason()); } + private PlayerDeniedEvent publishDeniedEvent(UUID id, String name, IPAddress address, PlayerDeniedEvent.Reason reason) { + java.util.Optional uuid = java.util.Optional.ofNullable(id); + java.util.Optional apiAddress = java.util.Optional.ofNullable(IpAddressMapper.toApi(address)); + return plugin.getEventBus().publish(new PlayerDeniedEvent(uuid, name, apiAddress, reason)); + } + + private static void applyDeniedPlaceholders(PlayerDeniedEvent event, Message message) { + if (event.placeholders().isEmpty()) return; + event.placeholders().forEach(message::set); + } + public void onPreJoin(UUID id, String name, IPAddress address) { PlayerData player = new PlayerData(id, name, address); diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java index 59596a10f..95b6a7b0d 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java @@ -1,10 +1,13 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.event.ip.IpMutedEvent; +import me.confuser.banmanager.api.event.player.PlayerMutedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; -import me.confuser.banmanager.common.data.IpMuteData; import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.data.PlayerMuteData; +import me.confuser.banmanager.common.impl.IpAddressMapper; import me.confuser.banmanager.common.util.DateUtils; import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.util.NotificationUtils; @@ -12,52 +15,54 @@ import java.util.List; public class CommonMuteListener { - private BanManagerPlugin plugin; + private final BanManagerPlugin plugin; public CommonMuteListener(BanManagerPlugin plugin) { this.plugin = plugin; + plugin.getEventBus().subscribe(PlayerMutedEvent.class, e -> notifyOnMute(e.mute(), e.silent())); + plugin.getEventBus().subscribe(IpMutedEvent.class, e -> notifyOnMute(e.mute(), e.silent())); } - public void notifyOnMute(PlayerMuteData data, boolean silent) { + public void notifyOnMute(PlayerMute data, boolean silent) { String broadcastPermission; String event; Message message; - if (data.getExpires() == 0 && !data.isOnlineOnly()) { + if (data.expires() == 0 && !data.onlineOnly()) { broadcastPermission = "bm.notify.mute"; event = "mute"; message = Message.get("mute.notify"); - } else if (data.isOnlineOnly()) { + } else if (data.onlineOnly()) { broadcastPermission = "bm.notify.tempmute"; event = "tempmute"; message = Message.get("tempmute.notifyOnline"); if (data.isPaused()) { - message.set("expires", DateUtils.formatDifference(data.getPausedRemaining())); + message.set("expires", DateUtils.formatDifference(data.pausedRemaining())); } else { - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } } else { broadcastPermission = "bm.notify.tempmute"; event = "tempmute"; message = Message.get("tempmute.notify"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } message - .set("id", data.getId()) - .set("player", data.getPlayer().getName()) - .set("playerId", data.getPlayer().getUUID().toString()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()); + .set("id", data.id()) + .set("player", data.player().name()) + .set("playerId", data.player().uuid().toString()) + .set("actor", data.actor().name()) + .set("reason", data.reason()); if (!silent) { NotificationUtils.notifyStaff(plugin, event, message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; @@ -68,12 +73,12 @@ public void notifyOnMute(PlayerMuteData data, boolean silent) { } } - public void notifyOnMute(IpMuteData data, boolean silent) { + public void notifyOnMute(IpMute data, boolean silent) { String broadcastPermission; String event; Message message; - if (data.getExpires() == 0) { + if (data.expires() == 0) { broadcastPermission = "bm.notify.muteip"; event = "mute"; message = Message.get("muteip.notify"); @@ -81,10 +86,12 @@ public void notifyOnMute(IpMuteData data, boolean silent) { broadcastPermission = "bm.notify.tempmuteip"; event = "tempmute"; message = Message.get("tempmuteip.notify"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.expires())); } - List players = plugin.getPlayerStorage().getDuplicatesInTime(data.getIp(), plugin.getConfig().getTimeAssociatedAlts()); + me.confuser.banmanager.common.ipaddr.IPAddress internalIp = IpAddressMapper.toInternal(data.ip()); + List players = plugin.getPlayerStorage().getDuplicatesInTime(internalIp, + plugin.getConfig().getTimeAssociatedAlts()); StringBuilder playerNames = new StringBuilder(); for (PlayerData player : players) { @@ -96,20 +103,20 @@ public void notifyOnMute(IpMuteData data, boolean silent) { if (playerNames.length() >= 2) playerNames.setLength(playerNames.length() - 2); message - .set("id", data.getId()) - .set("ip", data.getIp().toString()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()) + .set("id", data.id()) + .set("ip", data.ip().toString()) + .set("actor", data.actor().name()) + .set("reason", data.reason()) .set("players", playerNames.toString()); if (!silent) { NotificationUtils.notifyStaff(plugin, event, message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java index 2771b6c8a..00668a029 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java @@ -1,41 +1,41 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.event.player.PlayerNoteCreatedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; -import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.util.Message; public class CommonNoteListener { - private BanManagerPlugin plugin; + private final BanManagerPlugin plugin; public CommonNoteListener(BanManagerPlugin plugin) { this.plugin = plugin; + plugin.getEventBus().subscribe(PlayerNoteCreatedEvent.class, e -> notifyOnNote(e.note(), false)); } - public void notifyOnNote(PlayerNoteData data) { + public void notifyOnNote(PlayerNote data) { notifyOnNote(data, false); } - public void notifyOnNote(PlayerNoteData data, boolean silent) { + public void notifyOnNote(PlayerNote data, boolean silent) { final String broadcastPermission = "bm.notify.notes"; Message message = Message.get("notes.notify"); - message.set("player", data.getPlayer().getName()) - .set("playerId", data.getPlayer().getUUID().toString()) - .set("actor", data.getActor().getName()) - .set("id", data.getId()) - .set("message", data.getMessage()); + message.set("player", data.player().name()) + .set("playerId", data.player().uuid().toString()) + .set("actor", data.actor().name()) + .set("id", data.id()) + .set("message", data.message()); if (!silent) { plugin.getServer().broadcast(message, broadcastPermission); - } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { + } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.actor().uuid())) { plugin.getServer().getConsoleSender().sendMessage(message); return; } - // Check if the sender is online and does not have the - // broadcastPermission - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java index 11c3db18f..a88696ec2 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java @@ -1,34 +1,36 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.event.player.PlayerReportDeletedEvent; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; -import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; import me.confuser.banmanager.common.util.Message; import java.sql.SQLException; public class CommonReportListener { - private BanManagerPlugin plugin; + private final BanManagerPlugin plugin; public CommonReportListener(BanManagerPlugin plugin) { this.plugin = plugin; + plugin.getEventBus().subscribe(PlayerReportedEvent.class, e -> notifyOnReport(e.report())); + plugin.getEventBus().subscribe(PlayerReportDeletedEvent.class, e -> deleteReferences(e.report().id())); } - public void notifyOnReport(PlayerReportData data) { + public void notifyOnReport(PlayerReport data) { Message message = Message.get("report.notify"); - message.set("player", data.getPlayer().getName()) - .set("playerId", data.getPlayer().getUUID().toString()) - .set("actor", data.getActor().getName()) - .set("reason", data.getReason()) - .set("id", data.getId()); + message.set("player", data.player().name()) + .set("playerId", data.player().uuid().toString()) + .set("actor", data.actor().name()) + .set("reason", data.reason()) + .set("id", data.id()); plugin.getServer().broadcast(message, "bm.notify.report"); - // Check if the sender is online and does not have the - // broadcastPermission - CommonPlayer player = plugin.getServer().getPlayer(data.getActor().getUUID()); + CommonPlayer player = plugin.getServer().getPlayer(data.actor().uuid()); if (player == null || !player.isOnline()) { return; @@ -39,20 +41,18 @@ public void notifyOnReport(PlayerReportData data) { } } - public void deleteReferences(PlayerReportData data) { - int id = data.getId(); - + public void deleteReferences(int reportId) { try { DeleteBuilder location = plugin.getPlayerReportLocationStorage().deleteBuilder(); - location.where().eq("report_id", id); + location.where().eq("report_id", reportId); location.delete(); DeleteBuilder commands = plugin.getPlayerReportCommandStorage().deleteBuilder(); - commands.where().eq("report_id", id); + commands.where().eq("report_id", reportId); commands.delete(); DeleteBuilder comments = plugin.getPlayerReportCommentStorage().deleteBuilder(); - comments.where().eq("report_id", id); + comments.where().eq("report_id", reportId); comments.delete(); } catch (SQLException e) { plugin.getLogger().warning("Failed to process report", e); diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonWebhookListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonWebhookListener.java index b5e14b49a..6960133ec 100644 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonWebhookListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonWebhookListener.java @@ -1,28 +1,60 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.event.ip.IpBannedEvent; +import me.confuser.banmanager.api.event.ip.IpUnbannedEvent; +import me.confuser.banmanager.api.event.player.PlayerBannedEvent; +import me.confuser.banmanager.api.event.player.PlayerKickedEvent; +import me.confuser.banmanager.api.event.player.PlayerMutedEvent; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; +import me.confuser.banmanager.api.event.player.PlayerUnbannedEvent; +import me.confuser.banmanager.api.event.player.PlayerUnmutedEvent; +import me.confuser.banmanager.api.event.player.PlayerWarnedEvent; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.data.*; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerReportLocationData; +import me.confuser.banmanager.common.data.Webhook; +import me.confuser.banmanager.common.impl.IpAddressMapper; import me.confuser.banmanager.common.util.DateUtils; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import java.net.URI; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.format.DateTimeFormatter; import java.sql.SQLException; +import java.time.Duration; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class CommonWebhookListener { - private BanManagerPlugin plugin; + private final BanManagerPlugin plugin; public CommonWebhookListener(BanManagerPlugin plugin) { this.plugin = plugin; + plugin.getEventBus().subscribe(PlayerBannedEvent.class, e -> sendAll(notifyOnBan(e.ban()), e.silent())); + plugin.getEventBus().subscribe(PlayerMutedEvent.class, e -> sendAll(notifyOnMute(e.mute()), e.silent())); + plugin.getEventBus().subscribe(IpBannedEvent.class, e -> sendAll(notifyOnBan(e.ban()), e.silent())); + plugin.getEventBus().subscribe(PlayerKickedEvent.class, + e -> sendAll(notifyOnKick(e.id(), e.player(), e.actor(), e.reason(), e.created()), e.silent())); + plugin.getEventBus().subscribe(PlayerWarnedEvent.class, e -> sendAll(notifyOnWarn(e.warn()), e.silent())); + plugin.getEventBus().subscribe(PlayerUnbannedEvent.class, + e -> sendAll(notifyOnUnban(e.ban(), e.actor(), e.reason()), e.silent())); + plugin.getEventBus().subscribe(IpUnbannedEvent.class, + e -> sendAll(notifyOnUnban(e.ban(), e.actor(), e.reason()), e.silent())); + plugin.getEventBus().subscribe(PlayerUnmutedEvent.class, + e -> sendAll(notifyOnUnmute(e.mute(), e.actor(), e.reason()), e.silent())); + plugin.getEventBus().subscribe(PlayerReportedEvent.class, + e -> sendAll(notifyOnReport(e.report(), e.report().actor(), e.report().reason()), false)); } private String toISO8601(long timestamp) { @@ -30,31 +62,32 @@ private String toISO8601(long timestamp) { .format(java.time.Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault())); } - public List notifyOnBan(PlayerBanData ban) { - String type = ban.getExpires() == 0 ? "ban" : "tempban"; + public List notifyOnBan(PlayerBan ban) { + String type = ban.expires() == 0 ? "ban" : "tempban"; List hooks = plugin.getWebhookConfig().getHooks(type); Map replacements = new HashMap<>(); - replacements.put("[player]", ban.getPlayer().getName()); - replacements.put("[playerId]", ban.getPlayer().getUUID().toString()); - replacements.put("[actor]", ban.getActor().getName()); - replacements.put("[actorId]", ban.getActor().getUUID().toString()); - replacements.put("[id]", String.valueOf(ban.getId())); - replacements.put("[created]", toISO8601(ban.getCreated())); - replacements.put("[reason]", ban.getReason()); - - if (ban.getExpires() != 0) { - replacements.put("[expires]", DateUtils.getDifferenceFormat(ban.getExpires())); + replacements.put("[player]", ban.player().name()); + replacements.put("[playerId]", ban.player().uuid().toString()); + replacements.put("[actor]", ban.actor().name()); + replacements.put("[actorId]", ban.actor().uuid().toString()); + replacements.put("[id]", String.valueOf(ban.id())); + replacements.put("[created]", toISO8601(ban.created())); + replacements.put("[reason]", ban.reason()); + + if (ban.expires() != 0) { + replacements.put("[expires]", DateUtils.getDifferenceFormat(ban.expires())); } return resolve(hooks, replacements); } - public List notifyOnBan(IpBanData ban) { - String type = ban.getExpires() == 0 ? "banip" : "tempbanip"; + public List notifyOnBan(IpBan ban) { + String type = ban.expires() == 0 ? "banip" : "tempbanip"; List hooks = plugin.getWebhookConfig().getHooks(type); - List players = plugin.getPlayerStorage().getDuplicatesInTime(ban.getIp(), + me.confuser.banmanager.common.ipaddr.IPAddress internalIp = IpAddressMapper.toInternal(ban.ip()); + List players = plugin.getPlayerStorage().getDuplicatesInTime(internalIp, plugin.getConfig().getTimeAssociatedAlts()); StringBuilder playerNames = new StringBuilder(); @@ -67,145 +100,145 @@ public List notifyOnBan(IpBanData ban) { playerNames.setLength(playerNames.length() - 2); Map replacements = new HashMap<>(); - replacements.put("[ip]", ban.getIp().toString()); - replacements.put("[actor]", ban.getActor().getName()); - replacements.put("[actorId]", ban.getActor().getUUID().toString()); - replacements.put("[reason]", ban.getReason()); - replacements.put("[created]", toISO8601(ban.getCreated())); + replacements.put("[ip]", ban.ip().toString()); + replacements.put("[actor]", ban.actor().name()); + replacements.put("[actorId]", ban.actor().uuid().toString()); + replacements.put("[reason]", ban.reason()); + replacements.put("[created]", toISO8601(ban.created())); replacements.put("[players]", playerNames.toString()); - if (ban.getExpires() != 0) { - replacements.put("[expires]", DateUtils.getDifferenceFormat(ban.getExpires())); + if (ban.expires() != 0) { + replacements.put("[expires]", DateUtils.getDifferenceFormat(ban.expires())); } return resolve(hooks, replacements); } - public List notifyOnKick(PlayerKickData kick) { + public List notifyOnKick(int id, Player player, Player actor, String reason, long created) { List hooks = plugin.getWebhookConfig().getHooks("kick"); Map replacements = new HashMap<>(); - replacements.put("[player]", kick.getPlayer().getName()); - replacements.put("[playerId]", kick.getPlayer().getUUID().toString()); - replacements.put("[actor]", kick.getActor().getName()); - replacements.put("[actorId]", kick.getActor().getUUID().toString()); - replacements.put("[id]", String.valueOf(kick.getId())); - replacements.put("[created]", toISO8601(kick.getCreated())); - replacements.put("[reason]", kick.getReason()); + replacements.put("[player]", player.name()); + replacements.put("[playerId]", player.uuid().toString()); + replacements.put("[actor]", actor.name()); + replacements.put("[actorId]", actor.uuid().toString()); + replacements.put("[id]", String.valueOf(id)); + replacements.put("[created]", toISO8601(created)); + replacements.put("[reason]", reason); return resolve(hooks, replacements); } - public List notifyOnMute(PlayerMuteData mute) { - String type = mute.getExpires() == 0 ? "mute" : "tempmute"; + public List notifyOnMute(PlayerMute mute) { + String type = mute.expires() == 0 ? "mute" : "tempmute"; List hooks = plugin.getWebhookConfig().getHooks(type); Map replacements = new HashMap<>(); - replacements.put("[player]", mute.getPlayer().getName()); - replacements.put("[playerId]", mute.getPlayer().getUUID().toString()); - replacements.put("[actor]", mute.getActor().getName()); - replacements.put("[actorId]", mute.getActor().getUUID().toString()); - replacements.put("[id]", String.valueOf(mute.getId())); - replacements.put("[created]", toISO8601(mute.getCreated())); - replacements.put("[reason]", mute.getReason()); - - if (mute.getExpires() != 0) { - replacements.put("[expires]", DateUtils.getDifferenceFormat(mute.getExpires())); + replacements.put("[player]", mute.player().name()); + replacements.put("[playerId]", mute.player().uuid().toString()); + replacements.put("[actor]", mute.actor().name()); + replacements.put("[actorId]", mute.actor().uuid().toString()); + replacements.put("[id]", String.valueOf(mute.id())); + replacements.put("[created]", toISO8601(mute.created())); + replacements.put("[reason]", mute.reason()); + + if (mute.expires() != 0) { + replacements.put("[expires]", DateUtils.getDifferenceFormat(mute.expires())); } return resolve(hooks, replacements); } - public List notifyOnWarn(PlayerWarnData warn) { - String type = warn.getExpires() == 0 ? "warning" : "tempwarning"; + public List notifyOnWarn(PlayerWarn warn) { + String type = warn.expires() == 0 ? "warning" : "tempwarning"; List hooks = plugin.getWebhookConfig().getHooks(type); Map replacements = new HashMap<>(); - replacements.put("[player]", warn.getPlayer().getName()); - replacements.put("[playerId]", warn.getPlayer().getUUID().toString()); - replacements.put("[actor]", warn.getActor().getName()); - replacements.put("[actorId]", warn.getActor().getUUID().toString()); - replacements.put("[id]", String.valueOf(warn.getId())); - replacements.put("[created]", toISO8601(warn.getCreated())); - replacements.put("[points]", String.valueOf(warn.getPoints())); - replacements.put("[reason]", warn.getReason()); - - if (warn.getExpires() != 0) { - replacements.put("[expires]", DateUtils.getDifferenceFormat(warn.getExpires())); + replacements.put("[player]", warn.player().name()); + replacements.put("[playerId]", warn.player().uuid().toString()); + replacements.put("[actor]", warn.actor().name()); + replacements.put("[actorId]", warn.actor().uuid().toString()); + replacements.put("[id]", String.valueOf(warn.id())); + replacements.put("[created]", toISO8601(warn.created())); + replacements.put("[points]", String.valueOf(warn.points())); + replacements.put("[reason]", warn.reason()); + + if (warn.expires() != 0) { + replacements.put("[expires]", DateUtils.getDifferenceFormat(warn.expires())); } return resolve(hooks, replacements); } - public List notifyOnUnban(PlayerBanData ban, PlayerData actor, String reason) { + public List notifyOnUnban(PlayerBan ban, Player actor, String reason) { List hooks = plugin.getWebhookConfig().getHooks("unban"); Map replacements = new HashMap<>(); - replacements.put("[player]", ban.getPlayer().getName()); - replacements.put("[playerId]", ban.getPlayer().getUUID().toString()); - replacements.put("[actor]", actor.getName()); - replacements.put("[actorId]", actor.getUUID().toString()); - replacements.put("[id]", String.valueOf(ban.getId())); - replacements.put("[created]", toISO8601(ban.getCreated())); + replacements.put("[player]", ban.player().name()); + replacements.put("[playerId]", ban.player().uuid().toString()); + replacements.put("[actor]", actor.name()); + replacements.put("[actorId]", actor.uuid().toString()); + replacements.put("[id]", String.valueOf(ban.id())); + replacements.put("[created]", toISO8601(ban.created())); replacements.put("[reason]", reason); return resolve(hooks, replacements); } - public List notifyOnUnban(IpBanData ban, PlayerData actor, String reason) { + public List notifyOnUnban(IpBan ban, Player actor, String reason) { List hooks = plugin.getWebhookConfig().getHooks("unbanip"); Map replacements = new HashMap<>(); - replacements.put("[ip]", ban.getIp().toString()); - replacements.put("[actor]", actor.getName()); - replacements.put("[actorId]", actor.getUUID().toString()); - replacements.put("[id]", String.valueOf(ban.getId())); - replacements.put("[created]", toISO8601(ban.getCreated())); + replacements.put("[ip]", ban.ip().toString()); + replacements.put("[actor]", actor.name()); + replacements.put("[actorId]", actor.uuid().toString()); + replacements.put("[id]", String.valueOf(ban.id())); + replacements.put("[created]", toISO8601(ban.created())); replacements.put("[reason]", reason); return resolve(hooks, replacements); } - public List notifyOnUnmute(PlayerMuteData mute, PlayerData actor, String reason) { + public List notifyOnUnmute(PlayerMute mute, Player actor, String reason) { List hooks = plugin.getWebhookConfig().getHooks("unmute"); Map replacements = new HashMap<>(); - replacements.put("[player]", mute.getPlayer().getName()); - replacements.put("[playerId]", mute.getPlayer().getUUID().toString()); - replacements.put("[actor]", actor.getName()); - replacements.put("[actorId]", actor.getUUID().toString()); - replacements.put("[id]", String.valueOf(mute.getId())); - replacements.put("[created]", toISO8601(mute.getCreated())); + replacements.put("[player]", mute.player().name()); + replacements.put("[playerId]", mute.player().uuid().toString()); + replacements.put("[actor]", actor.name()); + replacements.put("[actorId]", actor.uuid().toString()); + replacements.put("[id]", String.valueOf(mute.id())); + replacements.put("[created]", toISO8601(mute.created())); replacements.put("[reason]", reason); return resolve(hooks, replacements); } - public List notifyOnReport(PlayerReportData report, PlayerData actor, String reason) { + public List notifyOnReport(PlayerReport report, Player actor, String reason) { List hooks = plugin.getWebhookConfig().getHooks("report"); List locations = null; try { - locations = plugin.getPlayerReportLocationStorage().getByReport(report); + locations = plugin.getPlayerReportLocationStorage().getAllByReportId(report.id()); } catch (SQLException e) { plugin.getLogger().warning("Failed to load report locations for webhook", e); } Map replacements = new HashMap<>(); - replacements.put("[player]", report.getPlayer().getName()); - replacements.put("[playerId]", report.getPlayer().getUUID().toString()); - replacements.put("[actor]", actor.getName()); - replacements.put("[actorId]", actor.getUUID().toString()); - replacements.put("[id]", String.valueOf(report.getId())); - replacements.put("[created]", toISO8601(report.getCreated())); + replacements.put("[player]", report.player().name()); + replacements.put("[playerId]", report.player().uuid().toString()); + replacements.put("[actor]", actor.name()); + replacements.put("[actorId]", actor.uuid().toString()); + replacements.put("[id]", String.valueOf(report.id())); + replacements.put("[created]", toISO8601(report.created())); replacements.put("[reason]", reason); - if (locations != null && locations.size() > 0) { + if (locations != null && !locations.isEmpty()) { PlayerReportLocationData playerLocation = null; PlayerReportLocationData actorLocation = null; for (PlayerReportLocationData location : locations) { - if (location.getPlayer().equals(actor)) { + if (location.getPlayer() != null && location.getPlayer().getUUID().equals(actor.uuid())) { actorLocation = location; } else { playerLocation = location; @@ -260,6 +293,14 @@ private String applyReplacements(String input, Map replacements) return result; } + private void sendAll(List webhooks, boolean isSilent) { + for (Webhook data : webhooks) { + if (isSilent && data.ignoreSilent()) continue; + if (data.url() == null || data.payload() == null || data.url().isEmpty() || data.payload().isEmpty()) continue; + sendAsync(data); + } + } + public void sendAsync(Webhook data) { if (plugin.getConfig().isDebugEnabled()) { plugin.getLogger().info("Sending webhook '" + data.name() + "' to " + data.url() + " with method " + data.method()); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/HistoryStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/HistoryStorage.java index 7b0656d15..85849fada 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/HistoryStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/HistoryStorage.java @@ -20,6 +20,14 @@ public class HistoryStorage { " ( {QUERIES}" + " ) subquery" + " ORDER BY created DESC"; + /** + * Same UNION as {@link #playerSql} but wrapped in a COUNT so the API + * pagination layer can report a real {@code total} without buffering + * every row. + */ + private final String countSql = "SELECT COUNT(*) FROM" + + " ( {QUERIES}" + + " ) subquery"; private BanManagerPlugin plugin; private final String banSql; @@ -104,16 +112,85 @@ public List getSince(PlayerData player, long since, InfoCommandPar } public List getAll(PlayerData player, InfoCommandParser parser) { + return getAllInternal( + player, + parser.isBans(), + parser.isMutes(), + parser.isKicks(), + parser.isNotes(), + parser.isReports(), + parser.isWarnings()); + } + + /** + * Type-flag overload used by the public {@code HistoryService} API so it + * does not need to fabricate a {@link InfoCommandParser} via reflection. + */ + public List getAll(PlayerData player, + boolean bans, boolean mutes, boolean kicks, + boolean notes, boolean reports, boolean warnings) { + return getAllInternal(player, bans, mutes, kicks, notes, reports, warnings); + } + + /** + * Paginated player history with {@code LIMIT}/{@code OFFSET} pushed into the + * UNION query so memory stays bounded regardless of history length. + * + * @param player target player + * @param page zero-indexed page + * @param size page size (must be {@code > 0}) + * @return ordered slice; never {@code null} + */ + public List getPaged(PlayerData player, + boolean bans, boolean mutes, boolean kicks, + boolean notes, boolean reports, boolean warnings, + int page, int size) { + StringBuilder unions = buildPlayerUnions(bans, mutes, kicks, notes, reports, warnings); + if (unions.length() == 0) return new ArrayList<>(); + + int typeCount = countActive(bans, mutes, kicks, notes, reports, warnings); + return executePagedQuery(player.getId(), SqlType.BYTE_ARRAY, unions, typeCount, page, size); + } + + /** + * Total row count for the same filter set as + * {@link #getPaged(PlayerData, boolean, boolean, boolean, boolean, boolean, boolean, int, int)}. + * Used to populate {@link me.confuser.banmanager.api.Page#total()} without + * paying the I/O cost of materialising every row. + */ + public long count(PlayerData player, + boolean bans, boolean mutes, boolean kicks, + boolean notes, boolean reports, boolean warnings) { + StringBuilder unions = buildPlayerUnions(bans, mutes, kicks, notes, reports, warnings); + if (unions.length() == 0) return 0L; + + int typeCount = countActive(bans, mutes, kicks, notes, reports, warnings); + return executeCount(player.getId(), SqlType.BYTE_ARRAY, unions, typeCount); + } + + private StringBuilder buildPlayerUnions(boolean bans, boolean mutes, boolean kicks, + boolean notes, boolean reports, boolean warnings) { StringBuilder unions = new StringBuilder(); - int typeCount = 0; + if (bans) unions.append(banSql).append(" UNION ALL "); + if (mutes) unions.append(muteSql).append(" UNION ALL "); + if (kicks) unions.append(kickSql).append(" UNION ALL "); + if (notes) unions.append(noteSql).append(" UNION ALL "); + if (reports) unions.append(reportSql).append(" UNION ALL "); + if (warnings) unions.append(warningSql).append(" UNION ALL "); + return unions; + } - if (parser.isBans()) { unions.append(banSql).append(" UNION ALL "); typeCount++; } - if (parser.isMutes()) { unions.append(muteSql).append(" UNION ALL "); typeCount++; } - if (parser.isKicks()) { unions.append(kickSql).append(" UNION ALL "); typeCount++; } - if (parser.isNotes()) { unions.append(noteSql).append(" UNION ALL "); typeCount++; } - if (parser.isReports()) { unions.append(reportSql).append(" UNION ALL "); typeCount++; } - if (parser.isWarnings()) { unions.append(warningSql).append(" UNION ALL "); typeCount++; } + private static int countActive(boolean... flags) { + int n = 0; + for (boolean f : flags) if (f) n++; + return n; + } + private List getAllInternal(PlayerData player, + boolean bans, boolean mutes, boolean kicks, + boolean notes, boolean reports, boolean warnings) { + StringBuilder unions = buildPlayerUnions(bans, mutes, kicks, notes, reports, warnings); + int typeCount = countActive(bans, mutes, kicks, notes, reports, warnings); return executeQuery(player.getId(), SqlType.BYTE_ARRAY, unions, typeCount); } @@ -187,4 +264,78 @@ private List executeQuery(Object paramValue, SqlType paramType, return null; } } + + private List executePagedQuery(Object paramValue, SqlType paramType, + StringBuilder unions, int typeCount, + int page, int size) { + if (typeCount == 0) return new ArrayList<>(); + + unions.setLength(unions.length() - 11); + long offset = (long) page * (long) size; + String sql = playerSql.replace("{QUERIES}", unions.toString()) + + " LIMIT " + size + " OFFSET " + offset; + + try (DatabaseConnection connection = plugin.getLocalConn().getReadOnlyConnection("")) { + CompiledStatement statement = connection.compileStatement( + sql, StatementBuilder.StatementType.SELECT, null, + DatabaseConnection.DEFAULT_RESULT_FLAGS, false); + List results = new ArrayList<>(); + try { + for (int i = 0; i < typeCount; i++) { + statement.setObject(i, paramValue, paramType); + } + + DatabaseResults dbResults = statement.runQuery(null); + try { + while (dbResults.next()) { + results.add(new HistoryEntry( + dbResults.getInt(0), + dbResults.getString(1), + dbResults.getString(2), + dbResults.getLong(3), + dbResults.getString(4), + dbResults.getString(5))); + } + } finally { + dbResults.closeQuietly(); + } + } finally { + try { statement.close(); } catch (Exception ignored) { } + } + return results; + } catch (Exception e) { + plugin.getLogger().warning("Failed to process history operation", e); + return null; + } + } + + private long executeCount(Object paramValue, SqlType paramType, + StringBuilder unions, int typeCount) { + if (typeCount == 0) return 0L; + + unions.setLength(unions.length() - 11); + String sql = countSql.replace("{QUERIES}", unions.toString()); + + try (DatabaseConnection connection = plugin.getLocalConn().getReadOnlyConnection("")) { + CompiledStatement statement = connection.compileStatement( + sql, StatementBuilder.StatementType.SELECT, null, + DatabaseConnection.DEFAULT_RESULT_FLAGS, false); + try { + for (int i = 0; i < typeCount; i++) { + statement.setObject(i, paramValue, paramType); + } + DatabaseResults dbResults = statement.runQuery(null); + try { + return dbResults.next() ? dbResults.getLong(0) : 0L; + } finally { + dbResults.closeQuietly(); + } + } finally { + try { statement.close(); } catch (Exception ignored) { } + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to count history rows", e); + return 0L; + } + } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java index 3bf1888b4..3cf29da00 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpBanStorage.java @@ -1,10 +1,15 @@ package me.confuser.banmanager.common.storage; import lombok.Getter; +import me.confuser.banmanager.api.event.ip.IpBanEvent; +import me.confuser.banmanager.api.event.ip.IpBannedEvent; +import me.confuser.banmanager.api.event.ip.IpUnbanEvent; +import me.confuser.banmanager.api.event.ip.IpUnbannedEvent; +import me.confuser.banmanager.api.request.IpBanRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.IpBanData; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ipaddr.AddressValueException; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; @@ -18,6 +23,7 @@ import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; import me.confuser.banmanager.common.util.IPUtils; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.util.TransactionHelper; import me.confuser.banmanager.common.util.UUIDUtils; @@ -143,7 +149,8 @@ public IpBanData getBan(InetAddress address) { public void addBan(IpBanData ban) { bans.put(ban.getIp().toString(), ban); - plugin.getServer().callEvent("IpBannedEvent", ban, ban.isSilent() || !plugin.getConfig().isBroadcastOnSync()); + boolean silent = ban.isSilent() || !plugin.getConfig().isBroadcastOnSync(); + plugin.getEventBus().publish(new IpBannedEvent(EntityMappers.ipBan(ban), silent)); } public void removeBan(IpBanData ban) { @@ -155,20 +162,37 @@ public void removeBan(IPAddress ip) { } public boolean ban(IpBanData ban) throws SQLException { - return ban(ban, false); + return ban(ban, false, null); } public boolean ban(IpBanData ban, boolean fromSync) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("IpBanEvent", ban, ban.isSilent()); + return ban(ban, fromSync, null); + } + + public boolean ban(IpBanData ban, boolean fromSync, Message kickMessage) throws SQLException { + IpBanRequest request = EntityMappers.ipBanRequest(ban); + IpBanEvent pre = new IpBanEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, ban); + create(ban); bans.put(ban.getIp().toString(), ban); - plugin.getServer().callEvent("IpBannedEvent", ban, event.isSilent() || (fromSync && !plugin.getConfig().isBroadcastOnSync())); + if (kickMessage != null) { + kickMessage.set("id", ban.getId()); + } + + boolean silent = request.silent() || (fromSync && !plugin.getConfig().isBroadcastOnSync()); + IpBannedEvent post = plugin.getEventBus().publish(new IpBannedEvent(EntityMappers.ipBan(ban), silent)); + + if (kickMessage != null && !post.placeholders().isEmpty()) { + post.placeholders().forEach(kickMessage::set); + } return true; } @@ -186,19 +210,33 @@ public boolean unban(IpBanData ban, PlayerData actor, String reason, boolean del } public boolean unban(IpBanData ban, PlayerData actor, String reason, boolean delete, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("IpUnbanEvent", ban, actor, reason, silent); - - if (event.isCancelled()) { + IpUnbanEvent pre = new IpUnbanEvent( + EntityMappers.ipBan(ban), + EntityMappers.player(actor), + reason, + silent); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + TransactionHelper.runInTransaction(connectionSource, () -> { delete(ban); - if (!delete) plugin.getIpBanRecordStorage().addRecord(ban, actor, reason); + if (!delete) plugin.getIpBanRecordStorage().addRecord(ban, actor, finalReason); }); bans.remove(ban.getIp().toString()); + plugin.getEventBus().publish(new IpUnbannedEvent( + EntityMappers.ipBan(ban), + EntityMappers.player(actor), + finalReason, + finalSilent)); + return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java index 00da52a7f..226206d7f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpMuteStorage.java @@ -1,9 +1,14 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.ip.IpMuteEvent; +import me.confuser.banmanager.api.event.ip.IpMutedEvent; +import me.confuser.banmanager.api.event.ip.IpUnmuteEvent; +import me.confuser.banmanager.api.event.ip.IpUnmutedEvent; +import me.confuser.banmanager.api.request.IpMuteRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.IpMuteData; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ipaddr.AddressValueException; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; @@ -144,7 +149,8 @@ public IpMuteData getMute(InetAddress address) { public void addMute(IpMuteData mute) { mutes.put(mute.getIp().toString(), mute); - plugin.getServer().callEvent("IpMutedEvent", mute, mute.isSilent() || !plugin.getConfig().isBroadcastOnSync()); + boolean silent = mute.isSilent() || !plugin.getConfig().isBroadcastOnSync(); + plugin.getEventBus().publish(new IpMutedEvent(EntityMappers.ipMute(mute), silent)); } public void removeMute(IpMuteData mute) { @@ -156,16 +162,20 @@ public void removeMute(IPAddress ip) { } public boolean mute(IpMuteData mute) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("IpMuteEvent", mute, mute.isSilent()); + IpMuteRequest request = EntityMappers.ipMuteRequest(mute); + IpMuteEvent pre = new IpMuteEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, mute); + create(mute); mutes.put(mute.getIp().toString(), mute); - plugin.getServer().callEvent("IpMutedEvent", mute, event.isSilent()); + plugin.getEventBus().publish(new IpMutedEvent(EntityMappers.ipMute(mute), request.silent())); return true; } @@ -179,19 +189,33 @@ public boolean unmute(IpMuteData mute, PlayerData actor, String reason) throws S } public boolean unmute(IpMuteData mute, PlayerData actor, String reason, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("IpUnmutedEvent", mute, actor, reason, silent); - - if (event.isCancelled()) { + IpUnmuteEvent pre = new IpUnmuteEvent( + EntityMappers.ipMute(mute), + EntityMappers.player(actor), + reason, + silent); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + TransactionHelper.runInTransaction(connectionSource, () -> { delete(mute); - plugin.getIpMuteRecordStorage().addRecord(mute, actor, reason); + plugin.getIpMuteRecordStorage().addRecord(mute, actor, finalReason); }); mutes.remove(mute.getIp().toString()); + plugin.getEventBus().publish(new IpUnmutedEvent( + EntityMappers.ipMute(mute), + EntityMappers.player(actor), + finalReason, + finalSilent)); + return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java index 4acd2b3b7..f312ab5eb 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/IpRangeBanStorage.java @@ -1,9 +1,14 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.ip.IpRangeBanEvent; +import me.confuser.banmanager.api.event.ip.IpRangeBannedEvent; +import me.confuser.banmanager.api.event.ip.IpRangeUnbanEvent; +import me.confuser.banmanager.api.event.ip.IpRangeUnbannedEvent; +import me.confuser.banmanager.api.request.IpRangeBanRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.IpRangeBanData; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.google.guava.collect.Range; import me.confuser.banmanager.common.google.guava.collect.TreeRangeSet; import me.confuser.banmanager.common.ipaddr.AddressValueException; @@ -19,6 +24,7 @@ import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; import me.confuser.banmanager.common.util.IPUtils; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.util.TransactionHelper; import me.confuser.banmanager.common.util.UUIDUtils; @@ -189,7 +195,8 @@ public void addBan(IpRangeBanData ban) { } bans.put(range, ban); - plugin.getServer().callEvent("IpRangeBannedEvent", ban, ban.isSilent() || !plugin.getConfig().isBroadcastOnSync()); + boolean silent = ban.isSilent() || !plugin.getConfig().isBroadcastOnSync(); + plugin.getEventBus().publish(new IpRangeBannedEvent(EntityMappers.ipRangeBan(ban), silent)); } public void removeBan(IpRangeBanData ban) { @@ -204,12 +211,20 @@ public void removeBan(Range range) { } public boolean ban(IpRangeBanData ban) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("IpRangeBanEvent", ban, ban.isSilent()); + return ban(ban, null); + } + + public boolean ban(IpRangeBanData ban, Message kickMessage) throws SQLException { + IpRangeBanRequest request = EntityMappers.ipRangeBanRequest(ban); + IpRangeBanEvent pre = new IpRangeBanEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, ban); + create(ban); Range range = Range.closed(ban.getFromIp(), ban.getToIp()); @@ -218,7 +233,15 @@ public boolean ban(IpRangeBanData ban) throws SQLException { ranges.add(range); } - plugin.getServer().callEvent("IpRangeBannedEvent", ban, event.isSilent()); + if (kickMessage != null) { + kickMessage.set("id", ban.getId()); + } + + IpRangeBannedEvent post = plugin.getEventBus().publish(new IpRangeBannedEvent(EntityMappers.ipRangeBan(ban), request.silent())); + + if (kickMessage != null && !post.placeholders().isEmpty()) { + post.placeholders().forEach(kickMessage::set); + } return true; } @@ -232,15 +255,23 @@ public boolean unban(IpRangeBanData ban, PlayerData actor, String reason) throws } public boolean unban(IpRangeBanData ban, PlayerData actor, String reason, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("IpRangeUnbanEvent", ban, actor, reason, silent); - - if (event.isCancelled()) { + IpRangeUnbanEvent pre = new IpRangeUnbanEvent( + EntityMappers.ipRangeBan(ban), + EntityMappers.player(actor), + reason, + silent); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + TransactionHelper.runInTransaction(connectionSource, () -> { delete(ban); - plugin.getIpRangeBanRecordStorage().addRecord(ban, actor, reason); + plugin.getIpRangeBanRecordStorage().addRecord(ban, actor, finalReason); }); Range range = Range.closed(ban.getFromIp(), ban.getToIp()); @@ -249,6 +280,12 @@ public boolean unban(IpRangeBanData ban, PlayerData actor, String reason, boolea ranges.remove(range); } + plugin.getEventBus().publish(new IpRangeUnbannedEvent( + EntityMappers.ipRangeBan(ban), + EntityMappers.player(actor), + finalReason, + finalSilent)); + return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java index c107b7b39..47fc42750 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/NameBanStorage.java @@ -1,9 +1,14 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.name.NameBanEvent; +import me.confuser.banmanager.api.event.name.NameBannedEvent; +import me.confuser.banmanager.api.event.name.NameUnbanEvent; +import me.confuser.banmanager.api.event.name.NameUnbannedEvent; +import me.confuser.banmanager.api.request.NameBanRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.NameBanData; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ipaddr.AddressValueException; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; import me.confuser.banmanager.common.ormlite.stmt.QueryBuilder; @@ -16,6 +21,7 @@ import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; import me.confuser.banmanager.common.ormlite.table.TableUtils; import me.confuser.banmanager.common.util.IPUtils; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.util.TransactionHelper; import me.confuser.banmanager.common.util.UUIDUtils; @@ -138,16 +144,32 @@ public NameBanData getBan(String playerName) { } public boolean ban(NameBanData ban) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("NameBanEvent", ban, ban.isSilent()); + return ban(ban, null); + } + + public boolean ban(NameBanData ban, Message kickMessage) throws SQLException { + NameBanRequest request = EntityMappers.nameBanRequest(ban); + NameBanEvent pre = new NameBanEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, ban); + create(ban); bans.put(ban.getName().toLowerCase(), ban); - plugin.getServer().callEvent("NameBannedEvent", ban, event.isSilent()); + if (kickMessage != null) { + kickMessage.set("id", ban.getId()); + } + + NameBannedEvent post = plugin.getEventBus().publish(new NameBannedEvent(EntityMappers.nameBan(ban), request.silent())); + + if (kickMessage != null && !post.placeholders().isEmpty()) { + post.placeholders().forEach(kickMessage::set); + } return true; } @@ -165,19 +187,33 @@ public boolean unban(NameBanData ban, PlayerData actor, String reason, boolean d } public boolean unban(NameBanData ban, PlayerData actor, String reason, boolean delete, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("NameUnbanEvent", ban, actor, reason, silent); - - if (event.isCancelled()) { + NameUnbanEvent pre = new NameUnbanEvent( + EntityMappers.nameBan(ban), + EntityMappers.player(actor), + reason, + silent); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + TransactionHelper.runInTransaction(connectionSource, () -> { delete(ban); - if (!delete) plugin.getNameBanRecordStorage().addRecord(ban, actor, reason); + if (!delete) plugin.getNameBanRecordStorage().addRecord(ban, actor, finalReason); }); bans.remove(ban.getName().toLowerCase()); + plugin.getEventBus().publish(new NameUnbannedEvent( + EntityMappers.nameBan(ban), + EntityMappers.player(actor), + finalReason, + finalSilent)); + return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java index 13f790aee..84d17e8a2 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerBanStorage.java @@ -1,9 +1,14 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.player.PlayerBanEvent; +import me.confuser.banmanager.api.event.player.PlayerBannedEvent; +import me.confuser.banmanager.api.event.player.PlayerUnbanEvent; +import me.confuser.banmanager.api.event.player.PlayerUnbannedEvent; +import me.confuser.banmanager.api.request.BanRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.PlayerBanData; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ipaddr.AddressValueException; import me.confuser.banmanager.common.ipaddr.IPAddress; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; @@ -158,7 +163,8 @@ public PlayerBanData getBan(UUID uuid) { public void addBan(PlayerBanData ban) { bans.put(ban.getPlayer().getUUID(), ban); - plugin.getServer().callEvent("PlayerBannedEvent", ban, ban.isSilent() || !plugin.getConfig().isBroadcastOnSync()); + boolean silent = ban.isSilent() || !plugin.getConfig().isBroadcastOnSync(); + plugin.getEventBus().publish(new PlayerBannedEvent(EntityMappers.playerBan(ban), silent)); } public void removeBan(PlayerBanData ban) { @@ -188,12 +194,16 @@ public boolean ban(PlayerBanData ban, boolean fromSync) throws SQLException { } public boolean ban(PlayerBanData ban, boolean fromSync, Message kickMessage) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerBanEvent", ban, ban.isSilent()); + BanRequest request = EntityMappers.banRequest(ban); + PlayerBanEvent pre = new PlayerBanEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, ban); + create(ban); bans.put(ban.getPlayer().getUUID(), ban); @@ -201,7 +211,12 @@ public boolean ban(PlayerBanData ban, boolean fromSync, Message kickMessage) thr kickMessage.set("id", ban.getId()); } - plugin.getServer().callEvent("PlayerBannedEvent", ban, event.isSilent() || (fromSync && !plugin.getConfig().isBroadcastOnSync()), kickMessage); + boolean silent = request.silent() || (fromSync && !plugin.getConfig().isBroadcastOnSync()); + PlayerBannedEvent post = plugin.getEventBus().publish(new PlayerBannedEvent(EntityMappers.playerBan(ban), silent)); + + if (kickMessage != null && !post.placeholders().isEmpty()) { + post.placeholders().forEach(kickMessage::set); + } return true; } @@ -219,19 +234,33 @@ public boolean unban(PlayerBanData ban, PlayerData actor, String reason, boolean } public boolean unban(PlayerBanData ban, PlayerData actor, String reason, boolean delete, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerUnbanEvent", ban, actor, reason, silent); - - if (event.isCancelled()) { + PlayerUnbanEvent pre = new PlayerUnbanEvent( + EntityMappers.playerBan(ban), + EntityMappers.player(actor), + reason, + silent); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + TransactionHelper.runInTransaction(connectionSource, () -> { delete(ban); - if (!delete) plugin.getPlayerBanRecordStorage().addRecord(ban, actor, reason); + if (!delete) plugin.getPlayerBanRecordStorage().addRecord(ban, actor, finalReason); }); bans.remove(ban.getPlayer().getUUID()); + plugin.getEventBus().publish(new PlayerUnbannedEvent( + EntityMappers.playerBan(ban), + EntityMappers.player(actor), + finalReason, + finalSilent)); + return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java index 3c80a3254..f5bc59d83 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerHistoryStorage.java @@ -136,13 +136,44 @@ public boolean hasActiveSession(UUID uuid) { return activeSessions.containsKey(uuid); } + /** + * Legacy fixed-page-size accessor kept for callers (e.g. the Bukkit + * {@code /alts} command) that hard-coded a 10-row layout. New callers + * should prefer {@link #getSince(PlayerData, long, int, int)} which + * honours an explicit {@code size}. + */ public CloseableIterator getSince(PlayerData player, long since, int page) throws SQLException { - return queryBuilder().limit(10L).offset(10L * page) + return getSince(player, since, page, 10); + } + + /** + * Page through sessions for a player whose {@code join} time is at or + * after {@code since}. Uses {@code LIMIT}/{@code OFFSET} so the database + * does the slicing rather than the JVM. + * + * @param page zero-indexed page + * @param size page size; must be {@code > 0} + */ + public CloseableIterator getSince(PlayerData player, long since, int page, int size) throws SQLException { + if (size <= 0) throw new IllegalArgumentException("size must be > 0"); + if (page < 0) throw new IllegalArgumentException("page must be >= 0"); + return queryBuilder().limit((long) size).offset((long) size * page) .orderBy("join", false) .where().ge("join", since).and().eq("player_id", player) .iterator(); } + /** + * Total number of sessions for {@code player} on or after {@code since}. + * Used by the public {@code HistoryService} pagination layer to populate + * {@link me.confuser.banmanager.api.Page#total()}. + */ + public long countSince(PlayerData player, long since) throws SQLException { + return queryBuilder() + .where().ge("join", since).and().eq("player_id", player) + .countOf(); + } + /** * Get a summary of distinct names with aggregated first/last seen times. * firstSeen = MIN(join) across all sessions for that name @@ -252,7 +283,7 @@ public void purge(CleanUp cleanup) throws SQLException { if (cleanup.getDays() == 0) return; String table = getTableInfo().getTableName(); - String banTable = BanManagerPlugin.getInstance().getIpBanStorage() + String banTable = plugin.getIpBanStorage() .getTableInfo() .getTableName(); diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java index 6a071f3c8..10141a8a4 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerKickStorage.java @@ -1,9 +1,11 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.player.PlayerKickedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.configs.CleanUp; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerKickData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.table.DatabaseTableConfig; @@ -34,7 +36,13 @@ public PlayerKickStorage(BanManagerPlugin plugin, ConnectionSource connection, D public boolean addKick(PlayerKickData data, boolean isSilent) throws SQLException { if (create(data) != 1) return false; - plugin.getServer().callEvent("PlayerKickedEvent", data, isSilent); + plugin.getEventBus().publish(new PlayerKickedEvent( + data.getId(), + EntityMappers.player(data.getPlayer()), + EntityMappers.player(data.getActor()), + data.getReason(), + data.getCreated(), + isSilent)); return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java index e01d2455a..e00ad0781 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerMuteStorage.java @@ -1,9 +1,14 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.player.PlayerMuteEvent; +import me.confuser.banmanager.api.event.player.PlayerMutedEvent; +import me.confuser.banmanager.api.event.player.PlayerUnmuteEvent; +import me.confuser.banmanager.api.event.player.PlayerUnmutedEvent; +import me.confuser.banmanager.api.request.MuteRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerMuteData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ipaddr.AddressValueException; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; import me.confuser.banmanager.common.ormlite.stmt.QueryBuilder; @@ -173,7 +178,8 @@ public PlayerMuteData getMute(String playerName) { public void addMute(PlayerMuteData mute) { mutes.put(mute.getPlayer().getUUID(), mute); - plugin.getServer().callEvent("PlayerMutedEvent", mute, mute.isSilent() || !plugin.getConfig().isBroadcastOnSync()); + boolean silent = mute.isSilent() || !plugin.getConfig().isBroadcastOnSync(); + plugin.getEventBus().publish(new PlayerMutedEvent(EntityMappers.playerMute(mute), silent)); } /** @@ -191,16 +197,21 @@ public boolean mute(PlayerMuteData mute) throws SQLException { } public boolean mute(PlayerMuteData mute, boolean fromSync) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerMuteEvent", mute, mute.isSilent()); + MuteRequest request = EntityMappers.muteRequest(mute); + PlayerMuteEvent pre = new PlayerMuteEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, mute); + create(mute); mutes.put(mute.getPlayer().getUUID(), mute); - plugin.getServer().callEvent("PlayerMutedEvent", mute, event.isSilent() || (fromSync && !plugin.getConfig().isBroadcastOnSync())); + boolean silent = request.silent() || (fromSync && !plugin.getConfig().isBroadcastOnSync()); + plugin.getEventBus().publish(new PlayerMutedEvent(EntityMappers.playerMute(mute), silent)); return true; } @@ -226,12 +237,20 @@ public boolean unmute(PlayerMuteData mute, PlayerData actor, String reason, bool } public boolean unmute(PlayerMuteData mute, PlayerData actor, String reason, boolean skipRecord, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerUnmuteEvent", mute, actor, reason, silent); - - if (event.isCancelled()) { + PlayerUnmuteEvent pre = new PlayerUnmuteEvent( + EntityMappers.playerMute(mute), + EntityMappers.player(actor), + reason, + silent); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + int deleted = delete(mute); // Always remove from cache to prevent stale entries @@ -240,10 +259,16 @@ public boolean unmute(PlayerMuteData mute, PlayerData actor, String reason, bool if (deleted > 0) { if (!skipRecord) { - plugin.getPlayerMuteRecordStorage().addRecord(mute, actor, reason); + plugin.getPlayerMuteRecordStorage().addRecord(mute, actor, finalReason); } } + plugin.getEventBus().publish(new PlayerUnmutedEvent( + EntityMappers.playerMute(mute), + EntityMappers.player(actor), + finalReason, + finalSilent)); + // Return true if mute was deleted or if we at least cleared the cache // This ensures the player is no longer considered muted return true; @@ -260,12 +285,20 @@ public boolean unmute(PlayerMuteData mute, PlayerData actor, String reason, bool * @return true if the mute was deleted, false if it was already paused/modified or event was cancelled */ public boolean unmuteIfExpired(PlayerMuteData mute, PlayerData actor) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerUnmuteEvent", mute, actor, "", false); - - if (event.isCancelled()) { + PlayerUnmuteEvent pre = new PlayerUnmuteEvent( + EntityMappers.playerMute(mute), + EntityMappers.player(actor), + "", + false); + plugin.getEventBus().publish(pre); + + if (pre.isCancelled()) { return false; } + String finalReason = pre.reason(); + boolean finalSilent = pre.silent(); + final int[] rowsDeleted = {0}; TransactionHelper.runInTransaction(connectionSource, () -> { @@ -274,12 +307,17 @@ public boolean unmuteIfExpired(PlayerMuteData mute, PlayerData actor) throws SQL rowsDeleted[0] = executeRaw(deleteSql, String.valueOf(mute.getId())); if (rowsDeleted[0] > 0) { - plugin.getPlayerMuteRecordStorage().addRecord(mute, actor, ""); + plugin.getPlayerMuteRecordStorage().addRecord(mute, actor, finalReason); } }); if (rowsDeleted[0] > 0) { mutes.remove(mute.getPlayer().getUUID()); + plugin.getEventBus().publish(new PlayerUnmutedEvent( + EntityMappers.playerMute(mute), + EntityMappers.player(actor), + finalReason, + finalSilent)); return true; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java index 92c446fa1..30abb5d6d 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerNoteStorage.java @@ -1,9 +1,12 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.player.PlayerNoteCreatedEvent; +import me.confuser.banmanager.api.event.player.PlayerNoteEvent; +import me.confuser.banmanager.api.request.NoteRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerNoteData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; import me.confuser.banmanager.common.ormlite.support.ConnectionSource; @@ -39,9 +42,23 @@ public boolean addNote(PlayerNoteData data) throws SQLException { } public boolean addNote(PlayerNoteData data, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerNoteCreatedEvent", data, silent); + NoteRequest request = EntityMappers.noteRequest(data); + PlayerNoteEvent pre = new PlayerNoteEvent(request); + plugin.getEventBus().publish(pre); - return !event.isCancelled() && create(data) == 1; + if (pre.isCancelled()) { + return false; + } + + EntityMappers.applyTo(request, data); + + boolean created = create(data) == 1; + + if (created) { + plugin.getEventBus().publish(new PlayerNoteCreatedEvent(EntityMappers.playerNote(data))); + } + + return created; } public CloseableIterator getNotes(UUID uniqueId) throws SQLException { diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportLocationStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportLocationStorage.java index 4eae06396..984fc5eef 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportLocationStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportLocationStorage.java @@ -32,7 +32,11 @@ public PlayerReportLocationData getByReportId(int id) throws SQLException { } public List getByReport(PlayerReportData data) throws SQLException { - return queryBuilder().where().eq("report_id", data.getId()).query(); + return getAllByReportId(data.getId()); + } + + public List getAllByReportId(int id) throws SQLException { + return queryBuilder().where().eq("report_id", id).query(); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java index e8852c868..ea96dc419 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerReportStorage.java @@ -1,9 +1,13 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.player.PlayerReportDeletedEvent; +import me.confuser.banmanager.api.event.player.PlayerReportEvent; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; +import me.confuser.banmanager.api.request.ReportRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerReportData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; import me.confuser.banmanager.common.ormlite.stmt.QueryBuilder; import me.confuser.banmanager.common.ormlite.stmt.Where; @@ -34,15 +38,19 @@ public PlayerReportStorage(BanManagerPlugin plugin, ConnectionSource connection, } public boolean report(PlayerReportData data, boolean isSilent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerReportEvent", data, isSilent); + ReportRequest request = EntityMappers.reportRequest(data); + PlayerReportEvent pre = new PlayerReportEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, data); + if (create(data) != 1) return false; - plugin.getServer().callEvent("PlayerReportedEvent", data, isSilent); + plugin.getEventBus().publish(new PlayerReportedEvent(EntityMappers.playerReport(data))); return true; } @@ -79,17 +87,23 @@ public ReportList getReports(long page, int state) throws SQLException { return getReports(page, state, null); } - public int deleteAll(PlayerData player) throws SQLException { + public int deleteAll(PlayerData player, PlayerData actor) throws SQLException { List reports = queryForEq("player_id", player); if (reports.isEmpty()) return 0; - for (PlayerReportData report : reports) { - plugin.getServer().callEvent("PlayerReportDeletedEvent", report); - } - DeleteBuilder builder = deleteBuilder(); builder.where().eq("player_id", player); - return builder.delete(); + int deleted = builder.delete(); + + if (deleted > 0 && actor != null) { + for (PlayerReportData report : reports) { + plugin.getEventBus().publish(new PlayerReportDeletedEvent( + EntityMappers.playerReport(report), + EntityMappers.player(actor))); + } + } + + return deleted; } public boolean isRecentlyReported(PlayerData player, long cooldown) throws SQLException { @@ -104,30 +118,54 @@ public boolean isRecentlyReported(PlayerData player, long cooldown) throws SQLEx } public int deleteById(Integer id) throws SQLException { + return deleteById(id, null); + } + + public int deleteById(Integer id, PlayerData actor) throws SQLException { PlayerReportData report = queryForId(id); if (report == null) return 0; - plugin.getServer().callEvent("PlayerReportDeletedEvent", report); - super.deleteById(id); + if (actor != null) { + plugin.getEventBus().publish(new PlayerReportDeletedEvent( + EntityMappers.playerReport(report), + EntityMappers.player(actor))); + } + return 1; } public int deleteIds(Collection ids) throws SQLException { + return deleteIds(ids, null); + } + + public int deleteIds(Collection ids, PlayerData actor) throws SQLException { if (ids == null || ids.isEmpty()) return 0; - for (Integer id : ids) { - PlayerReportData report = queryForId(id); - if (report != null) { - plugin.getServer().callEvent("PlayerReportDeletedEvent", report); + List reports = null; + if (actor != null) { + reports = new java.util.ArrayList<>(ids.size()); + for (Integer id : ids) { + PlayerReportData report = queryForId(id); + if (report != null) reports.add(report); } } DeleteBuilder builder = deleteBuilder(); builder.where().in("id", ids); - return builder.delete(); + int deleted = builder.delete(); + + if (deleted > 0 && reports != null) { + for (PlayerReportData report : reports) { + plugin.getEventBus().publish(new PlayerReportDeletedEvent( + EntityMappers.playerReport(report), + EntityMappers.player(actor))); + } + } + + return deleted; } public long getCount(PlayerData player) throws SQLException { diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java index a3480cca7..06bf8ba39 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerStorage.java @@ -181,10 +181,8 @@ public PlayerData createIfNotExists(UUID uuid, String name) throws SQLException public PlayerData retrieve(String name, boolean mojangLookup) { try { - List results = queryForEq("name", new SelectArg(name)); - if (results.size() == 1) { - return results.get(0); - } + PlayerData hit = findByExactName(name); + if (hit != null) return hit; } catch (SQLException e) { plugin.getLogger().warning("Failed to process player storage operation", e); } @@ -213,6 +211,19 @@ public PlayerData retrieve(String name, boolean mojangLookup) { return null; } + /** + * Strict variant of {@link #retrieve(String, boolean)} that does not fall + * back to a Mojang lookup and propagates {@link SQLException} so callers + * can surface storage failures explicitly (e.g. wrap into + * {@link me.confuser.banmanager.api.exception.StorageException}). Returns + * the unique row matching {@code name}, or {@code null} when there are + * zero or multiple matches. + */ + public PlayerData findByExactName(String name) throws SQLException { + List results = queryForEq("name", new SelectArg(name)); + return results.size() == 1 ? results.get(0) : null; + } + public List retrieve(String name) { try { return queryForEq("name", new SelectArg(name)); @@ -247,6 +258,20 @@ public HashMap>> getDuplicateNames() } public List getDuplicatesInTime(IPAddress ip, long timeDiff) { + try { + return findDuplicatesInTime(ip, timeDiff); + } catch (SQLException e) { + plugin.getLogger().warning("Failed to process player storage operation", e); + return new ArrayList<>(); + } + } + + /** + * Strict variant of {@link #getDuplicatesInTime(IPAddress, long)} that + * propagates {@link SQLException}. Bypass-IP and per-player {@code alts} + * exemptions still apply. + */ + public List findDuplicatesInTime(IPAddress ip, long timeDiff) throws SQLException { ArrayList players = new ArrayList<>(); if (plugin.getConfig().getBypassPlayerIps().contains(ip.toString())) { @@ -254,40 +279,32 @@ public List getDuplicatesInTime(IPAddress ip, long timeDiff) { } QueryBuilder query = queryBuilder(); - try { - query.leftJoin(plugin.getPlayerBanStorage().queryBuilder()); - - Where where = query.where(); - - where.eq("ip", ip); + query.leftJoin(plugin.getPlayerBanStorage().queryBuilder()); - if (timeDiff != 0) { - long currentTime = System.currentTimeMillis() / 1000L; + Where where = query.where(); + where.eq("ip", ip); - where.and().ge("lastSeen", (currentTime - timeDiff)); - } - - query.setWhere(where); - } catch (SQLException e) { - plugin.getLogger().warning("Failed to process player storage operation", e); - return players; + if (timeDiff != 0) { + long currentTime = System.currentTimeMillis() / 1000L; + where.and().ge("lastSeen", (currentTime - timeDiff)); } + query.setWhere(where); - CloseableIterator itr = null; + // CloseableIterator extends AutoCloseable (close() throws Exception), + // so try-with-resources would force this method to declare + // `throws Exception`. Use closeQuietly() to drain the cursor while + // keeping the SQLException-only contract — close-time errors here just + // mean the result set is gone, which is fine; the caller already has + // the rows. + CloseableIterator itr = query.limit(300L).iterator(); try { - itr = query.limit(300L).iterator(); - while (itr.hasNext()) { PlayerData player = itr.next(); - if (!plugin.getExemptionsConfig().isExempt(player, "alts")) players.add(player); } - - } catch (SQLException e) { - plugin.getLogger().warning("Failed to process player storage operation", e); } finally { - if (itr != null) itr.closeQuietly(); + itr.closeQuietly(); } return players; diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java index 8cf83da34..564c645f9 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/PlayerWarnStorage.java @@ -1,10 +1,13 @@ package me.confuser.banmanager.common.storage; +import me.confuser.banmanager.api.event.player.PlayerWarnEvent; +import me.confuser.banmanager.api.event.player.PlayerWarnedEvent; +import me.confuser.banmanager.api.request.WarnRequest; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.configs.CleanUp; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerWarnData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.google.guava.cache.Cache; import me.confuser.banmanager.common.google.guava.cache.CacheBuilder; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; @@ -70,17 +73,23 @@ public PlayerWarnData removeMute(UUID uuid) { } public boolean addWarning(PlayerWarnData data, boolean silent) throws SQLException { - CommonEvent event = plugin.getServer().callEvent("PlayerWarnEvent", data, silent); + WarnRequest request = EntityMappers.warnRequest(data, silent); + PlayerWarnEvent pre = new PlayerWarnEvent(request); + plugin.getEventBus().publish(pre); - if (event.isCancelled()) { + if (pre.isCancelled()) { return false; } + EntityMappers.applyTo(request, data); + if (plugin.getConfig().isWarningMutesEnabled()) muteWarnings.put(data.getPlayer().getUUID(), data); boolean created = create(data) == 1; - if (created) plugin.getServer().callEvent("PlayerWarnedEvent", data, event.isSilent()); + if (created) { + plugin.getEventBus().publish(new PlayerWarnedEvent(EntityMappers.playerWarn(data), request.silent())); + } return created; } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java index 853142256..5d86910ae 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/conversion/AdvancedBan.java @@ -1,10 +1,11 @@ package me.confuser.banmanager.common.storage.conversion; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.commands.BanIpCommand; import me.confuser.banmanager.common.configs.DatabaseConfig; import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.ipaddr.IPAddress; +import me.confuser.banmanager.common.ipaddr.IPAddressString; +import me.confuser.banmanager.common.util.IPUtils; import me.confuser.banmanager.common.ormlite.dao.CloseableIterator; import me.confuser.banmanager.common.ormlite.stmt.StatementBuilder; import me.confuser.banmanager.common.ormlite.support.ConnectionSource; @@ -119,7 +120,7 @@ public void importPunishments() { plugin.getPlayerMuteStorage().mute(data); } } else if (type.equalsIgnoreCase("IP_BAN") || type.equalsIgnoreCase("TEMP_IP_BAN")) { - IPAddress ip = BanIpCommand.getIp(uuid); + IPAddress ip = resolveIp(uuid); if (ip == null) { plugin.getLogger().severe(name + " ip ban creation failed, invalid ip"); @@ -166,6 +167,17 @@ public void importPunishments() { plugin.getLogger().info("Imported " + count + " punishments from AdvancedBan"); } + private IPAddress resolveIp(String input) { + if (IPUtils.isValid(input)) { + return new IPAddressString(input).getAddress(); + } + + PlayerData player = plugin.getPlayerStorage().retrieve(input, false); + if (player == null) return null; + + return player.getIp(); + } + @Override public void importPlayerBans() { } diff --git a/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java b/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java index ac2937a03..f3d6d8b21 100644 --- a/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java +++ b/common/src/main/java/me/confuser/banmanager/common/storage/migration/MigrationRunner.java @@ -34,24 +34,58 @@ public class MigrationRunner { private final DatabaseConfig dbConfig; private final String scope; private final String detectionTableKey; + private final String detectionTableName; private final ClassLoader resourceLoader; + private final String resourceBase; private final String instanceScope; public MigrationRunner(BanManagerPlugin plugin, ConnectionSource connectionSource, DatabaseConfig dbConfig, String scope, String detectionTableKey, ClassLoader resourceLoader) { + this(plugin, connectionSource, dbConfig, scope, detectionTableKey, null, resourceLoader, "db/" + scope); + } + + /** + * Full constructor exposing the classpath resource base. Use this from the + * public {@code MigrationService} API to allow callers to ship migrations + * outside the {@code db//} convention. + * + * @param detectionTableKey logical key to look up in {@code dbConfig}; null + * when the caller already knows the resolved + * table name and provides {@code detectionTableName} + * instead (typical for companion plugins whose + * tables aren't registered in BanManager's config) + * @param detectionTableName explicit (already-resolved) table name to + * probe for fresh-install detection; pass + * {@code null} to always run migrations without + * the fresh-install short-circuit + * @param resourceBase classpath directory (no trailing slash) containing + * {@code migrations.list} and the {@code V*__*.sql} + * files + */ + public MigrationRunner(BanManagerPlugin plugin, ConnectionSource connectionSource, + DatabaseConfig dbConfig, String scope, + String detectionTableKey, String detectionTableName, + ClassLoader resourceLoader, String resourceBase) { this.plugin = plugin; this.connectionSource = connectionSource; this.dbConfig = dbConfig; this.scope = scope; this.detectionTableKey = detectionTableKey; + this.detectionTableName = detectionTableName; this.resourceLoader = resourceLoader; + this.resourceBase = stripTrailingSlash(resourceBase); String id = dbConfig.getInstanceId(); this.instanceScope = (id != null && !id.isEmpty()) ? scope + ":" + id : scope; } + private static String stripTrailingSlash(String path) { + if (path == null) return null; + return path.endsWith("/") ? path.substring(0, path.length() - 1) : path; + } + public void migrate() throws SQLException { List migrations = loadManifest(); if (migrations.isEmpty()) { @@ -59,7 +93,14 @@ public void migrate() throws SQLException { return; } - String detectionTableName = dbConfig.getTable(detectionTableKey).getTableName(); + String resolvedDetectionTable; + if (detectionTableName != null) { + resolvedDetectionTable = detectionTableName; + } else if (detectionTableKey != null) { + resolvedDetectionTable = dbConfig.getTable(detectionTableKey).getTableName(); + } else { + resolvedDetectionTable = null; + } int latestVersion = migrations.get(migrations.size() - 1).version(); boolean isH2 = dbConfig.getStorageType().equals("h2"); @@ -73,7 +114,7 @@ public void migrate() throws SQLException { try { TableUtils.createTableIfNotExists(connectionSource, SchemaVersion.class); - if (!tableExists(conn, detectionTableName)) { + if (resolvedDetectionTable != null && !tableExists(conn, resolvedDetectionTable)) { plugin.getLogger().info("[Migration:" + instanceScope + "] Fresh install detected, marking schema at V" + latestVersion); insertVersion(conn, latestVersion, "baseline (fresh install)"); return; @@ -81,7 +122,7 @@ public void migrate() throws SQLException { int currentVersion = getCurrentVersion(conn); - if (currentVersion == 0) { + if (resolvedDetectionTable != null && currentVersion == 0) { plugin.getLogger().info("[Migration:" + instanceScope + "] Existing install detected, marking V1 as baseline"); insertVersion(conn, 1, "baseline (existing install)"); currentVersion = 1; @@ -142,7 +183,7 @@ private void releaseAdvisoryLock(DatabaseConnection conn) { private List loadManifest() { List migrations = new ArrayList<>(); - String manifestPath = "db/" + scope + "/migrations.list"; + String manifestPath = resourceBase + "/migrations.list"; try (InputStream is = resourceLoader.getResourceAsStream(manifestPath)) { if (is == null) { @@ -214,7 +255,7 @@ private int getCurrentVersion(DatabaseConnection conn) throws SQLException { } private String loadSqlFile(String filename) { - String path = "db/" + scope + "/" + filename; + String path = resourceBase + "/" + filename; try (InputStream is = resourceLoader.getResourceAsStream(path)) { if (is == null) { diff --git a/common/src/main/java/me/confuser/banmanager/common/util/Message.java b/common/src/main/java/me/confuser/banmanager/common/util/Message.java index 3c78bdf30..8a000233f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/Message.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/Message.java @@ -1,7 +1,6 @@ package me.confuser.banmanager.common.util; import lombok.Getter; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonLogger; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.PlaceholderResolver; @@ -10,6 +9,8 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,6 +18,9 @@ public class Message { private static volatile MessageRegistry registry; private static volatile CommonLogger logger; + private static volatile MessageRenderer renderer; + private static volatile Supplier papiResolverSupplier = () -> null; + private static volatile BooleanSupplier perPlayerLocaleSupplier = () -> false; // Matches PlaceholderAPI %placeholder% tokens. May also match non-PAPI patterns like %100%, // but the resolver's no-op return for unrecognised placeholders makes this harmless. private static final Pattern PAPI_PATTERN = Pattern.compile("%([^%]+)%"); @@ -47,9 +51,34 @@ public Message(String key, String message) { } } - public static void init(MessageRegistry messageRegistry, CommonLogger commonLogger) { + public static void init(MessageRegistry messageRegistry, CommonLogger commonLogger, + MessageRenderer messageRenderer, + Supplier papiResolver, + BooleanSupplier perPlayerLocale) { registry = messageRegistry; logger = commonLogger; + renderer = messageRenderer; + papiResolverSupplier = papiResolver != null ? papiResolver : () -> null; + perPlayerLocaleSupplier = perPlayerLocale != null ? perPlayerLocale : () -> false; + } + + /** + * Reset all injected dependencies. Intended for test teardown. + */ + public static void reset() { + registry = null; + logger = null; + renderer = null; + papiResolverSupplier = () -> null; + perPlayerLocaleSupplier = () -> false; + } + + /** + * @return the active {@link MessageRenderer} as injected via {@link #init}, or + * {@code null} if no plugin has initialised the message system. + */ + public static MessageRenderer renderer() { + return renderer; } public static Message get(String key) { @@ -72,18 +101,16 @@ public static Component component(String key, String locale) { } public static String getString(String key) { - if (registry == null) return null; + if (registry == null || renderer == null) return null; String template = registry.getMessage(key); if (template == null) return null; - MessageRenderer renderer = MessageRenderer.getInstance(); return renderer.toLegacy(renderer.render(template)); } public static String getString(String key, String locale) { - if (registry == null) return null; + if (registry == null || renderer == null) return null; String template = registry.getMessage(key, locale); if (template == null) return null; - MessageRenderer renderer = MessageRenderer.getInstance(); return renderer.toLegacy(renderer.render(template)); } @@ -135,7 +162,8 @@ public Message set(String token, Float replace) { */ public String resolve(String locale) { Component component = resolveComponent(locale); - return MessageRenderer.getInstance().toLegacy(component); + if (renderer == null) return ""; + return renderer.toLegacy(component); } /** @@ -157,19 +185,17 @@ public Component resolveComponent(String locale) { * Resolve the message template to a Component using MiniMessage, with optional PAPI resolution. */ public Component resolveComponent(String locale, CommonPlayer player) { - if (registry == null) return Component.empty(); + if (registry == null || renderer == null) return Component.empty(); String template = registry.getMessage(key, locale); if (template == null) return Component.empty(); - MessageRenderer renderer = MessageRenderer.getInstance(); - // Step 1: Apply .replace() substitutions on the raw string template = applyRawReplacements(template); // Step 2: Resolve PAPI placeholders individually, escaping MiniMessage tags in output - if (player != null && BanManagerPlugin.getInstance() != null) { - PlaceholderResolver papiResolver = BanManagerPlugin.getInstance().getPlaceholderResolver(); + if (player != null) { + PlaceholderResolver papiResolver = papiResolverSupplier.get(); if (papiResolver != null) { template = resolvePapiSafe(papiResolver, player, template); } @@ -190,9 +216,8 @@ public Component resolveComponent(String locale, CommonPlayer player) { public Component resolveComponentFor(CommonPlayer player) { if (player == null) return resolveComponent(getDefaultLocale()); - BanManagerPlugin plugin = BanManagerPlugin.getInstance(); String locale = getDefaultLocale(); - if (plugin != null && plugin.getConfig() != null && plugin.getConfig().isPerPlayerLocale()) { + if (perPlayerLocaleSupplier.getAsBoolean()) { locale = player.getLocale(); } return resolveComponent(locale, player); @@ -200,7 +225,8 @@ public Component resolveComponentFor(CommonPlayer player) { public String resolveFor(CommonPlayer player) { Component component = resolveComponentFor(player); - return MessageRenderer.getInstance().toLegacy(component); + if (renderer == null) return ""; + return renderer.toLegacy(component); } public boolean sendTo(CommonSender sender) { @@ -250,7 +276,6 @@ private String applyRawReplacements(String template) { * to prevent injection of formatting/click/hover tags from external placeholder plugins. */ private static String resolvePapiSafe(PlaceholderResolver resolver, CommonPlayer player, String template) { - MessageRenderer renderer = MessageRenderer.getInstance(); Matcher matcher = PAPI_PATTERN.matcher(template); StringBuffer sb = new StringBuffer(); while (matcher.find()) { diff --git a/common/src/main/java/me/confuser/banmanager/common/util/MessageRenderer.java b/common/src/main/java/me/confuser/banmanager/common/util/MessageRenderer.java index ad8481a74..4a5883241 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/MessageRenderer.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/MessageRenderer.java @@ -18,8 +18,6 @@ public class MessageRenderer { - private static MessageRenderer instance; // guarded by class-level synchronization - private final MiniMessage miniMessage; private final LegacyComponentSerializer legacySerializer; private final PlainTextComponentSerializer plainTextSerializer; @@ -66,24 +64,6 @@ public MessageRenderer() { this.plainTextSerializer = PlainTextComponentSerializer.plainText(); } - public static synchronized MessageRenderer getInstance() { - if (instance == null) { - instance = new MessageRenderer(); - } - return instance; - } - - public static synchronized void setInstance(MessageRenderer renderer) { - instance = renderer; - } - - /** - * Reset the singleton instance. Intended for test teardown. - */ - public static synchronized void reset() { - instance = null; - } - /** * Render a MiniMessage template with additional tag resolvers. */ diff --git a/common/src/main/java/me/confuser/banmanager/common/util/NotificationUtils.java b/common/src/main/java/me/confuser/banmanager/common/util/NotificationUtils.java index 54a370d8f..ca26346c0 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/NotificationUtils.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/NotificationUtils.java @@ -35,7 +35,7 @@ public static void notifyStaff(BanManagerPlugin plugin, String event, Message me } plugin.getServer().getConsoleSender().sendMessage( - MessageRenderer.getInstance().toPlainText(message.resolveComponent())); + plugin.getMessageRenderer().toPlainText(message.resolveComponent())); } public static void notifyPlayer(CommonPlayer player, Message message) { diff --git a/common/src/main/java/me/confuser/banmanager/common/util/PaginatedView.java b/common/src/main/java/me/confuser/banmanager/common/util/PaginatedView.java index 486156fcb..2b3c1bfb7 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/PaginatedView.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/PaginatedView.java @@ -45,12 +45,13 @@ public void send(CommonSender sender, int page, Component header, Component foot sender.sendMessage(items.get(i)); } + MessageRenderer renderer = Message.renderer(); if (totalPages > 1 && !sender.isConsole()) { - sender.sendMessage(buildNavigation(safePage, totalPages)); - } else if (totalPages > 1) { + sender.sendMessage(buildNavigation(renderer, safePage, totalPages)); + } else if (totalPages > 1 && renderer != null) { // Console doesn't support click events, so send a plain legacy string instead - sender.sendMessage(MessageRenderer.getInstance().toLegacy( - MessageRenderer.getInstance().render("Page " + safePage + " of " + totalPages + ""))); + sender.sendMessage(renderer.toLegacy( + renderer.render("Page " + safePage + " of " + totalPages + ""))); } if (footer != null) { @@ -58,8 +59,8 @@ public void send(CommonSender sender, int page, Component header, Component foot } } - private Component buildNavigation(int currentPage, int totalPages) { - MessageRenderer renderer = MessageRenderer.getInstance(); + private Component buildNavigation(MessageRenderer renderer, int currentPage, int totalPages) { + if (renderer == null) return Component.empty(); String escapedCommand = renderer.escapeTags(command); StringBuilder nav = new StringBuilder(); diff --git a/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java b/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java index 7a00bd3d9..de986c7fa 100644 --- a/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java +++ b/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java @@ -8,19 +8,21 @@ public class TestPlayer implements CommonPlayer { + private final BanManagerPlugin plugin; private final UUID uuid; private final String name; private final boolean onlineMode; private String locale = "en"; - public TestPlayer(UUID uuid, String name, boolean onlineMode) { + public TestPlayer(BanManagerPlugin plugin, UUID uuid, String name, boolean onlineMode) { + this.plugin = plugin; this.uuid = uuid; this.name = name; this.onlineMode = onlineMode; } - public TestPlayer(UUID uuid, String name, boolean onlineMode, String locale) { - this(uuid, name, onlineMode); + public TestPlayer(BanManagerPlugin plugin, UUID uuid, String name, boolean onlineMode, String locale) { + this(plugin, uuid, name, onlineMode); this.locale = locale; } @@ -51,7 +53,7 @@ public boolean isConsole() { @Override public PlayerData getData() { - return BanManagerPlugin.getInstance().getPlayerStorage().retrieve(this.name, false); + return plugin.getPlayerStorage().retrieve(this.name, false); } @Override diff --git a/common/src/test/java/me/confuser/banmanager/common/TestScheduler.java b/common/src/test/java/me/confuser/banmanager/common/TestScheduler.java index ce4ad7225..8b085bca3 100644 --- a/common/src/test/java/me/confuser/banmanager/common/TestScheduler.java +++ b/common/src/test/java/me/confuser/banmanager/common/TestScheduler.java @@ -29,4 +29,16 @@ public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration per // In tests, just run the task once immediately task.run(); } + + /** + * Tests run every task on the calling thread, so by definition + * {@link #runSync(Runnable)} executes "wherever the caller already is" — + * not a real game tick thread. Reporting {@code false} keeps tests honest + * about that and matches the proxy semantics that production code may + * branch on. + */ + @Override + public boolean isMainThreadAware() { + return false; + } } diff --git a/common/src/test/java/me/confuser/banmanager/common/TestSender.java b/common/src/test/java/me/confuser/banmanager/common/TestSender.java index 7c63c6ed4..619234a12 100644 --- a/common/src/test/java/me/confuser/banmanager/common/TestSender.java +++ b/common/src/test/java/me/confuser/banmanager/common/TestSender.java @@ -7,11 +7,13 @@ public class TestSender implements CommonSender { + private final BanManagerPlugin plugin; private final UUID uuid; private final String name; private final boolean onlineMode; - public TestSender(UUID uuid, String name, boolean onlineMode) { + public TestSender(BanManagerPlugin plugin, UUID uuid, String name, boolean onlineMode) { + this.plugin = plugin; this.uuid = uuid; this.name = name; this.onlineMode = onlineMode; @@ -39,8 +41,8 @@ public boolean isConsole() { @Override public PlayerData getData() { - if (isConsole()) return BanManagerPlugin.getInstance().getPlayerStorage().getConsole(); + if (isConsole()) return plugin.getPlayerStorage().getConsole(); - return CommonCommand.getPlayer(this, getName(), false); + return CommonCommand.resolvePlayer(plugin, this, getName(), false); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/TestServer.java b/common/src/test/java/me/confuser/banmanager/common/TestServer.java index b8276661c..214c69c65 100644 --- a/common/src/test/java/me/confuser/banmanager/common/TestServer.java +++ b/common/src/test/java/me/confuser/banmanager/common/TestServer.java @@ -1,6 +1,5 @@ package me.confuser.banmanager.common; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.util.Message; @@ -35,7 +34,7 @@ public CommonPlayer getPlayer(UUID uniqueId) { if (player == null) return null; - return new TestPlayer(uniqueId, player.getName(), true); + return new TestPlayer(plugin, uniqueId, player.getName(), true); } catch (SQLException e) { e.printStackTrace(); } @@ -62,7 +61,7 @@ public CommonPlayer getPlayer(String name) { PlayerData player = plugin.getPlayerStorage().retrieve(name, false); if (player == null) return null; - return new TestPlayer(player.getUUID(), player.getName(), true); + return new TestPlayer(plugin, player.getUUID(), player.getName(), true); } @Override @@ -116,7 +115,7 @@ public void broadcast(String message, String permission, CommonSender sender) { public CommonSender getConsoleSender() { PlayerData console = plugin.getPlayerStorage().getConsole(); - return new TestSender(console.getUUID(), console.getName(), true); + return new TestSender(plugin, console.getUUID(), console.getName(), true); } @Override @@ -129,11 +128,6 @@ public CommonWorld getWorld(String name) { return new CommonWorld(name); } - @Override - public CommonEvent callEvent(String name, Object... args) { - return new CommonEvent(false, false); - } - public void enable(BanManagerPlugin plugin) { this.plugin = plugin; } diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/BanCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/BanCommandTest.java index 87bf75981..d754fe3da 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/BanCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/BanCommandTest.java @@ -91,7 +91,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -108,7 +108,7 @@ public void shouldFailIfAmbiguousPartialMatch() { public void shouldBlockSelfTargetThroughPartialName() { PlayerData self = testUtils.createPlayerWithName("Player123"); PlayerData offlinePlayer = testUtils.createPlayerWithName("Player"); - CommonSender sender = spy(new TestPlayer(self.getUUID(), self.getName(), true)); + CommonSender sender = spy(new TestPlayer(plugin, self.getUUID(), self.getName(), true)); String[] args = new String[]{offlinePlayer.getName(), "test"}; server.setUseStorageForOnlineLookups(false); @@ -137,7 +137,7 @@ public void shouldAllowPartialWhenNoExactCollision() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -156,7 +156,7 @@ public void shouldFailIfExempt() { PlayerData player = testUtils.createRandomPlayer(); CommonServer server = this.server; CommonSender sender = spy(server.getConsoleSender()); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); this.server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "test"}; @@ -181,7 +181,7 @@ public void shouldFailIfOfflineExempt() { PlayerData player = testUtils.createRandomPlayer(); CommonServer server = this.server; CommonSender sender = spy(server.getConsoleSender()); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); this.server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "test"}; diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/MuteCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/MuteCommandTest.java index e350aa59d..e7db5f13c 100755 --- a/common/src/test/java/me/confuser/banmanager/common/commands/MuteCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/MuteCommandTest.java @@ -91,7 +91,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -113,7 +113,7 @@ public void shouldAllowPartialWhenNoExactCollision() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -132,7 +132,7 @@ public void shouldFailIfExempt() { PlayerData player = testUtils.createRandomPlayer(); CommonServer server = this.server; CommonSender sender = spy(server.getConsoleSender()); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); this.server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "test"}; @@ -157,7 +157,7 @@ public void shouldFailIfOfflineExempt() { PlayerData player = testUtils.createRandomPlayer(); CommonServer server = this.server; CommonSender sender = spy(server.getConsoleSender()); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); this.server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "test"}; diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/ReportCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/ReportCommandTest.java index 0e4df47f0..f30deb1d1 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/ReportCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/ReportCommandTest.java @@ -73,7 +73,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission("bm.command.report.offline")).thenReturn(false); try { @@ -95,7 +95,7 @@ public void shouldAllowPartialWhenNoExactCollision() throws SQLException { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission("bm.command.report.offline")).thenReturn(false); try { diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/RollbackCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/RollbackCommandTest.java index 14d2076f6..46b394ee1 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/RollbackCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/RollbackCommandTest.java @@ -1,5 +1,6 @@ package me.confuser.banmanager.common.commands; +import me.confuser.banmanager.api.event.player.PlayerReportDeletedEvent; import me.confuser.banmanager.common.BasePluginDbTest; import me.confuser.banmanager.common.CommonServer; import me.confuser.banmanager.common.data.*; @@ -7,6 +8,7 @@ import org.junit.jupiter.api.Test; import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicInteger; import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.*; @@ -382,6 +384,9 @@ public void shouldRollbackReportsAndFireEvents() throws SQLException { assertEquals(2, plugin.getPlayerReportStorage().getCount(victim)); + AtomicInteger deletedEvents = new AtomicInteger(); + plugin.getEventBus().subscribe(PlayerReportDeletedEvent.class, e -> deletedEvents.incrementAndGet()); + CommonSender sender = spy(plugin.getServer().getConsoleSender()); String[] args = new String[]{maliciousMod.getName(), "1d", "reports"}; @@ -396,6 +401,6 @@ public void shouldRollbackReportsAndFireEvents() throws SQLException { }); assertEquals(0, plugin.getPlayerReportStorage().getCount(victim)); - verify(server, times(2)).callEvent(eq("PlayerReportDeletedEvent"), any(PlayerReportData.class)); + assertEquals(2, deletedEvents.get()); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/TempBanCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/TempBanCommandTest.java index e549ee01d..8ea745a7e 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/TempBanCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/TempBanCommandTest.java @@ -91,7 +91,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -113,7 +113,7 @@ public void shouldAllowPartialWhenNoExactCollision() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -132,7 +132,7 @@ public void shouldFailIfExempt() { PlayerData player = testUtils.createRandomPlayer(); CommonServer server = this.server; CommonSender sender = spy(server.getConsoleSender()); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); this.server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "1d", "test"}; @@ -157,7 +157,7 @@ public void shouldFailIfOfflineExempt() { PlayerData player = testUtils.createRandomPlayer(); CommonServer server = this.server; CommonSender sender = spy(server.getConsoleSender()); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); this.server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "1d", "test"}; diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/TempMuteCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/TempMuteCommandTest.java index 124311dd2..221202177 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/TempMuteCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/TempMuteCommandTest.java @@ -167,7 +167,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { @@ -189,7 +189,7 @@ public void shouldAllowPartialWhenNoExactCollision() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission(cmd.getPermission() + ".offline")).thenReturn(false); try { diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/TempWarnCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/TempWarnCommandTest.java index a2ed0ca0c..73e2d7f44 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/TempWarnCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/TempWarnCommandTest.java @@ -96,7 +96,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission("bm.command.tempwarn.offline")).thenReturn(false); try { @@ -118,7 +118,7 @@ public void shouldAllowPartialWhenNoExactCollision() throws SQLException { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission("bm.command.tempwarn.offline")).thenReturn(false); try { diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/WarnCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/WarnCommandTest.java index 7386fb85d..f95f4cbb6 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/WarnCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/WarnCommandTest.java @@ -91,7 +91,7 @@ public void shouldFailIfAmbiguousPartialMatch() { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Player", new TestPlayer(UUID.randomUUID(), "Player123", true)); + server.setPartialMatch("Player", new TestPlayer(plugin, UUID.randomUUID(), "Player123", true)); when(sender.hasPermission("bm.command.warn.offline")).thenReturn(false); try { @@ -113,7 +113,7 @@ public void shouldAllowPartialWhenNoExactCollision() throws SQLException { server.setUseStorageForOnlineLookups(false); server.clearExactMatches(); server.clearPartialMatches(); - server.setPartialMatch("Play", new TestPlayer(targetPlayer.getUUID(), targetPlayer.getName(), true)); + server.setPartialMatch("Play", new TestPlayer(plugin, targetPlayer.getUUID(), targetPlayer.getName(), true)); when(sender.hasPermission("bm.command.warn.offline")).thenReturn(false); try { @@ -131,7 +131,7 @@ public void shouldAllowPartialWhenNoExactCollision() throws SQLException { public void shouldFailIfPlayerExempt() { CommonSender sender = spy(plugin.getServer().getConsoleSender()); PlayerData player = testUtils.createRandomPlayer(); - CommonPlayer commonPlayer = spy(new TestPlayer(player.getUUID(), player.getName(), true)); + CommonPlayer commonPlayer = spy(new TestPlayer(plugin, player.getUUID(), player.getName(), true)); server.setExactMatch(player.getName(), commonPlayer); String[] args = new String[]{player.getName(), "test"}; @@ -183,7 +183,7 @@ public void shouldTriggerPointsTimeframeWarningActions() throws Exception { actionsField.setAccessible(true); actionsField.set(warningActionsConfig, actions); - List commands = warningActionsConfig.getCommands(player, 5); + List commands = warningActionsConfig.getCommands(plugin, player, 5); assertEquals(2, commands.size()); diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/AsyncSupportTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/AsyncSupportTest.java new file mode 100644 index 000000000..5a73831a1 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/AsyncSupportTest.java @@ -0,0 +1,251 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.exception.BanManagerException; +import me.confuser.banmanager.api.exception.OperationCancelledException; +import me.confuser.banmanager.api.exception.StorageException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests {@link AsyncSupport}'s exception bridging contract: + *
    + *
  • {@link SQLException} is wrapped in {@link StorageException}
  • + *
  • {@link BanManagerException} subtypes propagate unchanged
  • + *
  • Other checked exceptions become {@link BanManagerException}
  • + *
  • {@code asyncCancellable} converts {@link OperationCancelledException} + * into a sentinel return value rather than a failed future
  • + *
+ */ +public class AsyncSupportTest { + + private ExecutorService executor; + private AsyncSupport async; + + @BeforeEach + public void setUp() { + executor = Executors.newSingleThreadExecutor(); + async = new AsyncSupport(executor); + } + + @AfterEach + public void tearDown() throws InterruptedException { + executor.shutdown(); + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + } + + @Test + public void syncReturnsCallableResult() { + String result = AsyncSupport.sync(() -> "hello"); + assertEquals("hello", result); + } + + @Test + public void syncWrapsSqlException() { + SQLException root = new SQLException("connection refused"); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.sync(() -> { throw root; })); + assertSame(root, thrown.getCause()); + } + + @Test + public void syncPropagatesBanManagerExceptionUnchanged() { + OperationCancelledException original = new OperationCancelledException("vetoed"); + OperationCancelledException thrown = assertThrows(OperationCancelledException.class, + () -> AsyncSupport.sync(() -> { throw original; })); + assertSame(original, thrown); + } + + @Test + public void syncPropagatesStorageExceptionUnchanged() { + StorageException original = new StorageException("boom", new SQLException("driver")); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.sync(() -> { throw original; })); + assertSame(original, thrown); + } + + @Test + public void syncWrapsOtherCheckedExceptionsAsBanManagerException() { + IOException root = new IOException("disk gone"); + BanManagerException thrown = assertThrows(BanManagerException.class, + () -> AsyncSupport.sync(() -> { throw root; })); + assertSame(root, thrown.getCause()); + } + + @Test + public void syncVoidExecutesRunnable() { + AtomicBoolean executed = new AtomicBoolean(); + AsyncSupport.syncVoid(() -> executed.set(true)); + assertTrue(executed.get()); + } + + @Test + public void syncVoidWrapsSqlException() { + SQLException root = new SQLException("connection refused"); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.syncVoid(() -> { throw root; })); + assertSame(root, thrown.getCause()); + } + + @Test + public void asyncCompletesWithResult() throws Exception { + CompletableFuture future = async.async(() -> 42); + assertEquals(42, future.get(5, TimeUnit.SECONDS)); + } + + @Test + public void asyncRunsOnSuppliedExecutor() throws Exception { + CompletableFuture future = async.async(Thread::currentThread); + Thread runner = future.get(5, TimeUnit.SECONDS); + assertFalse(runner == Thread.currentThread(), + "callable should not run on the calling thread"); + } + + @Test + public void asyncFailsFutureWithStorageExceptionForSqlException() { + SQLException root = new SQLException("network"); + CompletableFuture future = async.async(() -> { throw root; }); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + assertTrue(ex.getCause() instanceof StorageException, + "expected StorageException, got " + ex.getCause()); + assertSame(root, ex.getCause().getCause()); + } + + @Test + public void asyncCancellableReturnsResultWhenCallableSucceeds() throws Exception { + CompletableFuture> future = + async.asyncCancellable(() -> Optional.of("ok"), Optional.empty()); + + assertEquals(Optional.of("ok"), future.get(5, TimeUnit.SECONDS)); + } + + @Test + public void asyncCancellableReturnsSentinelWhenCancelled() throws Exception { + CompletableFuture> future = async.asyncCancellable(() -> { + throw new OperationCancelledException("vetoed"); + }, Optional.empty()); + + assertEquals(Optional.empty(), future.get(5, TimeUnit.SECONDS)); + assertFalse(future.isCompletedExceptionally(), + "asyncCancellable must not surface OperationCancelledException as a failed future"); + } + + @Test + public void asyncCancellableSupportsBooleanSentinel() throws Exception { + CompletableFuture future = async.asyncCancellable(() -> { + throw new OperationCancelledException("vetoed"); + }, Boolean.FALSE); + + assertEquals(Boolean.FALSE, future.get(5, TimeUnit.SECONDS)); + } + + @Test + public void asyncCancellablePropagatesStorageException() { + SQLException root = new SQLException("boom"); + CompletableFuture> future = async.asyncCancellable(() -> { + throw root; + }, Optional.empty()); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + assertTrue(ex.getCause() instanceof StorageException); + assertSame(root, ex.getCause().getCause()); + } + + @Test + public void asyncCancellablePropagatesUnrelatedRuntimeException() { + IllegalStateException root = new IllegalStateException("nope"); + CompletableFuture> future = async.asyncCancellable(() -> { + throw root; + }, Optional.empty()); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + assertTrue(ex.getCause() instanceof BanManagerException, + "non-cancellation exceptions should still surface; got " + ex.getCause()); + } + + @Test + public void asyncVoidCompletes() throws Exception { + AtomicBoolean ran = new AtomicBoolean(); + async.asyncVoid(() -> ran.set(true)).get(5, TimeUnit.SECONDS); + assertTrue(ran.get()); + } + + @Test + public void executorAccessorReturnsConfiguredExecutor() { + assertSame(executor, async.executor()); + } + + @Test + public void syncWithContextMessageWrapsSqlExceptionWithThatMessage() { + SQLException root = new SQLException("driver"); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.sync(() -> { throw root; }, "Failed to delete report 12")); + assertEquals("Failed to delete report 12", thrown.getMessage()); + assertSame(root, thrown.getCause()); + } + + @Test + public void syncWithContextMessageWrapsOtherCheckedExceptionsWithThatMessage() { + IOException root = new IOException("disk gone"); + BanManagerException thrown = assertThrows(BanManagerException.class, + () -> AsyncSupport.sync(() -> { throw root; }, "Failed to load report states")); + assertEquals("Failed to load report states", thrown.getMessage()); + assertSame(root, thrown.getCause()); + } + + @Test + public void syncWithContextMessagePropagatesBanManagerExceptionUnchanged() { + OperationCancelledException original = new OperationCancelledException("vetoed"); + OperationCancelledException thrown = assertThrows(OperationCancelledException.class, + () -> AsyncSupport.sync(() -> { throw original; }, "Should not be used")); + assertSame(original, thrown); + assertEquals("vetoed", thrown.getMessage()); + } + + @Test + public void syncVoidWithContextMessageWrapsSqlExceptionWithThatMessage() { + SQLException root = new SQLException("driver"); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.syncVoid(() -> { throw root; }, "Failed to mark warning 5 read")); + assertEquals("Failed to mark warning 5 read", thrown.getMessage()); + assertSame(root, thrown.getCause()); + } + + @Test + public void syncVoidWithContextMessagePropagatesBanManagerExceptionUnchanged() { + StorageException original = new StorageException("original", new SQLException()); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.syncVoid(() -> { throw original; }, "Should not be used")); + assertSame(original, thrown); + } + + @Test + public void defaultMessageMatchesPreviousContract() { + SQLException root = new SQLException("driver"); + StorageException thrown = assertThrows(StorageException.class, + () -> AsyncSupport.sync(() -> { throw root; })); + assertEquals("BanManager storage operation failed", thrown.getMessage()); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/EntityMappersTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/EntityMappersTest.java new file mode 100644 index 000000000..e66bbad00 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/EntityMappersTest.java @@ -0,0 +1,367 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.dto.HistoryEntry; +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.dto.PlayerNameSummary; +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.dto.PlayerSession; +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.common.data.IpBanData; +import me.confuser.banmanager.common.data.NameBanData; +import me.confuser.banmanager.common.data.PlayerBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.data.PlayerHistoryData; +import me.confuser.banmanager.common.data.PlayerMuteData; +import me.confuser.banmanager.common.data.PlayerNoteData; +import me.confuser.banmanager.common.data.PlayerWarnData; +import me.confuser.banmanager.common.ipaddr.IPAddressString; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Verifies {@link EntityMappers} preserves all fields from internal entity + * objects to API record DTOs, that {@code null} inputs propagate as {@code + * null} (so callers can chain mappers safely), and that {@link Optional} + * fields are populated correctly. + */ +public class EntityMappersTest { + + private static final UUID PLAYER_UUID = UUID.fromString("11111111-1111-1111-1111-111111111111"); + private static final UUID ACTOR_UUID = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private static PlayerData newPlayerData(UUID uuid, String name, String ip) throws Exception { + return new PlayerData(uuid, name, new IPAddressString(ip).toAddress(), 1_700_000_000L); + } + + // -- player --------------------------------------------------------------- + + @Test + public void playerMapsAllFields() throws Exception { + PlayerData data = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + + Player mapped = EntityMappers.player(data); + + assertNotNull(mapped); + assertEquals(PLAYER_UUID, mapped.uuid()); + assertEquals("Alice", mapped.name()); + assertEquals("203.0.113.42", mapped.ip().toCanonicalString()); + assertEquals(1_700_000_000L, mapped.lastSeen()); + assertEquals(Optional.empty(), mapped.locale(), + "missing locale should map to Optional.empty()"); + } + + @Test + public void playerLocalePreservesNonEmptyValue() throws Exception { + PlayerData data = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + data.setLocale("en_GB"); + + Player mapped = EntityMappers.player(data); + + assertEquals(Optional.of("en_GB"), mapped.locale()); + } + + @Test + public void playerLocaleCollapsesEmptyStringToEmptyOptional() throws Exception { + PlayerData data = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + data.setLocale(""); + + Player mapped = EntityMappers.player(data); + + assertEquals(Optional.empty(), mapped.locale(), + "empty-string locale must collapse to Optional.empty(), not Optional.of(\"\")"); + } + + @Test + public void playerNullReturnsNull() { + assertNull(EntityMappers.player(null)); + } + + // -- player ban ---------------------------------------------------------- + + @Test + public void playerBanMapsAllFields() throws Exception { + PlayerData player = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + PlayerBanData data = new PlayerBanData(player, actor, "spam", true, 9_000_000L); + + PlayerBan mapped = EntityMappers.playerBan(data); + + assertNotNull(mapped); + assertEquals(PLAYER_UUID, mapped.player().uuid()); + assertEquals(ACTOR_UUID, mapped.actor().uuid()); + assertEquals("spam", mapped.reason()); + assertTrue(mapped.silent()); + assertEquals(9_000_000L, mapped.expires()); + assertFalse(mapped.isPermanent(), "non-zero expires should not be permanent"); + } + + @Test + public void playerBanWithZeroExpiresIsPermanent() throws Exception { + PlayerData player = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + PlayerBanData data = new PlayerBanData(player, actor, "spam", false); + + PlayerBan mapped = EntityMappers.playerBan(data); + + assertTrue(mapped.isPermanent(), "zero expires should map to permanent=true"); + assertFalse(mapped.silent()); + } + + @Test + public void playerBanNullReturnsNull() { + assertNull(EntityMappers.playerBan(null)); + } + + // -- player mute --------------------------------------------------------- + + @Test + public void playerMuteMapsAllFields() throws Exception { + PlayerData player = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + PlayerMuteData data = new PlayerMuteData(player, actor, "loud", false, true, 5_000L); + + PlayerMute mapped = EntityMappers.playerMute(data); + + assertNotNull(mapped); + assertEquals(PLAYER_UUID, mapped.player().uuid()); + assertEquals(ACTOR_UUID, mapped.actor().uuid()); + assertEquals("loud", mapped.reason()); + assertFalse(mapped.silent()); + assertTrue(mapped.soft()); + assertEquals(5_000L, mapped.expires()); + } + + @Test + public void playerMuteNullReturnsNull() { + assertNull(EntityMappers.playerMute(null)); + } + + // -- player warn --------------------------------------------------------- + + @Test + public void playerWarnMapsAllFields() throws Exception { + PlayerData player = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + PlayerWarnData data = new PlayerWarnData(player, actor, "rule break", 2.0d, false); + + PlayerWarn mapped = EntityMappers.playerWarn(data); + + assertNotNull(mapped); + assertEquals(PLAYER_UUID, mapped.player().uuid()); + assertEquals("rule break", mapped.reason()); + assertEquals(2.0d, mapped.points(), 0.0001); + assertFalse(mapped.read()); + } + + @Test + public void playerWarnNullReturnsNull() { + assertNull(EntityMappers.playerWarn(null)); + } + + // -- ip ban --------------------------------------------------------------- + + @Test + public void ipBanMapsAddressAndFields() throws Exception { + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + IpBanData data = new IpBanData( + new IPAddressString("203.0.113.42").toAddress(), + actor, + "open proxy", + true); + + IpBan mapped = EntityMappers.ipBan(data); + + assertNotNull(mapped); + assertEquals("203.0.113.42", mapped.ip().toCanonicalString(), + "internal IP should be remapped to API IPAddress"); + assertEquals(ACTOR_UUID, mapped.actor().uuid()); + assertEquals("open proxy", mapped.reason()); + assertTrue(mapped.silent()); + } + + @Test + public void ipBanNullReturnsNull() { + assertNull(EntityMappers.ipBan(null)); + } + + // -- name ban ------------------------------------------------------------ + + @Test + public void nameBanMapsAllFields() throws Exception { + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + NameBanData data = new NameBanData("BadName", actor, "offensive", false); + + NameBan mapped = EntityMappers.nameBan(data); + + assertNotNull(mapped); + assertEquals("BadName", mapped.name()); + assertEquals("offensive", mapped.reason()); + assertEquals(ACTOR_UUID, mapped.actor().uuid()); + assertFalse(mapped.silent()); + } + + @Test + public void nameBanNullReturnsNull() { + assertNull(EntityMappers.nameBan(null)); + } + + // -- note ---------------------------------------------------------------- + + @Test + public void playerNoteMapsAllFields() throws Exception { + PlayerData player = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + PlayerData actor = newPlayerData(ACTOR_UUID, "ModBob", "203.0.113.43"); + PlayerNoteData data = new PlayerNoteData(player, actor, "watching", 1_700_001_234L); + + PlayerNote mapped = EntityMappers.playerNote(data); + + assertNotNull(mapped); + assertEquals(PLAYER_UUID, mapped.player().uuid()); + assertEquals(ACTOR_UUID, mapped.actor().uuid()); + assertEquals("watching", mapped.message()); + assertEquals(1_700_001_234L, mapped.created()); + } + + @Test + public void playerNoteNullReturnsNull() { + assertNull(EntityMappers.playerNote(null)); + } + + // -- session ------------------------------------------------------------ + + @Test + public void playerSessionMapsIpToOptionalAddress() throws Exception { + PlayerData player = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + PlayerHistoryData history = new PlayerHistoryData( + player, + new IPAddressString("203.0.113.42").toAddress(), + 1_700_000_100L, + 1_700_000_200L); + + PlayerSession mapped = EntityMappers.playerSession(history); + + assertNotNull(mapped); + assertEquals("Alice", mapped.name()); + assertTrue(mapped.ip().isPresent(), "ip should round-trip into Optional.present"); + assertEquals("203.0.113.42", mapped.ip().get().toCanonicalString()); + assertEquals(1_700_000_100L, mapped.join()); + assertEquals(1_700_000_200L, mapped.leave()); + } + + @Test + public void playerSessionNullReturnsNull() { + assertNull(EntityMappers.playerSession(null)); + } + + // -- history entry ------------------------------------------------------ + + @Test + public void historyEntryReplacesNullStringsWithEmptyToHonourNonNullContract() { + me.confuser.banmanager.common.data.HistoryEntry internal = + new me.confuser.banmanager.common.data.HistoryEntry( + 42, "ban", "console", 1_700_000_000L, null, null); + + HistoryEntry mapped = EntityMappers.historyEntry(internal); + + assertNotNull(mapped); + assertEquals(42, mapped.id()); + assertEquals("ban", mapped.type()); + assertEquals("console", mapped.actor()); + assertEquals(1_700_000_000L, mapped.created()); + assertEquals("", mapped.reason(), + "null reason should be flattened to empty string for null-free record"); + assertEquals("", mapped.meta(), + "null meta should be flattened to empty string for null-free record"); + } + + @Test + public void historyEntryPreservesNonNullStrings() { + me.confuser.banmanager.common.data.HistoryEntry internal = + new me.confuser.banmanager.common.data.HistoryEntry( + 42, "warn", "console", 1_700_000_000L, "spam", "{}"); + + HistoryEntry mapped = EntityMappers.historyEntry(internal); + + assertEquals("spam", mapped.reason()); + assertEquals("{}", mapped.meta()); + } + + @Test + public void historyEntryNullReturnsNull() { + assertNull(EntityMappers.historyEntry(null)); + } + + // -- name summary ------------------------------------------------------- + + @Test + public void playerNameSummaryRoundTripsRecord() { + me.confuser.banmanager.common.data.PlayerNameSummary internal = + new me.confuser.banmanager.common.data.PlayerNameSummary("Alice", 100L, 200L); + + PlayerNameSummary mapped = EntityMappers.playerNameSummary(internal); + + assertNotNull(mapped); + assertEquals("Alice", mapped.name()); + assertEquals(100L, mapped.firstSeen()); + assertEquals(200L, mapped.lastSeen()); + } + + @Test + public void playerNameSummaryNullReturnsNull() { + assertNull(EntityMappers.playerNameSummary(null)); + } + + // -- mapper invariants -------------------------------------------------- + + @Test + public void mapperReturnsNewInstanceEachInvocation() throws Exception { + PlayerData data = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + + Player a = EntityMappers.player(data); + Player b = EntityMappers.player(data); + + assertNotNull(a); + assertNotNull(b); + assertEquals(a, b); + // Records compare by value but identity should not be cached: that would + // tie API DTO lifetimes to entity reuse and leak mutable internal state. + assertFalse(a == b, + "EntityMappers must not memoise — DTOs should be cheap, fresh values"); + } + + @Test + public void mapperDoesNotShareReferencesWithSource() throws Exception { + PlayerData data = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + + Player mapped = EntityMappers.player(data); + + // The IP gets converted into the API class via canonical-string round + // trip, so it must NOT alias the internal one. + assertNotNull(mapped); + assertFalse(((Object) mapped.ip()) == ((Object) data.getIp()), + "mapped IP must be a freshly constructed API IPAddress, not aliased"); + } + + @Test + public void mapperPlayerUuidIsByValueIdentity() throws Exception { + PlayerData data = newPlayerData(PLAYER_UUID, "Alice", "203.0.113.42"); + + Player mapped = EntityMappers.player(data); + + assertSame(PLAYER_UUID, mapped.uuid(), + "UUID is immutable and getUUID() returns the cached instance — sharing it is safe"); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/IpAddressMapperTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/IpAddressMapperTest.java new file mode 100644 index 000000000..709d83cac --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/IpAddressMapperTest.java @@ -0,0 +1,118 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.common.ipaddr.IPAddressString; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Round-trip tests for {@link IpAddressMapper}. The two {@code IPAddress} + * classes (shaded under {@code me.confuser.banmanager.common.ipaddr} and the + * unshaded API form under {@code inet.ipaddr}) are bytecode-identical but + * incompatible at the type level — we verify the mapper preserves canonical + * form across both directions for IPv4, IPv6, and subnet inputs. + */ +public class IpAddressMapperTest { + + @Test + public void nullInternalReturnsNullApi() { + assertNull(IpAddressMapper.toApi(null)); + } + + @Test + public void nullApiReturnsNullInternal() { + assertNull(IpAddressMapper.toInternal(null)); + } + + @Test + public void ipv4InternalRoundTripsToApi() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress internal = + new IPAddressString("203.0.113.42").toAddress(); + + inet.ipaddr.IPAddress api = IpAddressMapper.toApi(internal); + + assertNotNull(api); + assertEquals("203.0.113.42", api.toCanonicalString()); + } + + @Test + public void ipv4ApiRoundTripsToInternal() throws Exception { + inet.ipaddr.IPAddress api = + new inet.ipaddr.IPAddressString("198.51.100.7").toAddress(); + + me.confuser.banmanager.common.ipaddr.IPAddress internal = IpAddressMapper.toInternal(api); + + assertNotNull(internal); + assertEquals("198.51.100.7", internal.toCanonicalString()); + } + + @Test + public void ipv6CompressedFormIsPreserved() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress internal = + new IPAddressString("2001:db8::1").toAddress(); + + inet.ipaddr.IPAddress api = IpAddressMapper.toApi(internal); + + assertNotNull(api); + assertEquals(internal.toCanonicalString(), api.toCanonicalString()); + } + + @Test + public void ipv6FullFormIsPreserved() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress internal = + new IPAddressString("2001:0db8:85a3:0000:0000:8a2e:0370:7334").toAddress(); + + inet.ipaddr.IPAddress api = IpAddressMapper.toApi(internal); + + assertNotNull(api); + assertEquals(internal.toCanonicalString(), api.toCanonicalString()); + } + + @Test + public void ipv4SubnetPrefixRoundTrips() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress internal = + new IPAddressString("10.0.0.0/8").toAddress(); + + inet.ipaddr.IPAddress api = IpAddressMapper.toApi(internal); + + assertNotNull(api); + assertEquals(internal.toCanonicalString(), api.toCanonicalString()); + } + + @Test + public void ipv6SubnetPrefixRoundTrips() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress internal = + new IPAddressString("2001:db8::/48").toAddress(); + + inet.ipaddr.IPAddress api = IpAddressMapper.toApi(internal); + + assertNotNull(api); + assertEquals(internal.toCanonicalString(), api.toCanonicalString()); + } + + @Test + public void doubleRoundTripPreservesAddress() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress original = + new IPAddressString("203.0.113.42").toAddress(); + + me.confuser.banmanager.common.ipaddr.IPAddress restored = + IpAddressMapper.toInternal(IpAddressMapper.toApi(original)); + + assertNotNull(restored); + assertEquals(original.toCanonicalString(), restored.toCanonicalString()); + } + + @Test + public void doubleRoundTripPreservesIpv6() throws Exception { + me.confuser.banmanager.common.ipaddr.IPAddress original = + new IPAddressString("2001:db8::dead:beef").toAddress(); + + me.confuser.banmanager.common.ipaddr.IPAddress restored = + IpAddressMapper.toInternal(IpAddressMapper.toApi(original)); + + assertNotNull(restored); + assertEquals(original.toCanonicalString(), restored.toCanonicalString()); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/MigrationPrefixRegistryTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/MigrationPrefixRegistryTest.java new file mode 100644 index 000000000..972c1438a --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/MigrationPrefixRegistryTest.java @@ -0,0 +1,109 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.exception.BanManagerException; +import org.junit.jupiter.api.Test; + +import java.lang.ref.WeakReference; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Behavioural contract for {@link MigrationPrefixRegistry}: rejects + * different-classloader collisions, allows same-classloader idempotency, + * and reclaims slots whose classloader has been GC'd. + */ +class MigrationPrefixRegistryTest { + + @Test + void firstClaimSucceeds() { + MigrationPrefixRegistry registry = new MigrationPrefixRegistry(); + assertDoesNotThrow(() -> registry.claim("plugin-a", new ClassLoader() {})); + } + + @Test + void sameClassloaderRepeatClaimIsNoop() { + MigrationPrefixRegistry registry = new MigrationPrefixRegistry(); + ClassLoader cl = new ClassLoader() {}; + registry.claim("plugin-a", cl); + assertDoesNotThrow(() -> registry.claim("plugin-a", cl)); + assertDoesNotThrow(() -> registry.claim("plugin-a", cl)); + } + + @Test + void differentClassloaderSamePrefixRejected() { + MigrationPrefixRegistry registry = new MigrationPrefixRegistry(); + ClassLoader first = new ClassLoader() {}; + ClassLoader second = new ClassLoader() {}; + registry.claim("plugin-a", first); + + BanManagerException ex = assertThrows(BanManagerException.class, + () -> registry.claim("plugin-a", second)); + assertTrue(ex.getMessage().contains("plugin-a"), + "Error must name the colliding prefix; got: " + ex.getMessage()); + assertTrue(ex.getMessage().contains("classloader"), + "Error must mention classloader to point operators at the cause; got: " + ex.getMessage()); + } + + @Test + void differentPrefixSameClassloaderAllowed() { + MigrationPrefixRegistry registry = new MigrationPrefixRegistry(); + ClassLoader cl = new ClassLoader() {}; + assertDoesNotThrow(() -> registry.claim("plugin-a", cl)); + assertDoesNotThrow(() -> registry.claim("plugin-b", cl)); + } + + @Test + void deadClassloaderReleasesSlot() throws Exception { + MigrationPrefixRegistry registry = new MigrationPrefixRegistry(); + ClassLoader replacement = new ClassLoader() {}; + + // Claim with a classloader that can be GC'd, then drop the strong + // reference. We don't have direct control over GC, but we can force + // it deterministically by allocating until the weak ref is cleared. + WeakReference tombstone = registerAndDrop(registry, "plugin-a"); + forceWeakRefClear(tombstone); + + // Slot must now be reclaimable by a different classloader without + // throwing — the previous holder is gone. + assertDoesNotThrow(() -> registry.claim("plugin-a", replacement)); + } + + /** + * Registers a freshly-allocated classloader against {@code prefix}, then + * returns a weak reference to it so the test can drop the strong + * reference and let the GC reclaim it. Kept in its own method so the + * local variable goes out of scope cleanly. + */ + private WeakReference registerAndDrop(MigrationPrefixRegistry registry, String prefix) { + ClassLoader transientLoader = new ClassLoader() {}; + registry.claim(prefix, transientLoader); + return new WeakReference<>(transientLoader); + } + + /** + * Spin {@link System#gc()} (with allocation pressure) until the + * referent is cleared, or fail loud if the JVM refuses to collect after + * a generous budget. Required for the dead-classloader path to be a + * deterministic test rather than a flaky hope. + */ + private void forceWeakRefClear(WeakReference ref) { + for (int i = 0; i < 50 && ref.get() != null; i++) { + System.gc(); + // Allocate enough garbage to push the young gen into a collection. + byte[] pressure = new byte[1024 * 1024]; + pressure[0] = (byte) i; + try { + Thread.sleep(20); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + if (ref.get() != null) { + throw new AssertionError("WeakReference was not cleared after repeated GC hints — " + + "test cannot proceed deterministically on this JVM"); + } + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/SchedulerContractTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/SchedulerContractTest.java new file mode 100644 index 000000000..acedb8bdc --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/SchedulerContractTest.java @@ -0,0 +1,198 @@ +package me.confuser.banmanager.common.impl; + +import me.confuser.banmanager.api.scheduler.BanManagerScheduler; +import me.confuser.banmanager.common.CommonScheduler; +import me.confuser.banmanager.common.TestScheduler; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Verifies the {@link BanManagerScheduler}/{@link CommonScheduler} contract + * around main-thread awareness: + *
    + *
  • {@link CommonScheduler#isMainThreadAware()} defaults to {@code true} + * so server platforms (Bukkit/Sponge/Fabric) inherit the right answer + * without having to re-implement it
  • + *
  • Proxy-style schedulers (Bungee/Velocity) override to {@code false} + * and route {@code runSync} to {@code runAsync}
  • + *
  • {@link SchedulerAdapter} forwards {@code isMainThreadAware()} from + * the underlying delegate (so the API doesn't lie about the platform)
  • + *
  • {@link TestScheduler} reports {@code false}, since it runs everything + * inline on the calling thread and is not a real tick thread
  • + *
+ * + *

This test deliberately avoids loading the real Bukkit/Bungee/etc. classes + * — those require their hosting server to be up. Instead it pins down the + * contract on {@link CommonScheduler} itself so any future platform impl that + * forgets to implement it gets the platform-correct default and any proxy + * impl that forgets to override it surfaces here.

+ */ +public class SchedulerContractTest { + + // -- defaults -------------------------------------------------------------- + + @Test + public void defaultCommonSchedulerReportsMainThreadAware() { + CommonScheduler bareImpl = new RecordingScheduler(); + assertTrue(bareImpl.isMainThreadAware(), + "Default isMainThreadAware() must be true so server platforms " + + "(Bukkit/Sponge/Fabric) get the correct answer for free."); + } + + // -- adapter --------------------------------------------------------------- + + @Test + public void schedulerAdapterPropagatesMainThreadAwarenessFromDelegate() { + BanManagerScheduler proxyAware = new SchedulerAdapter(new ProxyLikeScheduler()); + assertFalse(proxyAware.isMainThreadAware(), + "SchedulerAdapter must surface the underlying CommonScheduler's value, " + + "not always return true."); + + BanManagerScheduler serverAware = new SchedulerAdapter(new RecordingScheduler()); + assertTrue(serverAware.isMainThreadAware(), + "SchedulerAdapter must surface a true value from a server-style delegate."); + } + + @Test + public void schedulerAdapterDelegatesEachMethodOnce() { + RecordingScheduler delegate = new RecordingScheduler(); + BanManagerScheduler adapter = new SchedulerAdapter(delegate); + + Runnable noop = () -> {}; + adapter.runAsync(noop); + adapter.runAsyncLater(noop, Duration.ZERO); + adapter.runSync(noop); + adapter.runSyncLater(noop, Duration.ZERO); + adapter.runAsyncRepeating(noop, Duration.ZERO, Duration.ofSeconds(1)); + + assertEquals(1, delegate.runAsyncCalls.get(), "runAsync delegated count"); + assertEquals(1, delegate.runAsyncLaterCalls.get(), "runAsyncLater delegated count"); + assertEquals(1, delegate.runSyncCalls.get(), "runSync delegated count"); + assertEquals(1, delegate.runSyncLaterCalls.get(), "runSyncLater delegated count"); + assertEquals(1, delegate.runAsyncRepeatingCalls.get(), "runAsyncRepeating delegated count"); + } + + // -- proxy semantics ------------------------------------------------------- + + @Test + public void proxyLikeSchedulerRoutesRunSyncIntoRunAsync() { + ProxyLikeScheduler proxy = new ProxyLikeScheduler(); + Runnable noop = () -> {}; + + proxy.runSync(noop); + + assertEquals(0, proxy.directRunSyncCalls.get(), + "Proxy schedulers must not implement an independent runSync path; " + + "Bungee/Velocity have no main thread to dispatch to."); + assertEquals(1, proxy.runAsyncCalls.get(), + "runSync on a proxy must route to runAsync to preserve the documented " + + "BanManagerScheduler aliasing contract."); + } + + @Test + public void proxyLikeSchedulerReportsNotMainThreadAware() { + assertFalse(new ProxyLikeScheduler().isMainThreadAware(), + "Proxy implementations must override isMainThreadAware() to false so " + + "callers don't try to invoke main-thread-only APIs."); + } + + // -- TestScheduler -------------------------------------------------------- + + @Test + public void testSchedulerReportsNotMainThreadAware() { + assertFalse(new TestScheduler().isMainThreadAware(), + "TestScheduler runs everything inline on the calling thread; it is " + + "explicitly not on a server tick thread, so isMainThreadAware() " + + "must return false to keep tests honest about proxy semantics."); + } + + @Test + public void testSchedulerRunsTaskInlineOnRunSync() { + TestScheduler scheduler = new TestScheduler(); + Thread caller = Thread.currentThread(); + AtomicInteger counter = new AtomicInteger(); + + scheduler.runSync(() -> { + assertSame(caller, Thread.currentThread(), + "TestScheduler must run tasks on the calling thread"); + counter.incrementAndGet(); + }); + + assertEquals(1, counter.get(), + "Task should run exactly once and synchronously."); + } + + // -- helpers --------------------------------------------------------------- + + /** + * Bare-bones {@link CommonScheduler} that counts invocations. Defaults to + * the interface's {@code isMainThreadAware() == true} so we can test that + * server-style platforms get the right behaviour without writing it + * explicitly (mirrors Bukkit/Sponge/Fabric inheritance). + */ + static final class RecordingScheduler implements CommonScheduler { + final AtomicInteger runAsyncCalls = new AtomicInteger(); + final AtomicInteger runAsyncLaterCalls = new AtomicInteger(); + final AtomicInteger runSyncCalls = new AtomicInteger(); + final AtomicInteger runSyncLaterCalls = new AtomicInteger(); + final AtomicInteger runAsyncRepeatingCalls = new AtomicInteger(); + + @Override public void runAsync(Runnable task) { runAsyncCalls.incrementAndGet(); } + @Override public void runAsyncLater(Runnable task, Duration delay) { runAsyncLaterCalls.incrementAndGet(); } + @Override public void runSync(Runnable task) { runSyncCalls.incrementAndGet(); } + @Override public void runSyncLater(Runnable task, Duration delay) { runSyncLaterCalls.incrementAndGet(); } + @Override + public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration period) { + runAsyncRepeatingCalls.incrementAndGet(); + } + } + + /** + * Mirrors the Bungee/Velocity contract: {@code runSync == runAsync} and + * {@code isMainThreadAware() == false}. {@code directRunSyncCalls} would + * fire only if a careless impl did its own dispatch — used to assert that + * the alias really does collapse the two paths. + */ + static final class ProxyLikeScheduler implements CommonScheduler { + final AtomicInteger runAsyncCalls = new AtomicInteger(); + final AtomicInteger directRunSyncCalls = new AtomicInteger(); + + @Override + public void runAsync(Runnable task) { + runAsyncCalls.incrementAndGet(); + task.run(); + } + + @Override + public void runAsyncLater(Runnable task, Duration delay) { + runAsync(task); + } + + @Override + public void runSync(Runnable task) { + runAsync(task); + } + + @Override + public void runSyncLater(Runnable task, Duration delay) { + runAsyncLater(task, delay); + } + + @Override + public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration period) { + runAsync(task); + } + + @Override + public boolean isMainThreadAware() { + return false; + } + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/event/EventBusImplTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/event/EventBusImplTest.java new file mode 100644 index 000000000..b9a2d0310 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/event/EventBusImplTest.java @@ -0,0 +1,311 @@ +package me.confuser.banmanager.common.impl.event; + +import me.confuser.banmanager.api.event.AbstractCancellableEvent; +import me.confuser.banmanager.api.event.BanManagerEvent; +import me.confuser.banmanager.api.event.CancellableEvent; +import me.confuser.banmanager.api.event.EventPriority; +import me.confuser.banmanager.api.event.Subscription; +import me.confuser.banmanager.common.CommonLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests {@link EventBusImpl}'s priority ordering, transitive supertype + * dispatch, cancellation gating, error isolation, and unsubscribe semantics. + */ +public class EventBusImplTest { + + interface MarkerEvent extends BanManagerEvent {} + + static final class ConcreteEvent implements MarkerEvent {} + + static final class CancellableConcreteEvent extends AbstractCancellableEvent implements MarkerEvent {} + + abstract static class AbstractMidEvent implements BanManagerEvent {} + + static final class LeafEvent extends AbstractMidEvent {} + + private RecordingLogger logger; + private EventBusImpl bus; + + @BeforeEach + public void setUp() { + logger = new RecordingLogger(); + bus = new EventBusImpl(logger); + } + + @Test + public void publishReturnsSameEventInstance() { + ConcreteEvent event = new ConcreteEvent(); + assertEquals(event, bus.publish(event)); + } + + @Test + public void priorityOrderingFiresLowestFirst() { + List order = new ArrayList<>(); + + bus.subscribe(ConcreteEvent.class, EventPriority.HIGH, e -> order.add(EventPriority.HIGH)); + bus.subscribe(ConcreteEvent.class, EventPriority.LOWEST, e -> order.add(EventPriority.LOWEST)); + bus.subscribe(ConcreteEvent.class, EventPriority.MONITOR, e -> order.add(EventPriority.MONITOR)); + bus.subscribe(ConcreteEvent.class, EventPriority.NORMAL, e -> order.add(EventPriority.NORMAL)); + bus.subscribe(ConcreteEvent.class, EventPriority.LOW, e -> order.add(EventPriority.LOW)); + bus.subscribe(ConcreteEvent.class, EventPriority.HIGHEST, e -> order.add(EventPriority.HIGHEST)); + + bus.publish(new ConcreteEvent()); + + assertEquals(List.of( + EventPriority.LOWEST, + EventPriority.LOW, + EventPriority.NORMAL, + EventPriority.HIGH, + EventPriority.HIGHEST, + EventPriority.MONITOR), order); + } + + @Test + public void priorityOrderingHonoursInsertionForEqualPriorities() { + List order = new ArrayList<>(); + + bus.subscribe(ConcreteEvent.class, EventPriority.NORMAL, e -> order.add("first")); + bus.subscribe(ConcreteEvent.class, EventPriority.NORMAL, e -> order.add("second")); + bus.subscribe(ConcreteEvent.class, EventPriority.NORMAL, e -> order.add("third")); + + bus.publish(new ConcreteEvent()); + + assertEquals(List.of("first", "second", "third"), order); + } + + @Test + public void dispatchIncludesDirectSuperinterface() { + AtomicInteger marker = new AtomicInteger(); + AtomicInteger concrete = new AtomicInteger(); + + bus.subscribe(MarkerEvent.class, e -> marker.incrementAndGet()); + bus.subscribe(ConcreteEvent.class, e -> concrete.incrementAndGet()); + + bus.publish(new ConcreteEvent()); + + assertEquals(1, marker.get(), "marker interface listener should fire"); + assertEquals(1, concrete.get(), "concrete listener should fire"); + } + + @Test + public void dispatchIncludesRootBanManagerEvent() { + AtomicInteger root = new AtomicInteger(); + + bus.subscribe(BanManagerEvent.class, e -> root.incrementAndGet()); + + bus.publish(new ConcreteEvent()); + + assertEquals(1, root.get(), "root marker subscription should observe every event"); + } + + @Test + public void dispatchIncludesIndirectInterfaceViaAbstractClass() { + AtomicInteger cancellableHits = new AtomicInteger(); + AtomicInteger markerHits = new AtomicInteger(); + + bus.subscribe(CancellableEvent.class, e -> cancellableHits.incrementAndGet()); + bus.subscribe(MarkerEvent.class, e -> markerHits.incrementAndGet()); + + bus.publish(new CancellableConcreteEvent()); + + assertEquals(1, cancellableHits.get(), + "CancellableEvent listener should fire for an event whose direct supertype is AbstractCancellableEvent"); + assertEquals(1, markerHits.get(), + "MarkerEvent listener should fire even though the event extends an abstract class first"); + } + + @Test + public void dispatchWalksAbstractSuperclassChain() { + AtomicInteger leaf = new AtomicInteger(); + AtomicInteger root = new AtomicInteger(); + + bus.subscribe(LeafEvent.class, e -> leaf.incrementAndGet()); + bus.subscribe(BanManagerEvent.class, e -> root.incrementAndGet()); + + bus.publish(new LeafEvent()); + + assertEquals(1, leaf.get()); + assertEquals(1, root.get(), + "BanManagerEvent listener should fire even when LeafEvent's supertype is abstract"); + } + + @Test + public void supertypeListenersAreNotInvokedTwiceForDiamondHierarchy() { + AtomicInteger root = new AtomicInteger(); + + bus.subscribe(BanManagerEvent.class, e -> root.incrementAndGet()); + + bus.publish(new CancellableConcreteEvent()); + + assertEquals(1, root.get(), + "BanManagerEvent must only fire once even though it's reachable via both class and interface"); + } + + @Test + public void cancelledEventsSkipLaterListenersByDefault() { + AtomicInteger first = new AtomicInteger(); + AtomicInteger second = new AtomicInteger(); + + bus.subscribe(CancellableConcreteEvent.class, EventPriority.LOW, e -> { + first.incrementAndGet(); + e.cancel(); + }); + bus.subscribe(CancellableConcreteEvent.class, EventPriority.HIGH, e -> second.incrementAndGet()); + + bus.publish(new CancellableConcreteEvent()); + + assertEquals(1, first.get()); + assertEquals(0, second.get(), "second listener should be skipped because event was cancelled"); + } + + @Test + public void ignoreCancelledListenersStillFireWhenCancelled() { + AtomicInteger second = new AtomicInteger(); + + bus.subscribe(CancellableConcreteEvent.class, EventPriority.LOW, false, CancellableEvent::cancel); + bus.subscribe(CancellableConcreteEvent.class, EventPriority.HIGH, true, e -> second.incrementAndGet()); + + bus.publish(new CancellableConcreteEvent()); + + assertEquals(1, second.get(), + "ignoreCancelled=true listener should fire even after a prior listener cancelled the event"); + } + + @Test + public void uncancelAllowsLaterListenersToProceed() { + AtomicInteger third = new AtomicInteger(); + + bus.subscribe(CancellableConcreteEvent.class, EventPriority.LOW, CancellableEvent::cancel); + bus.subscribe(CancellableConcreteEvent.class, EventPriority.NORMAL, true, CancellableEvent::uncancel); + bus.subscribe(CancellableConcreteEvent.class, EventPriority.HIGH, e -> third.incrementAndGet()); + + bus.publish(new CancellableConcreteEvent()); + + assertEquals(1, third.get(), + "after uncancel(), default-cancellation-respecting listeners should resume firing"); + } + + @Test + public void throwingHandlerDoesNotInterruptOthersAndIsLogged() { + AtomicInteger after = new AtomicInteger(); + + bus.subscribe(ConcreteEvent.class, EventPriority.LOW, e -> { + throw new RuntimeException("boom"); + }); + bus.subscribe(ConcreteEvent.class, EventPriority.HIGH, e -> after.incrementAndGet()); + + bus.publish(new ConcreteEvent()); + + assertEquals(1, after.get(), "later listeners should still run after a sibling throws"); + assertEquals(1, logger.warningMessages.size(), "the failure should be logged exactly once"); + assertNotNull(logger.warningThrowables.get(0)); + assertEquals("boom", logger.warningThrowables.get(0).getMessage()); + } + + @Test + public void errorLogIncludesHandlerIdentity() { + bus.subscribe(ConcreteEvent.class, new NamedHandler()); + bus.publish(new ConcreteEvent()); + + assertEquals(1, logger.warningMessages.size()); + String msg = logger.warningMessages.get(0); + assertTrue(msg.contains(NamedHandler.class.getName()), + "warning should name the offending handler class; was: " + msg); + assertTrue(msg.contains(ConcreteEvent.class.getName()), + "warning should name the event type; was: " + msg); + } + + @Test + public void errorLogIdentifiesLambdaByEnclosingClass() { + bus.subscribe(ConcreteEvent.class, e -> { + throw new RuntimeException("lambda boom"); + }); + bus.publish(new ConcreteEvent()); + + assertEquals(1, logger.warningMessages.size()); + String msg = logger.warningMessages.get(0); + assertTrue(msg.contains(EventBusImplTest.class.getName()), + "lambda warning should name the enclosing class; was: " + msg); + assertTrue(msg.contains("lambda"), + "lambda warning should identify the handler as a lambda; was: " + msg); + } + + @Test + public void unsubscribeStopsFurtherDispatch() { + AtomicInteger counter = new AtomicInteger(); + Subscription sub = bus.subscribe(ConcreteEvent.class, e -> counter.incrementAndGet()); + + bus.publish(new ConcreteEvent()); + assertEquals(1, counter.get()); + + assertFalse(sub.isCancelled()); + sub.unsubscribe(); + assertTrue(sub.isCancelled()); + + bus.publish(new ConcreteEvent()); + assertEquals(1, counter.get(), "unsubscribed handler must not fire"); + } + + @Test + public void unsubscribeIsIdempotent() { + Subscription sub = bus.subscribe(ConcreteEvent.class, e -> {}); + sub.unsubscribe(); + sub.unsubscribe(); + assertTrue(sub.isCancelled()); + } + + @Test + public void publishWithNoSubscribersIsNoop() { + ConcreteEvent event = new ConcreteEvent(); + assertEquals(event, bus.publish(event)); + assertEquals(0, logger.warningMessages.size()); + } + + /** + * Concrete (non-lambda) handler so we can assert the class name appears in + * the error log without relying on the JVM's lambda naming scheme. + */ + static final class NamedHandler implements java.util.function.Consumer { + @Override + public void accept(ConcreteEvent concreteEvent) { + throw new IllegalStateException("named handler boom"); + } + } + + /** + * Captures messages routed through {@link CommonLogger} so tests can assert + * what got logged without going through real JUL infrastructure. Overrides + * {@code warning(String, Throwable)} so we can keep the throwable alongside + * the message instead of letting the default impl flatten it onto a second + * call to {@code warning(String)}. + */ + static final class RecordingLogger implements CommonLogger { + final List warningMessages = new ArrayList<>(); + final List warningThrowables = new ArrayList<>(); + + @Override public void info(String s) { /* tests don't assert on info output */ } + + @Override public void warning(String s) { + warningMessages.add(s); + warningThrowables.add(null); + } + + @Override public void warning(String s, Throwable t) { + warningMessages.add(s); + warningThrowables.add(t); + } + + @Override public void severe(String s) { /* tests don't assert on severe output */ } + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/BanServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/BanServiceImplCancellationTest.java new file mode 100644 index 000000000..db6e0ee92 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/BanServiceImplCancellationTest.java @@ -0,0 +1,169 @@ +package me.confuser.banmanager.common.impl.service; + +import inet.ipaddr.AddressStringException; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.dto.PlayerBan; +import me.confuser.banmanager.api.request.BanRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerBanData; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.ipaddr.IPAddressString; +import me.confuser.banmanager.common.storage.PlayerBanStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link BanServiceImpl} translates storage-level cancellation + * (storage returning {@code false} after a pre-event veto) into the documented + * sentinel values: + *
    + *
  • {@code banSync}/{@code ban} resolve to {@link Optional#empty()}
  • + *
  • {@code unbanSync}/{@code unban} resolve to {@link Boolean#FALSE}
  • + *
  • Neither path completes the future exceptionally
  • + *
+ * + *

Pre/post event publication itself lives on {@link PlayerBanStorage}; the + * storage-mock here is configured to mimic that contract without actually + * publishing.

+ */ +public class BanServiceImplCancellationTest { + + private static final UUID PLAYER = UUID.fromString("33333333-3333-3333-3333-333333333333"); + private static final UUID ACTOR = UUID.fromString("44444444-4444-4444-4444-444444444444"); + + private BanManagerPlugin plugin; + private PlayerBanStorage banStorage; + private PlayerStorage playerStorage; + private BanServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + plugin = mock(BanManagerPlugin.class); + banStorage = mock(PlayerBanStorage.class); + playerStorage = mock(PlayerStorage.class); + when(plugin.getPlayerBanStorage()).thenReturn(banStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(newPlayer(PLAYER, "Alice"), newPlayer(ACTOR, "ModBob")); + + AsyncSupport async = new AsyncSupport(synchronousExecutor()); + service = new BanServiceImpl(plugin, async); + } + + // -- ban: storage cancellation surfaces as empty ------------------------- + + @Test + public void banSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(banStorage.ban(any())).thenReturn(false); + + Optional result = service.banSync(new BanRequest(PLAYER, ACTOR, "spam")); + + assertEquals(Optional.empty(), result); + } + + @Test + public void asyncBanResolvesToEmptyWhenStorageReportsCancelled() throws Exception { + when(banStorage.ban(any())).thenReturn(false); + + CompletableFuture> future = + service.ban(new BanRequest(PLAYER, ACTOR, "spam")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + // -- unban: storage cancellation surfaces as false ----------------------- + + @Test + public void unbanSyncReturnsFalseWhenStorageReportsCancelled() throws Exception { + when(banStorage.getBan(PLAYER)).thenReturn(newBanData()); + when(banStorage.unban(any(), any(), anyString(), anyBoolean(), anyBoolean())).thenReturn(false); + + boolean result = service.unbanSync(PLAYER, newPlayerDto(ACTOR), "appealed", false); + + assertFalse(result); + } + + @Test + public void asyncUnbanResolvesToFalseWhenStorageReportsCancelled() throws Exception { + when(banStorage.getBan(PLAYER)).thenReturn(newBanData()); + when(banStorage.unban(any(), any(), anyString(), anyBoolean(), anyBoolean())).thenReturn(false); + + CompletableFuture future = service.unban(PLAYER, newPlayerDto(ACTOR), "appealed", false); + + assertEquals(Boolean.FALSE, future.join()); + assertFalse(future.isCompletedExceptionally()); + } + + // -- unban: missing-ban early exit --------------------------------------- + + @Test + public void asyncUnbanResolvesToFalseWhenNoActiveBan() throws java.sql.SQLException { + when(banStorage.getBan(PLAYER)).thenReturn(null); + + CompletableFuture future = service.unban(PLAYER, newPlayerDto(ACTOR), "appealed", false); + + assertSame(Boolean.FALSE, future.join(), + "missing-ban early-out should resolve FALSE without invoking storage.unban"); + verify(banStorage, never()).unban(any(), any(), anyString(), anyBoolean(), anyBoolean()); + } + + @Test + public void unbanSyncReturnsFalseWhenNoActiveBan() { + when(banStorage.getBan(PLAYER)).thenReturn(null); + + boolean result = service.unbanSync(PLAYER, newPlayerDto(ACTOR), "appealed", false); + + assertFalse(result); + } + + // -- helpers ------------------------------------------------------------- + + private static PlayerData newPlayer(UUID uuid, String name) throws Exception { + return new PlayerData(uuid, name, new IPAddressString("203.0.113.42").toAddress()); + } + + private static PlayerBanData newBanData() throws Exception { + return new PlayerBanData(newPlayer(PLAYER, "Alice"), newPlayer(ACTOR, "ModBob"), + "spam", false, 0L); + } + + private static Player newPlayerDto(UUID uuid) { + return new Player(uuid, "ModBob", apiAddress("203.0.113.43"), 1_700_000_000L); + } + + private static inet.ipaddr.IPAddress apiAddress(String s) { + try { + return new inet.ipaddr.IPAddressString(s).toAddress(); + } catch (AddressStringException e) { + throw new IllegalStateException(e); + } + } + + /** + * Inline executor so async work runs deterministically on the calling + * thread — no thread-pool teardown to fight at @AfterEach time and no risk + * of a flaky get() timeout under CI load. + */ + private static Executor synchronousExecutor() { + return Runnable::run; + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/HistoryServiceImplTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/HistoryServiceImplTest.java new file mode 100644 index 000000000..c23976c98 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/HistoryServiceImplTest.java @@ -0,0 +1,104 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.Page; +import me.confuser.banmanager.api.dto.HistoryEntry; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link HistoryServiceImpl} validates pagination up front and + * returns empty results (without ever hitting the history tables) when the + * target player is unknown. + */ +public class HistoryServiceImplTest { + + private static final UUID PLAYER = UUID.fromString("11111111-1111-1111-1111-111111111111"); + + private BanManagerPlugin plugin; + private PlayerStorage playerStorage; + private HistoryServiceImpl service; + + @BeforeEach + public void setUp() { + plugin = mock(BanManagerPlugin.class); + playerStorage = mock(PlayerStorage.class); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + + service = new HistoryServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void historySyncReturnsEmptyPageWhenPlayerUnknown() throws Exception { + when(playerStorage.queryForId(any())).thenReturn(null); + + Page page = service.historySync(PLAYER, 0, 10); + + assertTrue(page.items().isEmpty()); + assertEquals(0, page.page()); + assertEquals(10, page.size()); + verify(plugin, never()).getHistoryStorage(); + } + + @Test + public void namesSyncReturnsEmptyListWhenPlayerUnknown() throws Exception { + when(playerStorage.queryForId(any())).thenReturn(null); + + assertTrue(service.namesSync(PLAYER).isEmpty()); + verify(plugin, never()).getPlayerHistoryStorage(); + } + + @Test + public void nameAtSyncReturnsEmptyWhenPlayerUnknown() throws Exception { + when(playerStorage.queryForId(any())).thenReturn(null); + + assertEquals(Optional.empty(), service.nameAtSync(PLAYER, 1_700_000_000L)); + verify(plugin, never()).getPlayerHistoryStorage(); + } + + @Test + public void sessionsSyncReturnsEmptyPageWhenPlayerUnknown() throws Exception { + when(playerStorage.queryForId(any())).thenReturn(null); + + Page page = service.sessionsSync(PLAYER, 0L, 0, 25); + + assertTrue(page.items().isEmpty()); + verify(plugin, never()).getPlayerHistoryStorage(); + } + + @Test + public void historySyncRejectsNegativePage() { + assertThrows(IllegalArgumentException.class, () -> service.historySync(PLAYER, -1, 10)); + } + + @Test + public void historySyncRejectsNonPositiveSize() { + assertThrows(IllegalArgumentException.class, () -> service.historySync(PLAYER, 0, 0)); + } + + @Test + public void historySyncRejectsOversizedPage() { + assertThrows(IllegalArgumentException.class, () -> service.historySync(PLAYER, 0, Integer.MAX_VALUE)); + } + + @Test + public void sessionsSyncRejectsInvalidPagination() { + assertThrows(IllegalArgumentException.class, () -> service.sessionsSync(PLAYER, 0L, -1, 10)); + assertThrows(IllegalArgumentException.class, () -> service.sessionsSync(PLAYER, 0L, 0, 0)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/IpBanServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/IpBanServiceImplCancellationTest.java new file mode 100644 index 000000000..75f0623bb --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/IpBanServiceImplCancellationTest.java @@ -0,0 +1,90 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.IpBan; +import me.confuser.banmanager.api.request.IpBanRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.ipaddr.IPAddress; +import me.confuser.banmanager.common.storage.IpBanStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.apiIp; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerDto; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link IpBanServiceImpl} honours the sentinel contract: a storage + * veto on {@code ban} surfaces as {@link Optional#empty()}, and {@code unban} + * short-circuits to {@code false} without touching storage when no active ban + * exists for the address. + */ +public class IpBanServiceImplCancellationTest { + + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private IpBanStorage ipBanStorage; + private IpBanServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + ipBanStorage = mock(IpBanStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getIpBanStorage()).thenReturn(ipBanStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(ACTOR, "ModBob")); + + service = new IpBanServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void banSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(ipBanStorage.ban(any())).thenReturn(false); + + assertEquals(Optional.empty(), service.banSync(new IpBanRequest(apiIp("198.51.100.7"), ACTOR, "botnet"))); + } + + @Test + public void asyncBanResolvesToEmptyWhenCancelled() throws Exception { + when(ipBanStorage.ban(any())).thenReturn(false); + + CompletableFuture> future = + service.ban(new IpBanRequest(apiIp("198.51.100.7"), ACTOR, "botnet")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void unbanSyncReturnsFalseWhenNoActiveBan() throws Exception { + when(ipBanStorage.getBan(any(IPAddress.class))).thenReturn(null); + + boolean result = service.unbanSync(apiIp("198.51.100.7"), playerDto(ACTOR, "ModBob"), "appealed", false); + + assertFalse(result); + verify(ipBanStorage, never()).unban(any(), any(), anyString(), anyBoolean(), anyBoolean()); + } + + @Test + public void banSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.banSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/IpMuteServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/IpMuteServiceImplCancellationTest.java new file mode 100644 index 000000000..4b6e6bcf0 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/IpMuteServiceImplCancellationTest.java @@ -0,0 +1,90 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.IpMute; +import me.confuser.banmanager.api.request.IpMuteRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.ipaddr.IPAddress; +import me.confuser.banmanager.common.storage.IpMuteStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.apiIp; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerDto; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link IpMuteServiceImpl} honours the sentinel contract: a storage + * veto on {@code mute} surfaces as {@link Optional#empty()}, and {@code unmute} + * short-circuits to {@code false} without touching storage when no active mute + * exists for the address. + */ +public class IpMuteServiceImplCancellationTest { + + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private IpMuteStorage ipMuteStorage; + private IpMuteServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + ipMuteStorage = mock(IpMuteStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getIpMuteStorage()).thenReturn(ipMuteStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(ACTOR, "ModBob")); + + service = new IpMuteServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void muteSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(ipMuteStorage.mute(any())).thenReturn(false); + + assertEquals(Optional.empty(), service.muteSync(new IpMuteRequest(apiIp("198.51.100.7"), ACTOR, "spam"))); + } + + @Test + public void asyncMuteResolvesToEmptyWhenCancelled() throws Exception { + when(ipMuteStorage.mute(any())).thenReturn(false); + + CompletableFuture> future = + service.mute(new IpMuteRequest(apiIp("198.51.100.7"), ACTOR, "spam")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void unmuteSyncReturnsFalseWhenNoActiveMute() throws Exception { + when(ipMuteStorage.getMute(any(IPAddress.class))).thenReturn(null); + + boolean result = service.unmuteSync(apiIp("198.51.100.7"), playerDto(ACTOR, "ModBob"), "appealed", false); + + assertFalse(result); + verify(ipMuteStorage, never()).unmute(any(), any(), anyString(), anyBoolean()); + } + + @Test + public void muteSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.muteSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/IpRangeBanServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/IpRangeBanServiceImplCancellationTest.java new file mode 100644 index 000000000..3f7f8b42e --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/IpRangeBanServiceImplCancellationTest.java @@ -0,0 +1,95 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.IpRangeBan; +import me.confuser.banmanager.api.request.IpRangeBanRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.ipaddr.IPAddress; +import me.confuser.banmanager.common.storage.IpRangeBanStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.apiIp; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerDto; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link IpRangeBanServiceImpl} honours the sentinel contract: a + * storage veto on {@code ban} surfaces as {@link Optional#empty()}, and + * {@code unban} short-circuits to {@code false} without touching storage when + * no active range ban matches the supplied range. + */ +public class IpRangeBanServiceImplCancellationTest { + + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private IpRangeBanStorage rangeBanStorage; + private IpRangeBanServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + rangeBanStorage = mock(IpRangeBanStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getIpRangeBanStorage()).thenReturn(rangeBanStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(ACTOR, "ModBob")); + + service = new IpRangeBanServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + private static IpRangeBanRequest request() { + return new IpRangeBanRequest(apiIp("198.51.100.0"), apiIp("198.51.100.255"), ACTOR, "subnet abuse"); + } + + @Test + public void banSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(rangeBanStorage.ban(any())).thenReturn(false); + + assertEquals(Optional.empty(), service.banSync(request())); + } + + @Test + public void asyncBanResolvesToEmptyWhenCancelled() throws Exception { + when(rangeBanStorage.ban(any())).thenReturn(false); + + CompletableFuture> future = service.ban(request()); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void unbanSyncReturnsFalseWhenNoActiveBan() throws Exception { + when(rangeBanStorage.getBan(any(IPAddress.class))).thenReturn(null); + IpRangeBan ban = new IpRangeBan(1, apiIp("198.51.100.0"), apiIp("198.51.100.255"), + playerDto(ACTOR, "ModBob"), "subnet abuse", 0L, 0L, 0L, false); + + boolean result = service.unbanSync(ban, playerDto(ACTOR, "ModBob"), "appealed", false); + + assertFalse(result); + verify(rangeBanStorage, never()).unban(any(), any(), anyString(), anyBoolean()); + } + + @Test + public void banSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.banSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/MuteServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/MuteServiceImplCancellationTest.java new file mode 100644 index 000000000..d0055f1f2 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/MuteServiceImplCancellationTest.java @@ -0,0 +1,88 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.PlayerMute; +import me.confuser.banmanager.api.request.MuteRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.storage.PlayerMuteStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerDto; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link MuteServiceImpl} honours the documented sentinel contract: + * a storage-level veto surfaces as {@link Optional#empty()} (mute) / + * {@code false} (unmute) rather than a failed future, and {@code unmute} + * short-circuits without touching storage when no active mute exists. + */ +public class MuteServiceImplCancellationTest { + + private static final UUID PLAYER = UUID.fromString("11111111-1111-1111-1111-111111111111"); + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private PlayerMuteStorage muteStorage; + private MuteServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + muteStorage = mock(PlayerMuteStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getPlayerMuteStorage()).thenReturn(muteStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(PLAYER, "Alice")); + + service = new MuteServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void muteSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(muteStorage.mute(any())).thenReturn(false); + + assertEquals(Optional.empty(), service.muteSync(new MuteRequest(PLAYER, ACTOR, "spam"))); + } + + @Test + public void asyncMuteResolvesToEmptyWhenCancelled() throws Exception { + when(muteStorage.mute(any())).thenReturn(false); + + CompletableFuture> future = service.mute(new MuteRequest(PLAYER, ACTOR, "spam")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void unmuteSyncReturnsFalseWhenNoActiveMute() throws Exception { + when(muteStorage.getMute(any(UUID.class))).thenReturn(null); + + boolean result = service.unmuteSync(PLAYER, playerDto(ACTOR, "ModBob"), "appealed", false); + + assertFalse(result); + verify(muteStorage, never()).unmute(any(), any(), anyString(), anyBoolean(), anyBoolean()); + } + + @Test + public void muteSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.muteSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/NameBanServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/NameBanServiceImplCancellationTest.java new file mode 100644 index 000000000..38a8d5e18 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/NameBanServiceImplCancellationTest.java @@ -0,0 +1,88 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.NameBan; +import me.confuser.banmanager.api.request.NameBanRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.storage.NameBanStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerDto; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link NameBanServiceImpl} honours the sentinel contract: a + * storage veto on {@code ban} surfaces as {@link Optional#empty()}, and + * {@code unban} short-circuits to {@code false} without touching storage + * when no active name ban exists. + */ +public class NameBanServiceImplCancellationTest { + + private static final String NAME = "BadActor"; + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private NameBanStorage nameBanStorage; + private NameBanServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + nameBanStorage = mock(NameBanStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getNameBanStorage()).thenReturn(nameBanStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(ACTOR, "ModBob")); + + service = new NameBanServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void banSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(nameBanStorage.ban(any())).thenReturn(false); + + assertEquals(Optional.empty(), service.banSync(new NameBanRequest(NAME, ACTOR, "impersonation"))); + } + + @Test + public void asyncBanResolvesToEmptyWhenCancelled() throws Exception { + when(nameBanStorage.ban(any())).thenReturn(false); + + CompletableFuture> future = service.ban(new NameBanRequest(NAME, ACTOR, "impersonation")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void unbanSyncReturnsFalseWhenNoActiveBan() throws Exception { + when(nameBanStorage.getBan(anyString())).thenReturn(null); + + boolean result = service.unbanSync(NAME, playerDto(ACTOR, "ModBob"), "appealed", false); + + assertFalse(result); + verify(nameBanStorage, never()).unban(any(), any(), anyString(), anyBoolean(), anyBoolean()); + } + + @Test + public void banSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.banSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/NoteServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/NoteServiceImplCancellationTest.java new file mode 100644 index 000000000..e250a11fb --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/NoteServiceImplCancellationTest.java @@ -0,0 +1,72 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.PlayerNote; +import me.confuser.banmanager.api.request.NoteRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.storage.PlayerNoteStorage; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link NoteServiceImpl} surfaces a storage-level veto on + * {@code addNote} as {@link Optional#empty()} (sync) / a non-exceptional + * empty future (async). + */ +public class NoteServiceImplCancellationTest { + + private static final UUID PLAYER = UUID.fromString("11111111-1111-1111-1111-111111111111"); + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private PlayerNoteStorage noteStorage; + private NoteServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + noteStorage = mock(PlayerNoteStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getPlayerNoteStorage()).thenReturn(noteStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(PLAYER, "Alice")); + + service = new NoteServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void createSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(noteStorage.addNote(any())).thenReturn(false); + + assertEquals(Optional.empty(), service.createSync(new NoteRequest(PLAYER, ACTOR, "alt account"))); + } + + @Test + public void asyncCreateResolvesToEmptyWhenCancelled() throws Exception { + when(noteStorage.addNote(any())).thenReturn(false); + + CompletableFuture> future = service.create(new NoteRequest(PLAYER, ACTOR, "alt account")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void createSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.createSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/PlayerServiceImplPropagationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/PlayerServiceImplPropagationTest.java new file mode 100644 index 000000000..a1ef16d16 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/PlayerServiceImplPropagationTest.java @@ -0,0 +1,191 @@ +package me.confuser.banmanager.common.impl.service; + +import inet.ipaddr.AddressStringException; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.exception.StorageException; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.ipaddr.IPAddressString; +import me.confuser.banmanager.common.storage.PlayerStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Verifies the new strict-storage path on {@link PlayerServiceImpl}: prior to + * the async-layer audit, {@code findByNameSync} and {@code findByIpSync} + * silently swallowed {@link SQLException} (returning {@code null}/empty list) + * because they reused the legacy {@code retrieve} / {@code getDuplicatesInTime} + * helpers. The new wiring routes through + * {@code PlayerStorage#findByExactName} and {@code findDuplicatesInTime} via + * {@link AsyncSupport#sync(AsyncSupport.SqlCallable, String)} so failures + * surface as {@link StorageException} with a meaningful contextual message. + */ +public class PlayerServiceImplPropagationTest { + + private BanManagerPlugin plugin; + private PlayerStorage playerStorage; + private PlayerServiceImpl service; + + @BeforeEach + public void setUp() { + plugin = mock(BanManagerPlugin.class); + playerStorage = mock(PlayerStorage.class); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + + AsyncSupport async = new AsyncSupport(synchronousExecutor()); + service = new PlayerServiceImpl(plugin, async); + } + + // -- findByNameSync -------------------------------------------------------- + + @Test + public void findByNameSyncReturnsEmptyWhenStorageReturnsNull() throws Exception { + when(playerStorage.findByExactName("Ghost")).thenReturn(null); + + Optional result = service.findByNameSync("Ghost"); + + assertFalse(result.isPresent(), + "missing player must surface as Optional.empty(), not null"); + } + + @Test + public void findByNameSyncReturnsMappedPlayerWhenFound() throws Exception { + UUID uuid = UUID.fromString("11111111-1111-1111-1111-111111111111"); + when(playerStorage.findByExactName("Alice")) + .thenReturn(newInternalPlayer(uuid, "Alice", "203.0.113.10")); + + Optional result = service.findByNameSync("Alice"); + + assertTrue(result.isPresent()); + assertEquals(uuid, result.get().uuid()); + assertEquals("Alice", result.get().name()); + } + + @Test + public void findByNameSyncWrapsSqlExceptionWithContextualMessage() throws Exception { + SQLException root = new SQLException("connection refused"); + when(playerStorage.findByExactName(anyString())).thenThrow(root); + + StorageException ex = assertThrows(StorageException.class, + () -> service.findByNameSync("Bob")); + + assertEquals("Failed to look up player by name Bob", ex.getMessage()); + assertSame(root, ex.getCause(), + "original SQLException must be preserved as the cause"); + } + + @Test + public void findByNameAsyncFailsFutureWithStorageExceptionForSqlException() throws Exception { + SQLException root = new SQLException("connection refused"); + when(playerStorage.findByExactName(anyString())).thenThrow(root); + + CompletableFuture> future = service.findByName("Carol"); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + assertTrue(ex.getCause() instanceof StorageException, + "expected StorageException; got " + ex.getCause()); + assertEquals("Failed to look up player by name Carol", ex.getCause().getMessage()); + assertSame(root, ex.getCause().getCause()); + } + + // -- findByIpSync ---------------------------------------------------------- + + @Test + public void findByIpSyncMapsPlayersFromStorage() throws Exception { + UUID uuid = UUID.fromString("22222222-2222-2222-2222-222222222222"); + PlayerData data = newInternalPlayer(uuid, "Dave", "198.51.100.5"); + when(playerStorage.findDuplicatesInTime(any(), anyLong())) + .thenReturn(Collections.singletonList(data)); + + List matches = service.findByIpSync(apiAddress("198.51.100.5")); + + assertEquals(1, matches.size()); + assertEquals(uuid, matches.get(0).uuid()); + assertEquals("Dave", matches.get(0).name()); + } + + @Test + public void findByIpSyncReturnsEmptyListWhenNoMatches() throws Exception { + when(playerStorage.findDuplicatesInTime(any(), anyLong())).thenReturn(Arrays.asList()); + + List matches = service.findByIpSync(apiAddress("198.51.100.5")); + + assertTrue(matches.isEmpty()); + } + + @Test + public void findByIpSyncWrapsSqlExceptionWithContextualMessage() throws Exception { + SQLException root = new SQLException("driver lost"); + when(playerStorage.findDuplicatesInTime(any(), anyLong())).thenThrow(root); + + inet.ipaddr.IPAddress ip = apiAddress("203.0.113.99"); + StorageException ex = assertThrows(StorageException.class, + () -> service.findByIpSync(ip)); + + assertTrue(ex.getMessage().startsWith("Failed to look up players by IP "), + "context message should mention the lookup operation; got: " + ex.getMessage()); + assertTrue(ex.getMessage().contains("203.0.113.99"), + "context message should include the IP for forensics; got: " + ex.getMessage()); + assertSame(root, ex.getCause()); + } + + @Test + public void findByIpAsyncFailsFutureWithStorageExceptionForSqlException() throws Exception { + SQLException root = new SQLException("network"); + when(playerStorage.findDuplicatesInTime(any(), anyLong())).thenThrow(root); + + CompletableFuture> future = service.findByIp(apiAddress("203.0.113.99")); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + assertTrue(ex.getCause() instanceof StorageException, + "expected StorageException; got " + ex.getCause()); + assertSame(root, ex.getCause().getCause()); + } + + // -- helpers --------------------------------------------------------------- + + private static PlayerData newInternalPlayer(UUID uuid, String name, String ip) throws Exception { + return new PlayerData(uuid, name, new IPAddressString(ip).toAddress()); + } + + private static inet.ipaddr.IPAddress apiAddress(String s) { + try { + return new inet.ipaddr.IPAddressString(s).toAddress(); + } catch (AddressStringException e) { + throw new IllegalStateException(e); + } + } + + /** + * Inline executor so async work runs deterministically on the calling + * thread; avoids thread-pool teardown plumbing in the test. + */ + private static Executor synchronousExecutor() { + return Runnable::run; + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/ReportServiceImplStatesTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/ReportServiceImplStatesTest.java new file mode 100644 index 000000000..9c02352e9 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/ReportServiceImplStatesTest.java @@ -0,0 +1,164 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.ReportState; +import me.confuser.banmanager.api.exception.BanManagerException; +import me.confuser.banmanager.api.exception.StorageException; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.storage.ReportStateStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link ReportServiceImpl#states()} / {@code statesSync()} satisfy + * the async-layer contract: + *
    + *
  • {@code states()} returns a {@link CompletableFuture} so callers don't + * block the main thread on a workflow-state lookup
  • + *
  • {@code statesSync()} returns the same data synchronously for callers + * already on a worker thread
  • + *
  • {@link SQLException} from the storage layer surfaces as + * {@link StorageException} with the contextual error message + * ("Failed to load report states") on both variants
  • + *
+ */ +public class ReportServiceImplStatesTest { + + private BanManagerPlugin plugin; + private ReportStateStorage stateStorage; + private ReportServiceImpl service; + + @BeforeEach + public void setUp() { + plugin = mock(BanManagerPlugin.class); + stateStorage = mock(ReportStateStorage.class); + when(plugin.getReportStateStorage()).thenReturn(stateStorage); + + AsyncSupport async = new AsyncSupport(synchronousExecutor()); + + service = new ReportServiceImpl(plugin, async); + } + + @Test + public void statesSyncMapsInternalRowsToApiDtos() throws Exception { + when(stateStorage.queryForAll()).thenReturn(Arrays.asList( + internalState(1, "Open"), + internalState(2, "Resolved"))); + + List result = service.statesSync(); + + assertEquals(2, result.size()); + assertEquals(1, result.get(0).id()); + assertEquals("Open", result.get(0).name()); + assertEquals(2, result.get(1).id()); + assertEquals("Resolved", result.get(1).name()); + } + + @Test + public void statesSyncReturnsEmptyListWhenStorageEmpty() throws Exception { + when(stateStorage.queryForAll()).thenReturn(Collections.emptyList()); + + List result = service.statesSync(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void statesSyncWrapsSqlExceptionWithContextMessage() throws Exception { + SQLException root = new SQLException("connection refused"); + when(stateStorage.queryForAll()).thenThrow(root); + + StorageException ex = assertThrows(StorageException.class, () -> service.statesSync()); + + assertEquals("Failed to load report states", ex.getMessage()); + assertSame(root, ex.getCause()); + } + + @Test + public void statesAsyncReturnsCompletableFutureWithMappedDtos() throws Exception { + when(stateStorage.queryForAll()).thenReturn(Collections.singletonList(internalState(7, "Closed"))); + + CompletableFuture> future = service.states(); + + assertNotNull(future, "states() must return a non-null future"); + List result = future.get(5, TimeUnit.SECONDS); + assertEquals(1, result.size()); + assertEquals(7, result.get(0).id()); + assertEquals("Closed", result.get(0).name()); + } + + @Test + public void statesAsyncFailsFutureWithStorageExceptionForSqlException() throws Exception { + SQLException root = new SQLException("driver lost"); + when(stateStorage.queryForAll()).thenThrow(root); + + CompletableFuture> future = service.states(); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + assertTrue(ex.getCause() instanceof StorageException, + "expected StorageException cause; got " + ex.getCause()); + assertEquals("Failed to load report states", ex.getCause().getMessage()); + assertSame(root, ex.getCause().getCause()); + } + + @Test + public void statesAsyncFailsFutureWithBanManagerExceptionForRuntimeException() throws Exception { + RuntimeException root = new RuntimeException("unexpected"); + when(stateStorage.queryForAll()).thenThrow(root); + + CompletableFuture> future = service.states(); + + ExecutionException ex = assertThrows(ExecutionException.class, + () -> future.get(5, TimeUnit.SECONDS)); + // Plain RuntimeException isn't a BanManagerException so AsyncSupport.sync + // wraps it with the contextual message; the future should still surface + // it via getCause(). + assertTrue(ex.getCause() instanceof BanManagerException, + "expected BanManagerException cause; got " + ex.getCause()); + assertEquals("Failed to load report states", ex.getCause().getMessage()); + assertSame(root, ex.getCause().getCause()); + } + + private static me.confuser.banmanager.common.data.ReportState internalState(int id, String name) { + // ReportState only exposes a generated id from ORMLite, so we set the + // private field via reflection to keep the test free of a real DB. + me.confuser.banmanager.common.data.ReportState row = + new me.confuser.banmanager.common.data.ReportState(name); + try { + java.lang.reflect.Field idField = me.confuser.banmanager.common.data.ReportState.class + .getDeclaredField("id"); + idField.setAccessible(true); + idField.setInt(row, id); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Failed to set test ReportState id", e); + } + return row; + } + + /** + * Inline executor so async work runs deterministically on the calling + * thread; avoids thread-pool teardown plumbing in the test. + */ + private static Executor synchronousExecutor() { + return Runnable::run; + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/ServiceTestFixtures.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/ServiceTestFixtures.java new file mode 100644 index 000000000..c859134f9 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/ServiceTestFixtures.java @@ -0,0 +1,50 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.common.data.PlayerData; + +import java.util.UUID; +import java.util.concurrent.Executor; + +/** + * Shared fixtures for the {@code *ServiceImpl} contract tests. Keeps each + * test focused on the behaviour under test rather than re-deriving the + * shaded/unshaded IP and {@link PlayerData} plumbing in every file. + */ +final class ServiceTestFixtures { + + private ServiceTestFixtures() {} + + /** + * Inline executor so {@code async(...)} work runs deterministically on the + * calling thread — no thread-pool teardown to fight and no flaky + * {@code get()} timeouts under CI load. + */ + static Executor synchronousExecutor() { + return Runnable::run; + } + + /** Internal (shaded) {@link PlayerData} entity returned by mocked storage. */ + static PlayerData playerEntity(UUID uuid, String name) { + try { + return new PlayerData(uuid, name, + new me.confuser.banmanager.common.ipaddr.IPAddressString("203.0.113.42").toAddress()); + } catch (me.confuser.banmanager.common.ipaddr.AddressStringException e) { + throw new IllegalStateException("invalid fixture player IP", e); + } + } + + /** Public-API {@link Player} DTO used as the {@code actor} on unban/unmute calls. */ + static Player playerDto(UUID uuid, String name) { + return new Player(uuid, name, apiIp("203.0.113.43"), 1_700_000_000L); + } + + /** Unshaded public-API {@link inet.ipaddr.IPAddress} from a host literal. */ + static inet.ipaddr.IPAddress apiIp(String literal) { + inet.ipaddr.IPAddress address = new inet.ipaddr.IPAddressString(literal).getAddress(); + if (address == null) { + throw new IllegalArgumentException("invalid fixture IP: " + literal); + } + return address; + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/impl/service/WarnServiceImplCancellationTest.java b/common/src/test/java/me/confuser/banmanager/common/impl/service/WarnServiceImplCancellationTest.java new file mode 100644 index 000000000..b6a683c83 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/impl/service/WarnServiceImplCancellationTest.java @@ -0,0 +1,73 @@ +package me.confuser.banmanager.common.impl.service; + +import me.confuser.banmanager.api.dto.PlayerWarn; +import me.confuser.banmanager.api.request.WarnRequest; +import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.common.impl.AsyncSupport; +import me.confuser.banmanager.common.storage.PlayerStorage; +import me.confuser.banmanager.common.storage.PlayerWarnStorage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.playerEntity; +import static me.confuser.banmanager.common.impl.service.ServiceTestFixtures.synchronousExecutor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Verifies {@link WarnServiceImpl} surfaces a storage-level veto on + * {@code addWarning} as {@link Optional#empty()} (sync) / a non-exceptional + * empty future (async) rather than completing exceptionally. + */ +public class WarnServiceImplCancellationTest { + + private static final UUID PLAYER = UUID.fromString("11111111-1111-1111-1111-111111111111"); + private static final UUID ACTOR = UUID.fromString("22222222-2222-2222-2222-222222222222"); + + private PlayerWarnStorage warnStorage; + private WarnServiceImpl service; + + @BeforeEach + public void setUp() throws Exception { + BanManagerPlugin plugin = mock(BanManagerPlugin.class); + warnStorage = mock(PlayerWarnStorage.class); + PlayerStorage playerStorage = mock(PlayerStorage.class); + when(plugin.getPlayerWarnStorage()).thenReturn(warnStorage); + when(plugin.getPlayerStorage()).thenReturn(playerStorage); + when(playerStorage.queryForId(any())).thenReturn(playerEntity(PLAYER, "Alice")); + + service = new WarnServiceImpl(plugin, new AsyncSupport(synchronousExecutor())); + } + + @Test + public void warnSyncReturnsEmptyWhenStorageReportsCancelled() throws Exception { + when(warnStorage.addWarning(any(), anyBoolean())).thenReturn(false); + + assertEquals(Optional.empty(), service.warnSync(new WarnRequest(PLAYER, ACTOR, "spam"))); + } + + @Test + public void asyncWarnResolvesToEmptyWhenCancelled() throws Exception { + when(warnStorage.addWarning(any(), anyBoolean())).thenReturn(false); + + CompletableFuture> future = service.warn(new WarnRequest(PLAYER, ACTOR, "spam")); + + assertEquals(Optional.empty(), future.join()); + assertFalse(future.isCompletedExceptionally(), + "cancellation must resolve to empty, not a failed future"); + } + + @Test + public void warnSyncThrowsOnNullRequest() { + assertThrows(NullPointerException.class, () -> service.warnSync(null)); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java b/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java index bc9c6e8d9..c7f5535be 100755 --- a/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java @@ -1,9 +1,11 @@ package me.confuser.banmanager.common.listeners; +import me.confuser.banmanager.api.dto.PlayerNote; import me.confuser.banmanager.common.*; import me.confuser.banmanager.common.commands.CommonSender; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerNoteData; +import me.confuser.banmanager.common.impl.EntityMappers; import me.confuser.banmanager.common.util.Message; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -19,20 +21,21 @@ public void shouldBroadcast() { CommonServer server = this.server; CommonSender sender = plugin.getServer().getConsoleSender(); PlayerNoteData data = new PlayerNoteData(player, sender.getData(), "test"); - CommonPlayer testPlayer = spy(new TestPlayer(sender.getData().getUUID(), sender.getName(), false)); + PlayerNote note = EntityMappers.playerNote(data); + CommonPlayer testPlayer = spy(new TestPlayer(plugin, sender.getData().getUUID(), sender.getName(), false)); Message expected = Message.get("notes.notify"); - expected.set("player", data.getPlayer().getName()) - .set("playerId", data.getPlayer().getUUID().toString()) - .set("actor", data.getActor().getName()) - .set("message", data.getMessage()); + expected.set("player", note.player().name()) + .set("playerId", note.player().uuid().toString()) + .set("actor", note.actor().name()) + .set("message", note.message()); when(plugin.getServer()).thenReturn(server); when(server.getPlayer(sender.getData().getUUID())).thenReturn(testPlayer); when(testPlayer.hasPermission("bm.notify.notes")).thenReturn(false); CommonNoteListener listener = new CommonNoteListener(plugin); - listener.notifyOnNote(data); + listener.notifyOnNote(note); ArgumentCaptor broadcastCaptor = ArgumentCaptor.forClass(Message.class); verify(server).broadcast(broadcastCaptor.capture(), eq("bm.notify.notes")); diff --git a/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java b/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java index 4d97a94bd..f2abfb11f 100644 --- a/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java @@ -28,9 +28,12 @@ public void setUp() { de.put("greeting", "Hallo "); registry.loadLocale("de", de); - Message.init(registry, new TestLogger()); + renderer = new MessageRenderer(); + Message.init(registry, new TestLogger(), renderer, null, null); } + private MessageRenderer renderer; + @Test public void resolveWithDefaultLocale() { Component component = Message.get("ban.kick") @@ -38,7 +41,7 @@ public void resolveWithDefaultLocale() { .set("reason", "griefing") .resolveComponent("en"); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("You are banned by Admin for griefing", plain); } @@ -49,7 +52,7 @@ public void resolveWithSpecificLocale() { .set("reason", "griefing") .resolveComponent("de"); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("Du wurdest von Admin gebannt: griefing", plain); } @@ -59,29 +62,29 @@ public void toStringUsesDefaultLocale() { .set("player", "Steve") .resolveComponent(); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("Hello Steve", plain); } @Test public void resolveWithPlayerLocale() { - TestPlayer player = new TestPlayer(UUID.randomUUID(), "Steve", true, "de"); + TestPlayer player = new TestPlayer(null, UUID.randomUUID(), "Steve", true, "de"); Component component = Message.get("greeting") .set("player", "Steve") .resolveComponent(player.getLocale()); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("Hallo Steve", plain); } @Test public void resolveForFallsBackWithoutPlugin() { - TestPlayer player = new TestPlayer(UUID.randomUUID(), "Steve", true, "de"); + TestPlayer player = new TestPlayer(null, UUID.randomUUID(), "Steve", true, "de"); Component component = Message.get("greeting") .set("player", "Steve") .resolveComponentFor(player); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("Hello Steve", plain); } @@ -91,14 +94,14 @@ public void tokenReplacementOrderPreserved() { Map en = new HashMap<>(); en.put("test.order", " "); registry.loadLocale("en", en); - Message.init(registry, new TestLogger()); + Message.init(registry, new TestLogger(), renderer, null, null); Component component = Message.get("test.order") .set("first", "X") .set("second", "Y") .resolveComponent(); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("X Y X", plain); } @@ -124,7 +127,7 @@ public void replaceMethodWorks() { .replace("Hello", "Hey") .resolveComponent(); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = renderer.toPlainText(component); assertEquals("Hey Steve", plain); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/util/MessageLoadingTest.java b/common/src/test/java/me/confuser/banmanager/common/util/MessageLoadingTest.java index 9f42539fc..3e06fb8e2 100644 --- a/common/src/test/java/me/confuser/banmanager/common/util/MessageLoadingTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/util/MessageLoadingTest.java @@ -22,7 +22,7 @@ public void isValid() { .set("expires", "1d") .resolveComponent(); - String plain = MessageRenderer.getInstance().toPlainText(component); + String plain = plugin.getMessageRenderer().toPlainText(component); assertEquals("Currently banned for abc by def at 8th July which expires in 1d", plain); } @@ -37,7 +37,7 @@ private void loadTestMessages(String yaml) { } } - Message.init(registry, new TestLogger()); + Message.init(registry, new TestLogger(), plugin.getMessageRenderer(), null, null); } @Test diff --git a/common/src/test/java/me/confuser/banmanager/common/util/MessageRendererTest.java b/common/src/test/java/me/confuser/banmanager/common/util/MessageRendererTest.java index a04df5ffc..8c75f050c 100644 --- a/common/src/test/java/me/confuser/banmanager/common/util/MessageRendererTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/util/MessageRendererTest.java @@ -16,7 +16,7 @@ public class MessageRendererTest { @BeforeEach public void setUp() { - renderer = MessageRenderer.getInstance(); + renderer = new MessageRenderer(); } @AfterEach diff --git a/common/src/test/java/me/confuser/banmanager/common/util/MessagesYamlValidatorTest.java b/common/src/test/java/me/confuser/banmanager/common/util/MessagesYamlValidatorTest.java index faa4d6c50..3bbd44b21 100644 --- a/common/src/test/java/me/confuser/banmanager/common/util/MessagesYamlValidatorTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/util/MessagesYamlValidatorTest.java @@ -52,8 +52,7 @@ public class MessagesYamlValidatorTest { @BeforeEach public void setUp() { - MessageRenderer.reset(); - renderer = MessageRenderer.getInstance(); + renderer = new MessageRenderer(); Map staticTokens = new HashMap<>(); for (String token : KNOWN_STATIC_TOKENS) { staticTokens.put(token, "test_" + token); @@ -65,7 +64,6 @@ public void setUp() { @AfterEach public void tearDown() { renderer.loadStaticTokens(new HashMap<>()); - MessageRenderer.reset(); } @Test diff --git a/e2e/platforms/bungee/configs/banmanager/config.yml b/e2e/platforms/bungee/configs/banmanager/config.yml index 4c59ef67e..1d070666b 100644 --- a/e2e/platforms/bungee/configs/banmanager/config.yml +++ b/e2e/platforms/bungee/configs/banmanager/config.yml @@ -3,7 +3,6 @@ locale: default: en perPlayer: true - debug: false databases: local: diff --git a/e2e/platforms/bungee/configs/banmanager/notifications.yml b/e2e/platforms/bungee/configs/banmanager/notifications.yml index 75b1fb646..89ac64cf3 100644 --- a/e2e/platforms/bungee/configs/banmanager/notifications.yml +++ b/e2e/platforms/bungee/configs/banmanager/notifications.yml @@ -1,39 +1,18 @@ # Notifications Configuration # Controls how punishment notifications are delivered to staff and players -# -# Each notification event can specify delivery channels: -# chat: true/false - Send via chat message (default: true) -# actionbar: true/false - Send via action bar (disappears after a few seconds) -# title: true/false - Send via title/subtitle overlay -# sound: '' - Sound to play (empty to disable). Use Minecraft sound names e.g. entity.experience_orb.pickup -# -# Title settings (only used when title: true): -# titleFadeIn: ticks for fade in (20 ticks = 1 second) -# titleStay: ticks to stay on screen -# titleFadeOut: ticks for fade out -# -# Sound settings: -# soundVolume: 0.0 - 1.0 -# soundPitch: 0.0 - 2.0 -# -# Platform notes: -# - BungeeCord: title, showTitle and playSound are not supported (ignored silently) -# - Sponge API7: playSound is not supported (ignored silently) -# - All other platforms (Bukkit/Paper, Velocity, Fabric, Sponge API8+) support all channels - staff: ban: chat: true actionbar: false title: false - sound: 'entity.experience_orb.pickup' + sound: entity.experience_orb.pickup soundVolume: 1.0 soundPitch: 1.0 tempban: chat: true actionbar: false title: false - sound: 'entity.experience_orb.pickup' + sound: entity.experience_orb.pickup soundVolume: 1.0 soundPitch: 1.0 mute: @@ -54,14 +33,14 @@ staff: chat: true actionbar: false title: false - sound: 'entity.experience_orb.pickup' + sound: entity.experience_orb.pickup soundVolume: 1.0 soundPitch: 1.0 report: chat: true actionbar: false title: false - sound: 'block.note_block.pling' + sound: block.note_block.pling soundVolume: 1.0 soundPitch: 1.0 duplicateIp: @@ -71,11 +50,10 @@ staff: sound: '' soundVolume: 1.0 soundPitch: 1.0 - player: muted: actionbar: true warned: - sound: 'entity.villager.no' + sound: entity.villager.no soundVolume: 1.0 soundPitch: 1.0 diff --git a/e2e/platforms/bungee/configs/banmanager/schedules.yml b/e2e/platforms/bungee/configs/banmanager/schedules.yml index 36244ca10..0b127d410 100644 --- a/e2e/platforms/bungee/configs/banmanager/schedules.yml +++ b/e2e/platforms/bungee/configs/banmanager/schedules.yml @@ -16,15 +16,15 @@ scheduler: externalIpBans: 5 saveLastChecked: 60 lastChecked: - playerMutes: 1771692722 - expiresCheck: 1771692722 - nameBans: 1771692722 - playerBans: 1771692722 - playerWarnings: 1771692722 - externalPlayerNotes: 1771692722 - ipRangeBans: 1771692722 - externalPlayerMutes: 1771692722 - rollbacks: 1771692704 - externalPlayerBans: 1771692722 - externalIpBans: 1771692722 - ipBans: 1771692723 + playerMutes: 1776598718 + expiresCheck: 1776598719 + nameBans: 1776598719 + playerBans: 1776598719 + playerWarnings: 1776598719 + externalPlayerNotes: 1776598718 + ipRangeBans: 1776598718 + externalPlayerMutes: 1776598719 + rollbacks: 1776598707 + externalPlayerBans: 1776598719 + externalIpBans: 1776598719 + ipBans: 1776598719 diff --git a/e2e/platforms/bungee/configs/luckperms/translations/repository/status.json b/e2e/platforms/bungee/configs/luckperms/translations/repository/status.json index 7630fd3fb..9391cad31 100644 --- a/e2e/platforms/bungee/configs/luckperms/translations/repository/status.json +++ b/e2e/platforms/bungee/configs/luckperms/translations/repository/status.json @@ -1,3 +1,3 @@ { - "lastRefresh": 1771690383300 + "lastRefresh": 1776597069546 } \ No newline at end of file diff --git a/e2e/platforms/bungee/docker-compose.yml b/e2e/platforms/bungee/docker-compose.yml index 338fd5536..d7286b1b7 100644 --- a/e2e/platforms/bungee/docker-compose.yml +++ b/e2e/platforms/bungee/docker-compose.yml @@ -97,6 +97,11 @@ services: MODRINTH_ALLOWED_VERSION_TYPE: release # Minecraft version required for Modrinth plugin lookup MINECRAFT_VERSION: "1.21.4" + # Disable native compression/cipher: BungeeCord's bundled zlib-ng requires SSE 4.2 + + # PCLMUL CPU support which isn't always present in containerised/virtualised + # environments (notably macOS Docker on arm64). Falls back to Java's built-in + # implementations. See: https://github.com/SpigotMC/BungeeCord/pull/3718 + JVM_OPTS: "-Dnet.md_5.bungee.jni.native-compress.enable=false -Dnet.md_5.bungee.jni.native-cipher.enable=false" volumes: # BanManager BungeeCord plugin JAR (shadow JAR) - mounted directly to plugins dir # Note: Not using :ro because itzg/mc-proxy needs to chown all plugin files diff --git a/e2e/platforms/velocity/configs/banmanager/config.yml b/e2e/platforms/velocity/configs/banmanager/config.yml index 4c59ef67e..1d070666b 100644 --- a/e2e/platforms/velocity/configs/banmanager/config.yml +++ b/e2e/platforms/velocity/configs/banmanager/config.yml @@ -3,7 +3,6 @@ locale: default: en perPlayer: true - debug: false databases: local: diff --git a/e2e/platforms/velocity/configs/banmanager/notifications.yml b/e2e/platforms/velocity/configs/banmanager/notifications.yml index 75b1fb646..89ac64cf3 100644 --- a/e2e/platforms/velocity/configs/banmanager/notifications.yml +++ b/e2e/platforms/velocity/configs/banmanager/notifications.yml @@ -1,39 +1,18 @@ # Notifications Configuration # Controls how punishment notifications are delivered to staff and players -# -# Each notification event can specify delivery channels: -# chat: true/false - Send via chat message (default: true) -# actionbar: true/false - Send via action bar (disappears after a few seconds) -# title: true/false - Send via title/subtitle overlay -# sound: '' - Sound to play (empty to disable). Use Minecraft sound names e.g. entity.experience_orb.pickup -# -# Title settings (only used when title: true): -# titleFadeIn: ticks for fade in (20 ticks = 1 second) -# titleStay: ticks to stay on screen -# titleFadeOut: ticks for fade out -# -# Sound settings: -# soundVolume: 0.0 - 1.0 -# soundPitch: 0.0 - 2.0 -# -# Platform notes: -# - BungeeCord: title, showTitle and playSound are not supported (ignored silently) -# - Sponge API7: playSound is not supported (ignored silently) -# - All other platforms (Bukkit/Paper, Velocity, Fabric, Sponge API8+) support all channels - staff: ban: chat: true actionbar: false title: false - sound: 'entity.experience_orb.pickup' + sound: entity.experience_orb.pickup soundVolume: 1.0 soundPitch: 1.0 tempban: chat: true actionbar: false title: false - sound: 'entity.experience_orb.pickup' + sound: entity.experience_orb.pickup soundVolume: 1.0 soundPitch: 1.0 mute: @@ -54,14 +33,14 @@ staff: chat: true actionbar: false title: false - sound: 'entity.experience_orb.pickup' + sound: entity.experience_orb.pickup soundVolume: 1.0 soundPitch: 1.0 report: chat: true actionbar: false title: false - sound: 'block.note_block.pling' + sound: block.note_block.pling soundVolume: 1.0 soundPitch: 1.0 duplicateIp: @@ -71,11 +50,10 @@ staff: sound: '' soundVolume: 1.0 soundPitch: 1.0 - player: muted: actionbar: true warned: - sound: 'entity.villager.no' + sound: entity.villager.no soundVolume: 1.0 soundPitch: 1.0 diff --git a/e2e/platforms/velocity/configs/banmanager/schedules.yml b/e2e/platforms/velocity/configs/banmanager/schedules.yml index 50728f39b..ee1c08bbb 100644 --- a/e2e/platforms/velocity/configs/banmanager/schedules.yml +++ b/e2e/platforms/velocity/configs/banmanager/schedules.yml @@ -16,15 +16,15 @@ scheduler: externalIpBans: 5 saveLastChecked: 60 lastChecked: - playerMutes: 1771692351 - expiresCheck: 1771692351 - nameBans: 1771692351 - playerBans: 1771692351 - playerWarnings: 1771692351 - externalPlayerNotes: 1771692351 - ipRangeBans: 1771692351 - externalPlayerMutes: 1771692351 - rollbacks: 1771692339 - externalPlayerBans: 1771692351 - externalIpBans: 1771692351 - ipBans: 1771692351 + playerMutes: 1776598298 + expiresCheck: 1776598297 + nameBans: 1776598299 + playerBans: 1776598297 + playerWarnings: 1776598299 + externalPlayerNotes: 1776598296 + ipRangeBans: 1776598298 + externalPlayerMutes: 1776598299 + rollbacks: 1776598293 + externalPlayerBans: 1776598295 + externalIpBans: 1776598295 + ipBans: 1776598298 diff --git a/e2e/platforms/velocity/configs/luckperms/translations/repository/status.json b/e2e/platforms/velocity/configs/luckperms/translations/repository/status.json index 265fdd098..3a21e9665 100644 --- a/e2e/platforms/velocity/configs/luckperms/translations/repository/status.json +++ b/e2e/platforms/velocity/configs/luckperms/translations/repository/status.json @@ -1,3 +1,3 @@ { - "lastRefresh": 1771692338073 + "lastRefresh": 1776597695803 } \ No newline at end of file diff --git a/e2e/sample-plugin/README.md b/e2e/sample-plugin/README.md new file mode 100644 index 000000000..d0aeca3ce --- /dev/null +++ b/e2e/sample-plugin/README.md @@ -0,0 +1,54 @@ +# BanManager sample plugin + +A tiny Bukkit consumer plugin that exercises a representative slice of +`BanManagerAPI` — service resolution, async writes, event subscription +with placeholder injection, plugin-owned migrations, raw `DataSource` +access, and cross-platform scheduling — so the published artifact is +verified against a real consumer plugin on every CI run. It is not a +soak test of every sub-service (mutes, warnings, IP / range / name bans, +notes, reports, history are not exercised); it's the smallest plugin +that proves the build wiring is correct, kept small enough to read +cover-to-cover. + +## What it demonstrates + +| Feature | Where in the code | +| ------------------------------------ | ------------------------------------------------------------------------- | +| `BanManager.get()` resolution | `SamplePlugin#onEnable` | +| Bukkit `ServicesManager` lookup | `SamplePlugin#onEnable` (cross-checked against `BanManager.get()`) | +| `MigrationService` for plugin tables | `SamplePlugin#runMigrations` + `resources/db/sample/` | +| `EventBus` subscription | `SamplePlugin#registerEventSubscriptions` | +| Placeholder injection | `PlayerDeniedEvent` handler — pushes `` into `placeholders()` | +| `PluginReloadedEvent` re-registration | `SamplePlugin#registerEventSubscriptions` | +| Async `BanService` calls | `SamplePlugin#handleSampleBan` | +| `PlayerService` lookup (async) | `SamplePlugin#handleSampleBan` | +| `DatabaseAccess` `DataSource` | `SamplePlugin#printCounts` + `recordDeniedLogin` | +| `localTable("…")` lookup | `SamplePlugin#printCounts` | +| Cross-platform scheduler | `SamplePlugin#schedulePeriodicCounts` and `runSync` thread-handoff sites | + +## Build + +The sample plugin is wired into the BanManager Gradle build: + +```bash +./gradlew :BanManagerSamplePlugin:shadowJar +``` + +The resulting jar lands in `e2e/sample-plugin/build/libs/BanManagerSamplePlugin.jar`. + +## Run on a Paper server + +1. Drop `BanManagerBukkit.jar` and `BanManagerSamplePlugin.jar` into your + server's `plugins/` directory. +2. Start the server. BanManager enables first; the sample plugin then + resolves the service and registers its subscriptions. +3. Useful in-game commands: + - `/sampleban [reason]` — bans via `BanRequest`, shows the + async future surface. + - `/samplecount` — issues a custom SQL query through the shared + `DataSource`. + +A row is appended to `bm_sample_denied_logins` every time a banned UUID, +banned IP, banned IP range, or banned name attempts to log in — verifying +both the `PlayerDeniedEvent` subscription and the `MigrationService` +bootstrap. diff --git a/e2e/sample-plugin/build.gradle.kts b/e2e/sample-plugin/build.gradle.kts new file mode 100644 index 000000000..57b664ffd --- /dev/null +++ b/e2e/sample-plugin/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + `java-library` + id("com.gradleup.shadow") +} + +description = "Sample consumer plugin that exercises every public BanManager API service" + +applyCommonConfiguration() + +repositories { + maven { + name = "spigotmc" + url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + } + maven { + name = "sonatype" + url = uri("https://oss.sonatype.org/content/repositories/snapshots/") + } +} + +dependencies { + compileOnly(project(":BanManagerAPI")) + compileOnly("org.spigotmc:spigot-api:1.20.1-R0.1-SNAPSHOT") { + exclude("junit", "junit") + } +} + +tasks.named("processResources") { + val pluginVersion = project.version.toString() + inputs.property("pluginVersion", pluginVersion) + filesMatching("plugin.yml") { + expand("pluginVersion" to pluginVersion) + } +} + +tasks.named("shadowJar") { + archiveBaseName.set("BanManagerSamplePlugin") + archiveClassifier.set("") + archiveVersion.set("") +} + +tasks.named("assemble") { + dependsOn("shadowJar") +} diff --git a/e2e/sample-plugin/src/main/java/me/confuser/banmanager/sample/SamplePlugin.java b/e2e/sample-plugin/src/main/java/me/confuser/banmanager/sample/SamplePlugin.java new file mode 100644 index 000000000..46589d155 --- /dev/null +++ b/e2e/sample-plugin/src/main/java/me/confuser/banmanager/sample/SamplePlugin.java @@ -0,0 +1,241 @@ +package me.confuser.banmanager.sample; + +import me.confuser.banmanager.api.BanManager; +import me.confuser.banmanager.api.BanManagerService; +import me.confuser.banmanager.api.database.DatabaseKind; +import me.confuser.banmanager.api.database.MigrationService.MigrationConfig; +import me.confuser.banmanager.api.dto.Player; +import me.confuser.banmanager.api.event.EventPriority; +import me.confuser.banmanager.api.event.Subscription; +import me.confuser.banmanager.api.event.player.PlayerBannedEvent; +import me.confuser.banmanager.api.event.player.PlayerDeniedEvent; +import me.confuser.banmanager.api.event.player.PluginReloadedEvent; +import me.confuser.banmanager.api.request.BanRequest; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.regex.Pattern; + +/** + * End-to-end sample plugin that exercises every public surface of + * {@link BanManagerService}. It is built and shipped as part of the BanManager + * repository so the API artifact is verified by a real consumer plugin on + * every CI run. + * + *

Demonstrates:

+ *
    + *
  • Service resolution via both {@link BanManager#get()} and Bukkit's + * {@code ServicesManager}.
  • + *
  • Subscribing to {@link PlayerBannedEvent}, {@link PlayerDeniedEvent} + * (placeholder injection) and {@link PluginReloadedEvent} + * (re-registration on {@code /bmreload}).
  • + *
  • Async writes via {@link CompletableFuture} and synchronous variants + * via the {@code *Sync} siblings.
  • + *
  • Running plugin-owned SQL migrations via + * {@link me.confuser.banmanager.api.database.MigrationService}.
  • + *
  • Direct {@link DataSource} access via + * {@link me.confuser.banmanager.api.database.DatabaseAccess} including + * table-name resolution.
  • + *
  • Cross-platform scheduling via + * {@link me.confuser.banmanager.api.scheduler.BanManagerScheduler}.
  • + *
+ */ +public class SamplePlugin extends JavaPlugin { + + private static final Pattern SAFE_TABLE_NAME = Pattern.compile("[A-Za-z0-9_]+"); + + private final List subscriptions = new ArrayList<>(); + + @Override + public void onEnable() { + if (!BanManager.isAvailable()) { + getLogger().warning("BanManager is not enabled yet - aborting sample plugin"); + getServer().getPluginManager().disablePlugin(this); + return; + } + + BanManagerService bm = BanManager.get(); + + BanManagerService bukkitLookup = getServer().getServicesManager().load(BanManagerService.class); + if (bukkitLookup != bm) { + getLogger().warning("Bukkit ServicesManager and BanManager.get() returned different instances - this should never happen"); + } + + runMigrations(bm); + registerEventSubscriptions(bm); + schedulePeriodicCounts(bm); + + getLogger().info("Sample plugin ready - main-thread aware? " + bm.scheduler().isMainThreadAware()); + } + + @Override + public void onDisable() { + subscriptions.forEach(Subscription::unsubscribe); + subscriptions.clear(); + } + + private void runMigrations(BanManagerService bm) { + bm.migrations().run(new MigrationConfig( + DatabaseKind.LOCAL, + "sample", + "db/sample", + getClass().getClassLoader())); + } + + private void registerEventSubscriptions(BanManagerService bm) { + subscriptions.add(bm.events().subscribe( + PlayerBannedEvent.class, + EventPriority.MONITOR, + event -> { + if (event.silent()) return; + String message = "[Sample] " + event.ban().player().name() + + " was banned for " + event.ban().reason(); + bm.scheduler().runSync(() -> getServer().broadcastMessage(message)); + })); + + subscriptions.add(bm.events().subscribe( + PlayerDeniedEvent.class, + event -> { + event.placeholders().put("appeal_url", "https://bans.example.com/appeal"); + recordDeniedLogin(bm, event); + })); + + subscriptions.add(bm.events().subscribe(PluginReloadedEvent.class, event -> { + getLogger().info("BanManager reloaded - rebinding subscriptions"); + onDisable(); + registerEventSubscriptions(bm); + })); + } + + private void recordDeniedLogin(BanManagerService bm, PlayerDeniedEvent event) { + DataSource ds = bm.database().localDataSource(); + bm.scheduler().runAsync(() -> { + try (Connection conn = ds.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO bm_sample_denied_logins (uuid, name, reason, created) VALUES (?, ?, ?, ?)")) { + ps.setBytes(1, event.uuid().map(SamplePlugin::uuidToBytes).orElse(null)); + ps.setString(2, event.name()); + ps.setString(3, event.reason().name()); + ps.setLong(4, System.currentTimeMillis() / 1000L); + ps.executeUpdate(); + } catch (Exception ex) { + getLogger().log(Level.WARNING, "Failed to record denied login", ex); + } + }); + } + + private void schedulePeriodicCounts(BanManagerService bm) { + bm.scheduler().runAsyncRepeating( + () -> printCounts(bm, getServer().getConsoleSender(), false), + Duration.ofMinutes(5), + Duration.ofHours(1)); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!BanManager.isAvailable()) { + sender.sendMessage("BanManager is not available"); + return true; + } + BanManagerService bm = BanManager.get(); + return switch (command.getName().toLowerCase()) { + case "sampleban" -> handleSampleBan(bm, sender, args); + case "samplecount" -> { + printCounts(bm, sender, true); + yield true; + } + default -> false; + }; + } + + private boolean handleSampleBan(BanManagerService bm, CommandSender sender, String[] args) { + if (args.length < 1) { + sender.sendMessage("Usage: /sampleban [reason]"); + return true; + } + String reason = args.length > 1 ? String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length)) : "Sample ban"; + + CompletableFuture> playerFuture = bm.players().findByName(args[0]); + + playerFuture.thenCompose(maybe -> { + if (maybe.isEmpty()) { + bm.scheduler().runSync(() -> sender.sendMessage("Unknown player: " + args[0])); + return CompletableFuture.completedFuture(Optional.empty()); + } + Player target = maybe.get(); + Player actor = bm.players().console(); + return bm.bans().ban(new BanRequest(target.uuid(), actor.uuid(), reason)); + }).thenAccept(ban -> ban.ifPresentOrElse( + b -> bm.scheduler().runSync(() -> + sender.sendMessage("Banned " + b.player().name() + " for " + b.reason())), + () -> bm.scheduler().runSync(() -> + sender.sendMessage("Ban was cancelled by another plugin")))) + .whenComplete((unused, ex) -> { + if (ex == null) return; + getLogger().log(Level.WARNING, "Sample ban failed for " + args[0], ex); + bm.scheduler().runSync(() -> sender.sendMessage("Ban failed: " + ex.getMessage())); + }); + + return true; + } + + private void printCounts(BanManagerService bm, CommandSender sender, boolean fromCommand) { + DataSource ds = bm.database().localDataSource(); + Optional bansTable = bm.database().localTable("playerBans"); + Optional mutesTable = bm.database().localTable("playerMutes"); + if (bansTable.isEmpty() || mutesTable.isEmpty()) { + sender.sendMessage("BanManager has not finished provisioning its tables"); + return; + } + + bm.scheduler().runAsync(() -> { + try (Connection conn = ds.getConnection()) { + long bans = countOf(conn, bansTable.get()); + long mutes = countOf(conn, mutesTable.get()); + long denied = countOf(conn, "bm_sample_denied_logins"); + String summary = "[Sample] active player bans=" + bans + ", mutes=" + mutes + ", denied logins=" + denied; + if (fromCommand) { + bm.scheduler().runSync(() -> sender.sendMessage(summary)); + } else { + getLogger().info(summary); + } + } catch (Exception ex) { + getLogger().log(Level.WARNING, "Failed to count rows", ex); + } + }); + } + + private static long countOf(Connection conn, String table) throws SQLException { + // Table names cannot be bound as JDBC parameters, so allowlist the + // identifier before interpolating it to keep the query injection-safe. + if (!SAFE_TABLE_NAME.matcher(table).matches()) { + throw new IllegalArgumentException("Unsafe table name: " + table); + } + try (PreparedStatement ps = conn.prepareStatement("SELECT count(*) FROM " + table); + ResultSet rs = ps.executeQuery()) { + return rs.next() ? rs.getLong(1) : 0L; + } + } + + private static byte[] uuidToBytes(UUID uuid) { + byte[] bytes = new byte[16]; + long msb = uuid.getMostSignificantBits(); + long lsb = uuid.getLeastSignificantBits(); + for (int i = 0; i < 8; i++) bytes[i] = (byte) (msb >>> (56 - i * 8)); + for (int i = 0; i < 8; i++) bytes[i + 8] = (byte) (lsb >>> (56 - i * 8)); + return bytes; + } +} diff --git a/e2e/sample-plugin/src/main/resources/db/sample/V1__sample_login_log.sql b/e2e/sample-plugin/src/main/resources/db/sample/V1__sample_login_log.sql new file mode 100644 index 000000000..6a88b5239 --- /dev/null +++ b/e2e/sample-plugin/src/main/resources/db/sample/V1__sample_login_log.sql @@ -0,0 +1,12 @@ +-- Sample table demonstrating MigrationService usage. Stores one row per +-- denied login captured by the sample plugin's PlayerDeniedEvent listener. +CREATE TABLE IF NOT EXISTS bm_sample_denied_logins ( + id INT NOT NULL AUTO_INCREMENT, + uuid BINARY(16) NULL, + name VARCHAR(32) NOT NULL, + reason VARCHAR(64) NOT NULL, + created BIGINT NOT NULL, + PRIMARY KEY (id), + KEY idx_bm_sample_denied_logins_uuid (uuid), + KEY idx_bm_sample_denied_logins_created (created) +); diff --git a/e2e/sample-plugin/src/main/resources/db/sample/migrations.list b/e2e/sample-plugin/src/main/resources/db/sample/migrations.list new file mode 100644 index 000000000..74823c0b4 --- /dev/null +++ b/e2e/sample-plugin/src/main/resources/db/sample/migrations.list @@ -0,0 +1 @@ +V1__sample_login_log.sql diff --git a/e2e/sample-plugin/src/main/resources/plugin.yml b/e2e/sample-plugin/src/main/resources/plugin.yml new file mode 100644 index 000000000..434e34b43 --- /dev/null +++ b/e2e/sample-plugin/src/main/resources/plugin.yml @@ -0,0 +1,23 @@ +name: BanManagerSamplePlugin +main: me.confuser.banmanager.sample.SamplePlugin +version: ${pluginVersion} +api-version: '1.20' +authors: [BanManager] +description: Sample consumer plugin demonstrating the BanManagerAPI surface. +softdepend: [BanManager] + +commands: + sampleban: + description: Ban a player via the BanManager API + usage: / [reason] + permission: banmanager.sample.ban + samplecount: + description: Print live counts via DatabaseAccess + usage: / + permission: banmanager.sample.count + +permissions: + banmanager.sample.ban: + default: op + banmanager.sample.count: + default: op diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index a4807145f..4abdfb0cd 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -166,8 +166,10 @@ tasks.named("shadowJar") { archiveVersion.set("mc$minecraftVersion") dependencies { + include(dependency(":BanManagerAPI")) include(dependency(":BanManagerCommon")) include(dependency(":BanManagerLibs")) + include(dependency("com.github.seancfoley:ipaddress:.*")) } mergeServiceFiles() @@ -187,6 +189,8 @@ tasks.named("shadowJar") { minimize { exclude(dependency(":BanManagerLibs")) + exclude(dependency(":BanManagerAPI")) + exclude(dependency("com.github.seancfoley:ipaddress:.*")) } } diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/BMFabricPlugin.java b/fabric/src/main/java/me/confuser/banmanager/fabric/BMFabricPlugin.java index f57eaa93b..d23f7b354 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/BMFabricPlugin.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/BMFabricPlugin.java @@ -19,6 +19,12 @@ import me.confuser.banmanager.common.configs.PluginInfo; import me.confuser.banmanager.common.configuration.ConfigurationSection; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; +import me.confuser.banmanager.common.listeners.CommonBanListener; +import me.confuser.banmanager.common.listeners.CommonHooksListener; +import me.confuser.banmanager.common.listeners.CommonMuteListener; +import me.confuser.banmanager.common.listeners.CommonNoteListener; +import me.confuser.banmanager.common.listeners.CommonReportListener; +import me.confuser.banmanager.common.listeners.CommonWebhookListener; import me.confuser.banmanager.common.runnables.BanSync; import me.confuser.banmanager.common.runnables.ExpiresSync; import me.confuser.banmanager.common.runnables.GlobalBanSync; @@ -41,6 +47,12 @@ public class BMFabricPlugin implements DedicatedServerModInitializer { + // Mixins are bytecode-injected into Minecraft classes and cannot receive constructor + // injection. Fabric mods are singletons per JVM, so we expose this instance directly + // for the handful of mixin entry points that need to reach the plugin. + @Getter + private static BMFabricPlugin instance; + @Getter private BanManagerPlugin plugin; private String[] configs = new String[] { @@ -59,17 +71,20 @@ public class BMFabricPlugin implements DedicatedServerModInitializer { @Override public void onInitializeServer() { + instance = this; + try { pluginInfo = setupConfigs(); } catch (IOException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to set up plugin configuration", e); + LogManager.getLogger("BanManager").warn("Failed to set up plugin configuration", e); return; } this.server = new FabricServer(); - this.scheduler = new FabricScheduler(); + Logger log4j = LogManager.getLogger("BanManager"); + this.scheduler = new FabricScheduler(log4j); - plugin = new BanManagerPlugin(pluginInfo, new PluginLogger(LogManager.getLogger("BanManager")), getDataFolder(), + plugin = new BanManagerPlugin(pluginInfo, new PluginLogger(log4j), getDataFolder(), scheduler, this.server, null); try { @@ -114,12 +129,12 @@ private void onServerStopping(MinecraftServer server) { private void setupCommands() { for (CommonCommand cmd : plugin.getCommands()) { - new FabricCommand(cmd).register(); + new FabricCommand(plugin, cmd).register(); } if (plugin.getGlobalConn() != null) { for (CommonCommand cmd : plugin.getGlobalCommands()) { - new FabricCommand(cmd).register(); + new FabricCommand(plugin, cmd).register(); } } } @@ -142,7 +157,7 @@ private PluginInfo setupConfigs() throws IOException { try (InputStream in = getResourceAsStream(name)) { Files.copy(in, file.toPath()); } catch (IOException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to copy default config file", e); + LogManager.getLogger("BanManager").warn("Failed to copy default config file", e); } } else { try (InputStream in = getResourceAsStream(file.getName()); @@ -177,21 +192,22 @@ private PluginInfo setupConfigs() throws IOException { public void setupListeners() { new JoinListener(plugin); new LeaveListener(plugin); - new HookListener(plugin); + new CommonHooksListener(plugin); if (!plugin.getConfig().getChatPriority().equals("NONE")) { new ChatListener(plugin); } if (plugin.getConfig().isDisplayNotificationsEnabled()) { - new BanListener(plugin); - new MuteListener(plugin); - new NoteListener(plugin); + new CommonBanListener(plugin); + new CommonMuteListener(plugin); + new CommonNoteListener(plugin); + new CommonReportListener(plugin); new ReportListener(plugin, this.server); } if (plugin.getWebhookConfig().isHooksEnabled()) { - new WebhookListener(plugin); + new CommonWebhookListener(plugin); } } diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/BanManagerEvents.java b/fabric/src/main/java/me/confuser/banmanager/fabric/BanManagerEvents.java deleted file mode 100644 index 4f1329299..000000000 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/BanManagerEvents.java +++ /dev/null @@ -1,369 +0,0 @@ -package me.confuser.banmanager.fabric; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.*; -import me.confuser.banmanager.common.util.Message; -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; - -public class BanManagerEvents { - - public static final Event PLAYER_BAN_EVENT = EventFactory.createArrayBacked(PlayerBanEvent.class, - (listeners) -> (banData, silent) -> { - for (PlayerBanEvent listener : listeners) { - if (listener.onPlayerBan(banData, silent)) { - return true; - } - } - return false; - }); - - public static final Event PLAYER_BANNED_EVENT = EventFactory.createArrayBacked(PlayerBannedEvent.class, - (listeners) -> (banData, silent, kickMessage) -> { - for (PlayerBannedEvent listener : listeners) { - listener.onPlayerBanned(banData, silent, kickMessage); - } - }); - - public static final Event PLAYER_UNBAN_EVENT = EventFactory.createArrayBacked(PlayerUnbanEvent.class, - (listeners) -> (banData, actor, reason, silent) -> { - for (PlayerUnbanEvent listener : listeners) { - listener.onPlayerUnban(banData, actor, reason, silent); - } - }); - - public static final Event IP_BAN_EVENT = EventFactory.createArrayBacked(IpBanEvent.class, - (listeners) -> (banData, silent) -> { - for (IpBanEvent listener : listeners) { - if (listener.onIpBan(banData, silent)) { - return true; - } - } - return false; - }); - - public static final Event IP_BANNED_EVENT = EventFactory.createArrayBacked(IpBannedEvent.class, - (listeners) -> (banData, silent) -> { - for (IpBannedEvent listener : listeners) { - listener.onIpBanned(banData, silent); - } - }); - - public static final Event IP_UNBAN_EVENT = EventFactory.createArrayBacked(IpUnbanEvent.class, - (listeners) -> (banData, actor, reason, silent) -> { - for (IpUnbanEvent listener : listeners) { - listener.onIpUnban(banData, actor, reason, silent); - } - }); - - public static final Event IP_MUTE_EVENT = EventFactory.createArrayBacked(IpMuteEvent.class, - (listeners) -> (muteData, silent) -> { - for (IpMuteEvent listener : listeners) { - if (listener.onIpMute(muteData, silent)) { - return true; - } - } - return false; - }); - - public static final Event IP_MUTED_EVENT = EventFactory.createArrayBacked(IpMutedEvent.class, - (listeners) -> (muteData, silent) -> { - for (IpMutedEvent listener : listeners) { - listener.onIpMuted(muteData, silent); - } - }); - - public static final Event IP_UNMUTED_EVENT = EventFactory.createArrayBacked(IpUnmutedEvent.class, - (listeners) -> (muteData, actor, reason, silent) -> { - for (IpUnmutedEvent listener : listeners) { - listener.onIpUnmuted(muteData, actor, reason, silent); - } - }); - - public static final Event PLAYER_KICKED_EVENT = EventFactory.createArrayBacked(PlayerKickedEvent.class, - (listeners) -> (kickData, silent) -> { - for (PlayerKickedEvent listener : listeners) { - listener.onPlayerKicked(kickData, silent); - } - }); - - public static final Event PLAYER_NOTE_CREATED_EVENT = EventFactory.createArrayBacked(PlayerNoteCreatedEvent.class, - (listeners) -> (noteData, silent) -> { - for (PlayerNoteCreatedEvent listener : listeners) { - listener.onPlayerNoteCreated(noteData, silent); - } - }); - - public static final Event PLAYER_REPORT_EVENT = EventFactory.createArrayBacked(PlayerReportEvent.class, - (listeners) -> (reportData, silent) -> { - for (PlayerReportEvent listener : listeners) { - if (listener.onPlayerReport(reportData, silent)) { - return true; - } - } - return false; - }); - - public static final Event PLAYER_REPORTED_EVENT = EventFactory.createArrayBacked(PlayerReportedEvent.class, - (listeners) -> (reportData, silent) -> { - for (PlayerReportedEvent listener : listeners) { - listener.onPlayerReported(reportData, silent); - } - }); - - public static final Event PLAYER_REPORT_DELETED_EVENT = EventFactory.createArrayBacked(PlayerReportDeletedEvent.class, - (listeners) -> (reportData) -> { - for (PlayerReportDeletedEvent listener : listeners) { - listener.onPlayerReportDeleted(reportData); - } - }); - - public static final Event NAME_BAN_EVENT = EventFactory.createArrayBacked(NameBanEvent.class, - (listeners) -> (banData, silent) -> { - for (NameBanEvent listener : listeners) { - if (listener.onNameBan(banData, silent)) { - return true; - } - } - return false; - }); - - public static final Event NAME_BANNED_EVENT = EventFactory.createArrayBacked(NameBannedEvent.class, - (listeners) -> (banData, silent) -> { - for (NameBannedEvent listener : listeners) { - listener.onNameBanned(banData, silent); - } - }); - - public static final Event NAME_UNBAN_EVENT = EventFactory.createArrayBacked(NameUnbanEvent.class, - (listeners) -> (banData, actor, reason, silent) -> { - for (NameUnbanEvent listener : listeners) { - listener.onNameUnban(banData, actor, reason, silent); - } - }); - - public static final Event PLAYER_WARN_EVENT = EventFactory.createArrayBacked(PlayerWarnEvent.class, - (listeners) -> (warnData, silent) -> { - for (PlayerWarnEvent listener : listeners) { - if (listener.onPlayerWarn(warnData, silent)) { - return true; - } - } - return false; - }); - - public static final Event PLAYER_WARNED_EVENT = EventFactory.createArrayBacked(PlayerWarnedEvent.class, - (listeners) -> (warnData, silent) -> { - for (PlayerWarnedEvent listener : listeners) { - listener.onPlayerWarned(warnData, silent); - } - }); - - public static final Event IP_RANGE_BAN_EVENT = EventFactory.createArrayBacked(IpRangeBanEvent.class, - (listeners) -> (banData, silent) -> { - for (IpRangeBanEvent listener : listeners) { - if (listener.onIpRangeBan(banData, silent)) { - return true; - } - } - return false; - }); - - public static final Event IP_RANGE_BANNED_EVENT = EventFactory.createArrayBacked(IpRangeBannedEvent.class, - (listeners) -> (banData, silent) -> { - for (IpRangeBannedEvent listener : listeners) { - listener.onIpRangeBanned(banData, silent); - } - }); - - public static final Event IP_RANGE_UNBAN_EVENT = EventFactory.createArrayBacked(IpRangeUnbanEvent.class, - (listeners) -> (banData, actor, reason, silent) -> { - for (IpRangeUnbanEvent listener : listeners) { - listener.onIpRangeUnban(banData, actor, reason, silent); - } - }); - - public static final Event PLAYER_MUTE_EVENT = EventFactory.createArrayBacked(PlayerMuteEvent.class, - (listeners) -> (muteData, silent) -> { - for (PlayerMuteEvent listener : listeners) { - if (listener.onPlayerMute(muteData, silent)) { - return true; - } - } - return false; - }); - - public static final Event PLAYER_MUTED_EVENT = EventFactory.createArrayBacked(PlayerMutedEvent.class, - (listeners) -> (muteData, silent) -> { - for (PlayerMutedEvent listener : listeners) { - listener.onPlayerMuted(muteData, silent); - } - }); - - public static final Event PLAYER_UNMUTE_EVENT = EventFactory.createArrayBacked(PlayerUnmuteEvent.class, - (listeners) -> (muteData, actor, reason, silent) -> { - for (PlayerUnmuteEvent listener : listeners) { - listener.onPlayerUnmute(muteData, actor, reason, silent); - } - }); - - public static final Event PLUGIN_RELOADED_EVENT = EventFactory.createArrayBacked(PluginReloadedEvent.class, - (listeners) -> (actor) -> { - for (PluginReloadedEvent listener : listeners) { - listener.onPluginReloaded(actor); - } - }); - - public static final Event PLAYER_DENIED_EVENT = EventFactory.createArrayBacked(PlayerDeniedEvent.class, - (listeners) -> (player, message) -> { - for (PlayerDeniedEvent listener : listeners) { - listener.onPlayerDenied(player, message); - } - }); - - @FunctionalInterface - public interface PlayerBanEvent { - boolean onPlayerBan(PlayerBanData banData, SilentValue silent); - } - - @FunctionalInterface - public interface PlayerBannedEvent { - void onPlayerBanned(PlayerBanData banData, boolean silent, Message kickMessage); - } - - @FunctionalInterface - public interface PlayerUnbanEvent { - void onPlayerUnban(PlayerBanData banData, PlayerData actor, String reason, boolean silent); - } - - @FunctionalInterface - public interface IpBanEvent { - boolean onIpBan(IpBanData banData, SilentValue silent); - } - - @FunctionalInterface - public interface IpBannedEvent { - void onIpBanned(IpBanData banData, boolean silent); - } - - @FunctionalInterface - public interface IpUnbanEvent { - void onIpUnban(IpBanData banData, PlayerData actor, String reason, boolean silent); - } - - @FunctionalInterface - public interface IpMuteEvent { - boolean onIpMute(IpMuteData muteData, SilentValue silent); - } - - @FunctionalInterface - public interface IpMutedEvent { - void onIpMuted(IpMuteData muteData, boolean silent); - } - - @FunctionalInterface - public interface IpUnmutedEvent { - void onIpUnmuted(IpMuteData muteData, PlayerData actor, String reason, boolean silent); - } - - @FunctionalInterface - public interface PlayerKickedEvent { - void onPlayerKicked(PlayerKickData kickData, boolean silent); - } - - @FunctionalInterface - public interface PlayerNoteCreatedEvent { - void onPlayerNoteCreated(PlayerNoteData noteData, boolean silent); - } - - @FunctionalInterface - public interface PlayerReportEvent { - boolean onPlayerReport(PlayerReportData reportData, SilentValue silent); - } - - @FunctionalInterface - public interface PlayerReportedEvent { - void onPlayerReported(PlayerReportData reportData, boolean silent); - } - - @FunctionalInterface - public interface PlayerReportDeletedEvent { - void onPlayerReportDeleted(PlayerReportData reportData); - } - - @FunctionalInterface - public interface NameBanEvent { - boolean onNameBan(NameBanData banData, SilentValue silent); - } - - @FunctionalInterface - public interface NameBannedEvent { - void onNameBanned(NameBanData banData, boolean silent); - } - - @FunctionalInterface - public interface NameUnbanEvent { - void onNameUnban(NameBanData banData, PlayerData actor, String reason, boolean silent); - } - - @FunctionalInterface - public interface PlayerWarnEvent { - boolean onPlayerWarn(PlayerWarnData warnData, SilentValue silent); - } - - @FunctionalInterface - public interface PlayerWarnedEvent { - void onPlayerWarned(PlayerWarnData warnData, boolean silent); - } - - @FunctionalInterface - public interface IpRangeBanEvent { - boolean onIpRangeBan(IpRangeBanData banData, SilentValue silent); - } - - @FunctionalInterface - public interface IpRangeBannedEvent { - void onIpRangeBanned(IpRangeBanData banData, boolean silent); - } - - @FunctionalInterface - public interface IpRangeUnbanEvent { - void onIpRangeUnban(IpRangeBanData banData, PlayerData actor, String reason, boolean silent); - } - - @FunctionalInterface - public interface PlayerMuteEvent { - boolean onPlayerMute(PlayerMuteData muteData, SilentValue silent); - } - - @FunctionalInterface - public interface PlayerMutedEvent { - void onPlayerMuted(PlayerMuteData muteData, boolean silent); - } - - @FunctionalInterface - public interface PlayerUnmuteEvent { - void onPlayerUnmute(PlayerMuteData muteData, PlayerData actor, String reason, boolean silent); - } - - @FunctionalInterface - public interface PluginReloadedEvent { - void onPluginReloaded(PlayerData actor); - } - - @FunctionalInterface - public interface PlayerDeniedEvent { - void onPlayerDenied(PlayerData player, Message message); - } - - public static class SilentValue { - @Getter - @Setter - private boolean silent; - - public SilentValue(boolean silent) { - this.silent = silent; - } - } -} diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricCommand.java b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricCommand.java index c08b44479..742a9dce8 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricCommand.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricCommand.java @@ -29,9 +29,11 @@ public class FabricCommand { + private final BanManagerPlugin plugin; private CommonCommand command; - public FabricCommand(CommonCommand command) { + public FabricCommand(BanManagerPlugin plugin, CommonCommand command) { + this.plugin = plugin; this.command = command; } @@ -83,7 +85,7 @@ private int execute(CommandContext context) throws CommandS return 1; } } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to execute command", e); + plugin.getLogger().warning("Failed to execute command", e); } return 0; @@ -91,9 +93,9 @@ private int execute(CommandContext context) throws CommandS private CommonSender getSender(ServerCommandSource source) { if (source.getEntity() instanceof ServerPlayerEntity) { - return new FabricPlayer((ServerPlayerEntity) source.getEntity(), source.getServer(), BanManagerPlugin.getInstance().getConfig().isOnlineMode()); + return new FabricPlayer(plugin, (ServerPlayerEntity) source.getEntity(), source.getServer(), plugin.getConfig().isOnlineMode()); } else { - return new FabricSender(BanManagerPlugin.getInstance(), source); + return new FabricSender(plugin, source); } } diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java index fd25707e9..a79aa237c 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java @@ -27,12 +27,14 @@ import me.lucko.fabric.api.permissions.v0.Permissions; public class FabricPlayer implements CommonPlayer { + private final BanManagerPlugin plugin; private final UUID uuid; private final boolean onlineMode; private final ServerPlayerEntity player; private final MinecraftServer server; - public FabricPlayer(ServerPlayerEntity player, MinecraftServer server, boolean onlineMode) { + public FabricPlayer(BanManagerPlugin plugin, ServerPlayerEntity player, MinecraftServer server, boolean onlineMode) { + this.plugin = plugin; this.player = player; this.server = server; this.uuid = player.getUuid(); @@ -45,7 +47,7 @@ public void kick(String message) { @Override public void kick(Component component) { - this.player.networkHandler.disconnect(FabricServer.formatJsonMessage(MessageRenderer.getInstance().toJson(component))); + this.player.networkHandler.disconnect(FabricServer.formatJsonMessage(plugin.getMessageRenderer().toJson(component))); } public void sendMessage(String message) { @@ -60,19 +62,19 @@ public void sendMessage(String message) { @Override public void sendMessage(Component component) { - getPlayer().sendMessage(FabricServer.formatJsonMessage(MessageRenderer.getInstance().toJson(component))); + getPlayer().sendMessage(FabricServer.formatJsonMessage(plugin.getMessageRenderer().toJson(component))); } @Override public void sendActionBar(Component component) { - getPlayer().sendMessage(FabricServer.formatJsonMessage(MessageRenderer.getInstance().toJson(component)), true); + getPlayer().sendMessage(FabricServer.formatJsonMessage(plugin.getMessageRenderer().toJson(component)), true); } @Override public void showTitle(Component title, Component subtitle, int fadeIn, int stay, int fadeOut) { ServerPlayerEntity p = getPlayer(); if (p == null) return; - MessageRenderer renderer = MessageRenderer.getInstance(); + MessageRenderer renderer = plugin.getMessageRenderer(); if (title != null) { net.minecraft.text.Text titleText = FabricServer.formatJsonMessage(renderer.toJson(title)); p.networkHandler.sendPacket(new net.minecraft.network.packet.s2c.play.TitleS2CPacket(titleText)); @@ -121,9 +123,9 @@ public boolean isConsole() { public PlayerData getData() { try { - return BanManagerPlugin.getInstance().getPlayerStorage().queryForId(UUIDUtils.toBytes(getUniqueId())); + return plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(getUniqueId())); } catch (SQLException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to load player data", e); + plugin.getLogger().warning("Failed to load player data", e); sendMessage(Message.get("sender.error.exception").toString()); return null; } diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricScheduler.java b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricScheduler.java index 72a25b8b8..95912d89b 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricScheduler.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricScheduler.java @@ -1,8 +1,8 @@ package me.confuser.banmanager.fabric; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonScheduler; import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.Logger; import java.time.Duration; import java.util.concurrent.Executors; @@ -13,11 +13,16 @@ import java.util.concurrent.TimeUnit; public class FabricScheduler implements CommonScheduler { + // Constructed before BanManagerPlugin (it's a constructor argument), so we accept a + // Logger directly to keep failure reporting available without reaching into the plugin + // singleton. + private final Logger logger; private final ScheduledExecutorService schedulerService; private final ForkJoinPool executorService; private MinecraftServer server; - public FabricScheduler() { + public FabricScheduler(Logger logger) { + this.logger = logger; this.schedulerService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { @@ -33,7 +38,7 @@ public Thread newThread(Runnable r) { worker.setName("banmanager-worker-" + worker.getPoolIndex()); return worker; }, - (t, e) -> BanManagerPlugin.getInstance().getLogger().warning("Uncaught exception in scheduler worker thread", e), + (t, e) -> logger.warn("Uncaught exception in scheduler worker thread", e), false); } @@ -47,7 +52,7 @@ public void runAsync(Runnable task) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in async task", e); + logger.warn("Exception in async task", e); } }); } @@ -58,7 +63,7 @@ public void runAsyncLater(Runnable task, Duration delay) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in delayed async task", e); + logger.warn("Exception in delayed async task", e); } }), delay.toMillis(), TimeUnit.MILLISECONDS); } @@ -69,7 +74,7 @@ public void runSync(Runnable task) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in sync task", e); + logger.warn("Exception in sync task", e); } }); } @@ -80,7 +85,7 @@ public void runSyncLater(Runnable task, Duration delay) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in delayed sync task", e); + logger.warn("Exception in delayed sync task", e); } }), delay.toMillis(), TimeUnit.MILLISECONDS); } @@ -91,11 +96,22 @@ public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration per try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in repeating async task", e); + logger.warn("Exception in repeating async task", e); } }), initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS); } + /** + * Fabric dispatches {@link #runSync(Runnable)} via + * {@link MinecraftServer#execute(Runnable)}, which queues the task to run + * on the server's main thread. Worldgen and entity APIs that require the + * server thread are therefore safe to call from inside the submitted task. + */ + @Override + public boolean isMainThreadAware() { + return true; + } + public void shutdown() { schedulerService.shutdown(); executorService.shutdown(); diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricServer.java b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricServer.java index 4ddef55f8..7c32f0cb0 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricServer.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricServer.java @@ -18,19 +18,7 @@ import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.CommonServer; import me.confuser.banmanager.common.CommonWorld; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; -import me.confuser.banmanager.common.data.IpBanData; -import me.confuser.banmanager.common.data.IpMuteData; -import me.confuser.banmanager.common.data.IpRangeBanData; -import me.confuser.banmanager.common.data.NameBanData; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.data.PlayerKickData; -import me.confuser.banmanager.common.data.PlayerMuteData; -import me.confuser.banmanager.common.data.PlayerNoteData; -import me.confuser.banmanager.common.data.PlayerReportData; -import me.confuser.banmanager.common.data.PlayerWarnData; import me.confuser.banmanager.common.kyori.text.Component; import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer; import me.confuser.banmanager.common.kyori.text.serializer.legacy.LegacyComponentSerializer; @@ -44,8 +32,6 @@ //? if >=1.21 { import net.minecraft.text.TextCodecs; //?} -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; public class FabricServer implements CommonServer { private BanManagerPlugin plugin; @@ -65,7 +51,7 @@ public CommonPlayer getPlayer(UUID uniqueId) { if (player == null) return null; - return new FabricPlayer(player, this.server, plugin.getConfig().isOnlineMode()); + return new FabricPlayer(plugin, player, this.server, plugin.getConfig().isOnlineMode()); } public CommonPlayer getPlayer(String name) { @@ -73,7 +59,7 @@ public CommonPlayer getPlayer(String name) { if (player == null) return null; - return new FabricPlayer(player, this.server, plugin.getConfig().isOnlineMode()); + return new FabricPlayer(plugin, player, this.server, plugin.getConfig().isOnlineMode()); } @Override @@ -83,7 +69,7 @@ public CommonPlayer getPlayerExact(String name) { public CommonPlayer[] getOnlinePlayers() { return this.server.getPlayerManager().getPlayerList().stream() - .map(player -> new FabricPlayer(player, this.server, plugin.getConfig().isOnlineMode())) + .map(player -> new FabricPlayer(plugin, player, this.server, plugin.getConfig().isOnlineMode())) .filter(player -> player != null && player.isOnline()) .toArray(CommonPlayer[]::new); } @@ -125,150 +111,6 @@ public CommonWorld getWorld(String name) { return null; } - public CommonEvent callEvent(String name, Object... args) { - CommonEvent commonEvent; - BanManagerEvents.SilentValue silentValue = new BanManagerEvents.SilentValue(true); - - switch (name) { - case "PlayerBanEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.PLAYER_BAN_EVENT.invoker().onPlayerBan((PlayerBanData) args[0], silentValue), silentValue.isSilent()); - break; - case "PlayerBannedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - Message bannedKickMessage = args.length > 2 && args[2] instanceof Message ? (Message) args[2] : null; - BanManagerEvents.PLAYER_BANNED_EVENT.invoker().onPlayerBanned((PlayerBanData) args[0], silentValue.isSilent(), bannedKickMessage); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "PlayerUnbanEvent": - BanManagerEvents.PLAYER_UNBAN_EVENT.invoker().onPlayerUnban((PlayerBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - commonEvent = new CommonEvent(false, (boolean) args[3]); - break; - - case "IpBanEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.IP_BAN_EVENT.invoker().onIpBan((IpBanData) args[0], silentValue), silentValue.isSilent()); - break; - case "IpBannedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.IP_BANNED_EVENT.invoker().onIpBanned((IpBanData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "IpUnbanEvent": - BanManagerEvents.IP_UNBAN_EVENT.invoker().onIpUnban((IpBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - commonEvent = new CommonEvent(false, (boolean) args[3]); - break; - - case "IpMuteEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.IP_MUTE_EVENT.invoker().onIpMute((IpMuteData) args[0], silentValue), silentValue.isSilent()); - break; - case "IpMutedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.IP_MUTED_EVENT.invoker().onIpMuted((IpMuteData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "IpUnmutedEvent": - BanManagerEvents.IP_UNMUTED_EVENT.invoker().onIpUnmuted((IpMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - commonEvent = new CommonEvent(false, (boolean) args[3]); - break; - - case "PlayerKickedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.PLAYER_KICKED_EVENT.invoker().onPlayerKicked((PlayerKickData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - - case "PlayerNoteCreatedEvent": - boolean noteSilent = args.length > 1 && (boolean) args[1]; - BanManagerEvents.PLAYER_NOTE_CREATED_EVENT.invoker().onPlayerNoteCreated((PlayerNoteData) args[0], noteSilent); - commonEvent = new CommonEvent(false, noteSilent); - break; - - case "PlayerReportEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.PLAYER_REPORT_EVENT.invoker().onPlayerReport((PlayerReportData) args[0], silentValue), silentValue.isSilent()); - break; - case "PlayerReportedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.PLAYER_REPORTED_EVENT.invoker().onPlayerReported((PlayerReportData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "PlayerReportDeletedEvent": - BanManagerEvents.PLAYER_REPORT_DELETED_EVENT.invoker().onPlayerReportDeleted((PlayerReportData) args[0]); - commonEvent = new CommonEvent(false, false); - break; - - case "NameBanEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.NAME_BAN_EVENT.invoker().onNameBan((NameBanData) args[0], silentValue), silentValue.isSilent()); - break; - case "NameBannedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.NAME_BANNED_EVENT.invoker().onNameBanned((NameBanData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "NameUnbanEvent": - BanManagerEvents.NAME_UNBAN_EVENT.invoker().onNameUnban((NameBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - commonEvent = new CommonEvent(false, (boolean) args[3]); - break; - - case "PlayerWarnEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.PLAYER_WARN_EVENT.invoker().onPlayerWarn((PlayerWarnData) args[0], silentValue), silentValue.isSilent()); - break; - case "PlayerWarnedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.PLAYER_WARNED_EVENT.invoker().onPlayerWarned((PlayerWarnData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - - case "IpRangeBanEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.IP_RANGE_BAN_EVENT.invoker().onIpRangeBan((IpRangeBanData) args[0], silentValue), silentValue.isSilent()); - break; - case "IpRangeBannedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.IP_RANGE_BANNED_EVENT.invoker().onIpRangeBanned((IpRangeBanData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "IpRangeUnbanEvent": - BanManagerEvents.IP_RANGE_UNBAN_EVENT.invoker().onIpRangeUnban((IpRangeBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - commonEvent = new CommonEvent(false, (boolean) args[3]); - break; - - case "PlayerMuteEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - commonEvent = new CommonEvent(!BanManagerEvents.PLAYER_MUTE_EVENT.invoker().onPlayerMute((PlayerMuteData) args[0], silentValue), silentValue.isSilent()); - break; - case "PlayerMutedEvent": - silentValue = new BanManagerEvents.SilentValue((boolean) args[1]); - BanManagerEvents.PLAYER_MUTED_EVENT.invoker().onPlayerMuted((PlayerMuteData) args[0], silentValue.isSilent()); - commonEvent = new CommonEvent(false, silentValue.isSilent()); - break; - case "PlayerUnmuteEvent": - BanManagerEvents.PLAYER_UNMUTE_EVENT.invoker().onPlayerUnmute((PlayerMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - commonEvent = new CommonEvent(false, (boolean) args[3]); - break; - - case "PluginReloadedEvent": - BanManagerEvents.PLUGIN_RELOADED_EVENT.invoker().onPluginReloaded((PlayerData) args[0]); - commonEvent = new CommonEvent(false, false); - break; - - case "PlayerDeniedEvent": - BanManagerEvents.PLAYER_DENIED_EVENT.invoker().onPlayerDenied((PlayerData) args[0], (Message) args[1]); - commonEvent = new CommonEvent(false, false); - break; - - default: - commonEvent = new CommonEvent(false, false); - break; - } - - return commonEvent; - } - public CommonExternalCommand getPluginCommand(String commandName) { CommandNode node = null; diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/BanListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/BanListener.java deleted file mode 100644 index 41406be97..000000000 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/BanListener.java +++ /dev/null @@ -1,37 +0,0 @@ -package me.confuser.banmanager.fabric.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonBanListener; -import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.fabric.BanManagerEvents; -import me.confuser.banmanager.common.data.*; - -public class BanListener { - - private final CommonBanListener listener; - - public BanListener(BanManagerPlugin plugin) { - this.listener = new CommonBanListener(plugin); - - BanManagerEvents.PLAYER_BANNED_EVENT.register(this::notifyOnBan); - BanManagerEvents.IP_BANNED_EVENT.register(this::notifyOnIpBan); - BanManagerEvents.IP_RANGE_BANNED_EVENT.register(this::notifyOnIpRangeBan); - BanManagerEvents.NAME_BANNED_EVENT.register(this::notifyOnNameBan); - } - - private void notifyOnBan(PlayerBanData banData, boolean silent, Message kickMessage) { - listener.notifyOnBan(banData, silent); - } - - private void notifyOnIpBan(IpBanData banData, boolean silent) { - listener.notifyOnBan(banData, silent); - } - - private void notifyOnIpRangeBan(IpRangeBanData banData, boolean silent) { - listener.notifyOnBan(banData, silent); - } - - private void notifyOnNameBan(NameBanData banData, boolean silent) { - listener.notifyOnBan(banData, silent); - } -} diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/HookListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/HookListener.java deleted file mode 100644 index f7148d187..000000000 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/HookListener.java +++ /dev/null @@ -1,108 +0,0 @@ -package me.confuser.banmanager.fabric.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonHooksListener; -import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.fabric.BanManagerEvents; -import me.confuser.banmanager.common.data.*; - -public class HookListener { - - private final CommonHooksListener listener; - - public HookListener(BanManagerPlugin plugin) { - this.listener = new CommonHooksListener(plugin); - - BanManagerEvents.PLAYER_BAN_EVENT.register(this::onBan); - BanManagerEvents.PLAYER_BANNED_EVENT.register(this::onBan); - BanManagerEvents.PLAYER_UNBAN_EVENT.register(this::onUnban); - BanManagerEvents.PLAYER_MUTE_EVENT.register(this::onMute); - BanManagerEvents.PLAYER_MUTED_EVENT.register(this::onMute); - BanManagerEvents.PLAYER_UNMUTE_EVENT.register(this::onUnmute); - BanManagerEvents.IP_BAN_EVENT.register(this::onBan); - BanManagerEvents.IP_BANNED_EVENT.register(this::onBan); - BanManagerEvents.IP_UNBAN_EVENT.register(this::onUnban); - BanManagerEvents.IP_RANGE_BAN_EVENT.register(this::onBan); - BanManagerEvents.IP_RANGE_BANNED_EVENT.register(this::onBan); - BanManagerEvents.IP_RANGE_UNBAN_EVENT.register(this::onUnban); - BanManagerEvents.PLAYER_WARN_EVENT.register(this::onWarn); - BanManagerEvents.PLAYER_WARNED_EVENT.register(this::onWarn); - BanManagerEvents.PLAYER_NOTE_CREATED_EVENT.register(this::onNote); - BanManagerEvents.PLAYER_REPORT_EVENT.register(this::onReport); - BanManagerEvents.PLAYER_REPORTED_EVENT.register(this::onReport); - } - - private boolean onBan(PlayerBanData banData, BanManagerEvents.SilentValue silent) { - listener.onBan(banData, true, silent.isSilent()); - return true; - } - - private void onBan(PlayerBanData banData, boolean silent, Message kickMessage) { - listener.onBan(banData, false, silent); - } - - private void onUnban(PlayerBanData banData, PlayerData actor, String reason, boolean silent) { - listener.onUnban(banData, actor, reason, silent); - } - - private boolean onMute(PlayerMuteData muteData, BanManagerEvents.SilentValue silent) { - listener.onMute(muteData, true, silent.isSilent()); - return true; - } - - private void onMute(PlayerMuteData muteData, boolean silent) { - listener.onMute(muteData, false, silent); - } - - private void onUnmute(PlayerMuteData muteData, PlayerData actor, String reason, boolean silent) { - listener.onUnmute(muteData, actor, reason, silent); - } - - private boolean onBan(IpBanData banData, BanManagerEvents.SilentValue silent) { - listener.onBan(banData, true, silent.isSilent()); - return true; - } - - private void onBan(IpBanData banData, boolean silent) { - listener.onBan(banData, false, silent); - } - - private void onUnban(IpBanData banData, PlayerData actor, String reason, boolean silent) { - listener.onUnban(banData, actor, reason, silent); - } - - private boolean onBan(IpRangeBanData banData, BanManagerEvents.SilentValue silent) { - listener.onBan(banData, true, silent.isSilent()); - return true; - } - - private void onBan(IpRangeBanData banData, boolean silent) { - listener.onBan(banData, false, silent); - } - - private void onUnban(IpRangeBanData banData, PlayerData actor, String reason, boolean silent) { - listener.onUnban(banData, actor, reason, silent); - } - - private boolean onWarn(PlayerWarnData warnData, BanManagerEvents.SilentValue silent) { - listener.onWarn(warnData, true, silent.isSilent()); - return true; - } - - private void onWarn(PlayerWarnData warnData, boolean silent) { - listener.onWarn(warnData, false, silent); - } - - private void onNote(PlayerNoteData noteData, boolean silent) { - listener.onNote(noteData, silent); - } - - private boolean onReport(PlayerReportData reportData, BanManagerEvents.SilentValue silent) { - listener.onReport(reportData, true, silent.isSilent()); - return true; - } - - private void onReport(PlayerReportData reportData, boolean silent) { - listener.onReport(reportData, false, silent); - } -} diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java index 8d5d38d0e..2930b0359 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java @@ -41,10 +41,10 @@ public JoinListener(BanManagerPlugin plugin) { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { listener.onJoin( - new FabricPlayer(handler.getPlayer(), server, plugin.getConfig().isOnlineMode())); + new FabricPlayer(plugin, handler.getPlayer(), server, plugin.getConfig().isOnlineMode())); listener.onPlayerLogin( - new FabricPlayer(handler.getPlayer(), server, plugin.getConfig().isOnlineMode()), + new FabricPlayer(plugin, handler.getPlayer(), server, plugin.getConfig().isOnlineMode()), new LoginHandler(handler.getPlayer())); }); } @@ -58,7 +58,6 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { - plugin.getServer().callEvent("PlayerDeniedEvent", player, message); String locale = player.getLocale() != null ? player.getLocale() : "en"; isDenied = true; handler.disconnect(FabricServer.formatMessage(message.resolveComponent(locale))); diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/MuteListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/MuteListener.java deleted file mode 100644 index d2d0a31c2..000000000 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/MuteListener.java +++ /dev/null @@ -1,26 +0,0 @@ -package me.confuser.banmanager.fabric.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonMuteListener; -import me.confuser.banmanager.fabric.BanManagerEvents; -import me.confuser.banmanager.common.data.*; - -public class MuteListener { - - private final CommonMuteListener listener; - - public MuteListener(BanManagerPlugin plugin) { - this.listener = new CommonMuteListener(plugin); - - BanManagerEvents.PLAYER_MUTED_EVENT.register(this::notifyOnMute); - BanManagerEvents.IP_MUTED_EVENT.register(this::notifyOnIpMute); - } - - private void notifyOnMute(PlayerMuteData muteData, boolean silent) { - listener.notifyOnMute(muteData, silent); - } - - private void notifyOnIpMute(IpMuteData muteData, boolean silent) { - listener.notifyOnMute(muteData, silent); - } -} diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/NoteListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/NoteListener.java deleted file mode 100644 index 3cee3e505..000000000 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/NoteListener.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.confuser.banmanager.fabric.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonNoteListener; -import me.confuser.banmanager.fabric.BanManagerEvents; -import me.confuser.banmanager.common.data.PlayerNoteData; - -public class NoteListener { - - private final CommonNoteListener listener; - - public NoteListener(BanManagerPlugin plugin) { - this.listener = new CommonNoteListener(plugin); - - BanManagerEvents.PLAYER_NOTE_CREATED_EVENT.register(this::notifyOnNote); - } - - private void notifyOnNote(PlayerNoteData noteData, boolean silent) { - listener.notifyOnNote(noteData, silent); - } -} diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/ReportListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/ReportListener.java index 967aa6596..f7fff80c4 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/ReportListener.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/ReportListener.java @@ -2,52 +2,59 @@ import java.sql.SQLException; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonReportListener; -import me.confuser.banmanager.fabric.BanManagerEvents; -import me.confuser.banmanager.fabric.FabricServer; -import net.minecraft.server.network.ServerPlayerEntity; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.common.data.PlayerReportLocationData; +import me.confuser.banmanager.common.util.UUIDUtils; +import me.confuser.banmanager.fabric.FabricServer; +import net.minecraft.server.network.ServerPlayerEntity; +/** + * Stores the Fabric-specific player and actor location at the time a report + * is filed. Notification dispatch and reference cleanup live in the + * cross-platform {@link me.confuser.banmanager.common.listeners.CommonReportListener}. + */ public class ReportListener { - private final CommonReportListener listener; - private FabricServer server; - private BanManagerPlugin plugin; + private final FabricServer server; + private final BanManagerPlugin plugin; public ReportListener(BanManagerPlugin plugin, FabricServer server) { - this.listener = new CommonReportListener(plugin); this.plugin = plugin; this.server = server; - - BanManagerEvents.PLAYER_REPORTED_EVENT.register(this::notifyOnReported); - BanManagerEvents.PLAYER_REPORT_DELETED_EVENT.register(this::notifyOnReportDeleted); + plugin.getEventBus().subscribe(PlayerReportedEvent.class, this::storeLocation); } - private void notifyOnReported(PlayerReportData reportData, boolean silent) { - listener.notifyOnReport(reportData); - ServerPlayerEntity player = this.server.getServer().getPlayerManager().getPlayer(reportData.getPlayer().getUUID()); - ServerPlayerEntity actor = this.server.getServer().getPlayerManager().getPlayer(reportData.getActor().getUUID()); + private void storeLocation(PlayerReportedEvent event) { + PlayerReport report = event.report(); + PlayerReportData entity; + try { + entity = plugin.getPlayerReportStorage().queryForId(report.id()); + } catch (SQLException e) { + plugin.getLogger().warning("Failed to load report entity for location storage", e); + return; + } + if (entity == null) return; + + ServerPlayerEntity player = this.server.getServer().getPlayerManager().getPlayer(report.player().uuid()); + ServerPlayerEntity actor = this.server.getServer().getPlayerManager().getPlayer(report.actor().uuid()); try { - createLocation(reportData, player, reportData.getPlayer()); + createLocation(entity, player, entity.getPlayer()); } catch (SQLException e) { plugin.getLogger().warning("Failed to store report location for reported player", e); } try { - createLocation(reportData, actor, reportData.getActor()); + createLocation(entity, actor, entity.getActor()); } catch (SQLException e) { plugin.getLogger().warning("Failed to store report location for actor", e); } } - private void notifyOnReportDeleted(PlayerReportData reportData) { - listener.deleteReferences(reportData); - } - private void createLocation(PlayerReportData report, ServerPlayerEntity player, PlayerData playerData) throws SQLException { if (player == null || playerData == null) diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/WebhookListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/WebhookListener.java deleted file mode 100644 index 8b1d0fd7d..000000000 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/WebhookListener.java +++ /dev/null @@ -1,82 +0,0 @@ -package me.confuser.banmanager.fabric.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonWebhookListener; -import me.confuser.banmanager.common.data.Webhook; -import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.fabric.BanManagerEvents; -import me.confuser.banmanager.common.data.*; - -import java.util.List; - -public class WebhookListener { - - private final CommonWebhookListener listener; - - public WebhookListener(BanManagerPlugin plugin) { - this.listener = new CommonWebhookListener(plugin); - - BanManagerEvents.PLAYER_BANNED_EVENT.register(this::notifyOnBan); - BanManagerEvents.PLAYER_MUTED_EVENT.register(this::notifyOnMute); - BanManagerEvents.IP_BANNED_EVENT.register(this::notifyOnBan); - BanManagerEvents.PLAYER_KICKED_EVENT.register(this::notifyOnKick); - BanManagerEvents.PLAYER_WARNED_EVENT.register(this::notifyOnWarn); - BanManagerEvents.PLAYER_UNBAN_EVENT.register(this::notifyOnUnban); - BanManagerEvents.IP_UNBAN_EVENT.register(this::notifyOnUnban); - BanManagerEvents.PLAYER_UNMUTE_EVENT.register(this::notifyOnUnmute); - BanManagerEvents.PLAYER_REPORTED_EVENT.register(this::notifyOnReport); - } - - private void notifyOnBan(PlayerBanData banData, boolean silent, Message kickMessage) { - List webhooks = listener.notifyOnBan(banData); - sendAll(webhooks, silent); - } - - private void notifyOnMute(PlayerMuteData muteData, boolean silent) { - List webhooks = listener.notifyOnMute(muteData); - sendAll(webhooks, silent); - } - - private void notifyOnBan(IpBanData banData, boolean silent) { - List webhooks = listener.notifyOnBan(banData); - sendAll(webhooks, silent); - } - - private void notifyOnKick(PlayerKickData kickData, boolean silent) { - List webhooks = listener.notifyOnKick(kickData); - sendAll(webhooks, silent); - } - - private void notifyOnWarn(PlayerWarnData warnData, boolean silent) { - List webhooks = listener.notifyOnWarn(warnData); - sendAll(webhooks, silent); - } - - private void notifyOnUnban(PlayerBanData banData, PlayerData actor, String reason, boolean silent) { - List webhooks = listener.notifyOnUnban(banData, actor, reason); - sendAll(webhooks, silent); - } - - private void notifyOnUnban(IpBanData banData, PlayerData actor, String reason, boolean silent) { - List webhooks = listener.notifyOnUnban(banData, actor, reason); - sendAll(webhooks, silent); - } - - private void notifyOnUnmute(PlayerMuteData muteData, PlayerData actor, String reason, boolean silent) { - List webhooks = listener.notifyOnUnmute(muteData, actor, reason); - sendAll(webhooks, silent); - } - - private void notifyOnReport(PlayerReportData reportData, boolean silent) { - List webhooks = listener.notifyOnReport(reportData, reportData.getActor(), reportData.getReason()); - sendAll(webhooks, silent); - } - - private void sendAll(List webhooks, boolean isSilent) { - for (Webhook data : webhooks) { - if (isSilent && data.ignoreSilent()) continue; - if (data.url() == null || data.payload() == null || data.url().isEmpty() || data.payload().isEmpty()) continue; - listener.sendAsync(data); - } - } -} diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/ServerPlayerNetworkHandlerMixin.java b/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/ServerPlayerNetworkHandlerMixin.java index 6363ee54a..8d2921a5e 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/ServerPlayerNetworkHandlerMixin.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/ServerPlayerNetworkHandlerMixin.java @@ -16,6 +16,7 @@ import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.listeners.CommonCommandListener; +import me.confuser.banmanager.fabric.BMFabricPlugin; @Mixin(ServerPlayNetworkHandler.class) public abstract class ServerPlayerNetworkHandlerMixin { @@ -28,24 +29,32 @@ public abstract class ServerPlayerNetworkHandlerMixin { //? if >=1.21 { @Inject(method = "handleCommandExecution", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/message/SignedCommandArguments$Impl;(Ljava/util/Map;)V"), cancellable = true) private void banManager_checkCommand(ChatCommandSignedC2SPacket packet, LastSeenMessageList lastSeenMessages, CallbackInfo ci) { + BMFabricPlugin mod = BMFabricPlugin.getInstance(); + if (mod == null) return; + BanManagerPlugin plugin = mod.getPlugin(); + if (plugin == null) return; // Split the command String[] args = packet.command().split(" ", 6); // Get rid of the first / String cmd = args[0].replace("/", "").toLowerCase(); - if (new CommonCommandListener(BanManagerPlugin.getInstance()).onCommand(BanManagerPlugin.getInstance().getServer().getPlayer(player.getUuid()), cmd, args)) { + if (new CommonCommandListener(plugin).onCommand(plugin.getServer().getPlayer(player.getUuid()), cmd, args)) { ci.cancel(); } } //?} else { /*@Inject(method = "onCommandExecution", at = @At("HEAD"), cancellable = true) private void banManager_checkCommand(CommandExecutionC2SPacket packet, CallbackInfo ci) { + BMFabricPlugin mod = BMFabricPlugin.getInstance(); + if (mod == null) return; + BanManagerPlugin plugin = mod.getPlugin(); + if (plugin == null) return; // Split the command String[] args = packet.command().split(" ", 6); // Get rid of the first / String cmd = args[0].replace("/", "").toLowerCase(); - if (new CommonCommandListener(BanManagerPlugin.getInstance()).onCommand(BanManagerPlugin.getInstance().getServer().getPlayer(player.getUuid()), cmd, args)) { + if (new CommonCommandListener(plugin).onCommand(plugin.getServer().getPlayer(player.getUuid()), cmd, args)) { ci.cancel(); } } diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/SignUpdateMixin.java b/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/SignUpdateMixin.java index 953c4f92c..776e919bd 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/SignUpdateMixin.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/mixin/SignUpdateMixin.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.fabric.mixin; import me.confuser.banmanager.common.BanManagerPlugin; +import me.confuser.banmanager.fabric.BMFabricPlugin; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; import net.minecraft.server.network.ServerPlayNetworkHandler; @@ -27,7 +28,9 @@ public class SignUpdateMixin { } private void checkMutedSign(CallbackInfo ci) { - BanManagerPlugin plugin = BanManagerPlugin.getInstance(); + BMFabricPlugin mod = BMFabricPlugin.getInstance(); + if (mod == null) return; + BanManagerPlugin plugin = mod.getPlugin(); if (plugin == null) return; // Check player mute diff --git a/gradle.properties b/gradle.properties index 321afbf96..2d865ff62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ version=8.0.0-SNAPSHOT org.gradle.parallel=true description="The defacto plugin for Minecraft to manage punishments and moderate more effectively" -org.gradle.jvmargs=-Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication +org.gradle.jvmargs=-Xmx4G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication diff --git a/settings.gradle.kts b/settings.gradle.kts index 692a30d36..c1263d358 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ plugins { rootProject.name = "BanManager" // Non-Fabric modules (standard includes) +include(":BanManagerAPI") include(":BanManagerCommon") include(":BanManagerBukkit") include(":BanManagerBungee") @@ -21,7 +22,9 @@ include(":BanManagerSponge") include(":BanManagerLibs") include(":BanManagerVelocity") include(":BanManagerE2E") +include(":BanManagerSamplePlugin") +project(":BanManagerAPI").projectDir = file("api") project(":BanManagerCommon").projectDir = file("common") project(":BanManagerBukkit").projectDir = file("bukkit") project(":BanManagerBungee").projectDir = file("bungee") @@ -29,6 +32,7 @@ project(":BanManagerSponge").projectDir = file("sponge") project(":BanManagerLibs").projectDir = file("libs") project(":BanManagerVelocity").projectDir = file("velocity") project(":BanManagerE2E").projectDir = file("e2e") +project(":BanManagerSamplePlugin").projectDir = file("e2e/sample-plugin") // Fabric module with Stonecutter multi-version support stonecutter { diff --git a/sponge/build.gradle.kts b/sponge/build.gradle.kts index 9e43cbe1f..2ff1d99bd 100644 --- a/sponge/build.gradle.kts +++ b/sponge/build.gradle.kts @@ -131,8 +131,10 @@ tasks.named("shadowJar") { archiveVersion.set("") dependencies { + include(dependency(":BanManagerAPI")) include(dependency(":BanManagerCommon")) include(dependency(":BanManagerLibs")) + include(dependency("com.github.seancfoley:ipaddress:.*")) include(dependency("org.bstats:.*:.*")) relocate("org.bstats", "me.confuser.banmanager.common.bstats") @@ -159,6 +161,8 @@ tasks.named("shadowJar") { minimize { exclude(dependency("org.bstats:.*:.*")) exclude(dependency(":BanManagerLibs")) + exclude(dependency(":BanManagerAPI")) + exclude(dependency("com.github.seancfoley:ipaddress:.*")) } } diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/BMSpongePlugin.java b/sponge/src/main/java/me/confuser/banmanager/sponge/BMSpongePlugin.java index 95c124cfc..c6e01a9a5 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/BMSpongePlugin.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/BMSpongePlugin.java @@ -1,12 +1,19 @@ package me.confuser.banmanager.sponge; import com.google.inject.Inject; +import me.confuser.banmanager.api.event.player.PluginReloadedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonLogger; import me.confuser.banmanager.common.commands.CommonCommand; import me.confuser.banmanager.common.configs.PluginInfo; import me.confuser.banmanager.common.configuration.ConfigurationSection; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; +import me.confuser.banmanager.common.listeners.CommonBanListener; +import me.confuser.banmanager.common.listeners.CommonHooksListener; +import me.confuser.banmanager.common.listeners.CommonMuteListener; +import me.confuser.banmanager.common.listeners.CommonNoteListener; +import me.confuser.banmanager.common.listeners.CommonReportListener; +import me.confuser.banmanager.common.listeners.CommonWebhookListener; import me.confuser.banmanager.common.runnables.*; import me.confuser.banmanager.sponge.listeners.*; import org.apache.logging.log4j.Logger; @@ -194,21 +201,23 @@ public void setupListeners() { Sponge.eventManager().registerListeners(pluginContainer, new JoinListener(plugin)); Sponge.eventManager().registerListeners(pluginContainer, new LeaveListener(plugin)); Sponge.eventManager().registerListeners(pluginContainer, new CommandListener(plugin)); - Sponge.eventManager().registerListeners(pluginContainer, new HookListener(plugin)); + new CommonHooksListener(plugin); registerChatListener(); - Sponge.eventManager().registerListeners(pluginContainer, new ReloadListener(this)); + plugin.getEventBus().subscribe(PluginReloadedEvent.class, e -> registerChatListener()); if (plugin.getConfig().isDisplayNotificationsEnabled()) { - Sponge.eventManager().registerListeners(pluginContainer, new BanListener(plugin)); + new CommonBanListener(plugin); + new CommonMuteListener(plugin); Sponge.eventManager().registerListeners(pluginContainer, new MuteListener(plugin)); - Sponge.eventManager().registerListeners(pluginContainer, new NoteListener(plugin)); - Sponge.eventManager().registerListeners(pluginContainer, new ReportListener(plugin)); + new CommonNoteListener(plugin); + new CommonReportListener(plugin); + new ReportListener(plugin); } if (plugin.getWebhookConfig().isHooksEnabled()) { - Sponge.eventManager().registerListeners(pluginContainer, new WebhookListener(plugin)); + new CommonWebhookListener(plugin); } } diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeCommand.java b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeCommand.java index 83b6fcefe..1d3cbd9b3 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeCommand.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeCommand.java @@ -87,7 +87,7 @@ private CommandResult execute(CommandContext context, Parameter.Value ar private CommonSender getSender(CommandCause cause) { Object root = cause.root(); if (root instanceof ServerPlayer) { - return new SpongePlayer((ServerPlayer) root, plugin.getConfig().isOnlineMode()); + return new SpongePlayer(plugin, (ServerPlayer) root, plugin.getConfig().isOnlineMode()); } else { // Use CommandCause directly as the audience - this properly routes // messages back to RCON connections and other command sources diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java index e2e8fd7a8..32ca64588 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java @@ -6,7 +6,6 @@ import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.common.util.MessageRenderer; import me.confuser.banmanager.common.util.MessageRegistry; import me.confuser.banmanager.common.util.UUIDUtils; import net.kyori.adventure.key.Key; @@ -26,23 +25,25 @@ import java.util.UUID; public class SpongePlayer implements CommonPlayer { + private final BanManagerPlugin plugin; private ServerPlayer player; private final UUID uuid; private final boolean onlineMode; private InetAddress address; - public SpongePlayer(UUID uuid, String name, boolean onlineMode) { + public SpongePlayer(BanManagerPlugin plugin, UUID uuid, String name, boolean onlineMode) { + this.plugin = plugin; this.uuid = uuid; this.onlineMode = onlineMode; } - public SpongePlayer(ServerPlayer player, boolean onlineMode) { - this(player.uniqueId(), player.name(), onlineMode); + public SpongePlayer(BanManagerPlugin plugin, ServerPlayer player, boolean onlineMode) { + this(plugin, player.uniqueId(), player.name(), onlineMode); this.player = player; } - public SpongePlayer(ServerPlayer player, boolean onlineMode, InetAddress address) { - this(player, onlineMode); + public SpongePlayer(BanManagerPlugin plugin, ServerPlayer player, boolean onlineMode, InetAddress address) { + this(plugin, player, onlineMode); this.address = address; } @@ -113,7 +114,7 @@ public void sendJSONMessage(String jsonString) { } private Component convertToNative(me.confuser.banmanager.common.kyori.text.Component component) { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); return GsonComponentSerializer.gson().deserialize(json); } @@ -125,9 +126,9 @@ public boolean isConsole() { @Override public PlayerData getData() { try { - return BanManagerPlugin.getInstance().getPlayerStorage().queryForId(UUIDUtils.toBytes(getUniqueId())); + return plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(getUniqueId())); } catch (SQLException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to load player data", e); + plugin.getLogger().warning("Failed to load player data", e); sendMessage(Message.get("sender.error.exception").toString()); return null; } diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeScheduler.java b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeScheduler.java index ed25d7828..8f99bfa22 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeScheduler.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeScheduler.java @@ -1,7 +1,8 @@ package me.confuser.banmanager.sponge; -import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonScheduler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.spongepowered.api.Sponge; import org.spongepowered.api.scheduler.Task; import org.spongepowered.api.util.Ticks; @@ -16,6 +17,8 @@ import java.util.concurrent.TimeUnit; public class SpongeScheduler implements CommonScheduler { + private static final Logger LOGGER = LogManager.getLogger("BanManager"); + private final PluginContainer plugin; private final ScheduledExecutorService schedulerService; private final ForkJoinPool executorService; @@ -37,7 +40,7 @@ public Thread newThread(Runnable r) { worker.setName("banmanager-worker-" + worker.getPoolIndex()); return worker; }, - (t, e) -> BanManagerPlugin.getInstance().getLogger().warning("Uncaught exception in scheduler worker thread", e), + (t, e) -> LOGGER.warn("Uncaught exception in scheduler worker thread", e), false); } @@ -47,7 +50,7 @@ public void runAsync(Runnable task) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in async task", e); + LOGGER.warn("Exception in async task", e); } }); } @@ -58,7 +61,7 @@ public void runAsyncLater(Runnable task, Duration delay) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in delayed async task", e); + LOGGER.warn("Exception in delayed async task", e); } }), delay.toMillis(), TimeUnit.MILLISECONDS); } @@ -72,7 +75,7 @@ public void runSync(Runnable task) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in sync task", e); + LOGGER.warn("Exception in sync task", e); } }) .build() @@ -90,7 +93,7 @@ public void runSyncLater(Runnable task, Duration delay) { try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in delayed sync task", e); + LOGGER.warn("Exception in delayed sync task", e); } }) .build() @@ -103,11 +106,21 @@ public void runAsyncRepeating(Runnable task, Duration initialDelay, Duration per try { task.run(); } catch (Exception e) { - BanManagerPlugin.getInstance().getLogger().warning("Exception in repeating async task", e); + LOGGER.warn("Exception in repeating async task", e); } }), initialDelay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS); } + /** + * Sponge dispatches {@link #runSync(Runnable)} through the server's + * scheduler so the task runs on the main game tick thread, where the + * full Sponge API surface (worlds, inventories, etc.) is legal to touch. + */ + @Override + public boolean isMainThreadAware() { + return true; + } + public void shutdown() { schedulerService.shutdown(); executorService.shutdown(); diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeServer.java b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeServer.java index b10f3bc6d..54440a2cc 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeServer.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongeServer.java @@ -1,13 +1,10 @@ package me.confuser.banmanager.sponge; import me.confuser.banmanager.common.*; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; -import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer; import me.confuser.banmanager.common.util.ColorUtils; import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.sponge.api.events.*; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.spongepowered.api.Server; @@ -31,13 +28,13 @@ public void enable(BanManagerPlugin plugin, Server server) { @Override public CommonPlayer getPlayer(UUID uniqueId) { Optional player = Sponge.server().player(uniqueId); - return player.map(value -> new SpongePlayer(value, plugin.getConfig().isOnlineMode())).orElse(null); + return player.map(value -> new SpongePlayer(plugin, value, plugin.getConfig().isOnlineMode())).orElse(null); } @Override public CommonPlayer getPlayer(String name) { Optional player = Sponge.server().player(name); - return player.map(value -> new SpongePlayer(value, plugin.getConfig().isOnlineMode())).orElse(null); + return player.map(value -> new SpongePlayer(plugin, value, plugin.getConfig().isOnlineMode())).orElse(null); } @Override @@ -48,7 +45,7 @@ public CommonPlayer getPlayerExact(String name) { @Override public CommonPlayer[] getOnlinePlayers() { return Sponge.server().onlinePlayers().stream() - .map(player -> new SpongePlayer(player, plugin.getConfig().isOnlineMode())) + .map(player -> new SpongePlayer(plugin, player, plugin.getConfig().isOnlineMode())) .collect(Collectors.toList()).toArray(new CommonPlayer[0]); } @@ -115,128 +112,6 @@ public CommonWorld getWorld(String name) { return null; } - @Override - public CommonEvent callEvent(String name, Object... args) { - CustomEvent event = null; - CommonEvent commonEvent = new CommonEvent(false, true); - - switch (name) { - case "PlayerBanEvent": - event = new PlayerBanEvent((PlayerBanData) args[0], (boolean) args[1]); - break; - case "PlayerBannedEvent": - PlayerBannedEvent bannedEvent = new PlayerBannedEvent((PlayerBanData) args[0], (boolean) args[1]); - if (args.length > 2 && args[2] instanceof Message) { - bannedEvent.setKickMessage((Message) args[2]); - } - event = bannedEvent; - break; - case "PlayerUnbanEvent": - event = new PlayerUnbanEvent((PlayerBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpBanEvent": - event = new IpBanEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpBannedEvent": - event = new IpBannedEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpUnbanEvent": - event = new IpUnbanEvent((IpBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpMuteEvent": - event = new IpMuteEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpMutedEvent": - event = new IpMutedEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpUnmutedEvent": - event = new IpUnmutedEvent((IpMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerKickedEvent": - event = new PlayerKickedEvent((PlayerKickData) args[0], (boolean) args[1]); - break; - - case "PlayerNoteCreatedEvent": - event = new PlayerNoteCreatedEvent((PlayerNoteData) args[0], args.length > 1 && (boolean) args[1]); - break; - - case "PlayerReportEvent": - event = new PlayerReportEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportedEvent": - event = new PlayerReportedEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportDeletedEvent": - event = new PlayerReportDeletedEvent((PlayerReportData) args[0]); - break; - - case "NameBanEvent": - event = new NameBanEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameBannedEvent": - event = new NameBannedEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameUnbanEvent": - event = new NameUnbanEvent((NameBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerWarnEvent": - event = new PlayerWarnEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - case "PlayerWarnedEvent": - event = new PlayerWarnedEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - - case "IpRangeBanEvent": - event = new IpRangeBanEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeBannedEvent": - event = new IpRangeBannedEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeUnbanEvent": - event = new IpRangeUnbanEvent((IpRangeBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerMuteEvent": - event = new PlayerMuteEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerMutedEvent": - event = new PlayerMutedEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerUnmuteEvent": - event = new PlayerUnmuteEvent((PlayerMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PluginReloadedEvent": - event = new PluginReloadedEvent((PlayerData) args[0]); - break; - - case "PlayerDeniedEvent": - event = new PlayerDeniedEvent((PlayerData) args[0], (Message) args[1]); - break; - } - - if (event == null) { - plugin.getLogger().warning("Unable to call missing event " + name); - return commonEvent; - } - - Sponge.eventManager().post(event); - - if (event instanceof SilentCancellableEvent) { - commonEvent = new CommonEvent(((SilentCancellableEvent) event).isCancelled(), ((SilentCancellableEvent) event).isSilent()); - } else if (event instanceof SilentEvent) { - commonEvent = new CommonEvent(false, ((SilentEvent) event).isSilent()); - } else if (event instanceof CustomCancellableEvent) { - commonEvent = new CommonEvent(((CustomCancellableEvent) event).isCancelled(), true); - } - - return commonEvent; - } - @Override public CommonExternalCommand getPluginCommand(String commandName) { Optional commandMapping = Sponge.server().commandManager().commandMapping(commandName); diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/CustomCancellableEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/CustomCancellableEvent.java deleted file mode 100644 index 927ebd498..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/CustomCancellableEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import lombok.Setter; -import org.spongepowered.api.event.Cancellable; - -public abstract class CustomCancellableEvent extends CustomEvent implements Cancellable { - @Getter - @Setter - private boolean cancelled = false; -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/CustomEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/CustomEvent.java deleted file mode 100644 index d6f291e62..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/CustomEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.event.Cause; -import org.spongepowered.api.event.Event; -import org.spongepowered.api.event.EventContext; -import org.spongepowered.api.event.EventContextKeys; -import org.spongepowered.plugin.PluginContainer; - -public abstract class CustomEvent implements Event { - @Getter - private final Cause cause; - - public CustomEvent() { - PluginContainer plugin = Sponge.pluginManager().plugin("banmanager").orElseThrow(); - EventContext eventContext = EventContext.builder() - .add(EventContextKeys.PLUGIN, plugin) - .build(); - - this.cause = Cause.of(eventContext, plugin); - } - - @Override - public Cause cause() { - return cause; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpBanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpBanEvent.java deleted file mode 100644 index 5edd238af..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpBanEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBanEvent extends SilentCancellableEvent { - @Getter - private IpBanData ban; - - public IpBanEvent(IpBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpBannedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpBannedEvent.java deleted file mode 100644 index c0dfe4f80..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpBannedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBannedEvent extends SilentEvent { - @Getter - private IpBanData ban; - - public IpBannedEvent(IpBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpMuteEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpMuteEvent.java deleted file mode 100644 index 209f0cbaa..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpMuteEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMuteEvent extends SilentCancellableEvent { - @Getter - private IpMuteData mute; - - public IpMuteEvent(IpMuteData mute, boolean isSilent) { - super(isSilent); - this.mute = mute; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpMutedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpMutedEvent.java deleted file mode 100644 index 631082663..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpMutedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMutedEvent extends SilentEvent { - @Getter - private IpMuteData mute; - - public IpMutedEvent(IpMuteData mute, boolean isSilent) { - super(isSilent); - this.mute = mute; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeBanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeBanEvent.java deleted file mode 100644 index b4c5d8138..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeBanEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - -public class IpRangeBanEvent extends SilentCancellableEvent { - @Getter - private IpRangeBanData ban; - - public IpRangeBanEvent(IpRangeBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeBannedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeBannedEvent.java deleted file mode 100644 index 304ebeae8..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeBannedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - -public class IpRangeBannedEvent extends SilentEvent { - @Getter - private IpRangeBanData ban; - - public IpRangeBannedEvent(IpRangeBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeUnbanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeUnbanEvent.java deleted file mode 100644 index c942940a9..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpRangeUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; -import me.confuser.banmanager.common.data.PlayerData; - -public class IpRangeUnbanEvent extends SilentCancellableEvent { - @Getter - private IpRangeBanData ban; - - @Getter - private PlayerData actor; - - @Getter - private String reason; - - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpUnbanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpUnbanEvent.java deleted file mode 100644 index 20a47e58d..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; -import me.confuser.banmanager.common.data.PlayerData; - -public class IpUnbanEvent extends SilentCancellableEvent { - @Getter - private IpBanData ban; - - @Getter - private PlayerData actor; - - @Getter - private String reason; - - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpUnmutedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpUnmutedEvent.java deleted file mode 100644 index 3adc3bd83..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/IpUnmutedEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; -import me.confuser.banmanager.common.data.PlayerData; - -public class IpUnmutedEvent extends SilentCancellableEvent { - @Getter - private IpMuteData mute; - - @Getter - private PlayerData actor; - - @Getter - private String reason; - - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent); - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameBanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameBanEvent.java deleted file mode 100644 index 107f30433..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameBanEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - -public class NameBanEvent extends SilentCancellableEvent { - @Getter - private NameBanData ban; - - public NameBanEvent(NameBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameBannedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameBannedEvent.java deleted file mode 100644 index ff61e6aa9..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameBannedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - -public class NameBannedEvent extends SilentEvent { - @Getter - private NameBanData ban; - - public NameBannedEvent(NameBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameUnbanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameUnbanEvent.java deleted file mode 100644 index e4be64f54..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/NameUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; -import me.confuser.banmanager.common.data.PlayerData; - -public class NameUnbanEvent extends SilentCancellableEvent { - @Getter - private NameBanData ban; - - @Getter - private PlayerData actor; - - @Getter - private String reason; - - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerBanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerBanEvent.java deleted file mode 100644 index 2149a5428..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerBanEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; - -public class PlayerBanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - - public PlayerBanEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerBannedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerBannedEvent.java deleted file mode 100644 index 30d2bc710..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerBannedEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.util.Message; - -public class PlayerBannedEvent extends SilentEvent { - - @Getter - private PlayerBanData ban; - - @Getter - @Setter - private Message kickMessage; - - public PlayerBannedEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerDeniedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerDeniedEvent.java deleted file mode 100644 index d353e6ee8..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerDeniedEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.util.Message; - -public class PlayerDeniedEvent extends CustomEvent { - - @Getter - private PlayerData player; - - @Getter - private Message message; - - public PlayerDeniedEvent(PlayerData player, Message message) { - super(); - this.player = player; - this.message = message; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerKickedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerKickedEvent.java deleted file mode 100644 index bd771fb7e..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerKickedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerKickData; - -public class PlayerKickedEvent extends SilentEvent { - @Getter - private PlayerKickData kick; - - public PlayerKickedEvent(PlayerKickData kick, boolean isSilent) { - super(isSilent); - this.kick = kick; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerMuteEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerMuteEvent.java deleted file mode 100644 index 45bca19e4..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerMuteEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - -public class PlayerMuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMuteEvent(PlayerMuteData mute, boolean isSilent) { - super(isSilent); - this.mute = mute; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerMutedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerMutedEvent.java deleted file mode 100644 index 8499f522e..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerMutedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - -public class PlayerMutedEvent extends SilentEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMutedEvent(PlayerMuteData mute, boolean isSilent) { - super(isSilent); - this.mute = mute; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerNoteCreatedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerNoteCreatedEvent.java deleted file mode 100644 index 6579cc6ba..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerNoteCreatedEvent.java +++ /dev/null @@ -1,22 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerNoteData; - -public class PlayerNoteCreatedEvent extends SilentEvent { - @Getter - private PlayerNoteData note; - - public PlayerNoteCreatedEvent(PlayerNoteData note) { - this(note, false); - } - - public PlayerNoteCreatedEvent(PlayerNoteData note, boolean silent) { - super(silent); - this.note = note; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportDeletedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportDeletedEvent.java deleted file mode 100644 index dc04b24fa..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportDeletedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - -public class PlayerReportDeletedEvent extends CustomEvent { - @Getter - private PlayerReportData report; - - public PlayerReportDeletedEvent(PlayerReportData report) { - super(); - this.report = report; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportEvent.java deleted file mode 100644 index ef48886b0..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - -public class PlayerReportEvent extends SilentCancellableEvent { - @Getter - private PlayerReportData report; - - public PlayerReportEvent(PlayerReportData report, boolean isSilent) { - super(isSilent); - this.report = report; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportedEvent.java deleted file mode 100644 index 70f74d0da..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerReportedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - -public class PlayerReportedEvent extends SilentEvent { - @Getter - private PlayerReportData report; - - public PlayerReportedEvent(PlayerReportData report, boolean isSilent) { - super(isSilent); - this.report = report; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerUnbanEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerUnbanEvent.java deleted file mode 100644 index 508bbf517..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.data.PlayerData; - -public class PlayerUnbanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - - @Getter - private PlayerData actor; - - @Getter - private String reason; - - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerUnmuteEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerUnmuteEvent.java deleted file mode 100644 index f543230e8..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerUnmuteEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; -import me.confuser.banmanager.common.data.PlayerData; - -public class PlayerUnmuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - - @Getter - private PlayerData actor; - - @Getter - private String reason; - - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent); - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerWarnEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerWarnEvent.java deleted file mode 100644 index 06d6fe240..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerWarnEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - -public class PlayerWarnEvent extends SilentCancellableEvent { - @Getter - private PlayerWarnData warning; - - public PlayerWarnEvent(PlayerWarnData warning, boolean isSilent) { - super(isSilent); - this.warning = warning; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerWarnedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerWarnedEvent.java deleted file mode 100644 index ab6b65061..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PlayerWarnedEvent.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - -public class PlayerWarnedEvent extends SilentEvent { - @Getter - private PlayerWarnData warning; - - public PlayerWarnedEvent(PlayerWarnData warning, boolean isSilent) { - super(isSilent); - this.warning = warning; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PluginReloadedEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PluginReloadedEvent.java deleted file mode 100644 index 99cb3b651..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/PluginReloadedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; - -public class PluginReloadedEvent extends CustomEvent { - - @Getter - private PlayerData actor; - - public PluginReloadedEvent(PlayerData actor) { - super(); - this.actor = actor; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/SilentCancellableEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/SilentCancellableEvent.java deleted file mode 100644 index 96c7ae2ff..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/SilentCancellableEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import lombok.Setter; -import org.spongepowered.api.event.Cancellable; - -public abstract class SilentCancellableEvent extends CustomEvent implements Cancellable { - @Getter - @Setter - private boolean cancelled = false; - - @Getter - @Setter - private boolean silent; - - public SilentCancellableEvent(boolean silent) { - super(); - this.silent = silent; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/SilentEvent.java b/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/SilentEvent.java deleted file mode 100644 index f0509f941..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/api/events/SilentEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.confuser.banmanager.sponge.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentEvent extends CustomEvent { - @Getter - @Setter - private boolean silent; - - public SilentEvent(boolean silent) { - super(); - this.silent = silent; - } -} - - - - diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/BanListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/BanListener.java deleted file mode 100644 index 458b82e3b..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/BanListener.java +++ /dev/null @@ -1,38 +0,0 @@ -package me.confuser.banmanager.sponge.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonBanListener; -import me.confuser.banmanager.sponge.api.events.IpBannedEvent; -import me.confuser.banmanager.sponge.api.events.IpRangeBannedEvent; -import me.confuser.banmanager.sponge.api.events.NameBannedEvent; -import me.confuser.banmanager.sponge.api.events.PlayerBannedEvent; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; - -public class BanListener { - private final CommonBanListener listener; - - public BanListener(BanManagerPlugin plugin) { - this.listener = new CommonBanListener(plugin); - } - - @Listener(order = Order.POST) - public void notifyOnBan(PlayerBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnIpBan(IpBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnIpRangeBan(IpRangeBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnNameBan(NameBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/HookListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/HookListener.java deleted file mode 100644 index 83e72c5a2..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/HookListener.java +++ /dev/null @@ -1,103 +0,0 @@ -package me.confuser.banmanager.sponge.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonHooksListener; -import me.confuser.banmanager.sponge.api.events.*; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; - -/** - * Listens for BanManager custom events and triggers configured hooks. - */ -public class HookListener { - private final CommonHooksListener listener; - - public HookListener(BanManagerPlugin plugin) { - this.listener = new CommonHooksListener(plugin); - } - - @Listener(order = Order.POST) - public void onBan(final PlayerBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onBanned(final PlayerBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onUnban(final PlayerUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void onMute(final PlayerMuteEvent event) { - listener.onMute(event.getMute(), true, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onMuted(final PlayerMutedEvent event) { - listener.onMute(event.getMute(), false, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onUnmute(final PlayerUnmuteEvent event) { - listener.onUnmute(event.getMute(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void onIpBan(final IpBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onIpBanned(final IpBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onIpUnban(final IpUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void onIpRangeBan(final IpRangeBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onIpRangeBanned(final IpRangeBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onIpRangeUnban(final IpRangeUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void onWarn(final PlayerWarnEvent event) { - listener.onWarn(event.getWarning(), true, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onWarned(final PlayerWarnedEvent event) { - listener.onWarn(event.getWarning(), false, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onNote(final PlayerNoteCreatedEvent event) { - listener.onNote(event.getNote(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void onReport(final PlayerReportEvent event) { - listener.onReport(event.getReport(), true, event.isSilent()); - } - - @Listener(order = Order.POST) - public void onReported(final PlayerReportedEvent event) { - listener.onReport(event.getReport(), false, event.isSilent()); - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java index ea4749f44..72421dae5 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java @@ -61,10 +61,10 @@ public void onLogin(ServerSideConnectionEvent.Login event) { public void onJoin(ServerSideConnectionEvent.Join event) { ServerPlayer player = event.player(); - listener.onJoin(new SpongePlayer(player, plugin.getConfig().isOnlineMode())); + listener.onJoin(new SpongePlayer(plugin, player, plugin.getConfig().isOnlineMode())); listener.onPlayerLogin( - new SpongePlayer(player, plugin.getConfig().isOnlineMode(), player.connection().address().getAddress()), + new SpongePlayer(plugin, player, plugin.getConfig().isOnlineMode(), player.connection().address().getAddress()), new LoginHandler(player) ); } @@ -78,7 +78,6 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { - plugin.getServer().callEvent("PlayerDeniedEvent", player, message); String locale = player.getLocale() != null ? player.getLocale() : "en"; isDenied = true; event.setCancelled(true); @@ -104,7 +103,7 @@ public void handlePlayerDeny(PlayerData player, Message message) { @Override public void handleDeny(Message message) { - new SpongePlayer(player, plugin.getConfig().isOnlineMode()).kick(message); + new SpongePlayer(plugin, player, plugin.getConfig().isOnlineMode()).kick(message); } } } diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/MuteListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/MuteListener.java index 8dd22814a..bc02701b7 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/MuteListener.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/MuteListener.java @@ -1,10 +1,6 @@ package me.confuser.banmanager.sponge.listeners; import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonMuteListener; -import me.confuser.banmanager.sponge.api.events.IpMutedEvent; -import me.confuser.banmanager.sponge.api.events.PlayerMutedEvent; -import org.spongepowered.api.block.entity.Sign; import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.Order; @@ -13,21 +9,9 @@ public class MuteListener { private final BanManagerPlugin plugin; - private final CommonMuteListener listener; public MuteListener(BanManagerPlugin plugin) { this.plugin = plugin; - this.listener = new CommonMuteListener(plugin); - } - - @Listener(order = Order.POST) - public void notifyOnMute(PlayerMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnMute(IpMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); } @Listener(order = Order.DEFAULT) diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/NoteListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/NoteListener.java deleted file mode 100644 index 9623c795b..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/NoteListener.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.confuser.banmanager.sponge.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonNoteListener; -import me.confuser.banmanager.sponge.api.events.PlayerNoteCreatedEvent; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; - -public class NoteListener { - private final CommonNoteListener listener; - - public NoteListener(BanManagerPlugin plugin) { - this.listener = new CommonNoteListener(plugin); - } - - @Listener(order = Order.POST) - public void notifyOnNote(PlayerNoteCreatedEvent event) { - listener.notifyOnNote(event.getNote(), event.isSilent()); - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReloadListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReloadListener.java deleted file mode 100644 index a26fb64e9..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReloadListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package me.confuser.banmanager.sponge.listeners; - -import me.confuser.banmanager.sponge.BMSpongePlugin; -import me.confuser.banmanager.sponge.api.events.PluginReloadedEvent; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; - -public class ReloadListener { - private final BMSpongePlugin spongePlugin; - - public ReloadListener(BMSpongePlugin spongePlugin) { - this.spongePlugin = spongePlugin; - } - - @Listener(order = Order.POST) - public void onReload(PluginReloadedEvent event) { - spongePlugin.registerChatListener(); - } -} diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReportListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReportListener.java index 1a59c945f..9bf2d7bbc 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReportListener.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/ReportListener.java @@ -1,51 +1,54 @@ package me.confuser.banmanager.sponge.listeners; +import me.confuser.banmanager.api.dto.PlayerReport; +import me.confuser.banmanager.api.event.player.PlayerReportedEvent; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.common.data.PlayerReportLocationData; -import me.confuser.banmanager.common.listeners.CommonReportListener; import me.confuser.banmanager.common.util.UUIDUtils; -import me.confuser.banmanager.sponge.api.events.PlayerReportDeletedEvent; -import me.confuser.banmanager.sponge.api.events.PlayerReportedEvent; import org.spongepowered.api.Sponge; import org.spongepowered.api.entity.living.player.server.ServerPlayer; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; import org.spongepowered.api.world.server.ServerLocation; import java.sql.SQLException; import java.util.Optional; +/** + * Stores the Sponge-specific player and actor location at the time a report + * is filed. Notification dispatch and reference cleanup live in the + * cross-platform {@link me.confuser.banmanager.common.listeners.CommonReportListener}. + */ public class ReportListener { - private final CommonReportListener listener; private final BanManagerPlugin plugin; public ReportListener(BanManagerPlugin plugin) { this.plugin = plugin; - this.listener = new CommonReportListener(plugin); + plugin.getEventBus().subscribe(PlayerReportedEvent.class, this::storeLocation); } - @Listener(order = Order.POST) - public void notifyOnReport(PlayerReportedEvent event) { - listener.notifyOnReport(event.getReport()); - } - - @Listener(order = Order.POST) - public void storeLocation(PlayerReportedEvent event) { - PlayerReportData report = event.getReport(); + private void storeLocation(PlayerReportedEvent event) { + PlayerReport report = event.report(); + PlayerReportData entity; + try { + entity = plugin.getPlayerReportStorage().queryForId(report.id()); + } catch (SQLException e) { + plugin.getLogger().warning("Failed to load report entity for location storage", e); + return; + } + if (entity == null) return; - Optional player = Sponge.server().player(report.getPlayer().getUUID()); - Optional actor = Sponge.server().player(report.getActor().getUUID()); + Optional player = Sponge.server().player(report.player().uuid()); + Optional actor = Sponge.server().player(report.actor().uuid()); try { - if (player.isPresent()) createLocation(report, player.get()); + if (player.isPresent()) createLocation(entity, player.get()); } catch (SQLException e) { plugin.getLogger().warning("Failed to store report location for reported player", e); } try { - if (actor.isPresent()) createLocation(report, actor.get()); + if (actor.isPresent()) createLocation(entity, actor.get()); } catch (SQLException e) { plugin.getLogger().warning("Failed to store report location for actor", e); } @@ -69,9 +72,4 @@ private void createLocation(PlayerReportData report, ServerPlayer player) throws 0 )); } - - @Listener - public void deleteReferences(PlayerReportDeletedEvent event) { - listener.deleteReferences(event.getReport()); - } } diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/WebhookListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/WebhookListener.java deleted file mode 100644 index 02b1d11cd..000000000 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/WebhookListener.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.confuser.banmanager.sponge.listeners; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonWebhookListener; -import me.confuser.banmanager.common.data.Webhook; -import me.confuser.banmanager.sponge.api.events.*; -import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.Order; - -import java.util.List; - -public class WebhookListener { - private final CommonWebhookListener listener; - - public WebhookListener(BanManagerPlugin plugin) { - this.listener = new CommonWebhookListener(plugin); - } - - @Listener(order = Order.POST) - public void notifyOnBan(PlayerBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnMute(PlayerMutedEvent event) { - List webhooks = listener.notifyOnMute(event.getMute()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnWarn(PlayerWarnedEvent event) { - List webhooks = listener.notifyOnWarn(event.getWarning()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnBan(IpBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnKick(PlayerKickedEvent event) { - List webhooks = listener.notifyOnKick(event.getKick()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnUnban(PlayerUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnUnban(IpUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnUnmute(PlayerUnmuteEvent event) { - List webhooks = listener.notifyOnUnmute(event.getMute(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @Listener(order = Order.POST) - public void notifyOnReport(PlayerReportedEvent event) { - List webhooks = listener.notifyOnReport(event.getReport(), event.getReport().getActor(), event.getReport().getReason()); - sendAll(webhooks, event.isSilent()); - } - - private void sendAll(List webhooks, boolean isSilent) { - for (Webhook data : webhooks) { - if (isSilent && data.ignoreSilent()) continue; - if (data.url() == null || data.payload() == null || data.url().isEmpty() || data.payload().isEmpty()) continue; - listener.sendAsync(data); - } - } -} diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts index 8168bd78e..dc0779edf 100644 --- a/velocity/build.gradle.kts +++ b/velocity/build.gradle.kts @@ -90,8 +90,10 @@ tasks.named("shadowJar") { archiveVersion.set("") dependencies { + include(dependency(":BanManagerAPI")) include(dependency(":BanManagerCommon")) include(dependency(":BanManagerLibs")) + include(dependency("com.github.seancfoley:ipaddress:.*")) include(dependency("org.bstats:.*:.*")) relocate("org.bstats", "me.confuser.banmanager.common.bstats") @@ -113,6 +115,8 @@ tasks.named("shadowJar") { minimize { exclude(dependency("org.bstats:.*:.*")) exclude(dependency(":BanManagerLibs")) + exclude(dependency(":BanManagerAPI")) + exclude(dependency("com.github.seancfoley:ipaddress:.*")) } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/BMVelocityPlugin.java b/velocity/src/main/java/me/confuser/banmanager/velocity/BMVelocityPlugin.java index e9e49511d..a5b01abd1 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/BMVelocityPlugin.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/BMVelocityPlugin.java @@ -14,6 +14,7 @@ import lombok.SneakyThrows; import lombok.Getter; +import me.confuser.banmanager.api.event.player.PluginReloadedEvent; import me.confuser.banmanager.velocity.configs.VelocityConfig; import me.confuser.banmanager.velocity.listeners.*; import me.confuser.banmanager.common.BanManagerPlugin; @@ -21,6 +22,11 @@ import me.confuser.banmanager.common.configs.PluginInfo; import me.confuser.banmanager.common.configuration.ConfigurationSection; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; +import me.confuser.banmanager.common.listeners.CommonBanListener; +import me.confuser.banmanager.common.listeners.CommonHooksListener; +import me.confuser.banmanager.common.listeners.CommonMuteListener; +import me.confuser.banmanager.common.listeners.CommonNoteListener; +import me.confuser.banmanager.common.listeners.CommonWebhookListener; import me.confuser.banmanager.common.runnables.*; import org.slf4j.Logger; @@ -89,7 +95,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { pluginInfo = setupConfigs(); } catch (IOException e) { getPlugin().disable(); - BanManagerPlugin.getInstance().getLogger().warning("Failed to set up plugin configuration", e); + logger.warn("Failed to set up plugin configuration", e); return; } @@ -182,7 +188,7 @@ private PluginInfo setupConfigs() throws IOException { try (InputStream in = getResourceAsStream(name)) { Files.copy(in, file.toPath()); } catch (IOException e) { - BanManagerPlugin.getInstance().getLogger().warning("Failed to copy default config file", e); + logger.warn("Failed to copy default config file", e); } } else { try (InputStream in = getResourceAsStream(file.getName()); @@ -216,20 +222,20 @@ private PluginInfo setupConfigs() throws IOException { public void setupListeners() { registerEvent(new JoinListener(this)); registerEvent(new LeaveListener(plugin)); - registerEvent(new HookListener(plugin)); + new CommonHooksListener(plugin); registerChatListener(); - registerEvent(new ReloadListener(this)); + plugin.getEventBus().subscribe(PluginReloadedEvent.class, e -> registerChatListener()); if (plugin.getConfig().isDisplayNotificationsEnabled()) { - registerEvent(new BanListener(plugin)); - registerEvent(new MuteListener(plugin)); - registerEvent(new NoteListener(plugin)); + new CommonBanListener(plugin); + new CommonMuteListener(plugin); + new CommonNoteListener(plugin); } if (plugin.getWebhookConfig().isHooksEnabled()) { - registerEvent(new WebhookListener(plugin)); + new CommonWebhookListener(plugin); } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityCommand.java b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityCommand.java index b71ae0c49..0ff476985 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityCommand.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityCommand.java @@ -57,10 +57,11 @@ public boolean hasPermission(final Invocation invocation) { } private CommonSender getSender(CommandSource source) { + BanManagerPlugin core = plugin.getPlugin(); if (source instanceof Player) { - return new VelocityPlayer((Player) source, BanManagerPlugin.getInstance().getConfig().isOnlineMode()); + return new VelocityPlayer(core, (Player) source, core.getConfig().isOnlineMode()); } else { - return new VelocitySender(BanManagerPlugin.getInstance(), source); + return new VelocitySender(core, source); } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java index e2977477f..6551a6d52 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java @@ -1,6 +1,7 @@ package me.confuser.banmanager.velocity; import com.velocitypowered.api.proxy.Player; +import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.CommonWorld; import me.confuser.banmanager.common.commands.CommonCommand; @@ -8,7 +9,6 @@ import me.confuser.banmanager.common.kyori.text.Component; import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.common.util.MessageRenderer; import me.confuser.banmanager.common.util.MessageRegistry; import net.kyori.adventure.key.Key; import net.kyori.adventure.sound.Sound; @@ -24,11 +24,13 @@ public class VelocityPlayer implements CommonPlayer { private static final GsonComponentSerializer DOWNSAMPLING_GSON = GsonComponentSerializer.builder().downsampleColors().build(); + private final BanManagerPlugin plugin; private final UUID uuid; private final boolean onlineMode; private final Player player; - public VelocityPlayer(Player player, boolean onlineMode) { + public VelocityPlayer(BanManagerPlugin plugin, Player player, boolean onlineMode) { + this.plugin = plugin; this.player = player; this.uuid = this.player.getUniqueId(); this.onlineMode = onlineMode; @@ -100,7 +102,7 @@ public void sendJSONMessage(String jsonString) { } private net.kyori.adventure.text.Component convertToNative(Component component) { - String json = MessageRenderer.getInstance().toJson(component); + String json = plugin.getMessageRenderer().toJson(component); return GsonComponentSerializer.gson().deserialize(json); } @@ -111,7 +113,7 @@ public boolean isConsole() { @Override public PlayerData getData() { - return CommonCommand.getPlayer(this, getName(), false); + return CommonCommand.resolvePlayer(plugin, this, getName(), false); } @Override diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityScheduler.java b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityScheduler.java index f7d61a9b1..4b0f53b4f 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityScheduler.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityScheduler.java @@ -51,4 +51,15 @@ public void cancelAll() { } repeatingTasks.clear(); } + + /** + * Velocity is a fully-asynchronous proxy with no main-thread tick. + * {@link #runSync(Runnable)} is therefore an alias for + * {@link #runAsync(Runnable)} — exposed via this flag so callers can + * detect platforms where main-thread-only APIs are unavailable. + */ + @Override + public boolean isMainThreadAware() { + return false; + } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocitySender.java b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocitySender.java index e7270df8e..1d538aaca 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocitySender.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocitySender.java @@ -39,6 +39,6 @@ public boolean isConsole() { public PlayerData getData() { if (isConsole()) return plugin.getPlayerStorage().getConsole(); - return CommonCommand.getPlayer(this, getName(), false); + return CommonCommand.resolvePlayer(plugin, this, getName(), false); } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityServer.java b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityServer.java index f4357eacc..3a842150c 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityServer.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityServer.java @@ -6,12 +6,8 @@ import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import me.confuser.banmanager.common.*; -import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; -import me.confuser.banmanager.common.data.*; import me.confuser.banmanager.common.util.ColorUtils; -import me.confuser.banmanager.common.util.Message; -import me.confuser.banmanager.velocity.api.events.*; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -32,7 +28,7 @@ public class VelocityServer implements CommonServer { public CommonPlayer getPlayer(UUID uniqueId) { Optional player = server.getPlayer(uniqueId); - if (player.isPresent()) return new VelocityPlayer(player.get(), plugin.getConfig().isOnlineMode()); + if (player.isPresent()) return new VelocityPlayer(plugin, player.get(), plugin.getConfig().isOnlineMode()); return null; } @@ -41,7 +37,7 @@ public CommonPlayer getPlayer(UUID uniqueId) { public CommonPlayer getPlayer(String name) { Optional player = server.getPlayer(name); - if (player.isPresent()) return new VelocityPlayer(player.get(), plugin.getConfig().isOnlineMode()); + if (player.isPresent()) return new VelocityPlayer(plugin, player.get(), plugin.getConfig().isOnlineMode()); return null; } @@ -54,7 +50,7 @@ public CommonPlayer getPlayerExact(String name) { @Override public CommonPlayer[] getOnlinePlayers() { return server.getAllPlayers().stream() - .map(player -> new VelocityPlayer(player, plugin.getConfig().isOnlineMode())) + .map(player -> new VelocityPlayer(plugin, player, plugin.getConfig().isOnlineMode())) .collect(Collectors.toList()).toArray(new CommonPlayer[0]); } @@ -103,129 +99,6 @@ public CommonWorld getWorld(String name) { return null; } - @Override - public CommonEvent callEvent(String name, Object... args) { - // @TODO replace with a cleaner implementation - CustomEvent event = null; - CommonEvent commonEvent = new CommonEvent(false, true); - - switch (name) { - case "PlayerBanEvent": - event = new PlayerBanEvent((PlayerBanData) args[0], (boolean) args[1]); - break; - case "PlayerBannedEvent": - PlayerBannedEvent bannedEvent = new PlayerBannedEvent((PlayerBanData) args[0], (boolean) args[1]); - if (args.length > 2 && args[2] instanceof Message) { - bannedEvent.setKickMessage((Message) args[2]); - } - event = bannedEvent; - break; - case "PlayerUnbanEvent": - event = new PlayerUnbanEvent((PlayerBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpBanEvent": - event = new IpBanEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpBannedEvent": - event = new IpBannedEvent((IpBanData) args[0], (boolean) args[1]); - break; - case "IpUnbanEvent": - event = new IpUnbanEvent((IpBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "IpMuteEvent": - event = new IpMuteEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpMutedEvent": - event = new IpMutedEvent((IpMuteData) args[0], (boolean) args[1]); - break; - case "IpUnmutedEvent": - event = new IpUnmutedEvent((IpMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerKickedEvent": - event = new PlayerKickedEvent((PlayerKickData) args[0], (boolean) args[1]); - break; - - case "PlayerNoteCreatedEvent": - event = new PlayerNoteCreatedEvent((PlayerNoteData) args[0], args.length > 1 && (boolean) args[1]); - break; - - case "PlayerReportEvent": - event = new PlayerReportEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportedEvent": - event = new PlayerReportedEvent((PlayerReportData) args[0], (boolean) args[1]); - break; - case "PlayerReportDeletedEvent": - event = new PlayerReportDeletedEvent((PlayerReportData) args[0]); - break; - - case "NameBanEvent": - event = new NameBanEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameBannedEvent": - event = new NameBannedEvent((NameBanData) args[0], (boolean) args[1]); - break; - case "NameUnbanEvent": - event = new NameUnbanEvent((NameBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerWarnEvent": - event = new PlayerWarnEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - case "PlayerWarnedEvent": - event = new PlayerWarnedEvent((PlayerWarnData) args[0], (boolean) args[1]); - break; - - case "IpRangeBanEvent": - event = new IpRangeBanEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeBannedEvent": - event = new IpRangeBannedEvent((IpRangeBanData) args[0], (boolean) args[1]); - break; - case "IpRangeUnbanEvent": - event = new IpRangeUnbanEvent((IpRangeBanData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PlayerMuteEvent": - event = new PlayerMuteEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerMutedEvent": - event = new PlayerMutedEvent((PlayerMuteData) args[0], (boolean) args[1]); - break; - case "PlayerUnmuteEvent": - event = new PlayerUnmuteEvent((PlayerMuteData) args[0], (PlayerData) args[1], (String) args[2], (boolean) args[3]); - break; - - case "PluginReloadedEvent": - event = new PluginReloadedEvent((PlayerData) args[0]); - break; - - case "PlayerDeniedEvent": - event = new PlayerDeniedEvent((PlayerData) args[0], (Message) args[1]); - } - - if (event == null) { - plugin.getLogger().warning("Unable to call missing event " + name); - - return commonEvent; - } - - server.getEventManager().fire(event).join(); - - if (event instanceof SilentCancellableEvent) { - commonEvent = new CommonEvent(!(((SilentCancellableEvent) event).getResult().isAllowed()), ((SilentCancellableEvent) event).isSilent()); - } else if (event instanceof SilentEvent) { - commonEvent = new CommonEvent(false, ((SilentEvent) event).isSilent()); - } else if (event instanceof CustomCancellableEvent) { - commonEvent = new CommonEvent(!((CustomCancellableEvent) event).getResult().isAllowed(), true); - } - - return commonEvent; - } - public static @NotNull Component formatMessage(String message) { return LegacyComponentSerializer.builder() .character('&') diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/CustomCancellableEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/CustomCancellableEvent.java deleted file mode 100644 index 6f6e20ba6..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/CustomCancellableEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import com.velocitypowered.api.event.ResultedEvent; - -import java.util.Objects; - - -public abstract class CustomCancellableEvent extends CustomEvent implements ResultedEvent { - - private GenericResult result = GenericResult.allowed(); - - public CustomCancellableEvent() { - super(); - } - - @Override - public void setResult(GenericResult result) { - this.result = Objects.requireNonNull(result); - } - - public GenericResult getResult() { - return result; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/CustomEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/CustomEvent.java deleted file mode 100644 index 1a6be8f2c..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/CustomEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -public abstract class CustomEvent { -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpBanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpBanEvent.java deleted file mode 100644 index a4e8c54fb..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpBanEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBanEvent extends SilentCancellableEvent { - - @Getter - private IpBanData ban; - - public IpBanEvent(IpBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpBannedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpBannedEvent.java deleted file mode 100644 index 381abdd4e..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpBannedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; - -public class IpBannedEvent extends SilentEvent { - - @Getter - private IpBanData ban; - - public IpBannedEvent(IpBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpMuteEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpMuteEvent.java deleted file mode 100644 index ff9ab5d1b..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpMuteEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMuteEvent extends SilentCancellableEvent { - - @Getter - private IpMuteData mute; - - public IpMuteEvent(IpMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpMutedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpMutedEvent.java deleted file mode 100644 index d9601a958..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpMutedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; - -public class IpMutedEvent extends SilentEvent { - - @Getter - private IpMuteData mute; - - public IpMutedEvent(IpMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeBanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeBanEvent.java deleted file mode 100644 index 74cb30abd..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeBanEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - -public class IpRangeBanEvent extends SilentCancellableEvent { - - @Getter - private IpRangeBanData ban; - - public IpRangeBanEvent(IpRangeBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeBannedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeBannedEvent.java deleted file mode 100644 index 40c832ca5..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeBannedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; - - -public class IpRangeBannedEvent extends SilentEvent { - - @Getter - private IpRangeBanData ban; - - public IpRangeBannedEvent(IpRangeBanData ban, boolean silent) { - super(silent); - this.ban = ban; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeUnbanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeUnbanEvent.java deleted file mode 100644 index e9fad6774..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpRangeUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpRangeBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpRangeUnbanEvent extends SilentCancellableEvent { - - @Getter - private IpRangeBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpRangeUnbanEvent(IpRangeBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpUnbanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpUnbanEvent.java deleted file mode 100644 index d57e40e08..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpUnbanEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpUnbanEvent extends SilentCancellableEvent { - - @Getter - private IpBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public IpUnbanEvent(IpBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpUnmutedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpUnmutedEvent.java deleted file mode 100644 index 32663dff7..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/IpUnmutedEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.IpMuteData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class IpUnmutedEvent extends SilentCancellableEvent { - - @Getter - private IpMuteData mute; - @Getter - private PlayerData actor; - @Getter - private String reason; - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public IpUnmutedEvent(IpMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameBanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameBanEvent.java deleted file mode 100644 index c493cce3c..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameBanEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - - -public class NameBanEvent extends SilentCancellableEvent { - - @Getter - private NameBanData ban; - - public NameBanEvent(NameBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameBannedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameBannedEvent.java deleted file mode 100644 index 87da984a0..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameBannedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; - -public class NameBannedEvent extends SilentEvent { - - @Getter - private NameBanData ban; - - public NameBannedEvent(NameBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameUnbanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameUnbanEvent.java deleted file mode 100644 index 8a9f2f5c3..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/NameUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.NameBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class NameUnbanEvent extends SilentCancellableEvent { - - @Getter - private NameBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public NameUnbanEvent(NameBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerBanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerBanEvent.java deleted file mode 100644 index 26dc60a6d..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerBanEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; - - -public class PlayerBanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - - public PlayerBanEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerBannedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerBannedEvent.java deleted file mode 100644 index e4248ed29..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerBannedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.util.Message; - - -public class PlayerBannedEvent extends SilentEvent { - - @Getter - private PlayerBanData ban; - - @Getter - @Setter - private Message kickMessage; - - public PlayerBannedEvent(PlayerBanData ban, boolean isSilent) { - super(isSilent); - this.ban = ban; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerDeniedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerDeniedEvent.java deleted file mode 100644 index ba2f23997..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerDeniedEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import lombok.Setter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.util.Message; - -public class PlayerDeniedEvent extends CustomCancellableEvent { - - @Getter - @Setter - private Message message; - - @Getter - private PlayerData player; - - public PlayerDeniedEvent(PlayerData player, Message message) { - super(); - - this.player = player; - this.message = message; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerKickedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerKickedEvent.java deleted file mode 100644 index 0f936218d..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerKickedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerKickData; - -public class PlayerKickedEvent extends SilentEvent { - - @Getter - private PlayerKickData kick; - - public PlayerKickedEvent(PlayerKickData kick, boolean isSilent) { - super(isSilent); - this.kick = kick; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerMuteEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerMuteEvent.java deleted file mode 100644 index 4f2863845..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerMuteEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerMuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMuteEvent(PlayerMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerMutedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerMutedEvent.java deleted file mode 100644 index 02d395eb8..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerMutedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerMutedEvent extends SilentEvent { - - @Getter - private PlayerMuteData mute; - - public PlayerMutedEvent(PlayerMuteData mute, boolean silent) { - super(silent); - this.mute = mute; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerNoteCreatedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerNoteCreatedEvent.java deleted file mode 100644 index abc207569..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerNoteCreatedEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerNoteData; - - -public class PlayerNoteCreatedEvent extends SilentCancellableEvent { - - @Getter - private PlayerNoteData note; - - public PlayerNoteCreatedEvent(PlayerNoteData note) { - this(note, false); - } - - public PlayerNoteCreatedEvent(PlayerNoteData note, boolean silent) { - super(silent); - this.note = note; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportDeletedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportDeletedEvent.java deleted file mode 100644 index 6c94ec0f0..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportDeletedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - -public class PlayerReportDeletedEvent extends CustomEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportDeletedEvent(PlayerReportData report) { - super(); - this.report = report; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportEvent.java deleted file mode 100644 index 2ef94668d..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - - -public class PlayerReportEvent extends SilentCancellableEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportEvent(PlayerReportData report, boolean isSilent) { - super(isSilent); - this.report = report; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportedEvent.java deleted file mode 100644 index 4b1ad13e9..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerReportedEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerReportData; - - -public class PlayerReportedEvent extends SilentEvent { - - @Getter - private PlayerReportData report; - - public PlayerReportedEvent(PlayerReportData report, boolean isSilent) { - super(isSilent); - this.report = report; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerUnbanEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerUnbanEvent.java deleted file mode 100644 index 295d64930..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerUnbanEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerBanData; -import me.confuser.banmanager.common.data.PlayerData; - - -public class PlayerUnbanEvent extends SilentCancellableEvent { - - @Getter - private PlayerBanData ban; - @Getter - private PlayerData actor; - @Getter - private String reason; - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason) { - this(ban, actor, reason, false); - } - - public PlayerUnbanEvent(PlayerBanData ban, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.ban = ban; - this.actor = actor; - this.reason = reason; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerUnmuteEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerUnmuteEvent.java deleted file mode 100644 index 323622602..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerUnmuteEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; -import me.confuser.banmanager.common.data.PlayerMuteData; - - -public class PlayerUnmuteEvent extends SilentCancellableEvent { - - @Getter - private PlayerMuteData mute; - @Getter - private PlayerData actor; - @Getter - private String reason; - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason) { - this(mute, actor, reason, false); - } - - public PlayerUnmuteEvent(PlayerMuteData mute, PlayerData actor, String reason, boolean silent) { - super(silent); - - this.mute = mute; - this.actor = actor; - this.reason = reason; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerWarnEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerWarnEvent.java deleted file mode 100644 index 9776f20bc..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerWarnEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - - -public class PlayerWarnEvent extends SilentEvent { - - @Getter - private PlayerWarnData warning; - - public PlayerWarnEvent(PlayerWarnData warning, boolean silent) { - super(silent); - this.warning = warning; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerWarnedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerWarnedEvent.java deleted file mode 100644 index 809398ae3..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PlayerWarnedEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerWarnData; - - -public class PlayerWarnedEvent extends SilentEvent { - - @Getter - private PlayerWarnData warning; - - public PlayerWarnedEvent(PlayerWarnData warning, boolean silent) { - super(silent); - this.warning = warning; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PluginReloadedEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PluginReloadedEvent.java deleted file mode 100644 index 5fc1fd7c9..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/PluginReloadedEvent.java +++ /dev/null @@ -1,15 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import me.confuser.banmanager.common.data.PlayerData; - -public class PluginReloadedEvent extends CustomCancellableEvent { - - @Getter - private PlayerData actor; - - public PluginReloadedEvent(PlayerData actor) { - super(); - this.actor = actor; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/SilentCancellableEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/SilentCancellableEvent.java deleted file mode 100644 index 2ca3e215f..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/SilentCancellableEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentCancellableEvent extends CustomCancellableEvent { - - @Getter - @Setter - private boolean silent; - - public SilentCancellableEvent(boolean silent) { - super(); - this.silent = silent; - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/SilentEvent.java b/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/SilentEvent.java deleted file mode 100644 index f66b7ee6e..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/api/events/SilentEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.confuser.banmanager.velocity.api.events; - -import lombok.Getter; -import lombok.Setter; - -public abstract class SilentEvent extends CustomEvent { - @Getter - @Setter - private boolean silent = false; - - public SilentEvent(boolean silent) { - super(); - - this.silent = silent; - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/BanListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/BanListener.java deleted file mode 100644 index 8e052aa9c..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/BanListener.java +++ /dev/null @@ -1,42 +0,0 @@ -package me.confuser.banmanager.velocity.listeners; - - -import com.velocitypowered.api.event.Subscribe; -import me.confuser.banmanager.velocity.Listener; -import me.confuser.banmanager.velocity.api.events.IpBannedEvent; -import me.confuser.banmanager.velocity.api.events.IpRangeBannedEvent; -import me.confuser.banmanager.velocity.api.events.NameBannedEvent; -import me.confuser.banmanager.velocity.api.events.PlayerBannedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonBanListener; - -import com.velocitypowered.api.event.PostOrder; - -public class BanListener extends Listener { - - private final CommonBanListener listener; - - public BanListener(BanManagerPlugin plugin) { - this.listener = new CommonBanListener(plugin); - } - - @Subscribe(order = PostOrder.FIRST) - public void notifyOnBan(PlayerBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void notifyOnIpBan(IpBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void notifyOnIpRangeBan(IpRangeBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void notifyOnNameBan(NameBannedEvent event) { - listener.notifyOnBan(event.getBan(), event.isSilent()); - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/HookListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/HookListener.java deleted file mode 100644 index 6f8a364ef..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/HookListener.java +++ /dev/null @@ -1,103 +0,0 @@ -package me.confuser.banmanager.velocity.listeners; - -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.Subscribe; - -import me.confuser.banmanager.velocity.Listener; -import me.confuser.banmanager.velocity.api.events.*; - -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonHooksListener; - -public class HookListener extends Listener { - private final CommonHooksListener listener; - - public HookListener(BanManagerPlugin plugin) { - this.listener = new CommonHooksListener(plugin); - } - - @Subscribe(order = PostOrder.FIRST) - public void onBan(final PlayerBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onBan(final PlayerBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onUnban(final PlayerUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onMute(final PlayerMuteEvent event) { - listener.onMute(event.getMute(), true, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onMute(final PlayerMutedEvent event) { - listener.onMute(event.getMute(), false, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onUnmute(final PlayerUnmuteEvent event) { - listener.onUnmute(event.getMute(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onBan(final IpBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onBan(final IpBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onUnban(final IpUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onBan(final IpRangeBanEvent event) { - listener.onBan(event.getBan(), true, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onBan(final IpRangeBannedEvent event) { - listener.onBan(event.getBan(), false, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onUnban(final IpRangeUnbanEvent event) { - listener.onUnban(event.getBan(), event.getActor(), event.getReason(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onWarn(final PlayerWarnEvent event) { - listener.onWarn(event.getWarning(), true, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onWarn(final PlayerWarnedEvent event) { - listener.onWarn(event.getWarning(), false, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onNote(final PlayerNoteCreatedEvent event) { - listener.onNote(event.getNote(), event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onReport(final PlayerReportEvent event) { - listener.onReport(event.getReport(), true, event.isSilent()); - } - - @Subscribe(order = PostOrder.FIRST) - public void onReport(final PlayerReportedEvent event) { - listener.onReport(event.getReport(), false, event.isSilent()); - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java index c806feb0c..1ddfffbce 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java @@ -41,12 +41,12 @@ public void checkBanJoin(final LoginEvent event) { @Subscribe public void onJoin(PostLoginEvent event) { - listener.onJoin(new VelocityPlayer(event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode())); + listener.onJoin(new VelocityPlayer(plugin.getPlugin(), event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode())); } @Subscribe(order = PostOrder.LAST) public void onPlayerLogin(PostLoginEvent event) { - listener.onPlayerLogin(new VelocityPlayer(event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode()), new LoginHandler(event)); + listener.onPlayerLogin(new VelocityPlayer(plugin.getPlugin(), event.getPlayer(), plugin.getPlugin().getConfig().isOnlineMode()), new LoginHandler(event)); } @RequiredArgsConstructor @@ -61,7 +61,6 @@ public void handleDeny(Message message) { @Override public void handlePlayerDeny(PlayerData player, Message message) { - plugin.getServer().callEvent("PlayerDeniedEvent", player, message); event.setResult(ResultedEvent.ComponentResult.denied( VelocityServer.convert(message.resolveComponent(player.getLocale() != null ? player.getLocale() : "en")))); } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/MuteListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/MuteListener.java deleted file mode 100644 index 26fb3c502..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/MuteListener.java +++ /dev/null @@ -1,27 +0,0 @@ -package me.confuser.banmanager.velocity.listeners; - -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.Subscribe; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonMuteListener; -import me.confuser.banmanager.velocity.Listener; -import me.confuser.banmanager.velocity.api.events.IpMutedEvent; -import me.confuser.banmanager.velocity.api.events.PlayerMutedEvent; - -public class MuteListener extends Listener { - private final CommonMuteListener listener; - - public MuteListener(BanManagerPlugin plugin) { - this.listener = new CommonMuteListener(plugin); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnMute(PlayerMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnMute(IpMutedEvent event) { - listener.notifyOnMute(event.getMute(), event.isSilent()); - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/NoteListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/NoteListener.java deleted file mode 100644 index 62d8baad5..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/NoteListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package me.confuser.banmanager.velocity.listeners; - - -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.Subscribe; -import me.confuser.banmanager.velocity.Listener; -import me.confuser.banmanager.velocity.api.events.PlayerNoteCreatedEvent; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonNoteListener; - -public class NoteListener extends Listener { - private final CommonNoteListener listener; - - public NoteListener(BanManagerPlugin plugin) { - this.listener = new CommonNoteListener(plugin); - } - - @Subscribe(order = PostOrder.FIRST) - public void notifyOnNote(PlayerNoteCreatedEvent event) { - listener.notifyOnNote(event.getNote(), event.isSilent()); - } - -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/ReloadListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/ReloadListener.java deleted file mode 100644 index e46bebe5c..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/ReloadListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package me.confuser.banmanager.velocity.listeners; - -import com.velocitypowered.api.event.PostOrder; -import com.velocitypowered.api.event.Subscribe; -import lombok.RequiredArgsConstructor; -import me.confuser.banmanager.velocity.BMVelocityPlugin; -import me.confuser.banmanager.velocity.Listener; -import me.confuser.banmanager.velocity.api.events.PluginReloadedEvent; - -@RequiredArgsConstructor -public class ReloadListener extends Listener { - private final BMVelocityPlugin velocityPlugin; - - @Subscribe(order = PostOrder.LAST) - public void onReload(final PluginReloadedEvent event) { - velocityPlugin.registerChatListener(); - } -} diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/WebhookListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/WebhookListener.java deleted file mode 100644 index 3e37b287b..000000000 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/WebhookListener.java +++ /dev/null @@ -1,81 +0,0 @@ -package me.confuser.banmanager.velocity.listeners; - -import me.confuser.banmanager.velocity.api.events.*; -import me.confuser.banmanager.velocity.Listener; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.listeners.CommonWebhookListener; -import me.confuser.banmanager.common.data.Webhook; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.PostOrder; - -import java.util.List; - -public class WebhookListener extends Listener { - private CommonWebhookListener listener; - - public WebhookListener(BanManagerPlugin plugin) { - this.listener = new CommonWebhookListener(plugin); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnBan(PlayerBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnMute(PlayerMutedEvent event) { - List webhooks = listener.notifyOnMute(event.getMute()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnBan(IpBannedEvent event) { - List webhooks = listener.notifyOnBan(event.getBan()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnKick(PlayerKickedEvent event) { - List webhooks = listener.notifyOnKick(event.getKick()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnWarn(PlayerWarnedEvent event) { - List webhooks = listener.notifyOnWarn(event.getWarning()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnUnban(PlayerUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnUnban(IpUnbanEvent event) { - List webhooks = listener.notifyOnUnban(event.getBan(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnUnmute(PlayerUnmuteEvent event) { - List webhooks = listener.notifyOnUnmute(event.getMute(), event.getActor(), event.getReason()); - sendAll(webhooks, event.isSilent()); - } - - @Subscribe(order = PostOrder.LAST) - public void notifyOnReport(PlayerReportedEvent event) { - List webhooks = listener.notifyOnReport(event.getReport(), event.getReport().getActor(), event.getReport().getReason()); - sendAll(webhooks, event.isSilent()); - } - - private void sendAll(List webhooks, boolean isSilent) { - for (Webhook data : webhooks) { - if (isSilent && data.ignoreSilent()) continue; - if (data.url() == null || data.payload() == null || data.url().isEmpty() || data.payload().isEmpty()) continue; - listener.sendAsync(data); - } - } -}