diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a127f2a..7c5549a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,6 +26,12 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + with: + repository: BanManagement/BanManager + path: BanManager + ref: master + - name: Set up JDK 17 uses: actions/setup-java@v4 with: @@ -50,10 +56,19 @@ jobs: key: ${{ runner.os }}-loom-${{ hashFiles('**/libs.versions.*', '**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-loom- + - name: Publish BanManager to Maven Local + working-directory: BanManager + run: ./gradlew publishToMavenLocal --build-cache + + - name: Clear stale BanManager dependency caches + run: | + find ~/.gradle/caches -path "*/me.confuser.banmanager*" -exec rm -rf {} + 2>/dev/null || true + find .gradle/loom-cache -path "*/BanManager*" -exec rm -rf {} + 2>/dev/null || true + - name: Execute Gradle build env: STORAGE_TYPE: ${{ matrix.storageType }} - run: ./gradlew build --build-cache --info + run: ./gradlew build --build-cache --refresh-dependencies --info - name: Build all Fabric versions run: ./gradlew :fabric:1.20.1:remapJar :fabric:1.21.1:remapJar :fabric:1.21.4:remapJar :fabric:1.21.11:remapJar --build-cache diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 242ca5c..768eca2 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -135,6 +135,15 @@ jobs: key: ${{ runner.os }}-loom-${{ hashFiles('**/libs.versions.*', '**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-loom- + - name: Publish BanManager to Maven Local + working-directory: BanManager + run: ./gradlew publishToMavenLocal --build-cache + + - name: Clear stale BanManager dependency caches + run: | + find ~/.gradle/caches -path "*/me.confuser.banmanager*" -exec rm -rf {} + 2>/dev/null || true + find .gradle/loom-cache -path "*/BanManager*" -exec rm -rf {} + 2>/dev/null || true + # Docker Buildx for better caching - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -171,7 +180,7 @@ jobs: run: ./gradlew ${{ matrix.build_task }} --build-cache - name: Run E2E tests - run: ./gradlew :BanManagerWebEnhancerE2E:${{ matrix.task }} --build-cache -PbanManagerPath=BanManager + run: ./gradlew :BanManagerWebEnhancerE2E:${{ matrix.task }} --build-cache --refresh-dependencies -PbanManagerPath=BanManager timeout-minutes: 15 env: MC_VERSION: ${{ matrix.mc_version }} diff --git a/bukkit/src/main/java/me/confuser/banmanager/webenhancer/bukkit/listeners/ReportListener.java b/bukkit/src/main/java/me/confuser/banmanager/webenhancer/bukkit/listeners/ReportListener.java index 600af19..8dac0ea 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/webenhancer/bukkit/listeners/ReportListener.java +++ b/bukkit/src/main/java/me/confuser/banmanager/webenhancer/bukkit/listeners/ReportListener.java @@ -1,10 +1,12 @@ package me.confuser.banmanager.webenhancer.bukkit.listeners; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; +import me.confuser.banmanager.bukkit.api.events.PlayerBannedEvent; import me.confuser.banmanager.bukkit.api.events.PlayerReportDeletedEvent; import me.confuser.banmanager.bukkit.api.events.PlayerReportedEvent; import me.confuser.banmanager.bukkit.api.events.PlayerDeniedEvent; import me.confuser.banmanager.bukkit.api.events.PluginReloadedEvent; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.webenhancer.bukkit.BukkitPlugin; import me.confuser.banmanager.webenhancer.common.data.LogData; @@ -76,6 +78,17 @@ public void onDeny(PlayerDeniedEvent event) { listener.handlePin(event.getPlayer(), event.getMessage()); } + @EventHandler(priority = EventPriority.MONITOR) + public void onBanned(PlayerBannedEvent event) { + try { + Message kickMessage = event.getKickMessage(); + if (kickMessage != null) { + listener.handlePin(event.getBan().getPlayer(), kickMessage); + } + } catch (NoSuchMethodError ignored) { + } + } + @EventHandler public void onReload(PluginReloadedEvent event) { plugin.getPlugin().setupConfigs(); diff --git a/bungee/src/main/java/me/confuser/banmanager/webenhancer/bungee/listeners/BanListener.java b/bungee/src/main/java/me/confuser/banmanager/webenhancer/bungee/listeners/BanListener.java index ae1ae9e..6f0e91f 100644 --- a/bungee/src/main/java/me/confuser/banmanager/webenhancer/bungee/listeners/BanListener.java +++ b/bungee/src/main/java/me/confuser/banmanager/webenhancer/bungee/listeners/BanListener.java @@ -1,7 +1,9 @@ package me.confuser.banmanager.webenhancer.bungee.listeners; +import me.confuser.banmanager.bungee.api.events.PlayerBannedEvent; import me.confuser.banmanager.bungee.api.events.PlayerDeniedEvent; import me.confuser.banmanager.bungee.api.events.PluginReloadedEvent; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.webenhancer.bungee.BungeePlugin; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; @@ -21,6 +23,17 @@ public void onDeny(PlayerDeniedEvent event) { listener.handlePin(event.getPlayer(), event.getMessage()); } + @EventHandler + public void onBanned(PlayerBannedEvent event) { + try { + Message kickMessage = event.getKickMessage(); + if (kickMessage != null) { + listener.handlePin(event.getBan().getPlayer(), kickMessage); + } + } catch (NoSuchMethodError ignored) { + } + } + @EventHandler public void onReload(PluginReloadedEvent event) { plugin.getPlugin().setupConfigs(); diff --git a/common/src/main/java/me/confuser/banmanager/webenhancer/common/runnables/ExpiresSync.java b/common/src/main/java/me/confuser/banmanager/webenhancer/common/runnables/ExpiresSync.java index 849865a..a8d522a 100644 --- a/common/src/main/java/me/confuser/banmanager/webenhancer/common/runnables/ExpiresSync.java +++ b/common/src/main/java/me/confuser/banmanager/webenhancer/common/runnables/ExpiresSync.java @@ -1,8 +1,6 @@ package me.confuser.banmanager.webenhancer.common.runnables; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; -import me.confuser.banmanager.common.BanManagerPlugin; -import me.confuser.banmanager.common.runnables.BmRunnable; import me.confuser.banmanager.common.util.DateUtils; import me.confuser.banmanager.webenhancer.common.WebEnhancerPlugin; import me.confuser.banmanager.webenhancer.common.data.PlayerPinData; @@ -10,12 +8,10 @@ import java.sql.SQLException; -public class ExpiresSync extends BmRunnable { +public class ExpiresSync implements Runnable { private PlayerPinStorage pinStorage; public ExpiresSync(WebEnhancerPlugin plugin) { - super(BanManagerPlugin.getInstance(), "pinCheck"); - pinStorage = plugin.getPlayerPinStorage(); } diff --git a/e2e/platforms/bukkit/configs/banmanager/messages.yml b/e2e/platforms/bukkit/configs/banmanager/messages.yml index fdf0aab..459dab6 100644 --- a/e2e/platforms/bukkit/configs/banmanager/messages.yml +++ b/e2e/platforms/bukkit/configs/banmanager/messages.yml @@ -136,7 +136,7 @@ messages: ban: player: disallowed: '&6You have been banned from this server for &4[reason]&6. Your appeal pin is [pin]' - kick: '&6You have been banned permanently for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been permanently banned by [actor] for &4[reason]' error: @@ -149,7 +149,7 @@ messages: tempban: player: disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]. Your appeal pin is [pin]' - kick: '&6You have been temporarily banned for &4[reason]' + kick: '&6You have been temporarily banned for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' diff --git a/e2e/platforms/bungee/configs/banmanager/messages.yml b/e2e/platforms/bungee/configs/banmanager/messages.yml index 118dd99..5bacf5c 100644 --- a/e2e/platforms/bungee/configs/banmanager/messages.yml +++ b/e2e/platforms/bungee/configs/banmanager/messages.yml @@ -143,7 +143,7 @@ messages: player: disallowed: '&6You have been banned from this server for &4[reason]&6. Your appeal pin is [pin]' - kick: '&6You have been banned permanently for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: yyyy-MM-dd HH:mm:ss notify: '&6[player] has been permanently banned by [actor] for &4[reason]' error: @@ -155,7 +155,7 @@ messages: player: disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]. Your appeal pin is [pin]' - kick: '&6You have been temporarily banned for &4[reason]' + kick: '&6You have been temporarily banned for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: yyyy-MM-dd HH:mm:ss notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' tempbanall: diff --git a/e2e/platforms/fabric/configs/banmanager/messages.yml b/e2e/platforms/fabric/configs/banmanager/messages.yml index fb8d205..8a833ce 100644 --- a/e2e/platforms/fabric/configs/banmanager/messages.yml +++ b/e2e/platforms/fabric/configs/banmanager/messages.yml @@ -142,7 +142,7 @@ messages: ban: player: disallowed: '&6You have been banned from this server for &4[reason]&6. Your appeal pin is [pin]' - kick: '&6You have been banned permanently for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been permanently banned by [actor] for &4[reason]' error: @@ -155,7 +155,7 @@ messages: tempban: player: disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]. Your appeal pin is [pin]' - kick: '&6You have been temporarily banned for &4[reason]' + kick: '&6You have been temporarily banned for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' diff --git a/e2e/platforms/sponge/configs/banmanager/messages.yml b/e2e/platforms/sponge/configs/banmanager/messages.yml index fdf0aab..459dab6 100644 --- a/e2e/platforms/sponge/configs/banmanager/messages.yml +++ b/e2e/platforms/sponge/configs/banmanager/messages.yml @@ -136,7 +136,7 @@ messages: ban: player: disallowed: '&6You have been banned from this server for &4[reason]&6. Your appeal pin is [pin]' - kick: '&6You have been banned permanently for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been permanently banned by [actor] for &4[reason]' error: @@ -149,7 +149,7 @@ messages: tempban: player: disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]. Your appeal pin is [pin]' - kick: '&6You have been temporarily banned for &4[reason]' + kick: '&6You have been temporarily banned for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' diff --git a/e2e/platforms/sponge7/configs/banmanager/messages.yml b/e2e/platforms/sponge7/configs/banmanager/messages.yml index fdf0aab..459dab6 100644 --- a/e2e/platforms/sponge7/configs/banmanager/messages.yml +++ b/e2e/platforms/sponge7/configs/banmanager/messages.yml @@ -136,7 +136,7 @@ messages: ban: player: disallowed: '&6You have been banned from this server for &4[reason]&6. Your appeal pin is [pin]' - kick: '&6You have been banned permanently for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been permanently banned by [actor] for &4[reason]' error: @@ -149,7 +149,7 @@ messages: tempban: player: disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]. Your appeal pin is [pin]' - kick: '&6You have been temporarily banned for &4[reason]' + kick: '&6You have been temporarily banned for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' diff --git a/e2e/platforms/velocity/configs/banmanager/messages.yml b/e2e/platforms/velocity/configs/banmanager/messages.yml index 118dd99..5bacf5c 100644 --- a/e2e/platforms/velocity/configs/banmanager/messages.yml +++ b/e2e/platforms/velocity/configs/banmanager/messages.yml @@ -143,7 +143,7 @@ messages: player: disallowed: '&6You have been banned from this server for &4[reason]&6. Your appeal pin is [pin]' - kick: '&6You have been banned permanently for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: yyyy-MM-dd HH:mm:ss notify: '&6[player] has been permanently banned by [actor] for &4[reason]' error: @@ -155,7 +155,7 @@ messages: player: disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]. Your appeal pin is [pin]' - kick: '&6You have been temporarily banned for &4[reason]' + kick: '&6You have been temporarily banned for &4[reason]&6. Your appeal pin is [pin]' dateTimeFormat: yyyy-MM-dd HH:mm:ss notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' tempbanall: diff --git a/e2e/tests/src/denied-pin.test.ts b/e2e/tests/src/denied-pin.test.ts index 4242b16..13aa988 100644 --- a/e2e/tests/src/denied-pin.test.ts +++ b/e2e/tests/src/denied-pin.test.ts @@ -1,19 +1,24 @@ import { TestBot, createBot, sendCommand, disconnectRcon, sleep } from './helpers' +afterAll(async () => { + await disconnectRcon() +}) + describe('Denied Pin Placeholder', () => { - let bannedBot: TestBot | null = null const BANNED_USERNAME = 'DeniedPinPlayer' - const TEST_TIMEOUT_MS = 60000 + const TEST_TIMEOUT_MS = 120000 const expectDeniedConnection = async (): Promise => { let lastError: Error | null = null // Ban/tempban enforcement can be async across worker threads; retry denied connect checks. - for (let attempt = 1; attempt <= 3; attempt++) { + // Use a short connect timeout (10s) so limbo connections (neither kicked nor spawned) + // don't burn the full 30s default, leaving room for more retries. + for (let attempt = 1; attempt <= 8; attempt++) { + const bot = new TestBot(BANNED_USERNAME) try { - bannedBot = await createBot(BANNED_USERNAME) - await bannedBot.disconnect() - bannedBot = null + await bot.connect(10000) + await bot.disconnect() lastError = new Error(`Attempt ${attempt}: player connected while expected to be denied`) } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error) @@ -21,24 +26,19 @@ describe('Denied Pin Placeholder', () => { return errorMessage } lastError = error instanceof Error ? error : new Error(String(error)) + try { await bot.disconnect() } catch (e) {} } - await sleep(1000) + await sleep(2000) } throw lastError ?? new Error('Expected denied connection but did not receive a denial kick') } afterEach(async () => { - try { - await sendCommand(`bmunban ${BANNED_USERNAME}`) - } catch (e) {} - await bannedBot?.disconnect() - bannedBot = null - }) - - afterAll(async () => { - await disconnectRcon() + try { await sendCommand(`bmunban ${BANNED_USERNAME}`) } catch (e) {} + try { await sendCommand(`bmuntempban ${BANNED_USERNAME}`) } catch (e) {} + await sleep(2000) }) test('[pin] placeholder in ban message is replaced with actual pin', async () => { @@ -64,7 +64,7 @@ describe('Denied Pin Placeholder', () => { test('[pin] placeholder in tempban message is replaced with actual pin', async () => { const tempbanResponse = await sendCommand(`bmtempban ${BANNED_USERNAME} 1h Testing tempban pin placeholder`) console.log(`Tempban response: ${tempbanResponse}`) - await sleep(2000) + await sleep(5000) const errorMessage = await expectDeniedConnection() console.log(`Player was denied (tempban) as expected: ${errorMessage}`) @@ -82,3 +82,65 @@ describe('Denied Pin Placeholder', () => { } }, TEST_TIMEOUT_MS) }) + +describe('Online Kick Pin Placeholder', () => { + const KICK_USERNAME = 'OnlineKickPin' + const TEST_TIMEOUT_MS = 60000 + let bot: TestBot | null = null + + afterEach(async () => { + await bot?.disconnect() + bot = null + try { await sendCommand(`bmunban ${KICK_USERNAME}`) } catch (e) {} + try { await sendCommand(`bmuntempban ${KICK_USERNAME}`) } catch (e) {} + await sleep(2000) + }) + + test('[pin] placeholder in kick message is replaced when banning an online player', async () => { + bot = await createBot(KICK_USERNAME) + const kickPromise = bot.waitForKick() + + await sleep(1000) + const banResponse = await sendCommand(`bmban ${KICK_USERNAME} Testing online kick pin`) + console.log(`Ban response: ${banResponse}`) + + const kickReason = await kickPromise + console.log(`Online player kicked with reason: ${kickReason}`) + bot = null + + expect(kickReason).not.toContain('[pin]') + + const pinMatch = kickReason.match(/pin is (\d{6})/) + expect(pinMatch).not.toBeNull() + + if (pinMatch != null) { + const pin = pinMatch[1] + console.log(`Extracted pin from online kick message: ${pin}`) + expect(pin).toMatch(/^\d{6}$/) + } + }, TEST_TIMEOUT_MS) + + test('[pin] placeholder in kick message is replaced when tempbanning an online player', async () => { + bot = await createBot(KICK_USERNAME) + const kickPromise = bot.waitForKick() + + await sleep(1000) + const tempbanResponse = await sendCommand(`bmtempban ${KICK_USERNAME} 1h Testing online tempban kick pin`) + console.log(`Tempban response: ${tempbanResponse}`) + + const kickReason = await kickPromise + console.log(`Online player kicked (tempban) with reason: ${kickReason}`) + bot = null + + expect(kickReason).not.toContain('[pin]') + + const pinMatch = kickReason.match(/pin is (\d{6})/) + expect(pinMatch).not.toBeNull() + + if (pinMatch != null) { + const pin = pinMatch[1] + console.log(`Extracted pin from online tempban kick message: ${pin}`) + expect(pin).toMatch(/^\d{6}$/) + } + }, TEST_TIMEOUT_MS) +}) diff --git a/e2e/tests/src/helpers/bot.ts b/e2e/tests/src/helpers/bot.ts index 6517c91..539fbec 100644 --- a/e2e/tests/src/helpers/bot.ts +++ b/e2e/tests/src/helpers/bot.ts @@ -6,7 +6,40 @@ const SERVER_PORT = parseInt(process.env.SERVER_PORT ?? '25565', 10) const MC_VERSION = process.env.MC_VERSION ?? undefined /** - * Extract text from a kick reason (string on Bukkit, ChatMessage object on Fabric) + * Extract text from an NBT compound chat component (Paper 1.20.5+ sends kick + * reasons as PrismarineNBT compounds instead of JSON chat components). + */ +function extractNbtText (nbt: Record): string { + const value = nbt.value as Record + let result = '' + + const textField = value.text as Record | undefined + if (textField?.type === 'string' && typeof textField.value === 'string') { + result += textField.value + } + + const extra = value.extra as Record | undefined + if (extra?.type === 'list') { + const listVal = extra.value as Record + const items = listVal?.value + if (Array.isArray(items)) { + for (const item of items) { + if (typeof item === 'object' && item != null) { + const t = (item as Record).text as Record | undefined + if (t?.type === 'string' && typeof t.value === 'string') { + result += t.value + } + } + } + } + } + + return result +} + +/** + * Extract text from a kick reason (string on Bukkit, ChatMessage object on + * Fabric, or NBT compound on newer Paper). */ function extractKickReason (reason: unknown): string { if (typeof reason === 'string') { @@ -14,6 +47,11 @@ function extractKickReason (reason: unknown): string { } if (reason != null && typeof reason === 'object') { const reasonObj = reason as Record + + if (reasonObj.type === 'compound' && reasonObj.value != null) { + return extractNbtText(reasonObj) + } + if (typeof reasonObj.toString === 'function') { const result = reasonObj.toString() if (result !== '[object Object]') { @@ -64,7 +102,7 @@ export class TestBot { return this._username } - async connect (): Promise { + async connect (connectTimeoutMs: number = 30000): Promise { return await new Promise((resolve, reject) => { console.log(`Connecting bot ${this._username} to ${SERVER_HOST}:${SERVER_PORT}`) @@ -77,9 +115,26 @@ export class TestBot { version: MC_VERSION }) + const cleanup = (callback: () => void): void => { + if (this.bot != null) { + const b = this.bot + this.bot = null + + const fallback = setTimeout(() => callback(), 1500) + + b.once('end', () => { + clearTimeout(fallback) + callback() + }) + b.end() + } else { + callback() + } + } + const timeout = setTimeout(() => { - reject(new Error('Bot connection timeout')) - }, 30000) + cleanup(() => reject(new Error('Bot connection timeout'))) + }, connectTimeoutMs) this.bot.once('spawn', () => { clearTimeout(timeout) @@ -89,14 +144,14 @@ export class TestBot { this.bot.once('error', (err) => { clearTimeout(timeout) - reject(err) + cleanup(() => reject(err)) }) this.bot.once('kicked', (reason) => { clearTimeout(timeout) const reasonText = extractKickReason(reason) console.log(`Bot ${this._username} was kicked: ${reasonText}`) - reject(new Error(`Bot ${this._username} was kicked: ${reasonText}`)) + cleanup(() => reject(new Error(`Bot ${this._username} was kicked: ${reasonText}`))) }) this.bot.on('chat', (username, message) => { @@ -288,6 +343,62 @@ export class TestBot { } } + async waitForKick (timeoutMs: number = 30000): Promise { + if (this.bot == null) { + throw new Error('Bot not connected') + } + + const bot = this.bot + bot.removeAllListeners('kicked') + + return await new Promise((resolve, reject) => { + let settled = false + const timeout = setTimeout(() => { + if (!settled) { + settled = true + reject(new Error('Timeout waiting for kick')) + } + }, timeoutMs) + + bot.once('kicked', (reason) => { + if (settled) return + settled = true + clearTimeout(timeout) + const kickReason = extractKickReason(reason) + + // Wait for the 'end' event so mineflayer's internal cleanup (physics + // timer clearInterval) runs before we resolve. Resolving immediately + // lets the test finish and Jest tear down while the physics tick is + // still running, causing a ReferenceError. + const endTimeout = setTimeout(() => { + this.bot = null + resolve(kickReason) + }, 1500) + + bot.once('end', () => { + clearTimeout(endTimeout) + this.bot = null + resolve(kickReason) + }) + + bot.end() + }) + + bot.once('end', (reason) => { + // Delay before rejecting — mineflayer can fire 'end' (socketClosed) before + // 'kicked' when the server closes the TCP connection immediately after + // sending the disconnect packet. Give 'kicked' a chance to arrive first. + setTimeout(() => { + if (settled) return + settled = true + clearTimeout(timeout) + this.bot = null + reject(new Error(`Bot disconnected before kick: ${reason}`)) + }, 500) + }) + }) + } + private async sleep (ms: number): Promise { return await new Promise((resolve) => setTimeout(resolve, ms)) } diff --git a/fabric/src/main/java/me/confuser/banmanager/webenhancer/fabric/listeners/EventListener.java b/fabric/src/main/java/me/confuser/banmanager/webenhancer/fabric/listeners/EventListener.java index be416f9..449cf02 100644 --- a/fabric/src/main/java/me/confuser/banmanager/webenhancer/fabric/listeners/EventListener.java +++ b/fabric/src/main/java/me/confuser/banmanager/webenhancer/fabric/listeners/EventListener.java @@ -1,5 +1,6 @@ package me.confuser.banmanager.webenhancer.fabric.listeners; +import me.confuser.banmanager.common.data.PlayerBanData; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.fabric.BanManagerEvents; @@ -15,8 +16,8 @@ public EventListener(WebEnhancerPlugin plugin) { this.plugin = plugin; this.deniedListener = new CommonPlayerDeniedListener(plugin); - // Wire BanManager Fabric events BanManagerEvents.PLAYER_DENIED_EVENT.register(this::onPlayerDenied); + BanManagerEvents.PLAYER_BANNED_EVENT.register(this::onPlayerBanned); BanManagerEvents.PLUGIN_RELOADED_EVENT.register(this::onReload); } @@ -24,6 +25,12 @@ private void onPlayerDenied(PlayerData player, Message message) { deniedListener.handlePin(player, message); } + private void onPlayerBanned(PlayerBanData banData, boolean silent, Message kickMessage) { + if (kickMessage != null) { + deniedListener.handlePin(banData.getPlayer(), kickMessage); + } + } + private void onReload(PlayerData actor) { plugin.setupConfigs(); } diff --git a/gradle.properties b/gradle.properties index 18c8d47..2017cee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=me.confuser.banmanager.webenhancer -version=7.7.0-SNAPSHOT +version=7.11.0-SNAPSHOT org.gradle.parallel=true org.gradle.jvmargs=-Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 diff --git a/sponge-api7/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java b/sponge-api7/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java index 29cb6fb..8272850 100644 --- a/sponge-api7/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java +++ b/sponge-api7/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java @@ -1,10 +1,12 @@ package me.confuser.banmanager.webenhancer.sponge.listeners; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; +import me.confuser.banmanager.sponge.api.events.PlayerBannedEvent; import me.confuser.banmanager.sponge.api.events.PlayerReportDeletedEvent; import me.confuser.banmanager.sponge.api.events.PlayerReportedEvent; import me.confuser.banmanager.sponge.api.events.PlayerDeniedEvent; import me.confuser.banmanager.sponge.api.events.PluginReloadedEvent; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.webenhancer.sponge.SpongePlugin; import me.confuser.banmanager.webenhancer.common.data.LogData; @@ -79,6 +81,17 @@ public void onDeny(final PlayerDeniedEvent event) { listener.handlePin(event.getPlayer(), event.getMessage()); } + @Listener(order = Order.POST) + public void onBanned(PlayerBannedEvent event) { + try { + Message kickMessage = event.getKickMessage(); + if (kickMessage != null) { + listener.handlePin(event.getBan().getPlayer(), kickMessage); + } + } catch (NoSuchMethodError ignored) { + } + } + @Listener public void onReload(PluginReloadedEvent event) { plugin.getPlugin().setupConfigs(); diff --git a/sponge/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java b/sponge/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java index cf048ce..480d215 100644 --- a/sponge/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java +++ b/sponge/src/main/java/me/confuser/banmanager/webenhancer/sponge/listeners/ReportListener.java @@ -1,10 +1,12 @@ package me.confuser.banmanager.webenhancer.sponge.listeners; import me.confuser.banmanager.common.ormlite.stmt.DeleteBuilder; +import me.confuser.banmanager.sponge.api.events.PlayerBannedEvent; import me.confuser.banmanager.sponge.api.events.PlayerReportDeletedEvent; import me.confuser.banmanager.sponge.api.events.PlayerReportedEvent; import me.confuser.banmanager.sponge.api.events.PlayerDeniedEvent; import me.confuser.banmanager.sponge.api.events.PluginReloadedEvent; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.data.PlayerReportData; import me.confuser.banmanager.webenhancer.sponge.SpongePlugin; import me.confuser.banmanager.webenhancer.common.data.LogData; @@ -77,6 +79,17 @@ public void onDeny(final PlayerDeniedEvent event) { listener.handlePin(event.getPlayer(), event.getMessage()); } + @Listener(order = Order.POST) + public void onBanned(PlayerBannedEvent event) { + try { + Message kickMessage = event.getKickMessage(); + if (kickMessage != null) { + listener.handlePin(event.getBan().getPlayer(), kickMessage); + } + } catch (NoSuchMethodError ignored) { + } + } + @Listener public void onReload(PluginReloadedEvent event) { plugin.getPlugin().setupConfigs(); diff --git a/velocity/src/main/java/me/confuser/banmanager/webenhancer/velocity/listener/BanListener.java b/velocity/src/main/java/me/confuser/banmanager/webenhancer/velocity/listener/BanListener.java index 2eb2bf8..29e1548 100644 --- a/velocity/src/main/java/me/confuser/banmanager/webenhancer/velocity/listener/BanListener.java +++ b/velocity/src/main/java/me/confuser/banmanager/webenhancer/velocity/listener/BanListener.java @@ -2,7 +2,9 @@ import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyReloadEvent; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.velocity.Listener; +import me.confuser.banmanager.velocity.api.events.PlayerBannedEvent; import me.confuser.banmanager.velocity.api.events.PlayerDeniedEvent; import me.confuser.banmanager.webenhancer.common.listeners.CommonPlayerDeniedListener; import me.confuser.banmanager.webenhancer.velocity.VelocityPlugin; @@ -21,6 +23,17 @@ public void onDeny(PlayerDeniedEvent event) { listener.handlePin(event.getPlayer(), event.getMessage()); } + @Subscribe + public void onBanned(PlayerBannedEvent event) { + try { + Message kickMessage = event.getKickMessage(); + if (kickMessage != null) { + listener.handlePin(event.getBan().getPlayer(), kickMessage); + } + } catch (NoSuchMethodError ignored) { + } + } + @Subscribe public void onReload(ProxyReloadEvent event) { velocityPlugin.getPlugin().setupConfigs();