Compare commits

..
58 Commits
Author SHA1 Message Date
Nassim Jahnke dbdf811ae5 Update changelog 2026-10-17 10:15:59 +02:00
Nassim Jahnke 0518f848e1 5.1.1 Release 2026-10-17 10:12:45 +02:00
Nassim Jahnke 4ebfa8c750 Fix no-op task cancellation 2026-10-17 10:12:38 +02:00
Nassim Jahnke ea51ea9c63 Reload data from redis instead of config 2026-10-17 10:10:53 +02:00
Nassim Jahnke 3049f17250 Redact webhook url from dump 2026-10-17 10:10:24 +02:00
Nassim Jahnke 619da4abee Fix redis enabled key 2026-10-17 10:08:50 +02:00
Nassim Jahnke bb838fd4dd Fix aborttimer suggestions 2026-10-17 10:07:22 +02:00
Nassim Jahnke 695404ce5c Fix Folia task scheduling
Fixes #220
2026-10-17 09:41:23 +02:00
Maintenancer fd30fa4da6 New Crowdin updates (#205) 2026-10-17 09:26:58 +02:00
Nassim Jahnke 3aad351c7b Support days in duration arguments 2026-10-17 09:19:13 +02:00
Nassim Jahnke 95492fe0e1 Allow 'global' with specific mode in proxy timer commands
Closes #218
2026-09-26 17:53:03 +02:00
Nassim Jahnke 11b8fd2861 Update deps 2026-09-26 17:38:21 +02:00
Nassim Jahnke b9fdc90f7f Bump version to 5.1.1-SNAPSHOT 2026-09-26 17:29:55 +02:00
Nassim Jahnke acc5d89263 5.1.0 Release 2026-05-17 09:44:56 +02:00
Maintenancer 46b9dc4e04 New Crowdin updates (#204) 2026-05-15 14:02:24 +02:00
Maintenancer 5d37138b64 New Crowdin updates (#195) 2026-05-15 10:49:15 +02:00
Nassim Jahnke 83f3a850c5 Add separate webhook language strings; add timestamp param 2026-05-15 10:48:27 +02:00
Nassim Jahnke b58cb9709b Update deps 2026-05-15 10:26:35 +02:00
旋律 00f6b25a67 Fix initial loading of proxied-maintenance-servers (#197) 2026-04-25 09:08:47 +02:00
Nassim Jahnke e5fd9e6f37 Bump version to 5.0.1-SNAPSHOT 2026-04-25 09:08:10 +02:00
Nassim Jahnke 051d916f87 [ci skip] Add missing changelog for webhooks 2026-04-20 15:18:24 +02:00
Nassim Jahnke b1ace71d4b 5.0.0 Release 2026-04-17 13:10:58 +02:00
Nassim Jahnke da6fef8906 Hopefully don't break on adventure 5.0.0 2026-04-17 13:09:49 +02:00
Nassim Jahnke f6ad07a115 Fix publishing
not that it looks great
2026-04-07 12:54:00 +02:00
Nassim Jahnke 9072e03f79 Update deps 2026-04-07 11:41:14 +02:00
Nassim Jahnke 946c1a7f72 5.0.0-beta.2 2026-03-28 19:25:23 +01:00
Maintenancer 572fc63162 New Crowdin updates (#189) 2026-03-28 19:25:04 +01:00
Nassim Jahnke 87aedfaa3b Fix status command help messages, update default lang version 2026-03-28 19:22:39 +01:00
Maintenancer 8aba97d60d New Crowdin updates (#187) 2026-03-28 19:13:00 +01:00
Nassim Jahnke 04d85b9c3c Add status command to Paper; add timers to status 2026-03-28 19:07:03 +01:00
Maintenancer 0603a17b5e New Crowdin updates (#185) 2026-03-23 15:56:12 +01:00
Nassim Jahnke 3cd689f15d 5.0.0-beta1 2026-03-23 15:41:30 +01:00
Nassim Jahnke 6c28d31fec Add mode-specific messages and reasons 2026-03-23 15:29:12 +01:00
Nassim Jahnke 1988b0284b Don't require strict toolchain version 2026-03-20 21:29:35 +01:00
Nassim Jahnke c7f3dc72d4 Add setting for webhook on maintenance status changes and blocked joins
Closes #171
2026-03-15 21:48:14 +01:00
Maintenancer 9767322376 New Crowdin updates (#172) 2026-03-14 21:56:09 +01:00
Nassim Jahnke 0e50c39100 Don't do unnecessary profile lookups for invalid names 2026-02-22 12:23:05 +01:00
Nassim Jahnke ad2228b9c4 [ci skip] Remove gradle wrapper update action 2026-02-22 12:01:44 +01:00
Nassim Jahnke 90e880500a Fix fallback server handling on Velocity if the fallback is unreachable
Fixes #158
2026-02-22 11:56:35 +01:00
Nassim Jahnke 15c3d9c111 Update minimessage links 2026-02-22 11:40:26 +01:00
Nassim Jahnke ec55606737 [ci skip] Add dependabot config 2026-02-22 11:36:02 +01:00
Maintenancer a7438ca297 New Crowdin updates (#153) 2026-02-02 08:18:06 +01:00
Nassim JahnkeandFederico 65d68982f9 Replace MySQL with Redis for proxy status sync
Co-authored-by: Federico <developing@fedet.it>
2026-02-01 20:03:51 +01:00
Nassim Jahnke 03f0368c10 Make use of a few new language features 2026-02-01 13:00:23 +01:00
Nassim Jahnke c16637ee36 Only shade adventure in Bungee module, bump Java req and dependencies
This simplifies text handling a lot by directly using each platform's native adventure text component support (except for Bungee of course) instead of having a separate, pre-shaded adventure module that every other module has to depend on and convert between.

Due to various dependency bumps, the required Java version is now 21.
2026-02-01 12:39:04 +01:00
Nassim Jahnke 7034bb85f5 Bump version to 5.0.0-SNAPSHOT 2026-02-01 12:09:24 +01:00
github-actions[bot] 38b142e8c9 Update Gradle Wrapper from 9.2.1 to 9.3.1 (#169) 2026-02-01 10:51:28 +01:00
github-actions[bot]andgradle-update-robot 2af4929c3a Update Gradle Wrapper from 9.0.0 to 9.2.1 (#165)
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
Co-authored-by: gradle-update-robot <gradle-update-robot@regolo.cc>
2025-12-19 15:56:47 +01:00
Nassim Jahnke 035b720369 Update Gradle wrapper, add workflow 2025-09-20 22:15:00 +02:00
Nassim Jahnke 8a106e4b34 Fix schedule duration argument swap on proxies 2025-07-12 10:58:24 +02:00
Nassim Jahnke f4a34041b8 Bump version to 4.3.1-SNAPSHOT 2025-07-12 10:58:03 +02:00
Nassim Jahnke ceb812e025 Fix actions 2025-07-12 10:35:16 +02:00
Nassim Jahnke 5058e851d8 Update maven central publishing 2025-07-01 16:39:03 +02:00
ba04bae7bf New Crowdin updates (#139)
Supersedes #150
Supersedes #152

Co-authored-by: TwistedWar3713 <97833781+TwistedWar3713@users.noreply.github.com>
Co-authored-by: Furios12 <102328541+Furios12@users.noreply.github.com>
2025-05-18 21:13:01 +02:00
Nassim Jahnke d1abdd496e Update Gradle wrapper 2025-05-18 21:10:14 +02:00
Nassim Jahnke 956e7bb3ef Enable Gradle configuration cache 2024-12-29 13:10:50 +01:00
Nassim Jahnke 171e1368f4 [ci skip] Update Gradle wrapper 2024-12-21 19:27:06 +01:00
Nassim Jahnke 0fff12f368 [ci skip] Update Gradle wrapper 2024-11-17 17:51:22 +01:00
140 changed files with 3478 additions and 2044 deletions

No files matched your search

+49 -3
View File
@@ -1,6 +1,52 @@
# Maintenance Changelog
This file contains update logs for this project. The top may contain a `Unreleased` section, gathering update logs for a future update during development.
## 5.1.1 - October 17th 2026
### Changed
- Velocity/Bungee: Timer commands now accept `global` as the server argument, with an optional mode: `/maintenance starttimer global [mode] <time>`, `/maintenance scheduletimer global [mode] <time> <duration>`, `/maintenance endtimer global <time>`
- Timer durations now also accept days, e.g. `2d` or `1d12h`
- Folia: Now supports timers
### Fixed
- Folia: Fixed the update check stalling the global region thread
- Velocity/Bungee: Fixed the `redis.enabled` confg key
- Velocity/Bungee: `/maintenance reload` now keeps the maintenance status from Redis instead of overriding it with the local config
- Velocity/Bungee: Fixed `/maintenance aborttimer` tab completion not suggesting servers with running timers
---
## 5.1.0 - May 17th 2026
### Changed
- Made Discord webhooks configurable separately from command messages, including a new `%TIMESTAMP%` parameter for Discord timestamps (e.g. `<t:%TIMESTAMP%:R>`)
### Fixed
- Velocity/Bungee: Fixed proxied-maintenance-servers config loading
---
## 5.0.0 - April 17th 2026
This update removes *a lot* of legacy code, making the code-base a great bit more simple and a lot more performant.
### Requirements
- The Paper part now **requires at least Paper 1.20.1** and no longer supports Spigot
- The universal jar has been removed. Each platform now has its own jar (that is as small as possible)
- Maintenance now **requires at least Java 21**
### Changed
- **Added mode-based maintenance messages** for `ping-message`, `player-count-message`, and `player-list-hover-message`
- You can have different MOTDs depending on the pre-configured mode, e.g. a generic `default` or one for `upcoming-release`.
- Updated proxy/global toggle syntax: `/maintenance on [mode]`
- For proxy servers: `/maintenance on global [mode]`, `/maintenance on <server> [mode]`
- Other updated commands: `/maintenance motd [timer] [mode]`, `/maintenance setmotd [timer] [mode] ...`, `/maintenance removemotd [timer] [mode] ...`
- **Added `webhook` config option for Discord webhooks** when the maintenance status changes, a timer is started or a join is blocked
- **Added `%REASON%` variable to messages**, using the `active-reason` config option. This allows for dynamic MOTD and other messages via a quick command without modifying the messages themselves
- Can be changed via the `/maintenance setreason <reason>` command
- **Added `/maintenance status` command** on Paper/Sponge to display the current maintenance status as well as any running start or end timers.
- Velocity/Bungee: **The existing timer command now displays start and end timers** for proxied servers.
- Velocity/Bungee: **Replaced `mysql` section with `redis`**. This is a more suitable database for such reading and sharing of data and no longer means some server pings can take multiple seconds, if you have multiple proxies linked
### Fixed
- Velocity/Bungee: Fixed the `/maintenance schedule` command for single servers having swapped time arguments
---
## 4.3.0 - September 17th 2024
### Changed
@@ -64,8 +110,8 @@ This file contains update logs for this project. The top may contain a `Unreleas
### Changed
* Added support for hex colors in messages and replaced the component parsing with adventure-minimessage
* This means that section symbols (`§`) are no longer the intended/supported format
* You can use the following page to edit and preview formatted text: https://webui.adventure.kyori.net/
* You can find full documentation on the format (including normal colors and formatting, rgb, click/hover events, and more) here: https://docs.adventure.kyori.net/minimessage/format
* You can use the following page to edit and preview formatted text: https://webui.advntr.dev/
* You can find full documentation on the format (including normal colors and formatting, rgb, click/hover events, and more) here: https://docs.papermc.io/adventure/minimessage/format/
* Your config and language file will be updated to the new format automatically, you won't have to change anything manually!
* Note that the player count and player count hover messages do not support rgb colors
* Updated Sponge module to SpongeAPI v8 (Minecraft 1.15+)
@@ -327,4 +373,4 @@ This file contains update logs for this project. The top may contain a `Unreleas
### Fixed
* Fixed some permission and message bugs
* Fixed problems with adding and removing players from the maintenance whitelist
* Fixed update checks
* Fixed update checks
+14
View File
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: "github-actions"
cooldown:
default-days: 7
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "gradle"
cooldown:
default-days: 7
directory: "/"
schedule:
interval: "monthly"
+8 -6
View File
@@ -7,20 +7,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@v3
- uses: actions/checkout@v7
- name: Set up JDK 17
uses: actions/setup-java@v4
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
- name: Set up JDK 21
uses: actions/setup-java@v6
with:
java-version: 17
java-version: 21
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Maintenance Files
path: build/libs
+1
View File
@@ -2,6 +2,7 @@
build/
.gradle
bin/
run/
tmp/
*.tmp
*.bak
+2 -2
View File
@@ -14,7 +14,7 @@ Its features include:
* Nearly all messages are editable via the language file, given in a multitude of different languages
* Features specifically for Bungee/Velocity
* Only want to enable maintenance on a single server? You can also do so by using the `/maintenance <on/off> <servername>` command
* Link multiple proxy instances through a MySQL database connection, so you don't have to change maintenance on each proxy by hand
* Link multiple proxy instances through a Redis connection, so you don't have to change maintenance status or whitelisted players on each proxy by hand
* Use PlaceholderAPI placeholders with the [**MaintenanceAddon**](https://hangar.papermc.io/kennytv/MaintenanceAddon) (for Velocity and Bungee)
A full list of commands, permissions and configuration options can be found in the wiki listed below.
@@ -33,7 +33,7 @@ Join [our Discord](https://discord.gg/vGCUzHq) if you want to get the proofreade
Adding translations for languages not yet listed is always appreciated as well!
## Compiling
To create a working jar yourself, simply clone the project and compile it with Gradle (`gradlew build`).
To create a working jar yourself, simply clone the project and compile it with Gradle (`gradlew build`). To compile, you need a JDK 21+ (although it targets 17).
## License
This project is licensed under the [GNU General Public License v3](LICENSE.txt).
-24
View File
@@ -1,24 +0,0 @@
plugins {
id("com.gradleup.shadow")
}
// Shade and relocate adventure in an extra module, so that common/the rest can directly depend on a
// relocated adventure without breaking native platform's adventure usage with project wide relocation
tasks {
shadowJar {
relocate("net.kyori", "eu.kennytv.maintenance.lib.kyori")
}
build {
dependsOn(shadowJar)
}
}
dependencies {
api(libs.bundles.adventure) {
exclude("net.kyori", "adventure-api")
exclude("net.kyori", "adventure-bom")
exclude("com.google.code.gson", "gson")
}
}
publishShadowJar()
@@ -1,20 +0,0 @@
plugins {
id("com.gradleup.shadow")
}
tasks {
shadowJar {
relocate("net.kyori", "eu.kennytv.maintenance.lib.kyori")
}
build {
dependsOn(shadowJar)
}
}
dependencies {
api(libs.adventurePlatformBukkit) {
exclude("net.kyori", "adventure-api")
}
}
publishShadowJar()
@@ -1,20 +0,0 @@
plugins {
id("com.gradleup.shadow")
}
tasks {
shadowJar {
relocate("net.kyori", "eu.kennytv.maintenance.lib.kyori")
}
build {
dependsOn(shadowJar)
}
}
dependencies {
api(libs.adventurePlatformBungee) {
exclude("net.kyori", "adventure-api")
}
}
publishShadowJar()
View File
Whitespace-only changes.
+1 -3
View File
@@ -4,9 +4,7 @@ plugins {
dependencies {
api(projects.maintenanceApi)
compileOnly(projects.adventure.adventureApi) {
targetConfiguration = "shadow"
}
compileOnly(libs.adventureApi)
}
java {
@@ -15,17 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.proxy.server;
package eu.kennytv.maintenance.api.proxy;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
public final class DummyServer implements Server {
private final String name;
public DummyServer(final String name) {
this.name = name;
}
public record DummyServer(String name) implements Server {
@Override
public String getName() {
@@ -18,23 +18,26 @@
package eu.kennytv.maintenance.api.proxy;
import eu.kennytv.maintenance.api.Maintenance;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
public interface MaintenanceProxy extends Maintenance {
/**
* Enables/disables maintenance mode on a proxied server.
* If enabled, all non-permitted players will be kicked.
* If MySQL is enabled, it will also be written into the database.
* If Redis is enabled, it will also be written into the database.
*
* @param server server to apply the maintenance status to
* @param maintenance true to enable, false to disable maintenance mode
* @return true if the mode was changed
* @see #getServer(String)
*/
boolean setMaintenanceToServer(Server server, boolean maintenance);
boolean setMaintenanceToServer(Server server, boolean maintenance, @Nullable String mode);
default boolean setMaintenanceToServer(final Server server, final boolean maintenance) {
return setMaintenanceToServer(server, maintenance, null);
}
/**
* @param server server to check
@@ -59,6 +62,18 @@ public interface MaintenanceProxy extends Maintenance {
@Nullable
Server getServer(String server);
/**
* Returns a wrapped {@link Server} object of a proxied server, or a dummy server if not registered.
*
* @param serverName name of the proxied server
* @return wrapped server object, or a dummy if not registered
* @see Server#isRegisteredServer()
*/
default Server getServerOrDummy(final String serverName) {
final Server server = getServer(serverName);
return server != null ? server : new DummyServer(serverName);
}
/**
* Returns the currently registered proxied servers.
*
@@ -17,7 +17,7 @@
*/
package eu.kennytv.maintenance.api.proxy;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
/**
* Wrapper for a proxied server.
@@ -18,6 +18,7 @@
package eu.kennytv.maintenance.api;
import eu.kennytv.maintenance.api.event.manager.EventManager;
import org.jetbrains.annotations.Nullable;
public interface Maintenance {
@@ -25,13 +26,18 @@ public interface Maintenance {
* Enables/disables maintenance mode.
* If enabled, all non-permitted players will be kicked.
* <p>
* If using the BungeeCord version and MySQL is enabled,
* If using the BungeeCord version and Redis is enabled,
* it will also be written into the database.
* </p>
*
* @param maintenance true to enable, false to disable maintenance mode
* @param mode mode, used for custom messages
*/
void setMaintenance(boolean maintenance);
void setMaintenance(boolean maintenance, @Nullable String mode);
default void setMaintenance(boolean maintenance) {
setMaintenance(maintenance, null);
}
/**
* @return true if maintenance is currently enabled
@@ -19,16 +19,39 @@ package eu.kennytv.maintenance.api;
import java.util.Map;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
public interface Settings {
/**
* Note that the value might need a few seconds to update, if you're on BungeeCord with MySQL enabled
* Returns whether maintenance is currently enabled.
*
* @return true if maintenance is currently enabled
*/
boolean isMaintenance();
/**
* Returns the current active maintenance mode, or null is set to default.
* Used when getting MOTD/player count messages.
*
* @return the current active maintenance mode, or null if set to default
*/
@Nullable String activeMode();
/**
* Returns the reason for the current active mode.
*
* @return variable reason for the current active mode, or empty if no reason is set
*/
String activeReason();
/**
* Sets the reason for the current active mode, used in %REASON% message placeholders.
*
* @param reason reason to set for the current active mode, or null to remove the reason
*/
void setActiveReason(@Nullable String reason);
/**
* @return true if custom ping messages during maintenance should be used
*/
@@ -73,8 +96,8 @@ public interface Settings {
*
* @param name name of the player to remove with original lower-/uppercase letters
* @return true if removing the player was successful
* @deprecated if a player changes their name, the list will still contain the old one
* @see #removeWhitelistedPlayer(UUID)
* @deprecated if a player changes their name, the list will still contain the old one
*/
@Deprecated
boolean removeWhitelistedPlayer(String name);
@@ -22,17 +22,5 @@ import eu.kennytv.maintenance.api.event.manager.MaintenanceEvent;
/**
* Notification event fired when maintenance mode has been changed.
*/
public final class MaintenanceChangedEvent implements MaintenanceEvent {
private final boolean maintenance;
public MaintenanceChangedEvent(final boolean maintenance) {
this.maintenance = maintenance;
}
/**
* @return true if maintenance has been enabled, false otherwise
*/
public boolean isMaintenance() {
return maintenance;
}
public record MaintenanceChangedEvent(boolean isMaintenance) implements MaintenanceEvent {
}
+2 -1
View File
@@ -8,5 +8,6 @@ repositories {
dependencies {
// version must be manually kept in sync with the one in root project settings.gradle.kts
implementation("com.gradleup.shadow", "shadow-gradle-plugin", "8.3.0")
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.6.1")
implementation("com.gradleup.nmcp:com.gradleup.nmcp.gradle.plugin:1.6.2")
}
+3 -10
View File
@@ -6,12 +6,12 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
import java.io.ByteArrayOutputStream
fun Project.publishShadowJar() {
configurePublication {
artifact(tasks["shadowJar"])
artifact(tasks["sourcesJar"])
artifact(tasks["javadocJar"])
}
}
@@ -30,14 +30,7 @@ private fun Project.configurePublication(configurer: MavenPublication.() -> Unit
}
fun Project.latestCommitHash(): String {
val byteOut = ByteArrayOutputStream()
exec {
return providers.exec {
commandLine = listOf("git", "rev-parse", "--short", "HEAD")
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8.name()).trim()
}
fun JavaPluginExtension.javaTarget(version: Int) {
toolchain.languageVersion.set(JavaLanguageVersion.of(version))
}.standardOutput.asBytes.get().toString(Charsets.UTF_8).trim()
}
@@ -2,13 +2,16 @@ plugins {
`java-library`
`maven-publish`
signing
id("com.gradleup.nmcp")
}
tasks {
// Variable replacements
processResources {
val ver = project.version.toString()
val desc = project.description
filesMatching(listOf("plugin.yml", "bungee.yml", "META-INF/sponge_plugins.json")) {
expand("version" to project.version, "description" to project.description)
expand(mapOf("version" to ver, "description" to desc))
}
}
javadoc {
@@ -25,8 +28,10 @@ tasks {
}
java {
javaTarget(8)
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
withSourcesJar()
withJavadocJar()
}
signing {
@@ -43,6 +48,11 @@ publishing {
name.set("Maintenance")
description.set("Paper plugin to enable maintenance mode on your Minecraft server.")
url.set("https://github.com/kennytv/Maintenance")
scm {
url.set("https://github.com/kennytv/Maintenance")
connection.set("scm:git:https://github.com/kennytv/Maintenance.git")
developerConnection.set("scm:git:ssh://git@github.com/kennytv/Maintenance.git")
}
licenses {
license {
name.set("GNU GPLv3")
@@ -57,15 +67,4 @@ publishing {
}
}
}
if (!name.startsWith("adventure")) {
repositories.maven {
name = "OSSRH"
url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
credentials {
username = System.getenv("OSSRH_USERNAME")
password = System.getenv("OSSRH_PASSWORD")
}
}
}
}
}
@@ -1,6 +1,5 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.named
plugins {
id("maintenance.base-conventions")
@@ -24,6 +23,10 @@ tasks {
publishShadowJar()
fun ShadowJar.configureRelocations() {
relocate("com.zaxxer.hikari", "eu.kennytv.maintenance.lib.hikari")
relocate("org.bstats", "eu.kennytv.maintenance.lib.bstats")
// Proxy
relocate("redis.client", "eu.kennytv.maintenance.lib.redis.client")
relocate("org.json", "eu.kennytv.maintenance.lib.json")
relocate("org.apache.commons.pool2", "eu.kennytv.maintenance.lib.apache.commons.pool2")
}
+24 -10
View File
@@ -1,8 +1,10 @@
import org.gradle.api.plugins.JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME
import org.gradle.api.plugins.JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME
plugins {
base
id("maintenance.build-logic")
id("com.gradleup.nmcp.aggregation") version "1.6.2"
}
allprojects {
@@ -16,20 +18,15 @@ val platforms = setOf(
projects.maintenanceBungee,
projects.maintenanceSponge,
projects.maintenanceVelocity
).map { it.dependencyProject }
).map { it.path }
val special = setOf(
projects.maintenance,
projects.maintenanceApi,
projects.maintenanceApiProxy,
projects.adventure,
projects.adventure.adventureApi,
projects.adventure.adventurePlatformBukkit,
projects.adventure.adventurePlatformBungee,
).map { it.dependencyProject }
projects.maintenanceApiProxy
).map { it.path }
subprojects {
when (this) {
when (path) {
in platforms -> plugins.apply("maintenance.platform-conventions")
in special -> plugins.apply("maintenance.base-conventions")
else -> plugins.apply("maintenance.standard-conventions")
@@ -38,5 +35,22 @@ subprojects {
dependencies {
TEST_IMPLEMENTATION_CONFIGURATION_NAME(rootProject.libs.bundles.junit)
TEST_IMPLEMENTATION_CONFIGURATION_NAME(rootProject.libs.snakeyaml)
TEST_RUNTIME_ONLY_CONFIGURATION_NAME("org.junit.platform:junit-platform-launcher")
}
}
}
dependencies {
allprojects {
nmcpAggregation(project(path))
}
}
nmcpAggregation {
centralPortal {
username = System.getenv("MAVEN_CENTRAL_USERNAME")
password = System.getenv("MAVEN_CENTRAL_PASSWORD")
publishingType = "USER_MANAGED"
publicationName = "maintenance:$version"
publishingTimeout = java.time.Duration.ZERO
}
}
+14 -3
View File
@@ -1,8 +1,19 @@
dependencies {
implementation(projects.maintenanceCoreProxy)
implementation(projects.adventure.adventurePlatformBungee) {
targetConfiguration = "shadow"
}
implementation(libs.bstatsBungee)
compileOnly(libs.bungee)
}
tasks {
shadowJar {
relocate("net.kyori", "eu.kennytv.maintenance.lib.kyori")
}
}
dependencies {
api(libs.bundles.adventureBungee) {
exclude("com.google.code.gson", "gson")
exclude("org.slf4j", "slf4j-api")
exclude("net.kyori", "adventure-bom")
}
}
@@ -36,8 +36,6 @@ import eu.kennytv.maintenance.core.proxy.util.ProxyOfflineSenderInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.core.util.ServerType;
import eu.kennytv.maintenance.core.util.Task;
import eu.kennytv.maintenance.lib.kyori.adventure.platform.bungeecord.BungeeAudiences;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -50,6 +48,8 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
@@ -118,9 +118,9 @@ public final class MaintenanceBungeePlugin extends MaintenanceProxyPlugin {
@Override
protected void kickPlayers(final Server server, final Server fallback) {
// Kick players from a proxied server
final ServerInfo fallbackServer = fallback != null ? ((BungeeServer) fallback).getServer() : null;
final ServerInfo fallbackServer = fallback != null ? ((BungeeServer) fallback).server() : null;
final boolean checkForFallback = fallbackServer != null && !isMaintenance(fallback);
for (final ProxiedPlayer player : ((BungeeServer) server).getServer().getPlayers()) {
for (final ProxiedPlayer player : ((BungeeServer) server).server().getPlayers()) {
if (!hasPermission(player, "bypass") && !settingsProxy.isWhitelisted(player.getUniqueId())) {
if (checkForFallback && fallbackServer.canAccess(player)) {
audiences.player(player).sendMessage(settingsProxy.getMessage("singleMaintenanceActivated", "%SERVER%", server.getName()));
@@ -137,7 +137,7 @@ public final class MaintenanceBungeePlugin extends MaintenanceProxyPlugin {
@Override
protected void kickPlayersTo(final Server server) {
// Kick all players to a single waiting server
final ServerInfo serverInfo = ((BungeeServer) server).getServer();
final ServerInfo serverInfo = ((BungeeServer) server).server();
// Notifications done in global method
for (final ProxiedPlayer player : getProxy().getPlayers()) {
if (hasPermission(player, "bypass") || settingsProxy.isWhitelisted(player.getUniqueId())) continue;
@@ -167,7 +167,7 @@ public final class MaintenanceBungeePlugin extends MaintenanceProxyPlugin {
return CompletableFuture.supplyAsync(() -> {
try {
final ProfileLookup profile = doUUIDLookup(name);
return new ProxyOfflineSenderInfo(profile.getUuid(), profile.getName());
return new ProxyOfflineSenderInfo(profile.uuid(), profile.name());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -195,7 +195,7 @@ public final class MaintenanceBungeePlugin extends MaintenanceProxyPlugin {
@Override
@Nullable
public String getServerNameOf(final SenderInfo sender) {
final ProxiedPlayer player = getProxy().getPlayer(sender.getUuid());
final ProxiedPlayer player = getProxy().getPlayer(sender.uuid());
if (player == null || player.getServer() == null) return null;
return player.getServer().getInfo().getName();
}
@@ -22,6 +22,7 @@ import eu.kennytv.maintenance.core.proxy.SettingsProxy;
import eu.kennytv.maintenance.core.proxy.command.MaintenanceProxyCommand;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -40,7 +41,7 @@ public final class MaintenanceBungeeCommand extends MaintenanceProxyCommand {
final List<String> list = new ArrayList<>();
for (final Map.Entry<String, ServerInfo> entry : plugin.getProxy().getServers().entrySet()) {
final String serverName = entry.getValue().getName();
if (entry.getKey().toLowerCase().startsWith(s) && !plugin.getSettingsProxy().getMaintenanceServers().contains(serverName)) {
if (entry.getKey().toLowerCase(Locale.ROOT).startsWith(s) && !plugin.getSettingsProxy().getMaintenanceServers().contains(serverName)) {
list.add(serverName);
}
}
@@ -24,7 +24,7 @@ import eu.kennytv.maintenance.bungee.util.BungeeServer;
import eu.kennytv.maintenance.core.proxy.SettingsProxy;
import eu.kennytv.maintenance.core.proxy.listener.ProxyJoinListenerBase;
import eu.kennytv.maintenance.core.proxy.util.ServerConnectResult;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectEvent;
@@ -48,13 +48,13 @@ public final class ServerConnectListener extends ProxyJoinListenerBase implement
final BungeeSenderInfo player = new BungeeSenderInfo(event.getPlayer());
if (plugin.isDebug()) {
plugin.getLogger().info("Join permission check for " + event.getPlayer().getName()
+ " - Permission: " + player.hasMaintenancePermission("bypass") + ", whitelist: " + settings.isWhitelisted(player.getUuid()));
+ " - Permission: " + player.hasMaintenancePermission("bypass") + ", whitelist: " + settings.isWhitelisted(player.uuid()));
}
if (shouldKick(player)) {
final Server waitingServer = shouldConnectToWaitingServer(player);
if (waitingServer != null) {
event.setTarget(((BungeeServer) waitingServer).getServer());
event.setTarget(((BungeeServer) waitingServer).server());
player.send(settings.getMessage("sentToWaitingServer"));
if (plugin.isDebug()) {
@@ -67,7 +67,7 @@ public final class ServerConnectListener extends ProxyJoinListenerBase implement
player.disconnect(settings.getKickMessage());
if (settings.isJoinNotifications()) {
broadcastJoinNotification(player.getName());
broadcastJoinNotification(player.name());
}
}
}
@@ -83,7 +83,7 @@ public final class ServerConnectListener extends ProxyJoinListenerBase implement
final BungeeSenderInfo player = new BungeeSenderInfo(proxiedPlayer);
final ServerConnectResult connectResult = serverConnect(player, new BungeeServer(event.getTarget()), normalServerConnect);
if (plugin.isDebug()) {
plugin.getLogger().info("Connectresult for " + player.getName() + " to " + event.getTarget().getName() + ": " + connectResult);
plugin.getLogger().info("Connectresult for " + player.name() + " to " + event.getTarget().getName() + ": " + connectResult);
}
if (connectResult.isCancelled()) {
@@ -94,7 +94,7 @@ public final class ServerConnectListener extends ProxyJoinListenerBase implement
player.disconnect(settings.getKickMessage());
}
} else if (connectResult.getTarget() != null) {
event.setTarget(((BungeeServer) connectResult.getTarget()).getServer());
event.setTarget(((BungeeServer) connectResult.getTarget()).server());
}
}
@@ -105,7 +105,7 @@ public final class ServerConnectListener extends ProxyJoinListenerBase implement
@Override
protected void broadcastJoinNotification(final String name, final Server server) {
sendJoinMessage(((BungeeServer) server).getServer().getPlayers(), name);
sendJoinMessage(((BungeeServer) server).server().getPlayers(), name);
}
private void sendJoinMessage(final Iterable<ProxiedPlayer> players, final String name) {
@@ -21,7 +21,7 @@ import eu.kennytv.maintenance.api.MaintenanceProvider;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.bungee.MaintenanceBungeePlugin;
import eu.kennytv.maintenance.core.proxy.util.ProxySenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.Connection;
@@ -37,12 +37,12 @@ public final class BungeeSenderInfo implements ProxySenderInfo {
}
@Override
public UUID getUuid() {
return sender instanceof ProxiedPlayer ? ((ProxiedPlayer) sender).getUniqueId() : null;
public UUID uuid() {
return sender instanceof ProxiedPlayer proxiedPlayer ? proxiedPlayer.getUniqueId() : null;
}
@Override
public String getName() {
public String name() {
return sender.getName();
}
@@ -67,13 +67,13 @@ public final class BungeeSenderInfo implements ProxySenderInfo {
@Override
public boolean canAccess(final Server server) {
return ((BungeeServer) server).getServer().canAccess(sender);
return ((BungeeServer) server).server().canAccess(sender);
}
@Override
public void disconnect(final Component component) {
if (sender instanceof Connection) {
((Connection) sender).disconnect(ComponentUtil.toBadComponent(component));
if (sender instanceof Connection connection) {
connection.disconnect(ComponentUtil.toBadComponent(component));
}
}
}
@@ -18,17 +18,12 @@
package eu.kennytv.maintenance.bungee.util;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public final class BungeeServer implements Server {
private final ServerInfo server;
public BungeeServer(final ServerInfo server) {
this.server = server;
}
public record BungeeServer(ServerInfo server) implements Server {
@Override
public String getName() {
@@ -52,8 +47,4 @@ public final class BungeeServer implements Server {
public boolean isRegisteredServer() {
return true;
}
public ServerInfo getServer() {
return server;
}
}
@@ -17,8 +17,8 @@
*/
package eu.kennytv.maintenance.bungee.util;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
+7 -1
View File
@@ -1,7 +1,13 @@
dependencies {
api(projects.maintenanceApiProxy)
api(projects.maintenanceCore)
implementation(libs.hikariCP)
// Try to reasonably minimize it...
implementation(libs.jedis) {
exclude("com.google.code.gson", "gson")
exclude("org.slf4j", "slf4j-api")
}
compileOnly(libs.luckperms)
compileOnly(libs.guava)
compileOnly(libs.gson)
@@ -28,14 +28,15 @@ import eu.kennytv.maintenance.core.proxy.runnable.SingleMaintenanceRunnable;
import eu.kennytv.maintenance.core.proxy.runnable.SingleMaintenanceScheduleRunnable;
import eu.kennytv.maintenance.core.proxy.util.ProfileLookup;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.RateLimitedException;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.core.util.ServerType;
import eu.kennytv.maintenance.core.util.Task;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -46,6 +47,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Blocking;
import org.jetbrains.annotations.Nullable;
@@ -54,7 +57,8 @@ import org.jetbrains.annotations.Nullable;
* @since 3.0
*/
public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implements MaintenanceProxy {
private final Map<String, Task> serverTasks = new HashMap<>();
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[A-Za-z0-9_.]{1,16}$");
private final Map<String, MaintenanceRunnableBase> serverTasks = new HashMap<>();
protected SettingsProxy settingsProxy;
protected MaintenanceProxyPlugin(final String version, final ServerType serverType) {
@@ -64,49 +68,54 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
@Override
public void disable() {
super.disable();
if (settingsProxy.getMySQL() != null) {
settingsProxy.getMySQL().close();
if (settingsProxy.redisHandler() != null) {
settingsProxy.redisHandler().close();
}
}
@Override
public void setMaintenance(final boolean maintenance) {
if (settingsProxy.hasMySQL()) {
settingsProxy.setMaintenanceToSQL(maintenance);
}
super.setMaintenance(maintenance);
}
@Override
public boolean isMaintenance(final Server server) {
return settingsProxy.isMaintenance(server.getName());
}
@Override
public boolean setMaintenanceToServer(final Server server, final boolean maintenance) {
public boolean setMaintenanceToServer(final Server server, final boolean maintenance, @Nullable final String mode) {
if (maintenance) {
if (!settingsProxy.addMaintenanceServer(server.getName())) {
return false;
if (settingsProxy.addMaintenanceServer(server.getName(), mode)) {
serverActions(server, true);
return true;
}
} else if (!settingsProxy.removeMaintenanceServer(server.getName())) {
return mode != null && settingsProxy.setMaintenanceServerMode(server.getName(), mode);
}
if (!settingsProxy.removeMaintenanceServer(server.getName())) {
return false;
}
serverActions(server, maintenance);
for (final String command : (maintenance ? settingsProxy.getCommandsOnMaintenanceEnable(server) : settingsProxy.getCommandsOnMaintenanceDisable(server))) {
try {
executeConsoleCommand(command.replace("%SERVER%", server.getName()));
} catch (final Exception e) {
getLogger().severe("Error while executing extra maintenance " + (maintenance ? "enable" : "disable") + " command: " + command);
e.printStackTrace();
}
}
serverActions(server, false);
return true;
}
@Override
public boolean setMaintenanceToServer(final Server server, final boolean maintenance) {
return setMaintenanceToServer(server, maintenance, null);
}
public boolean setMaintenanceModeToServer(final Server server, @Nullable final String mode) {
if (!settingsProxy.isMaintenance(server.getName())) {
return false;
}
return settingsProxy.setMaintenanceServerMode(server.getName(), mode);
}
public @Nullable String activeMode(final Server server) {
return settingsProxy.activeMode(server.getName());
}
public void serverActions(final Server server, final boolean maintenance) {
if (server == null) return;
if (server == null) {
return;
}
// Skip to the even fire for dummy servers
if (server.isRegisteredServer()) {
@@ -126,6 +135,19 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
}
eventManager.callEvent(new ServerMaintenanceChangedEvent(server, maintenance));
if (maintenance) {
sendWebhookMessage("webhookSingleMaintenanceActivated", DiscordWebhook.EventType.MAINTENANCE_ENABLED, "%SERVER%", server.getName());
} else {
sendWebhookMessage("webhookSingleMaintenanceDeactivated", DiscordWebhook.EventType.MAINTENANCE_DISABLED, "%SERVER%", server.getName());
}
for (final String command : (maintenance ? settingsProxy.getCommandsOnMaintenanceEnable(server) : settingsProxy.getCommandsOnMaintenanceDisable(server))) {
try {
executeConsoleCommand(command.replace("%SERVER%", server.getName()));
} catch (final Exception e) {
getLogger().log(Level.SEVERE, "Error while executing extra maintenance " + (maintenance ? "enable" : "disable") + " command: " + command, e);
}
}
}
@Override
@@ -133,28 +155,45 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
return serverTasks.containsKey(server.getName());
}
public boolean hasServerTasks() {
return !serverTasks.isEmpty();
}
public Map<String, MaintenanceRunnableBase> getServerTasks() {
return serverTasks;
}
public MaintenanceRunnableBase getServerTask(final String server) {
return serverTasks.get(server);
}
@Override
public Set<String> getMaintenanceServers() {
return Collections.unmodifiableSet(settingsProxy.getMaintenanceServers());
}
public void cancelSingleTask(final Server server) {
final Task task = serverTasks.remove(server.getName());
final MaintenanceRunnableBase task = serverTasks.remove(server.getName());
if (task != null) {
task.cancel();
task.getTask().cancel();
}
}
public MaintenanceRunnableBase startSingleMaintenanceRunnable(final Server server, final Duration duration, final boolean enable) {
final MaintenanceRunnableBase runnable = new SingleMaintenanceRunnable(this, settingsProxy, (int) duration.getSeconds(), enable, server);
serverTasks.put(server.getName(), runnable.getTask());
public MaintenanceRunnableBase startSingleMaintenanceRunnable(final Server server, final Duration duration, final boolean enable, @Nullable final String mode) {
final MaintenanceRunnableBase runnable = new SingleMaintenanceRunnable(this, settingsProxy, (int) duration.getSeconds(), enable, server, mode);
serverTasks.put(server.getName(), runnable);
return runnable;
}
public MaintenanceRunnableBase scheduleSingleMaintenanceRunnable(final Server server, final Duration enableIn, final Duration maintenanceDuration) {
public MaintenanceRunnableBase startSingleMaintenanceRunnable(final Server server, final Duration duration, final boolean enable) {
return startSingleMaintenanceRunnable(server, duration, enable, null);
}
public MaintenanceRunnableBase scheduleSingleMaintenanceRunnable(final Server server, final Duration enableIn,
final Duration maintenanceDuration, @Nullable final String mode) {
final MaintenanceRunnableBase runnable = new SingleMaintenanceScheduleRunnable(this, settingsProxy,
(int) enableIn.getSeconds(), (int) maintenanceDuration.getSeconds(), server);
serverTasks.put(server.getName(), runnable.getTask());
(int) enableIn.getSeconds(), (int) maintenanceDuration.getSeconds(), server, mode);
serverTasks.put(server.getName(), runnable);
return runnable;
}
@@ -192,15 +231,17 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
@Blocking
@Nullable
protected ProfileLookup doUUIDLookup(final String name) throws IOException {
ProfileLookup profileLookup;
try {
profileLookup = doUUIDLookupMojangAPI(name);
} catch (RateLimitedException e) {
// Use fallback API if rate limit is reached
profileLookup = doUUIDLookupAshconAPI(name);
ProfileLookup profileLookup = null;
if (USERNAME_PATTERN.matcher(name).matches()) {
try {
profileLookup = doUUIDLookupMojangAPI(name);
} catch (RateLimitedException e) {
// Use fallback API if rate limit is reached
profileLookup = doUUIDLookupAshconAPI(name);
}
}
if (settingsProxy.isFallbackToOfflineUUID() && profileLookup == null) {
if (profileLookup == null && settingsProxy.isFallbackToOfflineUUID()) {
// Use offline uuid
return new ProfileLookup(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8)), name);
}
@@ -213,7 +254,7 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
*/
@Nullable
private ProfileLookup doUUIDLookupMojangAPI(final String name) throws IOException {
final URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name);
final URL url = URI.create("https://api.mojang.com/users/profiles/minecraft/" + name).toURL();
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
@@ -241,7 +282,7 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
*/
@Nullable
private ProfileLookup doUUIDLookupAshconAPI(final String name) throws IOException {
final URL url = new URL("https://api.ashcon.app/mojang/v2/user/" + name);
final URL url = URI.create("https://api.ashcon.app/mojang/v2/user/" + name).toURL();
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() == 403) {
@@ -279,4 +320,4 @@ public abstract class MaintenanceProxyPlugin extends MaintenancePlugin implement
protected abstract void kickPlayersTo(Server server);
protected abstract void kickPlayersFromProxy();
}
}
@@ -17,88 +17,55 @@
*/
package eu.kennytv.maintenance.core.proxy;
import eu.kennytv.maintenance.api.proxy.MaintenanceProxy;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.config.ConfigSection;
import eu.kennytv.maintenance.core.proxy.mysql.MySQL;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
import java.sql.SQLException;
import eu.kennytv.maintenance.core.proxy.redis.RedisHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public final class SettingsProxy extends Settings {
private final MaintenanceProxyPlugin proxyPlugin;
private Set<String> maintenanceServers;
private Map<String, String> maintenanceServers;
private List<String> fallbackServers;
private String waitingServer;
private boolean fallbackToOfflineUUID;
private Map<String, List<String>> commandsOnMaintenanceEnable;
private Map<String, List<String>> commandsOnMaintenanceDisable;
private String mySQLTable;
private String serverTable;
private String maintenanceQuery;
private String serverQuery;
private MySQL mySQL;
private long millisecondsToCheck;
private long lastMySQLCheck;
private long lastServerCheck;
private RedisHandler redisHandler;
public SettingsProxy(final MaintenanceProxyPlugin plugin) {
super(plugin);
this.proxyPlugin = plugin;
}
private void setupMySQL() {
plugin.getLogger().info("Trying to open database connection... (also, you can simply ignore the SLF4J soft-warning if it shows up)");
final ConfigSection section = config.getSection("mysql");
if (section == null) {
plugin.getLogger().warning("Section missing: mysql");
return;
}
mySQL = new MySQL(plugin.getLogger(),
section.getString("host"),
section.getInt("port"),
section.getString("username"),
section.getString("password"),
section.getString("database"),
section.getBoolean("use-ssl", true));
// Varchar as the value regarding the possibility of saving stuff like the motd as well in future updates
mySQLTable = section.getString("table", "maintenance_settings");
serverTable = section.getString("servertable", "maintenance_servers");
mySQL.executeUpdate("CREATE TABLE IF NOT EXISTS " + mySQLTable + " (setting VARCHAR(16) PRIMARY KEY, value VARCHAR(255))");
mySQL.executeUpdate("CREATE TABLE IF NOT EXISTS " + serverTable + " (server VARCHAR(64) PRIMARY KEY)");
maintenanceQuery = "SELECT * FROM " + mySQLTable + " WHERE setting = ?";
serverQuery = "SELECT * FROM " + serverTable;
plugin.getLogger().info("Done!");
}
@Override
protected void loadExtraSettings() {
// Open database connection if enabled and not already done
if (!hasMySQL() && config.getBoolean("mysql.use-mysql")) {
try {
setupMySQL();
} catch (final Exception e) {
mySQL = null;
plugin.getLogger().warning("Error while trying do open database connection!");
e.printStackTrace();
}
// Open Redis connection if enabled and not already done
final ConfigSection redisSection = config.getSection("redis");
if (!hasRedis() && redisSection.getBoolean("enabled")) {
connectToRedis(redisSection.getString("uri"));
}
if (hasRedis()) {
maintenance = redisHandler.loadMaintenanceStatus();
activeMode = normalizeMode(redisHandler.loadGlobalMode());
maintenanceServers = redisHandler.loadMaintenanceServers();
}
final Object fallback = config.getObject("fallback");
fallbackServers = fallback instanceof String ? Collections.singletonList((String) fallback) : config.getStringList("fallback", Collections.emptyList());
fallbackServers = fallback instanceof String s ? Collections.singletonList(s) : config.getStringList("fallback", Collections.emptyList());
waitingServer = config.getString("waiting-server", "");
if (waitingServer.isEmpty() || waitingServer.equalsIgnoreCase("none")) {
@@ -118,55 +85,36 @@ public final class SettingsProxy extends Settings {
commandsOnMaintenanceDisable.put(key.toLowerCase(Locale.ROOT), disableCommandsSection.getStringList(key));
}
if (hasMySQL()) {
maintenance = loadMaintenance();
maintenanceServers = loadMaintenanceServersFromSQL();
final long configValue = config.getInt("mysql.update-interval");
// Even if set to 0, only check every 500 millis
millisecondsToCheck = configValue > 0 ? configValue * 1000 : 500;
lastMySQLCheck = System.currentTimeMillis();
lastServerCheck = System.currentTimeMillis();
} else {
final List<String> list = config.getStringList("proxied-maintenance-servers");
maintenanceServers = list == null ? new HashSet<>() : new HashSet<>(list);
if (!hasRedis()) {
final ConfigSection maintenanceServersSection = config.getSection("proxied-maintenance-servers");
maintenanceServers = new LinkedHashMap<>();
for (final Map.Entry<String, ?> entry : maintenanceServersSection.getValues().entrySet()) {
if (entry.getKey() != null) {
maintenanceServers.put(entry.getKey(), normalizeServerMode(entry.getValue()));
}
}
}
}
@Override
public boolean isMaintenance() {
if (hasMySQL() && System.currentTimeMillis() - lastMySQLCheck > millisecondsToCheck) {
final boolean databaseValue = loadMaintenance();
if (databaseValue != maintenance) {
maintenance = databaseValue;
plugin.serverActions(maintenance);
}
lastMySQLCheck = System.currentTimeMillis();
private void connectToRedis(final String redisUri) {
redisHandler = new RedisHandler((MaintenanceProxyPlugin) plugin, this);
try {
redisHandler.setup(redisUri);
} catch (final Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error while trying to open Redis connection!", e);
e.printStackTrace();
redisHandler.close();
redisHandler = null;
}
return maintenance;
}
public boolean isMaintenance(final String serverName) {
if (hasMySQL() && System.currentTimeMillis() - lastServerCheck > millisecondsToCheck) {
final Set<String> databaseValue = loadMaintenanceServersFromSQL();
if (!maintenanceServers.equals(databaseValue)) {
// Enable maintenance on yet unlisted servers
for (final String s : databaseValue) {
if (!maintenanceServers.contains(s)) {
proxyPlugin.serverActions(proxyPlugin.getServer(s), true);
}
}
// Disable maintenance on now unlisted servers
for (final String s : maintenanceServers) {
if (!databaseValue.contains(s)) {
proxyPlugin.serverActions(proxyPlugin.getServer(s), false);
}
}
maintenanceServers = databaseValue;
}
lastServerCheck = System.currentTimeMillis();
}
return maintenanceServers.contains(serverName);
return maintenanceServers.containsKey(serverName);
}
public @Nullable String activeMode(final String serverName) {
final String mode = maintenanceServers.get(serverName);
return mode == null || mode.equals("default") ? null : mode;
}
public Component getServerKickMessage(final String server) {
@@ -174,101 +122,154 @@ public final class SettingsProxy extends Settings {
if (message == null) {
message = getLanguageString("singleMaintenanceKick", "%SERVER%", server);
}
return parse(plugin.replacePingVariables(message));
return parse(plugin.replacePingVariables(message, activeMode(server)));
}
// Full = being kicked from the proxy, not just a proxied server
public Component getFullServerKickMessage(final String server) {
String message = getLanguageStringOrNull("singleMaintenanceKicksComplete." + server, "%SERVER%", server);
if (message == null) {
message = getLanguageString("singleMaintenanceKickComplete", "%SERVER%", server);
}
return parse(plugin.replacePingVariables(message));
return parse(plugin.replacePingVariables(message, activeMode(server)));
}
public boolean hasMySQL() {
return mySQL != null;
public boolean hasRedis() {
return redisHandler != null;
}
void setMaintenanceToSQL(final boolean maintenance) {
plugin.async(() -> {
final String s = String.valueOf(maintenance);
mySQL.executeUpdate("INSERT INTO " + mySQLTable + " (setting, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = ?", "maintenance", s, s);
lastMySQLCheck = System.currentTimeMillis();
});
}
boolean addMaintenanceServer(final String server) {
if (hasMySQL()) {
maintenanceServers = loadMaintenanceServersFromSQL();
if (!maintenanceServers.add(server)) return false;
plugin.async(() -> mySQL.executeUpdate("INSERT INTO " + serverTable + " (server) VALUES (?)", server));
lastServerCheck = System.currentTimeMillis();
@Override
protected void loadWhitelistedPlayers() {
if (hasRedis()) {
redisHandler.loadPlayers(getWhitelistedPlayers());
} else {
super.loadWhitelistedPlayers();
}
}
@Override
public void setMaintenance(final boolean maintenance, @Nullable final String mode) {
super.setMaintenance(maintenance, mode);
if (hasRedis()) {
redisHandler.set(maintenance, mode != null ? mode : "default");
}
}
@Override
public void setActiveMode(@Nullable final String activeMode) {
super.setActiveMode(activeMode);
if (hasRedis() && isMaintenance()) {
redisHandler.set(true, activeMode != null ? activeMode : "default");
}
}
public void setActiveModeDirect(@Nullable final String activeMode) {
super.setActiveMode(activeMode);
}
public void setMaintenanceDirect(final boolean maintenance, @Nullable final String mode) {
super.setMaintenance(maintenance, mode);
}
@Override
public boolean addWhitelistedPlayer(final UUID uuid, final String name) {
if (super.addWhitelistedPlayer(uuid, name)) {
if (hasRedis()) {
redisHandler.addPlayer(uuid, name);
}
return true;
}
return false;
}
public boolean addWhitelistedPlayerDirect(final UUID uuid, final String name) {
return super.addWhitelistedPlayer(uuid, name);
}
@Override
public boolean removeWhitelistedPlayer(final UUID uuid) {
if (super.removeWhitelistedPlayer(uuid)) {
if (hasRedis()) {
redisHandler.removePlayer(uuid);
}
return true;
}
return false;
}
public boolean removeWhitelistedPlayerDirect(final UUID uuid) {
return super.removeWhitelistedPlayer(uuid);
}
boolean addMaintenanceServer(final String server, @Nullable final String mode) {
final String normalizedMode = normalizeServerMode(mode);
if (hasRedis()) {
if (redisHandler.addServer(server, normalizedMode)) {
maintenanceServers.put(server, normalizedMode);
return true;
}
} else if (maintenanceServers.putIfAbsent(server, normalizedMode) == null) {
saveServersToConfig();
return true;
}
return false;
}
boolean setMaintenanceServerMode(final String server, @Nullable final String mode) {
final String normalizedMode = normalizeServerMode(mode);
final String oldMode = maintenanceServers.get(server);
if (oldMode == null || oldMode.equals(normalizedMode)) {
return false;
}
maintenanceServers.put(server, normalizedMode);
if (hasRedis()) {
redisHandler.setServerMode(server, normalizedMode);
} else {
if (!maintenanceServers.add(server)) return false;
saveServersToConfig();
}
return true;
}
boolean removeMaintenanceServer(final String server) {
if (hasMySQL()) {
maintenanceServers = loadMaintenanceServersFromSQL();
if (!maintenanceServers.remove(server)) return false;
plugin.async(() -> mySQL.executeUpdate("DELETE FROM " + serverTable + " WHERE server = ?", server));
lastServerCheck = System.currentTimeMillis();
} else {
if (!maintenanceServers.remove(server)) return false;
if (hasRedis()) {
if (redisHandler.removeServer(server)) {
maintenanceServers.remove(server);
return true;
}
} else if (maintenanceServers.remove(server) != null) {
saveServersToConfig();
return true;
}
return true;
return false;
}
private Set<String> loadMaintenanceServersFromSQL() {
final Set<String> maintenanceServers = new HashSet<>();
mySQL.executeQuery(serverQuery, rs -> {
try {
while (rs.next()) {
maintenanceServers.add(rs.getString("server"));
}
} catch (final SQLException e) {
plugin.getLogger().warning("An error occured while trying to get the list of single servers with maintenance!");
e.printStackTrace();
}
});
return maintenanceServers;
public boolean addMaintenanceServerDirect(final String server, @Nullable final String mode) {
return maintenanceServers.put(server, normalizeServerMode(mode)) == null;
}
private boolean loadMaintenance() {
final boolean[] databaseValue = {false};
mySQL.executeQuery(maintenanceQuery, rs -> {
try {
if (rs.next()) {
databaseValue[0] = Boolean.parseBoolean(rs.getString("value"));
}
} catch (final SQLException e) {
plugin.getLogger().warning("An error occured while trying to get the maintenance value from the database!");
e.printStackTrace();
}
}, "maintenance");
return databaseValue[0];
public boolean removeMaintenanceServerDirect(final String server) {
return maintenanceServers.remove(server) != null;
}
public void setMaintenanceServerModeDirect(final String server, @Nullable final String mode) {
if (maintenanceServers.containsKey(server)) {
maintenanceServers.put(server, normalizeServerMode(mode));
}
}
private void saveServersToConfig() {
config.set("proxied-maintenance-servers", new ArrayList<>(maintenanceServers));
config.set("proxied-maintenance-servers", new LinkedHashMap<>(maintenanceServers));
saveConfig();
}
public Set<String> getMaintenanceServers() {
return maintenanceServers;
return maintenanceServers.keySet();
}
@Nullable
public Server getFallbackServer() {
for (final String fallbackServer : fallbackServers) {
final Server server = proxyPlugin.getServer(fallbackServer);
final Server server = ((MaintenanceProxy) plugin).getServer(fallbackServer);
if (server != null && !isMaintenance(server.getName())) {
return server;
}
@@ -286,7 +287,7 @@ public final class SettingsProxy extends Settings {
}
public List<String> getCommandsOnMaintenanceEnable(final Server server) {
final List<String> enableCommands = commandsOnMaintenanceEnable.getOrDefault("all", new ArrayList<>());
final List<String> enableCommands = new ArrayList<>(commandsOnMaintenanceEnable.getOrDefault("all", Collections.emptyList()));
final List<String> serverEnableCommands = commandsOnMaintenanceEnable.get(server.getName().toLowerCase(Locale.ROOT));
if (serverEnableCommands != null) {
enableCommands.addAll(serverEnableCommands);
@@ -295,7 +296,7 @@ public final class SettingsProxy extends Settings {
}
public List<String> getCommandsOnMaintenanceDisable(final Server server) {
final List<String> disableCommands = commandsOnMaintenanceDisable.getOrDefault("all", new ArrayList<>());
final List<String> disableCommands = new ArrayList<>(commandsOnMaintenanceDisable.getOrDefault("all", Collections.emptyList()));
final List<String> serverDisableCommands = commandsOnMaintenanceDisable.get(server.getName().toLowerCase(Locale.ROOT));
if (serverDisableCommands != null) {
disableCommands.addAll(serverDisableCommands);
@@ -303,8 +304,15 @@ public final class SettingsProxy extends Settings {
return disableCommands;
}
@Nullable
MySQL getMySQL() {
return mySQL;
public @Nullable RedisHandler redisHandler() {
return redisHandler;
}
private static String normalizeServerMode(@Nullable final Object mode) {
if (mode == null) {
return "default";
}
final String value = String.valueOf(mode).toLowerCase(Locale.ROOT);
return value.isBlank() ? "default" : value;
}
}
@@ -26,6 +26,7 @@ import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public abstract class MaintenanceProxyCommand extends MaintenanceCommand {
private final MaintenanceProxyPlugin plugin;
@@ -40,7 +41,7 @@ public abstract class MaintenanceProxyCommand extends MaintenanceCommand {
@Override
protected void addToggleAndTimerCommands() {
add(new SingleToggleCommand(plugin), "on", "off");
add(new StatusCommand(plugin), "status");
add(new ProxyStatusCommand(plugin), "status");
add(new SingleStarttimerCommand(plugin), "starttimer", "start");
add(new SingleEndtimerCommand(plugin), "endtimer", "end");
@@ -52,7 +53,7 @@ public abstract class MaintenanceProxyCommand extends MaintenanceCommand {
public List<String> getMaintenanceServersCompletion(final String s) {
final List<String> list = new ArrayList<>();
for (final String server : settingsBungee.getMaintenanceServers()) {
if (server.toLowerCase().startsWith(s)) {
if (server.toLowerCase(Locale.ROOT).startsWith(s)) {
list.add(server);
}
}
@@ -20,6 +20,8 @@ package eu.kennytv.maintenance.core.proxy.command;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.SettingsProxy;
import java.util.ArrayList;
import java.util.List;
public abstract class ProxyCommandInfo extends CommandInfo {
protected final MaintenanceProxyPlugin plugin;
@@ -33,4 +35,10 @@ public abstract class ProxyCommandInfo extends CommandInfo {
protected SettingsProxy getSettings() {
return plugin.getSettingsProxy();
}
protected List<String> getModesCompletion() {
final List<String> modes = new ArrayList<>(getSettings().getPingMessages().getKeys());
modes.remove("default");
return modes;
}
}
@@ -22,8 +22,10 @@ import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public final class AbortSingleTimerCommand extends ProxyCommandInfo {
@@ -67,6 +69,17 @@ public final class AbortSingleTimerCommand extends ProxyCommandInfo {
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
return args.length == 2 && sender.hasMaintenancePermission("singleserver.timer") ? plugin.getCommandManager().getServersCompletion(args[1]) : Collections.emptyList();
if (args.length != 2 || !sender.hasMaintenancePermission("singleserver.timer")) {
return Collections.emptyList();
}
final String prefix = args[1].toLowerCase(Locale.ROOT);
final List<String> list = new ArrayList<>();
for (final String server : plugin.getServerTasks().keySet()) {
if (server.toLowerCase(Locale.ROOT).startsWith(prefix)) {
list.add(server);
}
}
return list;
}
}
@@ -0,0 +1,74 @@
/*
* This file is part of Maintenance - https://github.com/kennytv/Maintenance
* Copyright (C) 2018-2024 kennytv (https://github.com/kennytv)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.proxy.command.subcommand;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnable;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.Map;
import net.kyori.adventure.text.Component;
public final class ProxyStatusCommand extends ProxyCommandInfo {
public ProxyStatusCommand(final MaintenanceProxyPlugin plugin) {
super(plugin, "singleserver.status");
}
@Override
public void execute(final SenderInfo sender, final String[] args) {
final MaintenanceRunnable task = plugin.getCurrentTask();
if (task != null) {
final String key = task.shouldEnable() ? "maintenanceStatusOffWithStartTimer" : "maintenanceStatusOnWithEndTimer";
sender.send(getMessage(key, "%TIME%", task.getTime()));
} else {
sender.send(getMessage(plugin.isMaintenance() ? "maintenanceStatusOn" : "maintenanceStatusOff"));
}
if (getSettings().getMaintenanceServers().isEmpty() && !plugin.hasServerTasks()) {
sender.send(getMessage("singleServerMaintenanceListEmpty"));
return;
}
sender.send(getMessage("singleServerMaintenanceList"));
// Enabled servers first
for (final String server : getSettings().getMaintenanceServers()) {
final MaintenanceRunnableBase serverTask = plugin.getServerTask(server);
if (serverTask != null) {
sender.send(getMessage("singleServerMaintenanceListEntryWithEndTimer", "%SERVER%", server, "%TIME%", serverTask.getTime()));
continue;
}
sender.send(getMessage("singleServerMaintenanceListEntry", "%SERVER%", server));
}
// Then enabling tasks
for (final Map.Entry<String, MaintenanceRunnableBase> entry : plugin.getServerTasks().entrySet()) {
final MaintenanceRunnableBase serverTask = entry.getValue();
if (serverTask.shouldEnable()) {
sender.send(getMessage("singleServerMaintenanceListEntryWithStartTimer", "%SERVER%", entry.getKey(), "%TIME%", serverTask.getTime()));
}
}
}
@Override
public Component getHelpMessage() {
return getMessage("helpStatus");
}
}
@@ -21,10 +21,14 @@ import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import net.kyori.adventure.text.Component;
public final class SingleEndtimerCommand extends ProxyCommandInfo {
@@ -40,49 +44,82 @@ public final class SingleEndtimerCommand extends ProxyCommandInfo {
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (args.length == 2) {
if (checkPermission(sender, "timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[1]);
if (duration == null) {
sender.send(getHelpMessage());
return;
}
if (!plugin.isMaintenance()) {
sender.send(getMessage("alreadyDisabled"));
return;
}
plugin.startMaintenanceRunnable(duration, false);
sender.send(getMessage("endtimerStarted", "%TIME%", plugin.getRunnable().getTime()));
endGlobal(sender, args[1]);
} else if (args.length == 3) {
if (checkPermission(sender, "singleserver.timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[2], false);
if (duration == null) {
sender.send(getHelpMessage());
return;
if (args[1].equalsIgnoreCase("global")) {
endGlobal(sender, args[2]);
} else {
endSingle(sender, args[1], args[2]);
}
final Server server = plugin.getCommandManager().checkSingleTimerServerArg(sender, args[1]);
if (server == null) return;
if (!plugin.isMaintenance(server)) {
sender.send(getMessage("singleServerAlreadyDisabled", "%SERVER%", server.getName()));
return;
}
final MaintenanceRunnableBase runnable = plugin.startSingleMaintenanceRunnable(server, duration, false);
sender.send(getMessage(
"singleEndtimerStarted",
"%TIME%", runnable.getTime(),
"%SERVER%", server.getName()
));
} else {
sender.send(getHelpMessage());
}
}
private void endGlobal(final SenderInfo sender, final String durationArg) {
if (checkPermission(sender, "timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, durationArg);
if (duration == null) {
sender.send(getHelpMessage());
return;
}
if (!plugin.isMaintenance()) {
sender.send(getMessage("alreadyDisabled"));
return;
}
plugin.startMaintenanceRunnable(duration, false);
final Component message = getMessage("endtimerStarted", "%TIME%", plugin.getRunnable().getTime());
sender.send(message);
plugin.sendWebhookMessage("webhookEndtimerStarted", DiscordWebhook.EventType.ENDTIMER_STARTED,
"%TIME%", plugin.getRunnable().getTime(),
"%TIMESTAMP%", plugin.getTargetTimestamp(duration));
}
private void endSingle(final SenderInfo sender, final String serverArg, final String durationArg) {
if (checkPermission(sender, "singleserver.timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, durationArg, false);
if (duration == null) {
sender.send(getHelpMessage());
return;
}
final Server server = plugin.getCommandManager().checkSingleTimerServerArg(sender, serverArg);
if (server == null) return;
if (!plugin.isMaintenance(server)) {
sender.send(getMessage("singleServerAlreadyDisabled", "%SERVER%", server.getName()));
return;
}
final MaintenanceRunnableBase runnable = plugin.startSingleMaintenanceRunnable(server, duration, false);
final Component message = getMessage(
"singleEndtimerStarted",
"%TIME%", runnable.getTime(),
"%SERVER%", server.getName()
);
sender.send(message);
plugin.sendWebhookMessage("webhookSingleEndtimerStarted", DiscordWebhook.EventType.ENDTIMER_STARTED,
"%TIME%", runnable.getTime(),
"%SERVER%", server.getName(),
"%TIMESTAMP%", plugin.getTargetTimestamp(duration));
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
return args.length == 2 && sender.hasMaintenancePermission("singleserver.timer") ? plugin.getCommandManager().getMaintenanceServersCompletion(args[1].toLowerCase()) : Collections.emptyList();
if (args.length != 2) {
return Collections.emptyList();
}
final List<String> suggestions = new ArrayList<>();
if (sender.hasMaintenancePermission("timer") && plugin.isMaintenance()) {
suggestions.add("global");
}
if (sender.hasMaintenancePermission("singleserver.timer")) {
suggestions.addAll(plugin.getCommandManager().getMaintenanceServersCompletion(args[1].toLowerCase(Locale.ROOT)));
}
return suggestions;
}
}
@@ -23,8 +23,11 @@ import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.jetbrains.annotations.Nullable;
public final class SingleScheduleTimerCommand extends ProxyCommandInfo {
@@ -40,56 +43,90 @@ public final class SingleScheduleTimerCommand extends ProxyCommandInfo {
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (args.length == 3) {
if (checkPermission(sender, "timer")) return;
final Duration enableIn = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[1]);
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[2], false);
if (enableIn == null || duration == null) {
sender.send(getHelpMessage());
return;
scheduleGlobal(sender, null, args[1], args[2]);
} else if (args.length == 4 || args.length == 5) {
final String mode = args.length == 5 ? args[2] : null;
final String enableInArg = args.length == 5 ? args[3] : args[2];
final String durationArg = args.length == 5 ? args[4] : args[3];
if (args[1].equalsIgnoreCase("global")) {
scheduleGlobal(sender, mode, enableInArg, durationArg);
} else {
scheduleSingle(sender, args[1], mode, enableInArg, durationArg);
}
if (plugin.isMaintenance()) {
sender.send(getMessage("alreadyEnabled"));
return;
}
plugin.scheduleMaintenanceRunnable(duration, enableIn);
sender.send(getMessage(
"scheduletimerStarted",
"%TIME%", plugin.getRunnable().getTime(),
"%DURATION%", plugin.getFormattedTime((int) duration.getSeconds())
));
} else if (args.length == 4) {
if (checkPermission(sender, "singleserver.timer")) return;
final Duration enableIn = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[2], false);
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[3], false);
if (enableIn == null || duration == null) {
sender.send(getHelpMessage());
return;
}
final Server server = plugin.getCommandManager().checkSingleTimerServerArg(sender, args[1]);
if (server == null) return;
if (plugin.isMaintenance(server)) {
sender.send(getMessage("singleServerAlreadyEnabled", "%SERVER%", server.getName()));
return;
}
final MaintenanceRunnableBase runnable = plugin.scheduleSingleMaintenanceRunnable(server, enableIn, duration);
sender.send(getMessage(
"singleScheduletimerStarted",
"%TIME%", runnable.getTime(),
"%DURATION%", plugin.getFormattedTime((int) duration.getSeconds()),
"%SERVER%", server.getName()
));
} else {
sender.send(getHelpMessage());
}
}
private void scheduleGlobal(final SenderInfo sender, @Nullable final String mode, final String enableInArg, final String durationArg) {
if (checkPermission(sender, "timer")) return;
final Duration enableIn = plugin.getCommandManager().parseDurationAndCheckTask(sender, enableInArg);
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, durationArg, false);
if (enableIn == null || duration == null) {
sender.send(getHelpMessage());
return;
}
if (plugin.isMaintenance()) {
sender.send(getMessage("alreadyEnabled"));
return;
}
plugin.scheduleMaintenanceRunnable(enableIn, duration, mode);
sender.send(getMessage(
"scheduletimerStarted",
"%TIME%", plugin.getRunnable().getTime(),
"%DURATION%", plugin.getFormattedTime((int) duration.getSeconds())
));
}
private void scheduleSingle(final SenderInfo sender, final String serverArg, @Nullable final String mode,
final String enableInArg, final String durationArg) {
if (checkPermission(sender, "singleserver.timer")) return;
final Duration enableIn = plugin.getCommandManager().parseDurationAndCheckTask(sender, enableInArg, false);
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, durationArg, false);
if (enableIn == null || duration == null) {
sender.send(getHelpMessage());
return;
}
final Server server = plugin.getCommandManager().checkSingleTimerServerArg(sender, serverArg);
if (server == null) return;
if (plugin.isMaintenance(server)) {
sender.send(getMessage("singleServerAlreadyEnabled", "%SERVER%", server.getName()));
return;
}
final MaintenanceRunnableBase runnable = plugin.scheduleSingleMaintenanceRunnable(server, enableIn, duration, mode);
sender.send(getMessage(
"singleScheduletimerStarted",
"%TIME%", runnable.getTime(),
"%DURATION%", plugin.getFormattedTime((int) duration.getSeconds()),
"%SERVER%", server.getName()
));
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
return args.length == 2 && sender.hasMaintenancePermission("singleserver.timer") ? plugin.getCommandManager().getServersCompletion(args[1].toLowerCase()) : Collections.emptyList();
if (args.length == 2) {
final List<String> suggestions = new ArrayList<>();
if (sender.hasMaintenancePermission("timer")) {
suggestions.add("global");
}
if (sender.hasMaintenancePermission("singleserver.timer")) {
suggestions.addAll(plugin.getCommandManager().getServersCompletion(args[1].toLowerCase(Locale.ROOT)));
}
return suggestions;
}
if (args.length == 3) {
if (args[1].equalsIgnoreCase("global")) {
return sender.hasMaintenancePermission("timer") ? getModesCompletion() : Collections.emptyList();
}
if (sender.hasMaintenancePermission("singleserver.timer") && plugin.getServer(args[1]) != null) {
return getModesCompletion();
}
}
return Collections.emptyList();
}
}
@@ -21,10 +21,15 @@ import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public final class SingleStarttimerCommand extends ProxyCommandInfo {
@@ -40,49 +45,90 @@ public final class SingleStarttimerCommand extends ProxyCommandInfo {
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (args.length == 2) {
if (checkPermission(sender, "timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[1]);
if (duration == null) {
sender.send(getHelpMessage());
return;
startGlobal(sender, null, args[1]);
} else if (args.length == 3 || args.length == 4) {
final String mode = args.length == 4 ? args[2] : null;
final String durationArg = args.length == 4 ? args[3] : args[2];
if (args[1].equalsIgnoreCase("global")) {
startGlobal(sender, mode, durationArg);
} else {
startSingle(sender, args[1], mode, durationArg);
}
if (plugin.isMaintenance()) {
sender.send(getMessage("alreadyEnabled"));
return;
}
plugin.startMaintenanceRunnable(duration, true);
sender.send(getMessage("starttimerStarted", "%TIME%", plugin.getRunnable().getTime()));
} else if (args.length == 3) {
if (checkPermission(sender, "singleserver.timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, args[2], false);
if (duration == null) {
sender.send(getHelpMessage());
return;
}
final Server server = plugin.getCommandManager().checkSingleTimerServerArg(sender, args[1]);
if (server == null) return;
if (plugin.isMaintenance(server)) {
sender.send(getMessage("singleServerAlreadyEnabled", "%SERVER%", server.getName()));
return;
}
final MaintenanceRunnableBase runnable = plugin.startSingleMaintenanceRunnable(server, duration, true);
sender.send(getMessage(
"singleStarttimerStarted",
"%TIME%", runnable.getTime(),
"%SERVER%", server.getName()
));
} else {
sender.send(getHelpMessage());
}
}
private void startGlobal(final SenderInfo sender, @Nullable final String mode, final String durationArg) {
if (checkPermission(sender, "timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, durationArg);
if (duration == null) {
sender.send(getHelpMessage());
return;
}
if (plugin.isMaintenance()) {
sender.send(getMessage("alreadyEnabled"));
return;
}
plugin.startMaintenanceRunnable(duration, true, mode);
final Component message = getMessage("starttimerStarted", "%TIME%", plugin.getRunnable().getTime());
sender.send(message);
plugin.sendWebhookMessage("webhookStarttimerStarted", DiscordWebhook.EventType.STARTTIMER_STARTED,
"%TIME%", plugin.getRunnable().getTime(),
"%TIMESTAMP%", plugin.getTargetTimestamp(duration));
}
private void startSingle(final SenderInfo sender, final String serverArg, @Nullable final String mode, final String durationArg) {
if (checkPermission(sender, "singleserver.timer")) return;
final Duration duration = plugin.getCommandManager().parseDurationAndCheckTask(sender, durationArg, false);
if (duration == null) {
sender.send(getHelpMessage());
return;
}
final Server server = plugin.getCommandManager().checkSingleTimerServerArg(sender, serverArg);
if (server == null) return;
if (plugin.isMaintenance(server)) {
sender.send(getMessage("singleServerAlreadyEnabled", "%SERVER%", server.getName()));
return;
}
final MaintenanceRunnableBase runnable = plugin.startSingleMaintenanceRunnable(server, duration, true, mode);
final Component message = getMessage(
"singleStarttimerStarted",
"%TIME%", runnable.getTime(),
"%SERVER%", server.getName()
);
sender.send(message);
plugin.sendWebhookMessage("webhookSingleStarttimerStarted", DiscordWebhook.EventType.STARTTIMER_STARTED,
"%TIME%", runnable.getTime(),
"%SERVER%", server.getName(),
"%TIMESTAMP%", plugin.getTargetTimestamp(duration));
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
return args.length == 2 && sender.hasMaintenancePermission("singleserver.timer") ? plugin.getCommandManager().getServersCompletion(args[1].toLowerCase()) : Collections.emptyList();
if (args.length == 2) {
final List<String> suggestions = new ArrayList<>();
if (sender.hasMaintenancePermission("timer")) {
suggestions.add("global");
}
if (sender.hasMaintenancePermission("singleserver.timer")) {
suggestions.addAll(plugin.getCommandManager().getServersCompletion(args[1].toLowerCase(Locale.ROOT)));
}
return suggestions;
}
if (args.length == 3) {
if (args[1].equalsIgnoreCase("global")) {
return sender.hasMaintenancePermission("timer") ? getModesCompletion() : Collections.emptyList();
}
if (sender.hasMaintenancePermission("singleserver.timer") && plugin.getServer(args[1]) != null) {
return getModesCompletion();
}
}
return Collections.emptyList();
}
}
@@ -17,14 +17,15 @@
*/
package eu.kennytv.maintenance.core.proxy.command.subcommand;
import eu.kennytv.maintenance.api.proxy.DummyServer;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.proxy.server.DummyServer;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public final class SingleToggleCommand extends ProxyCommandInfo {
@@ -39,48 +40,121 @@ public final class SingleToggleCommand extends ProxyCommandInfo {
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (args.length == 0 || args.length > 3) {
sender.send(getHelpMessage());
return;
}
final boolean maintenance = args[0].equalsIgnoreCase("on");
if (args.length == 1) {
if (checkPermission(sender, "toggle")) return;
final boolean maintenance = args[0].equalsIgnoreCase("on");
if (maintenance == plugin.isMaintenance()) {
sender.send(getMessage(maintenance ? "alreadyEnabled" : "alreadyDisabled"));
toggleGlobal(sender, maintenance, null);
return;
}
final String server = args[1];
final String mode = args.length == 3 ? args[2] : null;
if (server.equalsIgnoreCase("global")) {
if (!maintenance && args.length == 3) {
sender.send(getHelpMessage());
return;
}
plugin.setMaintenance(maintenance);
} else if (args.length == 2) {
if (checkPermission(sender, "singleserver.toggle")) return;
final boolean maintenance = args[0].equalsIgnoreCase("on");
Server server = plugin.getServer(args[1]);
if (server == null) {
if (maintenance) {
sender.send(getMessage("serverNotFound"));
return;
}
// Let servers be removed from the maintenance list, even if they don't exist on the proxy
server = new DummyServer(args[1]);
if (!plugin.isMaintenance(server)) {
sender.send(getMessage("serverNotFound"));
return;
}
toggleGlobal(sender, maintenance, mode);
return;
}
if (!maintenance && args.length != 2) {
sender.send(getHelpMessage());
return;
}
toggleServer(sender, maintenance, server, mode);
}
private void toggleGlobal(final SenderInfo sender, final boolean maintenance, final String mode) {
if (checkPermission(sender, "toggle")) {
return;
}
if (maintenance && plugin.isMaintenance()) {
if (mode != null) {
plugin.getSettings().setActiveMode(mode);
return;
}
if (plugin.setMaintenanceToServer(server, maintenance)) {
if (!sender.isPlayer() || !server.getName().equals(plugin.getServerNameOf(sender))) {
sender.send(getMessage(maintenance ? "singleMaintenanceActivated" : "singleMaintenanceDeactivated", "%SERVER%", server.getName()));
}
} else {
sender.send(getMessage(maintenance ? "singleServerAlreadyEnabled" : "singleServerAlreadyDisabled", "%SERVER%", server.getName()));
sender.send(getMessage("alreadyEnabled"));
return;
}
if (maintenance == plugin.isMaintenance()) {
sender.send(getMessage(maintenance ? "alreadyEnabled" : "alreadyDisabled"));
return;
}
plugin.setMaintenance(maintenance, mode);
}
private void toggleServer(final SenderInfo sender, final boolean maintenance, final String serverArg, final String mode) {
if (checkPermission(sender, "singleserver.toggle")) {
return;
}
Server server = plugin.getServer(serverArg);
if (server == null) {
if (maintenance) {
sender.send(getMessage("serverNotFound"));
return;
}
// Let servers be removed from the maintenance list, even if they don't exist on the proxy
server = new DummyServer(serverArg);
if (!plugin.isMaintenance(server)) {
sender.send(getMessage("serverNotFound"));
return;
}
}
if (maintenance && plugin.isMaintenance(server) && mode != null) {
if (plugin.setMaintenanceModeToServer(server, mode)) {
return;
}
sender.send(getMessage("singleServerAlreadyEnabled", "%SERVER%", server.getName()));
return;
}
if (plugin.setMaintenanceToServer(server, maintenance, mode)) {
if (!sender.isPlayer() || !server.getName().equals(plugin.getServerNameOf(sender))) {
sender.send(getMessage(maintenance ? "singleMaintenanceActivated" : "singleMaintenanceDeactivated", "%SERVER%", server.getName()));
}
} else {
sender.send(getHelpMessage());
sender.send(getMessage(maintenance ? "singleServerAlreadyEnabled" : "singleServerAlreadyDisabled", "%SERVER%", server.getName()));
}
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
if (args.length != 2 || !sender.hasMaintenancePermission("singleserver.toggle")) return Collections.emptyList();
return args[0].equalsIgnoreCase("off") ? plugin.getCommandManager().getMaintenanceServersCompletion(args[1].toLowerCase())
: plugin.getCommandManager().getServersCompletion(args[1].toLowerCase());
if (args.length == 2) {
final List<String> suggestions = new ArrayList<>();
if (sender.hasMaintenancePermission("toggle") && args[0].equalsIgnoreCase("on")) {
suggestions.add("global");
}
if (!sender.hasMaintenancePermission("singleserver.toggle")) {
return suggestions;
}
final List<String> serverSuggestions = args[0].equalsIgnoreCase("off")
? plugin.getCommandManager().getMaintenanceServersCompletion(args[1].toLowerCase(Locale.ROOT))
: plugin.getCommandManager().getServersCompletion(args[1].toLowerCase(Locale.ROOT));
suggestions.addAll(serverSuggestions);
return suggestions;
}
if (args.length == 3 && args[0].equalsIgnoreCase("on") && (sender.hasMaintenancePermission("toggle") || sender.hasMaintenancePermission("singleserver.toggle"))) {
final List<String> modes = new ArrayList<>(getSettings().getPingMessages().getKeys());
modes.remove("default");
return modes;
}
return Collections.emptyList();
}
}
@@ -24,6 +24,7 @@ import eu.kennytv.maintenance.core.proxy.SettingsProxy;
import eu.kennytv.maintenance.core.proxy.util.ProxySenderInfo;
import eu.kennytv.maintenance.core.proxy.util.ServerConnectResult;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.Locale;
import org.jetbrains.annotations.Nullable;
public abstract class ProxyJoinListenerBase extends JoinListenerBase {
@@ -48,7 +49,7 @@ public abstract class ProxyJoinListenerBase extends JoinListenerBase {
protected ServerConnectResult serverConnect(final ProxySenderInfo sender, final Server target, final boolean normalServerConnect) {
// Check waiting server for global maintenance
if (settings.isMaintenance()) {
if (sender.hasMaintenancePermission("bypass") || settings.isWhitelisted(sender.getUuid())) return ALLOWED;
if (sender.hasMaintenancePermission("bypass") || settings.isWhitelisted(sender.uuid())) return ALLOWED;
final Server waitingServer = shouldConnectToWaitingServer(sender);
// Should never be null, but just in case
@@ -70,13 +71,13 @@ public abstract class ProxyJoinListenerBase extends JoinListenerBase {
// Single server maintenance
if (!settings.isMaintenance(target.getName())) return ALLOWED;
if (sender.hasMaintenancePermission("bypass") || settings.isWhitelisted(sender.getUuid())
|| sender.hasMaintenancePermission("singleserver.bypass." + target.getName().toLowerCase())) {
if (sender.hasMaintenancePermission("bypass") || settings.isWhitelisted(sender.uuid())
|| sender.hasMaintenancePermission("singleserver.bypass." + target.getName().toLowerCase(Locale.ROOT))) {
return ALLOWED;
}
if (settings.isJoinNotifications()) {
broadcastJoinNotification(sender.getName(), target);
broadcastJoinNotification(sender.name(), target);
}
if (normalServerConnect) {
@@ -1,112 +0,0 @@
/*
* This file is part of Maintenance - https://github.com/kennytv/Maintenance
* Copyright (C) 2018-2024 kennytv (https://github.com/kennytv)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.proxy.mysql;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.jetbrains.annotations.Nullable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class MySQL {
private final Logger logger;
private final HikariDataSource hikariDataSource;
public MySQL(final Logger logger, final String hostname, final int port, final String username, final String password, final String database, final boolean useSSL) {
this.logger = logger;
final HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.addDataSourceProperty("serverName", hostname);
hikariConfig.addDataSourceProperty("user", username);
hikariConfig.addDataSourceProperty("password", password);
String urlProperty = "jdbc:mysql://" + hostname + ":" + port + "/" + database;
if (!useSSL) {
urlProperty += "?useSSL=false";
}
hikariConfig.addDataSourceProperty("url", urlProperty);
hikariConfig.setJdbcUrl(urlProperty);
hikariConfig.setDataSourceClassName(findDriver("com.mysql.jdbc.jdbc2.optional.MysqlDataSource", "com.mysql.cj.jdbc.MysqlDataSource", "org.mariadb.jdbc.MariaDbDataSource"));
hikariDataSource = new HikariDataSource(hikariConfig);
}
@Nullable
private String findDriver(final String... classNames) {
for (final String name : classNames) {
try {
Class.forName(name);
return name;
} catch (final ClassNotFoundException ignored) {
}
}
throw new IllegalArgumentException("No sql driver class found");
}
public void executeUpdate(final String query, final Consumer<Integer> callback, final Object... objects) {
try (final Connection connection = hikariDataSource.getConnection()) {
try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) {
int current = 1;
for (final Object object : objects) {
preparedStatement.setObject(current, object);
current++;
}
callback.accept(preparedStatement.executeUpdate());
}
} catch (final SQLException e) {
logger.log(Level.SEVERE, "Error while executing update method: " + query);
e.printStackTrace();
}
}
public void executeQuery(final String query, final Consumer<ResultSet> callback, final Object... objects) {
try (final Connection connection = hikariDataSource.getConnection()) {
try (final PreparedStatement preparedStatement = connection.prepareStatement(query)) {
int current = 1;
for (final Object object : objects) {
preparedStatement.setObject(current, object);
current++;
}
try (final ResultSet resultSet = preparedStatement.executeQuery()) {
callback.accept(resultSet);
}
}
} catch (final SQLException e) {
logger.log(Level.SEVERE, "Error while executing query method: " + query);
e.printStackTrace();
}
}
public void executeUpdate(final String query, final Object... objects) {
executeUpdate(query, res -> {
}, objects);
}
public void close() {
hikariDataSource.close();
}
}
@@ -0,0 +1,258 @@
package eu.kennytv.maintenance.core.proxy.redis;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.SettingsProxy;
import java.net.URI;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.Locale;
import org.jetbrains.annotations.Nullable;
import redis.clients.jedis.ConnectionPoolConfig;
import redis.clients.jedis.RedisClient;
import redis.clients.jedis.StreamEntryID;
import redis.clients.jedis.params.XReadParams;
import redis.clients.jedis.resps.StreamEntry;
public final class RedisHandler {
private static final String REDIS_STREAM_KEY = "maintenance:stream";
private static final String REDIS_MAINTENANCE_KEY = "maintenance:status";
private static final String REDIS_MODE_KEY = "maintenance:mode";
private static final String REDIS_SERVERS_KEY = "maintenance:servers";
private static final String REDIS_SERVER_MODES_KEY = "maintenance:server_modes";
private static final String REDIS_WHITELIST_KEY = "maintenance:whitelist";
private static final String MSG_TYPE_MAINTENANCE_UPDATE = "status";
private static final String MSG_TYPE_SERVER_ADD = "server_add";
private static final String MSG_TYPE_SERVER_REMOVE = "server_remove";
private static final String MSG_TYPE_PLAYER_ADD = "player_add";
private static final String MSG_TYPE_PLAYER_REMOVE = "player_remove";
private static final String MSG_TYPE = "type";
private static final String MSG_VALUE = "val";
private final AtomicBoolean running = new AtomicBoolean(false);
private final MaintenanceProxyPlugin plugin;
private final SettingsProxy settings;
private RedisClient client;
private StreamEntryID lastStreamId = new StreamEntryID();
public RedisHandler(final MaintenanceProxyPlugin plugin, final SettingsProxy settings) {
this.plugin = plugin;
this.settings = settings;
}
public void setup(final String redisUri) {
plugin.getLogger().info("Trying to open Redis connection...");
final ConnectionPoolConfig poolConfig = new ConnectionPoolConfig();
poolConfig.setMaxWait(Duration.ofSeconds(5));
poolConfig.setMaxTotal(2);
client = RedisClient.builder().poolConfig(poolConfig).fromURI(URI.create(redisUri)).build();
// Get the latest stream ID to only receive new messages
final List<StreamEntry> messages = client.xrevrange(REDIS_STREAM_KEY, (StreamEntryID) null, null, 1);
if (!messages.isEmpty()) {
lastStreamId = messages.getFirst().getID();
}
running.set(true);
plugin.async(this::listenToStream);
plugin.getLogger().info("Connected to Redis!");
}
public boolean loadMaintenanceStatus() {
return Boolean.parseBoolean(client.get(REDIS_MAINTENANCE_KEY));
}
public @Nullable String loadGlobalMode() {
return client.get(REDIS_MODE_KEY);
}
public Map<String, String> loadMaintenanceServers() {
final Set<String> servers = client.smembers(REDIS_SERVERS_KEY);
final Map<String, String> serverModes = new HashMap<>();
if (servers == null) {
return serverModes;
}
final Map<String, String> persistedModes = client.hgetAll(REDIS_SERVER_MODES_KEY);
for (final String server : servers) {
serverModes.put(server, normalizeMode(persistedModes.get(server)));
}
return serverModes;
}
public void loadPlayers(final Map<UUID, String> players) {
for (final Map.Entry<String, String> entry : client.hgetAll(REDIS_WHITELIST_KEY).entrySet()) {
players.put(UUID.fromString(entry.getKey()), entry.getValue());
}
}
public void set(final boolean maintenance, final String mode) {
client.set(REDIS_MAINTENANCE_KEY, Boolean.toString(maintenance));
client.set(REDIS_MODE_KEY, mode);
client.xadd(REDIS_STREAM_KEY, StreamEntryID.NEW_ENTRY, Map.of(
MSG_TYPE, MSG_TYPE_MAINTENANCE_UPDATE,
MSG_VALUE, Boolean.toString(maintenance),
"mode", mode
));
}
public boolean addServer(final String server, final String mode) {
if (client.sadd(REDIS_SERVERS_KEY, server) < 1) {
return false;
}
client.hset(REDIS_SERVER_MODES_KEY, server, mode);
client.xadd(REDIS_STREAM_KEY, StreamEntryID.NEW_ENTRY, Map.of(
MSG_TYPE, MSG_TYPE_SERVER_ADD,
MSG_VALUE, server,
"mode", mode
));
return true;
}
public void setServerMode(final String server, final String mode) {
client.hset(REDIS_SERVER_MODES_KEY, server, mode);
client.xadd(REDIS_STREAM_KEY, StreamEntryID.NEW_ENTRY, Map.of(
MSG_TYPE, MSG_TYPE_SERVER_ADD,
MSG_VALUE, server,
"mode", mode
));
}
public boolean removeServer(final String server) {
if (client.srem(REDIS_SERVERS_KEY, server) < 1) {
return false;
}
client.hdel(REDIS_SERVER_MODES_KEY, server);
send(MSG_TYPE_SERVER_REMOVE, server);
return true;
}
public void addPlayer(final UUID uuid, final String name) {
final String value = uuid.toString();
client.hset(REDIS_WHITELIST_KEY, value, name);
client.xadd(REDIS_STREAM_KEY, StreamEntryID.NEW_ENTRY, Map.of(
MSG_TYPE, MSG_TYPE_PLAYER_ADD,
MSG_VALUE, value,
"name", name
));
}
public void removePlayer(final UUID uuid) {
final String value = uuid.toString();
client.hdel(REDIS_WHITELIST_KEY, value);
send(MSG_TYPE_PLAYER_REMOVE, value);
}
private void send(final String type, final String value) {
client.xadd(REDIS_STREAM_KEY, StreamEntryID.NEW_ENTRY, Map.of(MSG_TYPE, type, MSG_VALUE, value));
}
private void listenToStream() {
while (running.get()) {
try {
final List<Map.Entry<String, List<StreamEntry>>> result = client.xread(
XReadParams.xReadParams().block(5000),
Map.of(REDIS_STREAM_KEY, lastStreamId)
);
if (result != null) {
for (final Map.Entry<String, List<StreamEntry>> streamEntries : result) {
for (final StreamEntry entry : streamEntries.getValue()) {
plugin.sync(() -> processStreamMessage(entry));
lastStreamId = entry.getID();
}
}
}
} catch (final Exception e) {
if (running.get()) {
plugin.getLogger().log(Level.WARNING, "Error reading from Redis stream", e);
try {
Thread.sleep(1000); // retry
} catch (final InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
}
}
}
private void processStreamMessage(final StreamEntry message) {
final Map<String, String> body = message.getFields();
final String type = body.get(MSG_TYPE);
if (type == null) {
return;
}
final String value = body.get(MSG_VALUE);
switch (type) {
case MSG_TYPE_MAINTENANCE_UPDATE -> handleStatus(value, body.get("mode"));
case MSG_TYPE_SERVER_ADD -> handleServerStatus(value, body.get("mode"), true, "Server added to maintenance via Redis: ");
case MSG_TYPE_SERVER_REMOVE -> handleServerStatus(value, null, false, "Server removed from maintenance via Redis: ");
case MSG_TYPE_PLAYER_ADD -> handlePlayerAdd(value, body.get("name"));
case MSG_TYPE_PLAYER_REMOVE -> handlePlayerRemove(value);
}
}
private void handleStatus(final String value, @Nullable final String mode) {
final boolean maintenance = Boolean.parseBoolean(value);
if (settings.isMaintenance() != maintenance) {
plugin.getLogger().info("Maintenance status updated via Redis: " + maintenance);
settings.setMaintenanceDirect(maintenance, mode);
plugin.serverActions(maintenance);
return;
}
if (maintenance && !normalizeMode(mode).equals(normalizeMode(settings.activeMode()))) {
settings.setActiveModeDirect(mode);
}
}
private void handleServerStatus(final String value, @Nullable final String mode, final boolean maintenance, final String log) {
if (value == null) {
return;
}
final boolean success;
if (maintenance) {
success = settings.addMaintenanceServerDirect(value, mode);
settings.setMaintenanceServerModeDirect(value, mode);
} else {
success = settings.removeMaintenanceServerDirect(value);
}
if (success) {
plugin.getLogger().info(log + value);
final Server server = plugin.getServerOrDummy(value);
plugin.serverActions(server, maintenance);
}
}
private void handlePlayerAdd(final String uuid, final String name) {
settings.addWhitelistedPlayerDirect(UUID.fromString(uuid), name);
}
private void handlePlayerRemove(final String value) {
settings.removeWhitelistedPlayerDirect(UUID.fromString(value));
}
public void close() {
running.set(false);
if (client != null) {
client.close();
client = null;
}
}
private String normalizeMode(@Nullable final String mode) {
return mode == null || mode.isBlank() ? "default" : mode.toLowerCase(Locale.ROOT);
}
}
@@ -17,19 +17,23 @@
*/
package eu.kennytv.maintenance.core.proxy.runnable;
import eu.kennytv.maintenance.api.proxy.MaintenanceProxy;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnableBase;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public class SingleMaintenanceRunnable extends MaintenanceRunnableBase {
protected final Server server;
protected final String mode;
public SingleMaintenanceRunnable(final MaintenancePlugin plugin, final Settings settings, final int seconds, final boolean enable, final Server server) {
public SingleMaintenanceRunnable(final MaintenancePlugin plugin, final Settings settings, final int seconds,
final boolean enable, final Server server, @Nullable final String mode) {
super(plugin, settings, seconds, enable);
this.server = server;
this.mode = mode;
}
@Override
@@ -39,7 +43,12 @@ public class SingleMaintenanceRunnable extends MaintenanceRunnableBase {
@Override
protected void finish() {
((MaintenanceProxy) plugin).setMaintenanceToServer(server, enable);
final MaintenanceProxyPlugin proxyPlugin = (MaintenanceProxyPlugin) plugin;
proxyPlugin.setMaintenanceToServer(server, enable, mode);
// Make sure no-ops have the platform task cancelled as well
if (proxyPlugin.getServerTasks().remove(server.getName(), this)) {
getTask().cancel();
}
}
@Override
@@ -21,14 +21,16 @@ import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.time.Duration;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public final class SingleMaintenanceScheduleRunnable extends SingleMaintenanceRunnable {
private final int maintenanceDurationSeconds;
public SingleMaintenanceScheduleRunnable(final MaintenancePlugin plugin, final Settings settings, final int seconds, final int maintenanceDuration, final Server server) {
super(plugin, settings, seconds, true, server);
public SingleMaintenanceScheduleRunnable(final MaintenancePlugin plugin, final Settings settings, final int seconds,
final int maintenanceDuration, final Server server, @Nullable final String mode) {
super(plugin, settings, seconds, true, server, mode);
this.maintenanceDurationSeconds = maintenanceDuration;
}
@@ -19,20 +19,5 @@ package eu.kennytv.maintenance.core.proxy.util;
import java.util.UUID;
public final class ProfileLookup {
private final UUID uuid;
private final String name;
public ProfileLookup(final UUID uuid, final String name) {
this.uuid = uuid;
this.name = name;
}
public UUID getUuid() {
return uuid;
}
public String getName() {
return name;
}
public record ProfileLookup(UUID uuid, String name) {
}
@@ -18,18 +18,11 @@
package eu.kennytv.maintenance.core.proxy.util;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.util.UUID;
import net.kyori.adventure.text.Component;
public final class ProxyOfflineSenderInfo implements ProxySenderInfo {
private final UUID uuid;
private final String name;
public ProxyOfflineSenderInfo(final UUID uuid, final String name) {
this.uuid = uuid;
this.name = name;
}
public record ProxyOfflineSenderInfo(UUID uuid, String name) implements ProxySenderInfo {
@Override
public boolean canAccess(final Server server) {
@@ -40,16 +33,6 @@ public final class ProxyOfflineSenderInfo implements ProxySenderInfo {
public void disconnect(final Component component) {
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPermission(final String permission) {
return false;
@@ -19,7 +19,7 @@ package eu.kennytv.maintenance.core.proxy.util;
import eu.kennytv.maintenance.api.proxy.Server;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
public interface ProxySenderInfo extends SenderInfo {
+4 -3
View File
@@ -16,9 +16,10 @@ sourceSets {
dependencies {
api(projects.maintenanceApi)
api(projects.adventure.adventureApi) {
targetConfiguration = "shadow"
}
compileOnlyApi(libs.adventureApi)
compileOnlyApi(libs.adventureTextMinimessage)
compileOnlyApi(libs.adventureTextSerializerPlain)
compileOnlyApi(libs.adventureTextSerializerLegacy)
compileOnly(libs.luckperms)
compileOnly(libs.serverlistplus)
compileOnly(libs.guava)
@@ -32,12 +32,11 @@ import eu.kennytv.maintenance.core.dump.PluginDump;
import eu.kennytv.maintenance.core.hook.ServerListPlusHook;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnable;
import eu.kennytv.maintenance.core.runnable.MaintenanceScheduleRunnable;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.core.util.ServerType;
import eu.kennytv.maintenance.core.util.Task;
import eu.kennytv.maintenance.core.util.Version;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.format.NamedTextColor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
@@ -47,7 +46,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URI;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -62,6 +61,8 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.Nullable;
public abstract class MaintenancePlugin implements Maintenance {
@@ -96,19 +97,9 @@ public abstract class MaintenancePlugin implements Maintenance {
}
@Override
public void setMaintenance(final boolean maintenance) {
settings.setMaintenance(maintenance);
settings.getConfig().set("maintenance-enabled", maintenance);
settings.saveConfig();
public void setMaintenance(final boolean maintenance, final String mode) {
settings.setMaintenance(maintenance, mode);
serverActions(maintenance);
for (final String command : (maintenance ? settings.getCommandsOnMaintenanceEnable() : settings.getCommandsOnMaintenanceDisable())) {
try {
executeConsoleCommand(command);
} catch (final Exception e) {
getLogger().log(Level.SEVERE, "Error while executing extra maintenance " + (maintenance ? "enable" : "disable") + " command: " + command, e);
}
}
}
public void serverActions(final boolean maintenance) {
@@ -119,22 +110,56 @@ public abstract class MaintenancePlugin implements Maintenance {
serverListPlusHook.setEnabled(!maintenance);
}
if (maintenance) {
broadcast(settings.getMessage("maintenanceActivated"));
if (settings.isKickOnlinePlayers()) {
kickPlayers();
}
} else {
broadcast(settings.getMessage("maintenanceDeactivated"));
broadcast(maintenance ? settings.getMessage("maintenanceActivated") : settings.getMessage("maintenanceDeactivated"));
if (maintenance && settings.isKickOnlinePlayers()) {
kickPlayers();
}
eventManager.callEvent(new MaintenanceChangedEvent(maintenance));
if (maintenance) {
sendWebhookMessage("webhookMaintenanceActivated", DiscordWebhook.EventType.MAINTENANCE_ENABLED);
} else {
sendWebhookMessage("webhookMaintenanceDeactivated", DiscordWebhook.EventType.MAINTENANCE_DISABLED);
}
for (final String command : (maintenance ? settings.getCommandsOnMaintenanceEnable() : settings.getCommandsOnMaintenanceDisable())) {
try {
executeConsoleCommand(command);
} catch (final Exception e) {
getLogger().log(Level.SEVERE, "Error while executing extra maintenance " + (maintenance ? "enable" : "disable") + " command: " + command, e);
}
}
}
public String replacePingVariables(String component) {
public void sendWebhookMessage(final String messageKey, final DiscordWebhook.EventType eventType, final String... replacements) {
if (!settings.isWebhookEnabled() || !settings.isWebhookEventEnabled(eventType.configKey())) {
return;
}
async(() -> {
try {
DiscordWebhook.sendMessage(settings.getWebhookMessage(messageKey, replacements), eventType, settings);
} catch (final Exception e) {
getLogger().log(Level.WARNING, "Could not send Discord webhook message", e);
}
});
}
public String getTargetTimestamp(final Duration duration) {
return Long.toString((System.currentTimeMillis() + duration.toMillis()) / TimeUnit.SECONDS.toMillis(1));
}
public String replacePingVariables(final String component) {
return replacePingVariables(component, settings.activeMode());
}
public String replacePingVariables(String component, @Nullable final String mode) {
if (component.contains("%TIMER%")) {
component = component.replace("%TIMER%", getTimerMessage());
}
final String reason = settings.activeReason() == null ? "" : settings.activeReason();
component = component.replace("%REASON%", reason);
component = component.replace("%MODE%", mode == null ? "default" : mode);
component = component.replace("%ONLINE%", String.valueOf(getOnlinePlayers()));
component = component.replace("%MAX%", String.valueOf(getMaxPlayers()));
return component;
@@ -168,14 +193,18 @@ public abstract class MaintenancePlugin implements Maintenance {
private void append(final StringBuilder builder, final String timeUnit, final int time) {
if (time == 0) return;
if (builder.length() != 0) {
if (!builder.isEmpty()) {
builder.append(' ');
}
builder.append(time).append(' ').append(settings.language.getString(time == 1 ? timeUnit : timeUnit + "s"));
}
public void startMaintenanceRunnable(final Duration duration, final boolean enable) {
runnable = new MaintenanceRunnable(this, settings, (int) duration.getSeconds(), enable);
startMaintenanceRunnable(duration, enable, null);
}
public void startMaintenanceRunnable(final Duration duration, final boolean enable, @Nullable final String mode) {
runnable = new MaintenanceRunnable(this, settings, (int) duration.getSeconds(), enable, mode);
// Save the endtimer to be able to continue it after a server stop
if (settings.isSaveEndtimerOnStop() && !runnable.shouldEnable()) {
settings.setSavedEndtimer(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(runnable.getSecondsLeft()));
@@ -183,7 +212,11 @@ public abstract class MaintenancePlugin implements Maintenance {
}
public void scheduleMaintenanceRunnable(final Duration enableIn, final Duration maintenanceDuration) {
runnable = new MaintenanceScheduleRunnable(this, settings, (int) enableIn.getSeconds(), (int) maintenanceDuration.getSeconds());
scheduleMaintenanceRunnable(enableIn, maintenanceDuration, null);
}
public void scheduleMaintenanceRunnable(final Duration enableIn, final Duration maintenanceDuration, @Nullable final String mode) {
runnable = new MaintenanceScheduleRunnable(this, settings, (int) enableIn.getSeconds(), (int) maintenanceDuration.getSeconds(), mode);
}
public boolean updateAvailable() {
@@ -227,7 +260,7 @@ public abstract class MaintenancePlugin implements Maintenance {
if (compare < 0) {
getLogger().warning("Newest version available: Version " + newestVersion + ", you're on " + version);
} else if (compare > 0) {
if (version.getTag().equalsIgnoreCase("snapshot")) {
if (version.getTag() != null && !version.getTag().isBlank()) {
getLogger().info("You're running a development version, please report bugs on the Discord server (https://discord.gg/vGCUzHq) or the GitHub issue tracker (https://github.com/kennytv/Maintenance/issues)");
} else {
getLogger().info("You're running a version, that doesn't exist!");
@@ -237,11 +270,9 @@ public abstract class MaintenancePlugin implements Maintenance {
}
public boolean installUpdate() throws Exception {
// Sponge and Velocity need their own jar
final String platformInfix = serverType == ServerType.VELOCITY ? "Velocity-" : serverType == ServerType.SPONGE ? "Sponge-" : "";
final String fileName = "Maintenance-" + platformInfix + newestVersion + ".jar";
final String fileName = "Maintenance-" + serverType.name() + "-" + newestVersion + ".jar";
final Path tempFilePath = Paths.get(getPluginFolder() + "Maintenance.tmp");
final URLConnection connection = new URL("https://github.com/kennytv/Maintenance/releases/download/" + newestVersion + "/" + fileName).openConnection();
final URLConnection connection = URI.create("https://github.com/kennytv/Maintenance/releases/download/" + newestVersion + "/" + fileName).toURL().openConnection();
try (final BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
final BufferedOutputStream os = new BufferedOutputStream(Files.newOutputStream(tempFilePath))) {
writeFile(is, os);
@@ -273,8 +304,10 @@ public abstract class MaintenancePlugin implements Maintenance {
}
private void checkNewestVersion() throws Exception {
final URLConnection connection = new URL("https://hangar.papermc.io/api/v1/projects/Maintenance/latestrelease").openConnection();
final URLConnection connection = URI.create("https://hangar.papermc.io/api/v1/projects/Maintenance/latestrelease").toURL().openConnection();
connection.setRequestProperty("User-Agent", "Maintenance/" + getVersion());
connection.setConnectTimeout(5_000);
connection.setReadTimeout(10_000);
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
final String newVersionString = reader.readLine();
final Version newVersion = new Version(newVersionString);
@@ -287,7 +320,7 @@ public abstract class MaintenancePlugin implements Maintenance {
public String pasteDump() {
final MaintenanceDump dump = new MaintenanceDump(this, settings);
try {
final HttpURLConnection connection = (HttpURLConnection) new URL("https://api.pastes.dev/post").openConnection();
final HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.pastes.dev/post").toURL().openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("User-Agent", "Maintenance/" + getVersion());
@@ -328,8 +361,7 @@ public abstract class MaintenancePlugin implements Maintenance {
try {
loadIcon(file);
} catch (final Exception e) {
getLogger().log(Level.WARNING, "Could not load the 'maintenance-icon.png' file!");
e.printStackTrace();
getLogger().log(Level.WARNING, "Could not load the 'maintenance-icon.png' file!", e);
}
}
@@ -374,6 +406,10 @@ public abstract class MaintenancePlugin implements Maintenance {
return runnable != null;
}
public @Nullable MaintenanceRunnable getCurrentTask() {
return runnable;
}
@Override
public Settings getSettings() {
return settings;
@@ -436,6 +472,10 @@ public abstract class MaintenancePlugin implements Maintenance {
return "plugins/";
}
public void sync(final Runnable runnable) {
async(runnable);
}
public abstract void async(Runnable runnable);
protected abstract void executeConsoleCommand(String command);
@@ -20,11 +20,8 @@ package eu.kennytv.maintenance.core;
import eu.kennytv.maintenance.api.event.MaintenanceReloadedEvent;
import eu.kennytv.maintenance.core.config.Config;
import eu.kennytv.maintenance.core.config.ConfigSection;
import eu.kennytv.maintenance.core.config.DisplayedMessages;
import eu.kennytv.maintenance.core.util.ServerType;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.TextComponent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.minimessage.MiniMessage;
import eu.kennytv.maintenance.lib.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -32,34 +29,43 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.logging.Level;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.Nullable;
public class Settings implements eu.kennytv.maintenance.api.Settings {
public static final String NEW_LINE_REPLACEMENT = "<br>";
private static final int CONFIG_VERSION = 9;
private static final int LANGUAGE_VERSION = 2;
private static final Random RANDOM = new Random();
private static final int CONFIG_VERSION = 12;
private static final int LANGUAGE_VERSION = 5;
protected final MaintenancePlugin plugin;
private final Map<UUID, String> whitelistedPlayers = new HashMap<>();
private final String[] unsupportedFields;
protected boolean maintenance;
protected String activeMode;
protected String activeReason;
private Set<Integer> broadcastIntervals;
private List<String> pingMessages;
private List<String> timerSpecificPingMessages;
private DisplayedMessages pingMessages;
private DisplayedMessages timerSpecificPingMessages;
private List<String> commandsOnMaintenanceEnable;
private List<String> commandsOnMaintenanceDisable;
private String legacyParsedPlayerCountMessage;
private String legacyParsedTimerPlayerCountMessage;
private List<String> legacyParsedPlayerCountHoverLines;
private List<String> legacyParsedTimerPlayerCountHoverLines;
private DisplayedMessages legacyParsedPlayerCountMessages;
private DisplayedMessages legacyParsedTimerPlayerCountMessages;
private DisplayedMessages legacyParsedPlayerCountHoverLines;
private DisplayedMessages legacyParsedTimerPlayerCountHoverLines;
private String prefixString;
private String plainTextPrefix;
private String languageName;
private boolean enablePingMessages;
private boolean customPlayerCountMessage;
@@ -72,6 +78,13 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
private boolean debug;
private long savedEndtimer;
// Webhook settings
private boolean webhookEnabled;
private Set<String> webhookEnabledEvents;
private String webhookUrl;
private String webhookUsername;
private String webhookAvatarUrl;
protected Config config;
protected Config language;
protected Config whitelist;
@@ -119,6 +132,7 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
}
prefixString = language.getString("prefix");
plainTextPrefix = PlainTextComponentSerializer.plainText().serialize(parse(prefixString));
plugin.getEventManager().callEvent(new MaintenanceReloadedEvent());
}
@@ -161,6 +175,9 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
if (file.exists()) return;
try (final InputStream in = plugin.getResource(fileName)) {
if (in == null) {
throw new IOException("No bundled language file " + fileName);
}
Files.copy(in, file.toPath());
} catch (final IOException e) {
plugin.getLogger().warning("Unable to provide language " + languageName);
@@ -179,14 +196,16 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
final ConfigSection pingMessageSection = config.getSection("ping-message");
enablePingMessages = pingMessageSection.getBoolean("enabled", true);
// Can't store this pre-parsed as components because gradients will break replacements
pingMessages = loadPingMessages(pingMessageSection.getStringList("messages"));
pingMessages = loadPingMessages(pingMessageSection.getSection("messages"));
if (pingMessageSection.getBoolean("enable-timer-specific-messages")) {
timerSpecificPingMessages = loadPingMessages(pingMessageSection.getStringList("timer-messages"));
timerSpecificPingMessages = loadPingMessages(pingMessageSection.getSection("timer-messages"));
} else {
timerSpecificPingMessages = null;
}
maintenance = config.getBoolean("maintenance-enabled");
activeMode = normalizeMode(config.getString("active-mode"));
activeReason = normalizeReason(config.getString("active-reason"));
commandsOnMaintenanceEnable = config.getStringList("commands-on-maintenance-enable");
commandsOnMaintenanceDisable = config.getStringList("commands-on-maintenance-disable");
customMaintenanceIcon = config.getBoolean("custom-maintenance-icon");
@@ -197,34 +216,35 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
if (plugin.getServerType() != ServerType.SPONGE) {
final ConfigSection playerCountMessageSection = config.getSection("player-count-message");
customPlayerCountMessage = playerCountMessageSection.getBoolean("enabled");
legacyParsedPlayerCountMessage = toLegacy(parse(getConfigMessage(playerCountMessageSection, "message")));
legacyParsedPlayerCountMessages = loadPlayerCountMessages(playerCountMessageSection.getSection("messages"));
if (playerCountMessageSection.getBoolean("enable-timer-specific-message")) {
legacyParsedTimerPlayerCountMessage = toLegacy(parse(getConfigMessage(playerCountMessageSection, "timer-message")));
legacyParsedTimerPlayerCountMessages = loadPlayerCountMessages(playerCountMessageSection.getSection("timer-messages"));
} else {
legacyParsedTimerPlayerCountMessage = null;
legacyParsedTimerPlayerCountMessages = null;
}
}
final ConfigSection listHoverMessageSection = config.getSection("player-list-hover-message");
customPlayerCountHoverMessage = listHoverMessageSection.getBoolean("enabled");
legacyParsedPlayerCountHoverLines = new ArrayList<>();
for (final String line : listHoverMessageSection.getString("message").split("<br>")) {
legacyParsedPlayerCountHoverLines.add(toLegacy(parse(line)));
}
legacyParsedPlayerCountHoverLines = loadPlayerCountHoverMessages(listHoverMessageSection.getSection("messages"));
if (listHoverMessageSection.getBoolean("enable-timer-specific-message")) {
legacyParsedTimerPlayerCountHoverLines = new ArrayList<>();
for (final String line : listHoverMessageSection.getString("timer-message").split("<br>")) {
legacyParsedTimerPlayerCountHoverLines.add(toLegacy(parse(line)));
}
legacyParsedTimerPlayerCountHoverLines = loadPlayerCountHoverMessages(listHoverMessageSection.getSection("timer-messages"));
} else {
legacyParsedTimerPlayerCountHoverLines = null;
}
languageName = config.getString("language").toLowerCase();
languageName = config.getString("language").toLowerCase(Locale.ROOT);
kickOnlinePlayers = config.getBoolean("kick-online-players", true);
updateChecks = config.getBoolean("update-checks", true);
debug = config.getBoolean("debug");
final ConfigSection webhookSection = config.getSection("webhook");
webhookEnabled = webhookSection.getBoolean("enabled");
webhookEnabledEvents = new HashSet<>(webhookSection.getStringList("enabled-events"));
webhookUrl = webhookSection.getString("url", "");
webhookUsername = webhookSection.getString("username", "Maintenance");
webhookAvatarUrl = webhookSection.getString("avatar-url", "");
final ConfigSection section = config.getSection("continue-endtimer-after-restart");
saveEndtimerOnStop = section.getBoolean("enabled");
savedEndtimer = section.getLong("end");
@@ -233,6 +253,12 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
plugin.loadMaintenanceIcon();
}
loadWhitelistedPlayers();
loadExtraSettings();
}
protected void loadWhitelistedPlayers() {
whitelistedPlayers.clear();
for (final Map.Entry<String, Object> entry : whitelist.getValues().entrySet()) {
try {
@@ -241,8 +267,6 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
plugin.getLogger().warning("Invalid WhitelistedPlayers entry: " + entry.getKey());
}
}
loadExtraSettings();
}
private void updatePluginDirectory() {
@@ -250,7 +274,7 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
String oldDirName = "Maintenance" + plugin.getServerType();
if (plugin.getServerType() == ServerType.SPONGE || plugin.getServerType() == ServerType.VELOCITY) {
oldDirName = oldDirName.toLowerCase(Locale.ROOT);
} else if (plugin.getServerType() == ServerType.SPIGOT) {
} else if (plugin.getServerType() == ServerType.PAPER) {
oldDirName = "MaintenanceSpigot";
}
@@ -290,6 +314,35 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
config.move("enable-playercounthovermessage", "player-list-hover-message.enabled");
config.move("playercounthovermessage", "player-list-hover-message.message");
}
if (version < 11) {
config.move("player-count-message.message", "player-count-message.messages.default");
config.move("player-count-message.timer-message", "player-count-message.timer-messages.default");
config.move("player-list-hover-message.message", "player-list-hover-message.messages.default");
config.move("player-list-hover-message.timer-message", "player-list-hover-message.timer-messages.default");
migrateModeSection("ping-message.messages");
migrateModeSection("ping-message.timer-messages");
migrateModeSection("player-count-message.messages");
migrateModeSection("player-count-message.timer-messages");
migrateModeSection("player-list-hover-message.messages");
migrateModeSection("player-list-hover-message.timer-messages");
final Object proxiedMaintenanceServers = config.getObject("proxied-maintenance-servers");
if (proxiedMaintenanceServers instanceof List<?> list) {
final Map<String, String> migratedServers = new LinkedHashMap<>();
for (final Object entry : list) {
if (entry != null) {
migratedServers.put(String.valueOf(entry), "default");
}
}
config.set("proxied-maintenance-servers", migratedServers);
}
}
if (version < 12) {
// Fix typo in the shipped config key :>
config.move("redis.enables", "redis.enabled");
}
createFile("config-new.yml", "config.yml");
final File file = new File(plugin.getDataFolder(), "config-new.yml");
@@ -321,11 +374,10 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
plugin.getLogger().info("Updating language file to the latest version...");
if (version < 1) {
for (final Map.Entry<String, Object> entry : language.getValues().entrySet()) {
if (!(entry.getValue() instanceof String)) {
if (!(entry.getValue() instanceof String value)) {
continue;
}
String value = (String) entry.getValue();
value = value.replace("&8[&eMaintenance&8] ", "<prefix>");
value = legacyToMinimessage(value);
value = value.replace("%NEWLINE%", NEW_LINE_REPLACEMENT);
@@ -361,6 +413,17 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return serialized.replaceAll("</[a-z_]+>", "");
}
private void migrateModeSection(final String path) {
final Object section = config.getObject(path);
if (section == null || section instanceof Map<?, ?>) {
return;
}
final Map<String, Object> map = new LinkedHashMap<>();
map.put("default", section);
config.set(path, map);
}
private String getConfigMessage(final ConfigSection section, final String path) {
final String s = section.getString(path);
if (s == null) {
@@ -393,6 +456,25 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return replaceNewlineVar(s).replace("<prefix>", prefixString);
}
public String getWebhookMessage(final String path, final String... replacements) {
String s = language.getString(path);
if (s == null) {
plugin.getLogger().warning("The language file is missing the following webhook message: " + path);
return path;
}
if (replacements.length != 0) {
if (replacements.length % 2 != 0) {
throw new IllegalArgumentException("Invalid replacement count: " + replacements.length);
}
for (int i = 0; i < replacements.length; i += 2) {
final String key = replacements[i];
s = s.replace(key, replacements[i + 1]);
}
}
return replaceNewlineVar(s);
}
public @Nullable String getLanguageStringOrNull(final String path, final String... replacements) {
return language.contains(path) ? getLanguageString(path, replacements) : null;
}
@@ -403,24 +485,53 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
// TODO Cache platform components if there are no replacements
public Component getRandomPingMessage() {
if (plugin.isTaskRunning() && !plugin.getRunnable().shouldEnable()
&& hasTimerSpecificPingMessages() && !timerSpecificPingMessages.isEmpty()) {
return getPingMessage(timerSpecificPingMessages);
}
return pingMessages.isEmpty() ? Component.empty() : getPingMessage(pingMessages);
return getRandomPingMessage(activeMode);
}
private Component getPingMessage(final List<String> list) {
final String component = list.size() == 1 ? list.get(0) : list.get(RANDOM.nextInt(list.size()));
return parse(plugin.replacePingVariables(component));
public Component getRandomPingMessage(@Nullable final String mode) {
if (plugin.isTaskRunning() && !plugin.getRunnable().shouldEnable() && hasTimerSpecificPingMessages()) {
return getPingMessage(timerSpecificPingMessages, mode);
}
return pingMessages.isEmpty() ? Component.empty() : getPingMessage(pingMessages, mode);
}
private List<String> loadPingMessages(final List<String> list) {
final List<String> components = new ArrayList<>(list.size());
for (final String s : list) {
components.add(replaceNewlineVar(s));
private Component getPingMessage(final DisplayedMessages list, @Nullable final String mode) {
final String component = list.getRandomEntry(mode);
return parse(plugin.replacePingVariables(component, mode));
}
private DisplayedMessages loadPingMessages(final ConfigSection section) {
return loadDisplayedMessages(section, (list, s) -> list.add(replaceNewlineVar(s)));
}
private DisplayedMessages loadPlayerCountHoverMessages(final ConfigSection section) {
return loadDisplayedMessages(section, (list, s) -> {
// Split into separate messages (one per player entry)
for (final String line : s.split("<br>")) {
list.add(toLegacy(parse(line)));
}
});
}
private DisplayedMessages loadPlayerCountMessages(final ConfigSection section) {
return loadDisplayedMessages(section, (list, s) -> list.add(toLegacy(parse(s))));
}
private DisplayedMessages loadDisplayedMessages(final ConfigSection section, final BiConsumer<List<String>, String> messageFunction) {
final Map<String, List<String>> modes = new HashMap<>(section.getKeys().size());
for (final Map.Entry<String, Object> entry : section.getValues().entrySet()) {
final List<String> components = new ArrayList<>();
if (entry.getValue() instanceof String s) {
messageFunction.accept(components, s);
} else if (entry.getValue() instanceof List<?> list) {
//noinspection unchecked
for (final String s : ((Iterable<String>) list)) {
messageFunction.accept(components, s);
}
}
modes.put(entry.getKey(), components);
}
return components;
return new DisplayedMessages(modes);
}
@Override
@@ -448,18 +559,19 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return false;
}
whitelistedPlayers.remove(uuid);
whitelist.remove(uuid.toString());
saveWhitelistedPlayers();
return true;
return removeWhitelistedPlayer(uuid);
}
@Override
public boolean addWhitelistedPlayer(final UUID uuid, final String name) {
final boolean added = whitelistedPlayers.put(uuid, name) == null;
final boolean added = !name.equals(whitelistedPlayers.put(uuid, name));
if (!added) {
return false;
}
whitelist.set(uuid.toString(), name);
saveWhitelistedPlayers();
return added;
return true;
}
@Override
@@ -477,8 +589,47 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return maintenance;
}
public void setMaintenance(final boolean maintenance) {
this.maintenance = maintenance;
@Override
public @Nullable String activeMode() {
return activeMode;
}
public void setActiveMode(@Nullable final String activeMode) {
final String normalized = normalizeMode(activeMode);
if (Objects.equals(this.activeMode, normalized)) {
return;
}
this.activeMode = normalized;
config.set("active-mode", normalized != null ? normalized : "default");
saveConfig();
}
@Override
public String activeReason() {
return activeReason;
}
@Override
public void setActiveReason(@Nullable final String reason) {
final String normalized = normalizeReason(reason);
if (Objects.equals(this.activeReason, normalized)) {
return;
}
this.activeReason = normalized;
config.set("active-reason", normalized);
saveConfig();
}
public void setMaintenance(final boolean maintenance, @Nullable final String mode) {
if (this.maintenance != maintenance) {
this.maintenance = maintenance;
config.set("maintenance-enabled", maintenance);
this.activeMode = normalizeMode(mode);
config.set("active-mode", this.activeMode != null ? this.activeMode : "default");
saveConfig();
}
}
@Override
@@ -535,7 +686,7 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return config;
}
public List<String> getPingMessages() {
public DisplayedMessages getPingMessages() {
return pingMessages;
}
@@ -547,7 +698,7 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return commandsOnMaintenanceDisable;
}
public @Nullable List<String> getTimerSpecificPingMessages() {
public @Nullable DisplayedMessages getTimerSpecificPingMessages() {
return timerSpecificPingMessages;
}
@@ -555,16 +706,50 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return broadcastIntervals;
}
public boolean isWebhookEnabled() {
return webhookEnabled;
}
public boolean isWebhookEventEnabled(final String event) {
return webhookEnabledEvents.contains(event);
}
public String getWebhookUrl() {
return webhookUrl;
}
public String getWebhookUsername() {
return webhookUsername;
}
public String getWebhookAvatarUrl() {
return webhookAvatarUrl;
}
public String getPlainTextPrefix() {
return plainTextPrefix;
}
public String getLegacyParsedPlayerCountMessage() {
final String message = timerDependent(legacyParsedPlayerCountMessage, legacyParsedTimerPlayerCountMessage);
return plugin.replacePingVariables(message);
return getLegacyParsedPlayerCountMessage(activeMode);
}
public String getLegacyParsedPlayerCountMessage(@Nullable final String mode) {
final DisplayedMessages messages = timerDependent(legacyParsedPlayerCountMessages, legacyParsedTimerPlayerCountMessages);
final String message = messages.getRandomEntry(mode);
return plugin.replacePingVariables(message, mode);
}
public String[] getLegacyParsedPlayerCountHoverLines() {
final List<String> lines = timerDependent(legacyParsedPlayerCountHoverLines, legacyParsedTimerPlayerCountHoverLines);
return getLegacyParsedPlayerCountHoverLines(activeMode);
}
public String[] getLegacyParsedPlayerCountHoverLines(@Nullable final String mode) {
final DisplayedMessages messages = timerDependent(legacyParsedPlayerCountHoverLines, legacyParsedTimerPlayerCountHoverLines);
final List<String> lines = messages.getMessages(mode);
final String[] parsedLines = new String[lines.size()];
for (int i = 0; i < lines.size(); i++) {
parsedLines[i] = plugin.replacePingVariables(lines.get(i));
parsedLines[i] = plugin.replacePingVariables(lines.get(i), mode);
}
return parsedLines;
}
@@ -598,7 +783,7 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
}
/*
* Note on why this even exists: Yaml will force save all strings containing line breaks '\n' in this rather chunky format:
* Note on why this even exists: YAML will force save all strings containing line breaks '\n' in this rather chunky format:
* key: |-
* First line text
* Second line text.
@@ -611,6 +796,17 @@ public class Settings implements eu.kennytv.maintenance.api.Settings {
return s.replace(NEW_LINE_REPLACEMENT, "\n");
}
protected @Nullable String normalizeMode(@Nullable final String mode) {
if (mode == null || mode.isBlank() || mode.equalsIgnoreCase("default")) {
return null;
}
return mode.toLowerCase(Locale.ROOT);
}
private String normalizeReason(@Nullable final String reason) {
return reason == null || reason.isBlank() ? "" : reason;
}
protected void loadExtraSettings() {
}
}
@@ -20,7 +20,7 @@ package eu.kennytv.maintenance.core.command;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
@@ -28,8 +28,10 @@ import eu.kennytv.maintenance.core.command.subcommand.MotdCommand;
import eu.kennytv.maintenance.core.command.subcommand.ReloadCommand;
import eu.kennytv.maintenance.core.command.subcommand.RemoveMotdCommand;
import eu.kennytv.maintenance.core.command.subcommand.ScheduleTimerCommand;
import eu.kennytv.maintenance.core.command.subcommand.SetReasonCommand;
import eu.kennytv.maintenance.core.command.subcommand.SetMotdCommand;
import eu.kennytv.maintenance.core.command.subcommand.StarttimerCommand;
import eu.kennytv.maintenance.core.command.subcommand.StatusCommand;
import eu.kennytv.maintenance.core.command.subcommand.ToggleCommand;
import eu.kennytv.maintenance.core.command.subcommand.UpdateCommand;
import eu.kennytv.maintenance.core.command.subcommand.WhitelistAddCommand;
@@ -74,6 +76,7 @@ public abstract class MaintenanceCommand {
add(new SetMotdCommand(plugin), "setmotd");
add(new RemoveMotdCommand(plugin), "removemotd");
add(new MotdCommand(plugin), "motd");
add(new SetReasonCommand(plugin), "setreason");
add(new UpdateCommand(plugin), "update", "forceupdate");
add(new DumpCommand(plugin), "dump");
@@ -97,7 +100,7 @@ public abstract class MaintenanceCommand {
return;
}
final CommandInfo command = commandExecutors.get(args[0].toLowerCase());
final CommandInfo command = commandExecutors.get(args[0].toLowerCase(Locale.ROOT));
if (command == null) {
help.sendUsage(sender);
return;
@@ -112,7 +115,7 @@ public abstract class MaintenanceCommand {
public List<String> getSuggestions(final SenderInfo sender, final String[] args) {
if (!sender.hasMaintenancePermission("command") || args.length == 0) return Collections.emptyList();
final String s = args[0].toLowerCase();
final String s = args[0].toLowerCase(Locale.ROOT);
if (args.length == 1) {
final List<String> list = new ArrayList<>();
for (final Map.Entry<String, CommandInfo> entry : commandExecutors.entrySet()) {
@@ -129,6 +132,24 @@ public abstract class MaintenanceCommand {
return info != null && info.hasPermission(sender) ? info.getTabCompletion(sender, args) : Collections.emptyList();
}
/**
* Converts a user given time like {@code 2d}, {@code 2d12h}, {@code 1h5m}, or {@code 30s} to an ISO-8601 duration string.
* Days are supported in addition to hours, minutes, and seconds.
*
* @return ISO duration string
*/
private static String toIsoDuration(final String time) {
final String upper = time.toUpperCase(Locale.ROOT);
final int dayIndex = upper.indexOf('D');
if (dayIndex == -1) {
return "PT" + upper;
}
final String days = upper.substring(0, dayIndex + 1);
final String rest = upper.substring(dayIndex + 1);
return rest.isEmpty() ? "P" + days : "P" + days + "T" + rest;
}
public @Nullable Duration parseDurationAndCheckTask(final SenderInfo sender, final String time, final boolean taskCheck) {
final Duration duration;
if (plugin.isNumeric(time)) {
@@ -136,8 +157,7 @@ public abstract class MaintenanceCommand {
duration = Duration.ofMinutes(Integer.parseInt(time));
} else {
try {
// Only accept hours, minutes, and seconds
duration = Duration.parse("PT" + time.toUpperCase(Locale.ROOT));
duration = Duration.parse(toIsoDuration(time));
} catch (final DateTimeParseException e) {
return null;
}
@@ -168,6 +188,7 @@ public abstract class MaintenanceCommand {
protected void addToggleAndTimerCommands() {
add(new ToggleCommand(plugin), "on", "off");
add(new StatusCommand(plugin), "status");
add(new StarttimerCommand(plugin), "starttimer", "start");
add(new EndtimerCommand(plugin), "endtimer", "end");
@@ -20,12 +20,14 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.TextComponent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.event.ClickEvent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.event.HoverEvent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.format.NamedTextColor;
import java.util.concurrent.TimeUnit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import static eu.kennytv.maintenance.core.util.ComponentUtil.clickEvent;
public final class DumpCommand extends CommandInfo {
private long lastDump;
@@ -57,7 +59,7 @@ public final class DumpCommand extends CommandInfo {
sender.sendPrefixedRich("<red><click:open_url:'" + url + "'>" + url + "</click>");
if (sender.isPlayer()) {
final TextComponent text = Component.text().content("Click here to copy the link").color(NamedTextColor.GRAY)
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, url))
.clickEvent(clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, url))
.hoverEvent(HoverEvent.showText(Component.text("Click here to copy the link to your clipboard").color(NamedTextColor.GREEN)))
.build();
sender.send(plugin.prefix().append(text));
@@ -19,9 +19,10 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.time.Duration;
import net.kyori.adventure.text.Component;
public final class EndtimerCommand extends CommandInfo {
@@ -44,6 +45,11 @@ public final class EndtimerCommand extends CommandInfo {
}
plugin.startMaintenanceRunnable(duration, false);
sender.send(getMessage("endtimerStarted", "%TIME%", plugin.getRunnable().getTime()));
final Component message = getMessage("endtimerStarted", "%TIME%", plugin.getRunnable().getTime());
sender.send(message);
plugin.sendWebhookMessage("webhookEndtimerStarted", DiscordWebhook.EventType.ENDTIMER_STARTED,
"%TIME%", plugin.getRunnable().getTime(),
"%TIMESTAMP%", plugin.getTargetTimestamp(duration));
}
}
@@ -21,9 +21,9 @@ import com.google.common.base.Preconditions;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.util.ArrayList;
import java.util.List;
import net.kyori.adventure.text.Component;
public final class HelpCommand extends CommandInfo {
private static final int COMMANDS_PER_PAGE = 8;
@@ -19,10 +19,14 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.config.ConfigSection;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.jetbrains.annotations.Nullable;
public final class MotdCommand extends CommandInfo {
@@ -32,21 +36,29 @@ public final class MotdCommand extends CommandInfo {
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (args.length == 1) {
sendList(sender, getSettings().getPingMessages());
} else if (args.length == 2 && args[1].equalsIgnoreCase("timer")) {
int indexOffset = 1;
boolean timer = false;
if (args.length > indexOffset && args[indexOffset].equalsIgnoreCase("timer")) {
if (!getSettings().hasTimerSpecificPingMessages()) {
sender.send(getMessage("timerMotdDisabled"));
return;
}
sendList(sender, getSettings().getTimerSpecificPingMessages());
} else {
sender.send(getHelpMessage());
timer = true;
indexOffset++;
}
if (args.length > indexOffset + 1) {
sender.send(getHelpMessage());
return;
}
final String mode = args.length == indexOffset + 1 ? args[indexOffset].toLowerCase(Locale.ROOT) : "default";
final ConfigSection section = getSettings().getConfig().getSection("ping-message").getSection(timer ? "timer-messages" : "messages");
sendList(sender, getModeMessages(section, mode));
}
private void sendList(final SenderInfo sender, final List<String> list) {
private void sendList(final SenderInfo sender, @Nullable final List<String> list) {
if (list == null || list.isEmpty()) {
sender.send(getMessage("motdListEmpty"));
return;
@@ -55,16 +67,43 @@ public final class MotdCommand extends CommandInfo {
sender.send(getMessage("motdList"));
for (int i = 0; i < list.size(); i++) {
sender.sendRich("<aqua>" + (i + 1) + "<dark_gray><st>---------");
sender.sendRich(list.get(i));
sender.sendRich(list.get(i).replace("<br>", "\n"));
}
sender.sendRich("<dark_gray><st>----------");
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
if (args.length != 2 || !getSettings().hasTimerSpecificPingMessages()) {
if (args.length < 2 || args.length > 3) {
return Collections.emptyList();
}
return Collections.singletonList("timer");
if (args.length == 2) {
final List<String> list = new ArrayList<>();
if (getSettings().hasTimerSpecificPingMessages()) {
list.add("timer");
}
final List<String> modes = new ArrayList<>(getSettings().getConfig().getSection("ping-message").getSection("messages").getKeys());
modes.remove("default");
list.addAll(modes);
return list;
}
if (!args[1].equalsIgnoreCase("timer") || !getSettings().hasTimerSpecificPingMessages()) {
return Collections.emptyList();
}
final List<String> modes = new ArrayList<>(getSettings().getConfig().getSection("ping-message").getSection("timer-messages").getKeys());
modes.remove("default");
return modes;
}
private @Nullable List<String> getModeMessages(final ConfigSection section, final String mode) {
final Object value = section.getObject(mode);
if (value instanceof String s) {
return Collections.singletonList(s);
}
return section.getStringList(mode);
}
}
@@ -22,10 +22,10 @@ import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.config.ConfigSection;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public final class RemoveMotdCommand extends CommandInfo {
@@ -34,32 +34,49 @@ public final class RemoveMotdCommand extends CommandInfo {
}
@Override
public void execute(final SenderInfo sender, String[] args) {
public void execute(final SenderInfo sender, final String[] args) {
int indexOffset = 1;
boolean timerPingMessages = false;
if (args.length == 3 && args[1].equalsIgnoreCase("timer")) {
if (args.length > indexOffset && args[indexOffset].equalsIgnoreCase("timer")) {
if (!getSettings().hasTimerSpecificPingMessages()) {
sender.send(getMessage("timerMotdDisabled"));
return;
}
args = plugin.removeArrayIndex(args, 1);
indexOffset++;
timerPingMessages = true;
}
if (checkArgs(sender, args, 2)) return;
if (!plugin.isNumeric(args[1])) {
if (args.length <= indexOffset) {
sender.send(getHelpMessage());
return;
}
String mode = "default";
if (!plugin.isNumeric(args[indexOffset])) {
mode = args[indexOffset].toLowerCase(Locale.ROOT);
indexOffset++;
}
if (args.length != indexOffset + 1 || !plugin.isNumeric(args[indexOffset])) {
sender.send(getHelpMessage());
return;
}
final Settings settings = getSettings();
final ConfigSection section = settings.getConfig().getSection("ping-message");
final List<String> pingMessages = section.getStringList(timerPingMessages ? "timer-messages" : "messages");
if (pingMessages.size() < 2) {
final ConfigSection messageSection = section.getSection(timerPingMessages ? "timer-messages" : "messages");
final List<String> pingMessages = getModeMessages(messageSection, mode);
if (pingMessages.isEmpty()) {
sender.send(getMessage("motdListEmpty"));
return;
}
if (mode.equals("default") && pingMessages.size() < 2) {
sender.send(getMessage("removeMotdError"));
return;
}
final int index = Integer.parseInt(args[1]);
final int index = Integer.parseInt(args[indexOffset]);
if (index == 0 || index > pingMessages.size()) {
sender.send(getMessage("setMotdIndexError",
"%MOTDS%", Integer.toString(pingMessages.size()),
@@ -67,24 +84,60 @@ public final class RemoveMotdCommand extends CommandInfo {
return;
}
final List<String> pingComponents = timerPingMessages ? settings.getTimerSpecificPingMessages() : settings.getPingMessages();
pingComponents.remove(index - 1);
pingMessages.remove(index - 1);
section.set(timerPingMessages ? "timer-messages" : "messages", pingMessages);
if (pingMessages.isEmpty()) {
messageSection.remove(mode);
} else {
messageSection.set(mode, pingMessages);
}
settings.saveConfig();
sender.send(getMessage("removedMotd", "%INDEX%", args[1]));
settings.reloadConfigs();
sender.send(getMessage("removedMotd", "%INDEX%", Integer.toString(index)));
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
if (args.length == 2 || (args.length == 3 && args[1].equalsIgnoreCase("timer"))) {
final int size = (args.length == 3 ? plugin.getSettings().getTimerSpecificPingMessages() : plugin.getSettings().getPingMessages()).size();
final int indexOffset;
final boolean timer;
if (args.length > 1 && args[1].equalsIgnoreCase("timer")) {
timer = true;
indexOffset = 2;
} else {
timer = false;
indexOffset = 1;
}
if (args.length == indexOffset + 1 && !plugin.isNumeric(args[indexOffset])) {
return getModeCompletions(timer);
}
if (args.length == indexOffset + 1 || (args.length == indexOffset + 2 && !plugin.isNumeric(args[indexOffset]))) {
final String mode = plugin.isNumeric(args[indexOffset]) ? "default" : args[indexOffset].toLowerCase(Locale.ROOT);
final int size = getModeMessages(getSettings().getConfig().getSection("ping-message").getSection(timer ? "timer-messages" : "messages"), mode).size();
final List<String> list = new ArrayList<>(size);
for (int i = 1; i <= size; i++) {
list.add(Integer.toString(i));
}
return list;
}
return Collections.emptyList();
}
private List<String> getModeCompletions(final boolean timer) {
final List<String> modes = new ArrayList<>(getSettings().getConfig().getSection("ping-message").getSection(timer ? "timer-messages" : "messages").getKeys());
modes.remove("default");
return modes;
}
private List<String> getModeMessages(final ConfigSection section, final String mode) {
final Object value = section.getObject(mode);
if (value == null) {
return List.of();
}
if (value instanceof String s) {
return new ArrayList<>(Collections.singletonList(s));
}
return new ArrayList<>(section.getStringList(mode));
}
}
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static eu.kennytv.maintenance.core.Settings.NEW_LINE_REPLACEMENT;
@@ -36,28 +37,41 @@ public final class SetMotdCommand extends CommandInfo {
}
@Override
public void execute(final SenderInfo sender, String[] args) {
public void execute(final SenderInfo sender, final String[] args) {
int indexOffset = 1;
boolean timerPingMessages = false;
if (args.length > 1 && args[1].equalsIgnoreCase("timer")) {
if (args.length > indexOffset && args[indexOffset].equalsIgnoreCase("timer")) {
if (!getSettings().hasTimerSpecificPingMessages()) {
sender.send(getMessage("timerMotdDisabled"));
return;
}
// remove the "timer" off the args to keep the rest the code cleaner
args = plugin.removeArrayIndex(args, 1);
indexOffset++;
timerPingMessages = true;
}
if (args.length < 4 || !plugin.isNumeric(args[1])) {
if (args.length <= indexOffset + 2) {
sender.send(getHelpMessage());
return;
}
String mode = "default";
if (!plugin.isNumeric(args[indexOffset])) {
mode = args[indexOffset].toLowerCase(Locale.ROOT);
indexOffset++;
}
if (args.length <= indexOffset + 2 || !plugin.isNumeric(args[indexOffset])) {
sender.send(getHelpMessage());
return;
}
final Settings settings = getSettings();
final List<String> pingComponents = timerPingMessages ? settings.getTimerSpecificPingMessages() : settings.getPingMessages();
final ConfigSection section = settings.getConfig().getSection("ping-message");
final List<String> pingMessages = section.getStringList(timerPingMessages ? "timer-messages" : "messages");
final int index = Integer.parseInt(args[1]);
final ConfigSection messageSection = section.getSection(timerPingMessages ? "timer-messages" : "messages");
final List<String> pingMessages = getOrCreateModeMessages(messageSection, mode);
final int index = Integer.parseInt(args[indexOffset]);
if (index == 0 || index > pingMessages.size() + 1) {
sender.send(getMessage("setMotdIndexError",
"%MOTDS%", Integer.toString(pingMessages.size()),
@@ -65,63 +79,96 @@ public final class SetMotdCommand extends CommandInfo {
return;
}
if (!plugin.isNumeric(args[2])) {
if (!plugin.isNumeric(args[indexOffset + 1])) {
sender.send(getMessage("setMotdLineError"));
return;
}
final int line = Integer.parseInt(args[2]);
final int line = Integer.parseInt(args[indexOffset + 1]);
if (line != 1 && line != 2) {
sender.send(getMessage("setMotdLineError"));
return;
}
final String message = String.join(" ", Arrays.copyOfRange(args, 3, args.length));
final String message = String.join(" ", Arrays.copyOfRange(args, indexOffset + 2, args.length));
final String oldMessage = index > pingMessages.size() ? "" : pingMessages.get(index - 1);
final String newMessage;
if (line == 1) {
newMessage = oldMessage.contains(NEW_LINE_REPLACEMENT) ?
message + NEW_LINE_REPLACEMENT + oldMessage.split(NEW_LINE_REPLACEMENT, 2)[1] : message;
newMessage = oldMessage.contains(NEW_LINE_REPLACEMENT)
? message + NEW_LINE_REPLACEMENT + oldMessage.split(NEW_LINE_REPLACEMENT, 2)[1]
: message;
} else {
newMessage = oldMessage.contains(NEW_LINE_REPLACEMENT) ?
oldMessage.split(NEW_LINE_REPLACEMENT, 2)[0] + NEW_LINE_REPLACEMENT + message : oldMessage + NEW_LINE_REPLACEMENT + message;
newMessage = oldMessage.contains(NEW_LINE_REPLACEMENT)
? oldMessage.split(NEW_LINE_REPLACEMENT, 2)[0] + NEW_LINE_REPLACEMENT + message
: oldMessage + NEW_LINE_REPLACEMENT + message;
}
// Replace component in config and the cached list
final String componentString = newMessage.replace(NEW_LINE_REPLACEMENT, "\n");
if (index > pingMessages.size()) {
pingMessages.add(newMessage);
pingComponents.add(componentString);
} else {
pingMessages.set(index - 1, newMessage);
pingComponents.set(index - 1, componentString);
}
section.set(timerPingMessages ? "timer-messages" : "messages", pingMessages);
messageSection.set(mode, pingMessages);
settings.saveConfig();
settings.reloadConfigs();
sender.send(settings.getMessage(
"setMotd",
"%LINE%", args[2],
"%INDEX%", args[1],
"%MOTD%", componentString
"%LINE%", Integer.toString(line),
"%INDEX%", Integer.toString(index),
"%MOTD%", newMessage.replace(NEW_LINE_REPLACEMENT, "\n")
));
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, String[] args) {
boolean timer = false;
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
final int indexOffset;
final boolean timer;
if (args.length > 1 && args[1].equalsIgnoreCase("timer")) {
args = plugin.removeArrayIndex(args, 1);
timer = true;
indexOffset = 2;
} else {
timer = false;
indexOffset = 1;
}
if (args.length == 3) return Arrays.asList("1", "2");
if (args.length == 2) {
if (args.length == indexOffset + 2) {
return Arrays.asList("1", "2");
}
if (args.length == indexOffset + 1 && !plugin.isNumeric(args[indexOffset])) {
return getModeCompletions(timer);
}
if (args.length == indexOffset + 1) {
final String mode = plugin.isNumeric(args[indexOffset]) ? "default" : args[indexOffset].toLowerCase(Locale.ROOT);
final ConfigSection section = getSettings().getConfig().getSection("ping-message").getSection(timer ? "timer-messages" : "messages");
final int size = getOrCreateModeMessages(section, mode).size();
final List<String> list = new ArrayList<>();
for (int i = 1; i <= (timer ? getSettings().getTimerSpecificPingMessages() : getSettings().getPingMessages()).size() + 1; i++) {
for (int i = 1; i <= size + 1; i++) {
list.add(String.valueOf(i));
}
return list;
}
return Collections.emptyList();
}
private List<String> getModeCompletions(final boolean timer) {
final List<String> modes = new ArrayList<>(getSettings().getConfig().getSection("ping-message").getSection(timer ? "timer-messages" : "messages").getKeys());
modes.remove("default");
return modes;
}
private List<String> getOrCreateModeMessages(final ConfigSection section, final String mode) {
final Object value = section.getObject(mode);
if (value == null) {
return new ArrayList<>();
}
if (value instanceof String s) {
return new ArrayList<>(Collections.singletonList(s));
}
return new ArrayList<>(section.getStringList(mode));
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of Maintenance - https://github.com/kennytv/Maintenance
* Copyright (C) 2018-2024 kennytv (https://github.com/kennytv)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.Collections;
import java.util.List;
public final class SetReasonCommand extends CommandInfo {
public SetReasonCommand(final MaintenancePlugin plugin) {
super(plugin, "setreason");
}
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (args.length < 2) {
sender.send(getHelpMessage());
return;
}
final String value = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
final String reason;
if (value.equalsIgnoreCase("none") || value.equalsIgnoreCase("clear") || value.equals("-")) {
reason = null;
} else {
reason = value;
}
getSettings().setActiveReason(reason);
sender.send(getMessage("setReason", "%REASON%", reason == null ? "-" : reason));
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
if (args.length != 2) {
return Collections.emptyList();
}
return Collections.singletonList("none");
}
}
@@ -19,9 +19,10 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.time.Duration;
import net.kyori.adventure.text.Component;
public final class StarttimerCommand extends CommandInfo {
@@ -44,6 +45,11 @@ public final class StarttimerCommand extends CommandInfo {
}
plugin.startMaintenanceRunnable(duration, true);
sender.send(getMessage("starttimerStarted", "%TIME%", plugin.getRunnable().getTime()));
final Component message = getMessage("starttimerStarted", "%TIME%", plugin.getRunnable().getTime());
sender.send(message);
plugin.sendWebhookMessage("webhookStarttimerStarted", DiscordWebhook.EventType.STARTTIMER_STARTED,
"%TIME%", plugin.getRunnable().getTime(),
"%TIMESTAMP%", plugin.getTargetTimestamp(duration));
}
}
@@ -15,28 +15,33 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.proxy.command.subcommand;
package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.proxy.MaintenanceProxyPlugin;
import eu.kennytv.maintenance.core.proxy.command.ProxyCommandInfo;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.runnable.MaintenanceRunnable;
import eu.kennytv.maintenance.core.util.SenderInfo;
import net.kyori.adventure.text.Component;
public final class StatusCommand extends ProxyCommandInfo {
public final class StatusCommand extends CommandInfo {
public StatusCommand(final MaintenanceProxyPlugin plugin) {
super(plugin, "singleserver.status");
public StatusCommand(final MaintenancePlugin plugin) {
super(plugin, "status");
}
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (getSettings().getMaintenanceServers().isEmpty()) {
sender.send(getMessage("singleServerMaintenanceListEmpty"));
return;
}
sender.send(getMessage("singleServerMaintenanceList"));
for (final String server : getSettings().getMaintenanceServers()) {
sender.send(getMessage("singleServerMaintenanceListEntry", "%SERVER%", server));
final MaintenanceRunnable task = plugin.getCurrentTask();
if (task != null) {
final String key = task.shouldEnable() ? "maintenanceStatusOffWithStartTimer" : "maintenanceStatusOnWithEndTimer";
sender.send(getMessage(key, "%TIME%", task.getTime()));
} else {
sender.send(getMessage(plugin.isMaintenance() ? "maintenanceStatusOn" : "maintenanceStatusOff"));
}
}
@Override
public Component getHelpMessage() {
return getMessage("helpGlobalStatus");
}
}
@@ -20,6 +20,10 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public final class ToggleCommand extends CommandInfo {
@@ -29,14 +33,45 @@ public final class ToggleCommand extends CommandInfo {
@Override
public void execute(final SenderInfo sender, final String[] args) {
if (checkArgs(sender, args, 1)) return;
if (args.length == 0 || args.length > 2) {
sender.send(getHelpMessage());
return;
}
final boolean maintenance = args[0].equalsIgnoreCase("on");
final String mode;
if (maintenance && args.length == 2) {
mode = args[1].toLowerCase(Locale.ROOT);
} else if (!maintenance && args.length == 1) {
mode = null;
} else if (maintenance) {
mode = null;
} else {
sender.send(getHelpMessage());
return;
}
if (maintenance == plugin.isMaintenance()) {
if (mode != null) {
plugin.getSettings().setActiveMode(mode);
return;
}
sender.send(getMessage(maintenance ? "alreadyEnabled" : "alreadyDisabled"));
return;
}
plugin.setMaintenance(maintenance);
plugin.setMaintenance(maintenance, mode);
}
@Override
public List<String> getTabCompletion(final SenderInfo sender, final String[] args) {
if (args.length != 2 || !args[0].equalsIgnoreCase("on")) {
return Collections.emptyList();
}
final List<String> modes = new ArrayList<>(getSettings().getPingMessages().getKeys());
modes.remove("default");
return modes;
}
}
@@ -20,11 +20,11 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.event.ClickEvent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.event.HoverEvent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.format.NamedTextColor;
import eu.kennytv.maintenance.lib.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
public final class UpdateCommand extends CommandInfo {
@@ -99,10 +99,10 @@ public final class WhitelistAddCommand extends CommandInfo {
}
private void addPlayerToWhitelist(final SenderInfo sender, final SenderInfo selected) {
if (getSettings().addWhitelistedPlayer(selected.getUuid(), selected.getName())) {
sender.send(getMessage("whitelistAdded", "%PLAYER%", selected.getName()));
if (getSettings().addWhitelistedPlayer(selected.uuid(), selected.name())) {
sender.send(getMessage("whitelistAdded", "%PLAYER%", selected.name()));
} else {
sender.send(getMessage("whitelistAlreadyAdded", "%PLAYER%", selected.getName()));
sender.send(getMessage("whitelistAlreadyAdded", "%PLAYER%", selected.name()));
}
}
}
@@ -20,10 +20,10 @@ package eu.kennytv.maintenance.core.command.subcommand;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.command.CommandInfo;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.util.Map;
import java.util.UUID;
import net.kyori.adventure.text.Component;
public final class WhitelistCommand extends CommandInfo {
@@ -24,6 +24,7 @@ import eu.kennytv.maintenance.core.util.SenderInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
public final class WhitelistRemoveCommand extends CommandInfo {
@@ -50,9 +51,9 @@ public final class WhitelistRemoveCommand extends CommandInfo {
if (args.length != 2) return Collections.emptyList();
final List<String> list = new ArrayList<>();
String matchNameLowerCase = args[1].toLowerCase();
String matchNameLowerCase = args[1].toLowerCase(Locale.ROOT);
for (final String name : getSettings().getWhitelistedPlayers().values()) {
if (name.toLowerCase().startsWith(matchNameLowerCase)) {
if (name.toLowerCase(Locale.ROOT).startsWith(matchNameLowerCase)) {
list.add(name);
}
}
@@ -70,7 +71,7 @@ public final class WhitelistRemoveCommand extends CommandInfo {
return;
}
removePlayerFromWhitelist(sender, selected.getUuid(), selected.getName());
removePlayerFromWhitelist(sender, selected.uuid(), selected.name());
});
}
@@ -79,13 +80,13 @@ public final class WhitelistRemoveCommand extends CommandInfo {
if (selected == null) {
removePlayerFromWhitelist(sender, uuid, uuid.toString());
} else {
removePlayerFromWhitelist(sender, selected.getUuid(), selected.getName());
removePlayerFromWhitelist(sender, selected.uuid(), selected.name());
}
});
}
private void removePlayerFromWhitelist(final SenderInfo sender, final UUID uuid, final String toReplace) {
if (getSettings().removeWhitelistedPlayer(uuid)) {
if (plugin.getSettings().removeWhitelistedPlayer(uuid)) {
sender.send(getMessage("whitelistRemoved", "%PLAYER%", toReplace));
} else {
sender.send(getMessage("whitelistNotFound"));
@@ -17,10 +17,8 @@
*/
package eu.kennytv.maintenance.core.config;
import com.google.common.collect.Sets;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
import java.util.HashMap;
@@ -40,18 +38,20 @@ import org.yaml.snakeyaml.Yaml;
public final class Config extends ConfigSection {
private static final String AWESOME_HEADER =
"###################################################################################################################\n" +
"# __ __ _ _ _ _ _ #\n" +
"# | \\/ | __ _(_)_ __ | |_ ___ _ __ __ _ _ __ ___ ___ | |__ _ _ | | _____ _ __ _ __ _ _| |___ __ #\n" +
"# | |\\/| |/ _` | | '_ \\| __/ _ \\ '_ \\ / _` | '_ \\ / __/ _ \\ | '_ \\| | | | | |/ / _ \\ '_ \\| '_ \\| | | | __\\ \\ / / #\n" +
"# | | | | (_| | | | | | || __/ | | | (_| | | | | (_| __/ | |_) | |_| | | < __/ | | | | | | |_| | |_ \\ V / #\n" +
"# |_| |_|\\__,_|_|_| |_|\\__\\___|_| |_|\\__,_|_| |_|\\___\\___| |_.__/ \\__, | |_|\\_\\___|_| |_|_| |_|\\__, |\\__| \\_/ #\n" +
"# |___/ |___/ #\n" +
"###################################################################################################################\n" +
"# You can report bugs here: https://github.com/kennytv/Maintenance/issues\n" +
"# If you need any other help/support, you can also join my Discord server: https://discord.gg/vGCUzHq\n" +
"# The config and language files use MiniMessage, NOT legacy text for input. Use https://webui.adventure.kyori.net/ to edit and preview the formatted text.\n" +
"# For a full list of formats and fancy examples of MiniMessage, see https://docs.adventure.kyori.net/minimessage/format.html\n";
"""
###################################################################################################################
# __ __ _ _ _ _ _ #
# | \\/ | __ _(_)_ __ | |_ ___ _ __ __ _ _ __ ___ ___ | |__ _ _ | | _____ _ __ _ __ _ _| |___ __ #
# | |\\/| |/ _` | | '_ \\| __/ _ \\ '_ \\ / _` | '_ \\ / __/ _ \\ | '_ \\| | | | | |/ / _ \\ '_ \\| '_ \\| | | | __\\ \\ / / #
# | | | | (_| | | | | | || __/ | | | (_| | | | | (_| __/ | |_) | |_| | | < __/ | | | | | | |_| | |_ \\ V / #
# |_| |_|\\__,_|_|_| |_|\\__\\___|_| |_|\\__,_|_| |_|\\___\\___| |_.__/ \\__, | |_|\\_\\___|_| |_|_| |_|\\__, |\\__| \\_/ #
# |___/ |___/ #
###################################################################################################################
# You can report bugs here: https://github.com/kennytv/Maintenance/issues
# If you need any other help/support, you can also join my Discord server: https://discord.gg/vGCUzHq
# The config and language files use MiniMessage, NOT legacy text for input. Use https://webui.advntr.dev/ to edit and preview the formatted text.
# For a full list of formats and fancy examples of MiniMessage, see https://docs.papermc.io/adventure/minimessage/format/
""";
private final Yaml yaml = createYaml();
private final File file;
private final Set<String> unsupportedFields;
@@ -61,11 +61,11 @@ public final class Config extends ConfigSection {
public Config(final File file, final String... unsupportedFields) {
super(null, "");
this.file = file;
this.unsupportedFields = unsupportedFields.length == 0 ? Collections.emptySet() : Sets.newHashSet(unsupportedFields);
this.unsupportedFields = unsupportedFields.length == 0 ? Collections.emptySet() : Set.of(unsupportedFields);
}
public void load() throws IOException {
final String data = new String(Files.readAllBytes(this.file.toPath()), StandardCharsets.UTF_8);
final String data = Files.readString(this.file.toPath());
final Map<String, Object> map = yaml.load(data);
this.values = map != null ? map : new LinkedHashMap<>();
this.comments = ConfigSerializer.deserializeComments(data);
@@ -101,12 +101,11 @@ public final class Config extends ConfigSection {
}
public void saveTo(final File file) throws IOException {
final byte[] bytes = toString().getBytes(StandardCharsets.UTF_8);
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
file.createNewFile();
Files.write(file.toPath(), bytes);
Files.writeString(file.toPath(), toString());
}
public void replaceComments(final Config fromConfig) {
@@ -17,7 +17,6 @@
*/
package eu.kennytv.maintenance.core.config;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -36,7 +35,7 @@ public class ConfigSection {
protected Map<String, Object> values;
ConfigSection(final Config root, final String currentPath) {
this(root, currentPath, new HashMap<>());
this(root, currentPath, new LinkedHashMap<>());
}
ConfigSection(final Config root, final String currentPath, final Map<String, Object> values) {
@@ -63,20 +62,18 @@ public class ConfigSection {
@Nullable
public Object getObject(final String key, final Object def) {
int nextSeparatorIndex = -1;
int sectionStartIndex;
final String[] parts = key.split("\\.");
ConfigSection section = this;
while ((nextSeparatorIndex = key.indexOf('.', sectionStartIndex = nextSeparatorIndex + 1)) != -1) {
section = section.getSection(key.substring(sectionStartIndex, nextSeparatorIndex));
if (section == null) return def;
for (int i = 0; i < parts.length - 1; i++) {
section = section.getSection(parts[i]);
if (section == null) {
return def;
}
}
final String subKey = key.substring(sectionStartIndex);
if (section == this) {
final Object result = values.get(subKey);
return result != null ? result : def;
}
return section.getObject(subKey, def);
final String valueKey = parts[parts.length - 1];
final Object o = section.values.get(valueKey);
return o != null ? o : def;
}
@Nullable
@@ -164,7 +161,7 @@ public class ConfigSection {
@Nullable
public String getString(final String key, @Nullable final String def) {
final Object o = getObject(key);
return o instanceof String ? (String) o : def;
return o instanceof String s ? s : def;
}
public int getInt(final String key) {
@@ -173,7 +170,7 @@ public class ConfigSection {
public int getInt(final String key, final int def) {
final Object o = getObject(key);
return o instanceof Number ? ((Number) o).intValue() : def;
return o instanceof Number number ? number.intValue() : def;
}
public double getDouble(final String key) {
@@ -182,7 +179,7 @@ public class ConfigSection {
public double getDouble(final String key, final double def) {
final Object o = getObject(key);
return o instanceof Number ? ((Number) o).doubleValue() : def;
return o instanceof Number number ? number.doubleValue() : def;
}
public long getLong(final String key) {
@@ -191,7 +188,7 @@ public class ConfigSection {
public long getLong(final String key, final long def) {
final Object o = getObject(key);
return o instanceof Number ? ((Number) o).longValue() : def;
return o instanceof Number number ? number.longValue() : def;
}
@Nullable
@@ -253,6 +250,6 @@ public class ConfigSection {
}
protected String getFullKeyInPath(final String key) {
return currentPath.isEmpty() ? key : currentPath + "." + key;
return currentPath.isEmpty() ? key : currentPath + '.' + key;
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of Maintenance - https://github.com/kennytv/Maintenance
* Copyright (C) 2018-2024 kennytv (https://github.com/kennytv)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.config;
import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class DisplayedMessages {
private static final Random RANDOM = new Random();
private final Map<String, List<String>> messages;
private final List<String> defaultMessages;
public DisplayedMessages(final Map<String, List<String>> messages) {
this.messages = messages;
this.defaultMessages = messages.get("default");
}
public List<String> getMessages(@Nullable final String key) {
Preconditions.checkNotNull(defaultMessages, "no default message configured in messages list");
if (key == null || messages.size() == 1) {
return defaultMessages;
}
return messages.getOrDefault(key, defaultMessages);
}
public String getRandomEntry(@Nullable final String key) {
final List<String> messages = getMessages(key);
return messages.get(RANDOM.nextInt(messages.size()));
}
public List<String> getDefaultMessages() {
return defaultMessages;
}
public boolean isEmpty() {
return messages.isEmpty();
}
public Set<String> getKeys() {
return messages.keySet();
}
}
@@ -24,6 +24,7 @@ import eu.kennytv.maintenance.core.Settings;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public final class MaintenanceDump {
@@ -35,12 +36,8 @@ public final class MaintenanceDump {
general = new ServerDump(plugin.getVersion(), plugin.getServerType().toString(), plugin.getServerVersion(), plugin.getMaintenanceServersDump());
configuration = new LinkedHashMap<>(settings.getConfig().getValues());
final Object o = configuration.get("mysql");
if (o instanceof Map) {
final Map<String, Object> map = new LinkedHashMap<>(((Map<String, Object>) o));
map.keySet().removeIf(key -> !key.equals("use-mysql") && !key.equals("update-interval"));
configuration.put("mysql", map);
}
addDBSettings("redis", "enabled");
redact("webhook", "url");
configuration.put("whitelisted-players", settings.getWhitelistedPlayers());
configuration.put("icon-exists", new File(plugin.getDataFolder(), "maintenance-icon.png").exists());
@@ -48,4 +45,28 @@ public final class MaintenanceDump {
jsonObject.add("plugins", MaintenancePlugin.GSON.toJsonTree(plugin.getPlugins()));
plugins = jsonObject;
}
private void redact(final String key, final String... subKeys) {
final Object o = configuration.get(key);
if (o instanceof Map) {
final Map<String, Object> map = new LinkedHashMap<>(((Map<String, Object>) o));
for (final String subKey : subKeys) {
final Object value = map.get(subKey);
if (value != null) {
map.put(subKey, value.toString().isEmpty() ? "" : "<redacted>");
}
}
configuration.put(key, map);
}
}
private void addDBSettings(final String key, final String... allowedSubKeys) {
final Set<String> allowedSubKeysSet = Set.of(allowedSubKeys);
final Object o = configuration.get(key);
if (o instanceof Map) {
final Map<String, Object> map = new LinkedHashMap<>(((Map<String, Object>) o));
map.keySet().removeIf(subKey -> !allowedSubKeysSet.contains(subKey));
configuration.put(key, map);
}
}
}
@@ -19,12 +19,7 @@ package eu.kennytv.maintenance.core.dump;
import java.util.List;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public final class PluginDump {
private final String name;
private final String version;
private final List<String> authors;
public record PluginDump(String name, String version, List<String> authors) {
public PluginDump(final String name, final String version, final List<String> authors) {
this.name = name;
this.version = version;
@@ -19,17 +19,5 @@ package eu.kennytv.maintenance.core.dump;
import java.util.List;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
public final class ServerDump {
private final String pluginVersion;
private final String platform;
private final String serverVersion;
private final List<String> maintenance;
public ServerDump(final String pluginVersion, final String platform, final String serverVersion, final List<String> maintenance) {
this.pluginVersion = pluginVersion;
this.platform = platform;
this.serverVersion = serverVersion;
this.maintenance = maintenance;
}
public record ServerDump(String pluginVersion, String platform, String serverVersion, List<String> maintenance) {
}
@@ -24,10 +24,10 @@ public final class ServerListPlusHook {
private final ServerListPlusCore serverListPlusCore;
public ServerListPlusHook(final Object serverListPlus) {
if (!(serverListPlus instanceof ServerListPlusPlugin)) {
if (!(serverListPlus instanceof ServerListPlusPlugin serverListPlusPlugin)) {
throw new IllegalArgumentException("Couldn't parse ServerListPlus instance!");
}
this.serverListPlusCore = ((ServerListPlusPlugin) serverListPlus).getCore();
this.serverListPlusCore = serverListPlusPlugin.getCore();
}
public void setEnabled(final boolean enable) {
@@ -19,17 +19,19 @@ package eu.kennytv.maintenance.core.listener;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.util.DiscordWebhook;
import eu.kennytv.maintenance.core.util.SenderInfo;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.TextComponent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.event.ClickEvent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.event.HoverEvent;
import eu.kennytv.maintenance.lib.kyori.adventure.text.format.NamedTextColor;
import eu.kennytv.maintenance.lib.kyori.adventure.text.format.TextDecoration;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import static eu.kennytv.maintenance.core.util.ComponentUtil.clickEvent;
public abstract class JoinListenerBase {
protected final MaintenancePlugin plugin;
@@ -48,13 +50,16 @@ public abstract class JoinListenerBase {
* @return true if the sender should be kicked
*/
protected boolean shouldKick(final SenderInfo sender, final boolean updateCheck) {
if (!settings.isMaintenance() || sender.hasMaintenancePermission("bypass") || settings.isWhitelisted(sender.getUuid())) {
if (!settings.isMaintenance() || sender.hasMaintenancePermission("bypass") || settings.isWhitelisted(sender.uuid())) {
if (updateCheck) {
updateCheck(sender);
}
return false;
}
if (settings.isWebhookEnabled()) {
plugin.sendWebhookMessage("webhookJoinNotification", DiscordWebhook.EventType.BLOCKED_JOIN, "%PLAYER%", sender.name());
}
return true;
}
@@ -64,19 +69,19 @@ public abstract class JoinListenerBase {
protected void updateCheck(final SenderInfo sender) {
if (!settings.hasUpdateChecks()) return;
if (!sender.hasPermission("maintenance.admin") || notifiedPlayers.contains(sender.getUuid())) return;
if (!sender.hasPermission("maintenance.admin") || notifiedPlayers.contains(sender.uuid())) return;
plugin.async(() -> {
if (!plugin.updateAvailable()) {
return;
}
notifiedPlayers.add(sender.getUuid());
notifiedPlayers.add(sender.uuid());
sender.sendPrefixedRich("<red>There is a newer version available: <green>Version " + plugin.getNewestVersion() + "<red>, you're on <green>" + plugin.getVersion());
final TextComponent text = Component.text().content("Download it at: ").color(NamedTextColor.RED)
.append(Component.text().content(MaintenancePlugin.HANGAR_URL).color(NamedTextColor.GOLD))
.append(Component.text().content(" (CLICK ME)").color(NamedTextColor.GRAY).decorate(TextDecoration.BOLD))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.OPEN_URL, MaintenancePlugin.HANGAR_URL))
.clickEvent(clickEvent(ClickEvent.Action.OPEN_URL, MaintenancePlugin.HANGAR_URL))
.hoverEvent(HoverEvent.showText(Component.text("Download the latest version").color(NamedTextColor.GREEN)))
.build();
sender.send(plugin.prefix().append(text));
@@ -19,17 +19,24 @@ package eu.kennytv.maintenance.core.runnable;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public class MaintenanceRunnable extends MaintenanceRunnableBase {
protected final String mode;
public MaintenanceRunnable(final MaintenancePlugin plugin, final Settings settings, final int seconds, final boolean enable) {
this(plugin, settings, seconds, enable, null);
}
public MaintenanceRunnable(final MaintenancePlugin plugin, final Settings settings, final int seconds, final boolean enable, @Nullable final String mode) {
super(plugin, settings, seconds, enable);
this.mode = mode;
}
@Override
protected void finish() {
plugin.setMaintenance(enable);
plugin.setMaintenance(enable, mode);
}
@Override
@@ -20,7 +20,7 @@ package eu.kennytv.maintenance.core.runnable;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.core.util.Task;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import net.kyori.adventure.text.Component;
public abstract class MaintenanceRunnableBase implements Runnable {
protected final MaintenancePlugin plugin;
@@ -19,14 +19,19 @@ package eu.kennytv.maintenance.core.runnable;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.core.Settings;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.time.Duration;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public final class MaintenanceScheduleRunnable extends MaintenanceRunnable {
private final int maintenanceDuration;
public MaintenanceScheduleRunnable(final MaintenancePlugin plugin, final Settings settings, final int secondsToEnable, final int maintenanceDuration) {
super(plugin, settings, secondsToEnable, true);
this(plugin, settings, secondsToEnable, maintenanceDuration, null);
}
public MaintenanceScheduleRunnable(final MaintenancePlugin plugin, final Settings settings, final int secondsToEnable, final int maintenanceDuration, @Nullable final String mode) {
super(plugin, settings, secondsToEnable, true, mode);
this.maintenanceDuration = maintenanceDuration;
}
@@ -0,0 +1,48 @@
/*
* This file is part of Maintenance - https://github.com/kennytv/Maintenance
* Copyright (C) 2018-2024 kennytv (https://github.com/kennytv)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.kennytv.maintenance.core.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import net.kyori.adventure.text.event.ClickEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class ComponentUtil {
private static final MethodHandle LEGACY_CLICK_EVENT = legacyClickEvent();
@SuppressWarnings({"rawtypes", "unchecked"})
public static ClickEvent clickEvent(final ClickEvent.Action action, final String value) {
if (LEGACY_CLICK_EVENT != null) {
try {
return (ClickEvent) LEGACY_CLICK_EVENT.invoke(action, value);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
return ClickEvent.clickEvent(action, ClickEvent.Payload.string(value));
}
private static @Nullable MethodHandle legacyClickEvent() {
try {
return MethodHandles.publicLookup().unreflect(ClickEvent.class.getDeclaredMethod("clickEvent", ClickEvent.Action.class, String.class));
} catch (final NoSuchMethodException | IllegalAccessException e) {
return null;
}
}
}
@@ -0,0 +1,74 @@
package eu.kennytv.maintenance.core.util;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import eu.kennytv.maintenance.core.Settings;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public final class DiscordWebhook {
private static final int COLOR_MAINTENANCE_ENABLED = 0xED4245; // Red
private static final int COLOR_MAINTENANCE_DISABLED = 0x57F287; // Green
private static final int COLOR_STARTTIMER = 0xFEE75C; // Yellow
private static final int COLOR_ENDTIMER = 0x5865F2; // Blue
private static final int COLOR_BLOCKED_JOIN = 0xE67E22; // Orange
public static void sendMessage(final String message, final EventType event, final Settings settings) throws IOException, InterruptedException {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("username", settings.getWebhookUsername());
final String avatarUrl = settings.getWebhookAvatarUrl();
if (!avatarUrl.isEmpty()) {
jsonObject.addProperty("avatar_url", avatarUrl);
}
final JsonObject embed = new JsonObject();
embed.addProperty("description", message);
embed.addProperty("color", getColorForEvent(event));
final JsonArray embeds = new JsonArray();
embeds.add(embed);
jsonObject.add("embeds", embeds);
try (final HttpClient client = HttpClient.newHttpClient()) {
final HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(settings.getWebhookUrl()))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonObject.toString()))
.build();
client.send(request, HttpResponse.BodyHandlers.discarding());
}
}
private static int getColorForEvent(final EventType event) {
return switch (event) {
case MAINTENANCE_ENABLED -> COLOR_MAINTENANCE_ENABLED;
case MAINTENANCE_DISABLED -> COLOR_MAINTENANCE_DISABLED;
case STARTTIMER_STARTED -> COLOR_STARTTIMER;
case ENDTIMER_STARTED -> COLOR_ENDTIMER;
case BLOCKED_JOIN -> COLOR_BLOCKED_JOIN;
};
}
public enum EventType {
MAINTENANCE_ENABLED("maintenance_status"),
MAINTENANCE_DISABLED("maintenance_status"),
STARTTIMER_STARTED("starttimer_started"),
ENDTIMER_STARTED("endtimer_started"),
BLOCKED_JOIN("blocked_join");
private final String configKey;
EventType(final String configKey) {
this.configKey = configKey;
}
public String configKey() {
return configKey;
}
}
}
@@ -17,28 +17,10 @@
*/
package eu.kennytv.maintenance.core.util;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import java.util.UUID;
import net.kyori.adventure.text.Component;
public final class DummySenderInfo implements SenderInfo {
private final UUID uuid;
private final String name;
public DummySenderInfo(final UUID uuid, final String name) {
this.uuid = uuid;
this.name = name;
}
@Override
public UUID getUuid() {
return uuid;
}
@Override
public String getName() {
return name;
}
public record DummySenderInfo(UUID uuid, String name) implements SenderInfo {
@Override
public boolean hasPermission(final String permission) {
@@ -19,15 +19,15 @@ package eu.kennytv.maintenance.core.util;
import eu.kennytv.maintenance.api.MaintenanceProvider;
import eu.kennytv.maintenance.core.MaintenancePlugin;
import eu.kennytv.maintenance.lib.kyori.adventure.text.Component;
import eu.kennytv.maintenance.lib.kyori.adventure.text.minimessage.MiniMessage;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
public interface SenderInfo {
UUID getUuid();
UUID uuid();
String getName();
String name();
boolean hasPermission(String permission);
@@ -20,7 +20,7 @@ package eu.kennytv.maintenance.core.util;
public enum ServerType {
BUNGEE("Bungee"),
SPIGOT("Spigot"),
PAPER("Paper"),
SPONGE("Sponge"),
VELOCITY("Velocity");
@@ -89,8 +89,7 @@ public final class Version implements Comparable<Version> {
@Override
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof Version)) return false;
final Version other = (Version) o;
if (!(o instanceof Version other)) return false;
return version.equals(other.version);
}
}
+60 -29
View File
@@ -8,8 +8,8 @@
###################################################################################################################
# You can report bugs here: https://github.com/kennytv/Maintenance/issues
# If you need any other help/support, you can also join my Discord server: https://discord.gg/vGCUzHq
# The config and language files use MiniMessage, NOT legacy text for input. Use https://webui.adventure.kyori.net/ to edit and preview the formatted text.
# For a full list of formats and fancy examples of MiniMessage, see https://docs.adventure.kyori.net/minimessage/format.html
# The config and language files use MiniMessage, NOT legacy text for input. Use https://webui.advntr.dev/ to edit and preview the formatted text.
# For a full list of formats and fancy examples of MiniMessage, see https://docs.papermc.io/adventure/minimessage/format/
# Changes the language of command feedback/messages.
# If you find missing translations or want to contribute, see https://crowdin.com/project/maintenance
@@ -20,10 +20,19 @@ language: en
# Enables maintenance mode.
maintenance-enabled: false
# The currently active maintenance mode key (used for mode-specific messages/icons).
# Use 'default' or leave empty to use fallback messages.
active-mode: default
# Value for the %REASON% placeholder in maintenance messages.
# This can be changed via /maintenance setreason <text>.
active-reason: ""
# Enables maintenance on certain servers managed by your proxy instance(s).
# The value is the mode key used for ping/player count messages for that server.
proxied-maintenance-servers:
- PaperServer1
# - AnotherServer
PaperServer1: default
# AnotherServer: release
# If maintenance is enabled on a proxied server with players on it, they will be sent to the fallback server instead of being kicked off the network.
# If the fallback server is not reachable/not set, they will just be kicked off the network.
@@ -36,35 +45,53 @@ waiting-server: ""
# The message (MOTD) shown in the multiplayer server list motd when maintenance is enabled.
# If you put in multiple entries, one of them will be chosen randomly on every ping.
# If running an endtimer, the time left can be displayed by including '%TIMER%' in a ping message.
# The type of mode can be defined in the command, or will be the default mode.
ping-message:
enabled: true
# Always uses 'default' if not defined in the command or not present in the config.
# Additional examples could look as follows:
# default:
# - "<red>Currently under maintenance<br><gradient:#fbffc2:#0fffff>We will be back soon!"
# - "<red>Wooow you found our secondary MOTD!"
# reason:
# - "<red>Currently under maintenance<br><gradient:#fbffc2:#0fffff>%REASON%"
# release:
# - "<gold>We're currently preparing the release, come back soon!"
messages:
- "<red>Currently under maintenance<br><gradient:#fbffc2:#0fffff>We will be back soon!"
default:
- "<red>Currently under maintenance<br><gradient:#fbffc2:#0fffff>We will be back soon!"
# If set to true and an endtimer is currently running, a message from this pool will be chosen
# instead of the ones above, so you can have different messages for when an endtimer is running/not running.
enable-timer-specific-messages: true
# If running an endtimer, the time left can be displayed by including '%TIMER%' in a ping message.
timer-messages:
- "<red>Currently under maintenance<br><gradient:#fbffc2:#fffff>Come back in:</gradient> <color:#aa55ee>%TIMER%"
default:
- "<red>Currently under maintenance<br><gradient:#fbffc2:#fffff>Come back in:</gradient> <color:#aa55ee>%TIMER%"
# If enabled, the message below will be shown in the top right corner of the server in the server list, where the player count would normally be displayed.
# Same as the ping-message, you can define messages for different modes.
# You can use '%ONLINE%' and '%MAX%' if you want to include the player count in a custom message (e.g. "Maintenance %ONLINE%/%MAX%").
# DOES NOT SUPPORT RGB!
player-count-message:
enabled: true
message: "<dark_red>Maintenance"
messages:
default: "<dark_red>Maintenance"
# If set to true and an endtimer is currently running, the timer message will be used instead of the normal one.
enable-timer-specific-message: false
timer-message: "<dark_red>Come back in: <yellow>%TIMER%"
timer-messages:
default: "<dark_red>Come back in: <yellow>%TIMER%"
# Is shown when you move your mouse above the text in the top right corner of the server in the server list, where the player count would normally be displayed.
# Same as the ping-message, you can define messages for different modes.
# DOES NOT SUPPORT RGB!
player-list-hover-message:
enabled: true
message: "<red>Currently under<br><red>maintenance"
messages:
default: "<red>Currently under<br><red>maintenance"
# If set to true and an endtimer is currently running, the timer message will be used instead of the normal one.
enable-timer-specific-message: false
timer-message: "<red>Come back in:<br><red><yellow>%TIMER%"
timer-messages:
default: "<red>Come back in:<br><red><yellow>%TIMER%"
# Any extra commands inside the arrays will be executed when maintenance is enabled/disabled.
# Example: commands-on-maintenance-enable: ["say hello!", "stop"]
@@ -104,23 +131,27 @@ continue-endtimer-after-restart:
# If using the timer command: In what intervalls before enabling/disabling maintenance there will be a broadcast.
timer-broadcast-for-seconds: [1200, 900, 600, 300, 120, 60, 30, 20, 10, 5, 4, 3, 2, 1]
# If you have 2 or more proxy instances, the database connection will make sure all proxies
# have the same maintenance status, so you won't have to enable/disable it on every proxy by hand.
mysql:
use-mysql: false
host: host
port: 3306
database: database
username: username
password: password
# Requests to the database will only be made at least x seconds after the last request,
# so there won't be a request on every single ping on a server with many players.
#
# Set this to 0 to make a database request on (almost) every single ping.
# (0 is not recommended if having more than a hundred concurrent players, then I'd recommend keeping it from 10-60).
update-interval: 15
# If you for some reason cannot establish SSL connections to your database, you may disable it here (not recommended).
use-ssl: true
# Allows sending a Discord webhook on certain events.
webhook:
enabled: false
# Possible values: maintenance_status, starttimer_started, endtimer_started, blocked_join
enabled-events: ["maintenance_status", "starttimer_started", "endtimer_started"]
# Copy and paste the webhook url from Server Settings --> Apps Integrations --> Webhooks
url: ""
# The username displayed for the webhook bot message.
username: "Maintenance"
# The avatar URL used for the webhook bot message. Leave empty to use the default Discord webhook avatar.
avatar-url: ""
# If you have 2 or more proxy instances, the Redis connection will make sure all proxies
# have the same maintenance status (global as well as per server) and whitelisted players,
# so you won't have to update these on every proxy by hand.
redis:
enabled: false
# Make sure your Redis instance is properly protected.
# URI format: redis[s]://[[username][:password]@][host][:port][/db-number]
# E.g. an SSL connection to a password protected redis could look like this: "rediss://user:password@127.0.0.1:6379"
uri: "redis://localhost:6379"
# If disabled, you will no longer receive any messages if there is an update.
# Not recommended to disable, as new versions generally tend to run better and with fewer bugs.
@@ -128,4 +159,4 @@ mysql:
update-checks: true
# Used for autoupdating the config, do not change this value.
config-version: 9
config-version: 12
+124
View File
@@ -0,0 +1,124 @@
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>No tens permís per executar aquesta ordre."
kickmessage: "<red>El servidor està en manteniment!<br>Torna-ho a provar més tard!"
maintenanceActivated: "<prefix><gold>El mode de manteniment s'ha activat."
maintenanceDeactivated: "<prefix><gold>El mode de manteniment s'ha desactivat."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>El manteniment ja està activat!"
alreadyDisabled: "<prefix><red>El manteniment ja està desactivat!"
endtimerBroadcast: "<prefix><gray>El mode de manteniment es desactivarà en <gold>%TIME%<gray>."
endtimerStarted: "<prefix><green>Temporitzador iniciat: <gray>El mode de manteniment es desactivarà en <gold>%TIME%<gray>."
starttimerBroadcast: "<prefix><gray>El mode de manteniment s'activarà en <gold>%TIME%<gray>."
starttimerStarted: "<prefix><green>Temporitzador iniciat: <gray>El mode de manteniment s'activarà en <gold>%TIME%<gray>."
scheduletimerBroadcast: "<prefix><gray>El mode de manteniment s'activarà en <gold>%TIME% <gray>i durarà <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><green>Temporitzador iniciat: <gray>El mode de manteniment s'activarà en <gold>%TIME% <gray>i durarà <gold>%DURATION%<gray>."
timerAlreadyRunning: "<prefix><red>Ja hi ha un temporitzador programat!"
timerNotRunning: "<prefix><red>No hi ha cap temporitzador en marxa."
timerCancelled: "<prefix><red>S'ha cancel·lat el temporitzador actual."
timerTooLong: "<prefix><red>La durada ha de ser inferior a 28 dies!"
timerMotdDisabled: "<prefix><red>Has de configurar 'enable-timerspecific-messages' a 'true' al fitxer de configuració per utilitzar/editar missatges MOTD específics del temporitzador."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
joinNotification: "<prefix><yellow>%PLAYER% <red>ha intentat unir-se al servidor."
motdList: "<prefix><gray>Llista dels teus missatges MOTD de manteniment:"
motdListEmpty: "<prefix><red>No tens cap missatge MOTD de manteniment configurat!"
reload: "<prefix><green>S'han recarregat la configuració, la llista de jugadors permitits, el fitxer d'idioma i la icona de manteniment."
removedMotd: "<prefix><green>S'ha eliminat el missatge MOTD número %INDEX%."
removeMotdError: "<prefix><red>Només tens un missatge MOTD, no en pots eliminar cap!"
setMotd: "<prefix><green>S'ha configurat la línia %LINE% del missatge MOTD de manteniment %INDEX% a %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Actualment tens %MOTDS% missatges MOTD, has de triar un número entre 1 i %NEWAMOUNT%."
setMotdLineError: "<prefix><red>El segon argument ha de ser el número de línia (1 o 2)!"
updateDownloading: "<prefix><red><bold>S'està descarregant l'actualització..."
updateFailed: "<prefix><dark_red><bold>L'actualització ha fallat!"
updateFinished: "<prefix><green><bold>L'actualització s'ha completat! Per evitar problemes amb tasques i completar l'actualització, has de reiniciar el servidor!"
whitelistedPlayers: "<prefix><gold>Jugadors permesos durant el manteniment:"
whitelistedPlayersFormat: "<dark_gray>- <yellow>%NAME% <dark_gray>(<gray>%UUID%<dark_gray>)"
whitelistAdded: "<prefix><green>S'ha afegit <aqua>%PLAYER% <green>a la llista blanca de manteniment!"
whitelistAlreadyAdded: "<prefix><aqua>%PLAYER% <red>ja està a la llista blanca de manteniment!"
whitelistRemoved: "<prefix><green>S'ha eliminat <aqua>%PLAYER% <green>de la llista blanca de manteniment!"
whitelistNotFound: "<prefix><red>Aquest jugador no està a la llista blanca de manteniment!"
whitelistEmpty: "<prefix><red>La llista blanca de manteniment està buida! Utilitza <yellow>/maintenance add <player/uuid> <red>per afegir algú!"
playerNotFound: "<prefix><red>No s'ha trobat cap jugador amb aquest nom al servidor."
playerNotFoundUuid: "<prefix><red>No s'ha trobat cap jugador amb aquesta UUID."
playerNotOnline: "<prefix><red>No hi ha cap jugador en línia amb aquest nom."
offlinePlayerFetchError: "<prefix><red>Hi ha hagut un error en obtenir les dades del jugador fora de línia. Torna-ho a provar més tard."
invalidUuid: "<prefix><red>Format d'UUID no vàlid!"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
sentToWaitingServer: "<prefix><red>Se t'ha enviat a un servidor d'espera!"
forceWaitingServer: "<prefix><red>No pots abandonar el servidor d'espera mentre el manteniment estigui actiu!"
serverNotFound: "<prefix><red>No hi ha cap servidor amb aquest nom registrat al proxy!"
singleTimerAlreadyRunning: "<prefix><red>Ja hi ha un temporitzador programat per a aquest servidor!"
singleTimerCancelled: "<prefix><red>S'ha cancel·lat el temporitzador actual per al servidor <yellow>%SERVER%<red>."
singleTimerNotRunning: "<prefix><red>No hi ha cap temporitzador en marxa per a aquest servidor."
singleEndtimerBroadcast: "<prefix><gray>El mode de manteniment al servidor %SERVER% es desactivarà en <gold>%TIME%<gray>."
singleEndtimerStarted: "<prefix><green>Temporitzador iniciat: <gray>El mode de manteniment al servidor %SERVER% es desactivarà en <gold>%TIME%<gray>."
singleStarttimerBroadcast: "<prefix><gray>El mode de manteniment al servidor %SERVER% s'activarà en <gold>%TIME%<gray>."
singleStarttimerStarted: "<prefix><green>Temporitzador iniciat: <gray>El mode de manteniment al servidor %SERVER% s'activarà en <gold>%TIME%<gray>."
singleScheduletimerBroadcast: "<prefix><gray>El mode de manteniment al servidor %SERVER% s'activarà en <gold>%TIME% <gray>i durarà <gold>%DURATION%<gray>."
singleScheduletimerStarted: "<prefix><green>Temporitzador iniciat: <gray>El mode de manteniment al servidor %SERVER% s'activarà en <gold>%TIME% <gray>i durarà <gold>%DURATION%<gray>."
singleMaintenanceKick: "<prefix><red>El servidor %SERVER% està en manteniment! Torna-ho a provar més tard!"
singleMaintenanceKickComplete: "<red>El servidor %SERVER% està en manteniment!<br>Pots tornar a connectar-te per anar a un altre servidor!"
singleMaintenanceActivated: "<prefix><gold>El mode de manteniment s'ha activat al servidor <yellow>%SERVER%<gold>."
singleMaintenanceDeactivated: "<prefix><gold>El mode de manteniment s'ha desactivat al servidor <yellow>%SERVER%<gold>."
singleServerAlreadyEnabled: "<prefix><red>El manteniment ja està activat al servidor <yellow>%SERVER%<red>!"
singleServerAlreadyDisabled: "<prefix><red>El manteniment ja està desactivat al servidor <yellow>%SERVER%<red>!"
singleServerMaintenanceList: "<prefix><gray>Servidors proxy que tenen el manteniment activat:"
singleServerMaintenanceListEmpty: "<prefix><gray>No hi ha cap servidor proxy en manteniment."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "segon"
seconds: "segons"
minute: "minut"
minutes: "minuts"
hour: "hora"
hours: "hores"
helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MAX% <dark_gray>]========"
helpPageNotFound: "<prefix><red>No hi ha cap pàgina amb aquest número!"
helpNextPage: "<gray>Fes servir <aqua>/maintenance help %PAGE% <gray>per anar a la següent pàgina d'ajuda."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Si està actiu, cancel·la el temporitzador actual)"
helpEndtimer: "<gold>/maintenance endtimer <desactivar en> <gray>(Després del temps indicat, p. ex. 1h5m, es desactivarà el manteniment)"
helpStarttimer: "<gold>/maintenance starttimer <activar en> <gray>(Després del temps indicat, p. ex. 1h5m, s'activarà el manteniment)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <activar en> <durada del manteniment> <gray>(Després del temps indicat, p. ex. 1h5m, s'activarà el manteniment)"
helpDebug: "<gold>/maintenance debug <gray>(Activa el registre de depuració)"
helpDump: "<gold>/maintenance dump <gray>(Extreu informació del servidor, útil per informes d'errors)"
helpHelp: "<gold>/maintenance help [pàgina] <gray>(Mostra aquesta finestra d'ajuda)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(Mostra els missatges MOTD de manteniment. Si s'especifica 'timer', es mostraran els MOTD del temporitzador)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <índex> <gray>(Elimina un missatge MOTD de manteniment. Si s'usa 'timer', s'eliminarà un missatge específic del temporitzador)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <índex> <1/2> <missatge> <gray>(Configura un missatge MOTD per al manteniment. Si s'usa 'timer', es configurarà un missatge específic del temporitzador)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Recarrega el fitxer de configuració, la llista de permitits i la icona del servidor)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Descarrega la versió més recent del plugin al servidor)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Mostra tots els jugadors permitits durant el manteniment)"
helpWhitelistAdd: "<gold>/maintenance add <nom/uuid> <gray>(Afegeix el jugador a la llista de permitits del manteniment)"
helpWhitelistRemove: "<gold>/maintenance remove <nom/uuid> <gray>(Elimina el jugador de la llista de permitits del manteniment)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [servidor] <gray>(Si està actiu, cancel·la el temporitzador del servidor)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Mostra tots els servidors proxy en manteniment)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 5
+30 -10
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>Du har ikke tilladelse til at udføre denne kommando."
kickmessage: "<red>Serveren er i øjeblikket under vedligeholdelse!<br>Prøv igen senere!"
maintenanceActivated: "<prefix><gold>Vedligeholdelsestilstand er nu aktiveret."
maintenanceDeactivated: "<prefix><gold>Vedligeholdelsestilstand er nu deaktiveret."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>Vedligeholdelsestilstand er allerede aktiveret!"
alreadyDisabled: "<prefix><red>Vedligeholdelsestilstand er allerede deaktiveret!"
endtimerBroadcast: "<prefix><gray>Vedligeholdelsestilstand vil blive deaktiveret om <gold>%TIME%<gray>."
@@ -29,6 +33,7 @@ reload: "<prefix><green>Genindlæs konfigurationsfilen, hvidlistede spillere, sp
removedMotd: "<prefix><green>Slet motd nummer %INDEX%."
removeMotdError: "<prefix><red>Du har kun én motd, så du kan ikke slette nogen!"
setMotd: "<prefix><green>Angiv linje %LINE% af %INDEX%. vedligeholdelses motd til %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Du har %MOTDS% motds, så du skal angive et nummer mellem 1 og %NEWAMOUNT%."
setMotdLineError: "<prefix><red>Det andet argument skal være linjenummer (1 or 2)!"
updateDownloading: "<prefix><red><bold>Downloader opdatering..."
@@ -44,7 +49,7 @@ whitelistEmpty: "<prefix><red>Vedligeholdelseshvidlisten er tom! Brug <yellow>/m
playerNotFound: "<prefix><red>Ingen spillere med dette navn har spillet på denne server før."
playerNotFoundUuid: "<prefix><red>Ingen spiller med den uuid kunne findes."
playerNotOnline: "<prefix><red>Der er ingen spiller online med det navn."
offlinePlayerFetchError: "<prefix><red>There was an error while fetching offline player. Please try again later."
offlinePlayerFetchError: "<prefix><red>Der opstod en fejl under hentning af offline afspiller. Prøv igen senere."
invalidUuid: "<prefix><red>Ugyldigt uuid-format!"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
sentToWaitingServer: "<prefix><red>Du er blevet sendt til en venteserver!"
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>Vedligeholdelsestilstand er allerede
singleServerMaintenanceList: "<prefix><gray>Proxyservere, der har Vedligeholdelsestilstand aktiveret:"
singleServerMaintenanceListEmpty: "<prefix><gray>Der er ingen proxy-servere, der er under vedligeholdelse."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "sekund"
seconds: "sekunder"
minute: "minut"
@@ -87,18 +94,31 @@ helpHelp: "<gold>/maintenance help [page] <gray>(Viser dette smukke hjælpevindu
helpMotd: "<gold>/maintenance motd [timer] <gray>(Viser den aktuelle vedligeholdelses motds. Hvis 'timer' er angivet, bliver dennes motds vist)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Sletter en vedligeholdelses motd. Hvis 'timer' er angivet, vil en tidsspecifik pingbesked blive slettet)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(Angiver en motd for vedligeholdelsestilstand. Hvis 'timer' er angivet, vil en tidsspecifik pingbesked blive angivet)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Genindlæser konfigurationsfilen, hvidlisten og serverikonet)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Aktiverer/deaktiverer vedligeholdelsestilstand)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Downloader den nyeste version af plugin'et på din server)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Viser alle hvidlistede spillere for vedligeholdelsestilstand)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(Tilføjer spilleren til vedligeholdelseshvidlisten, så de kan forbinde til serveren, selvom vedligeholdelsestilstand er aktiveret)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Sletter spilleren fra vedligeholdelseshvidlisten)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(Hvis timeren kører, vil den blive afbrudt)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Aktiverer/deaktiverer vedligeholdelsestilstand)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Viser alle proxyservere, der har Vedligeholdelsestilstand aktiveret)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+33 -13
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>Du hast nicht die erforderliche Berechtigung, um diesen Befehl auszuführen."
kickmessage: "<red>Der Server befindet sich momentan im Wartungsmodus!<br>Bitte versuche es später nochmal!"
maintenanceActivated: "<prefix><gold>Der Wartungsmodus ist jetzt aktiviert."
maintenanceDeactivated: "<prefix><gold>Der Wartungsmodus ist nun deaktiviert."
maintenanceStatusOn: "<prefix><gold>Wartungsmodus ist derzeit <green>aktiviert<gold>."
maintenanceStatusOff: "<prefix><gold>Wartungsmodus ist momentan <red>deaktiviert<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Wartungsmodus ist momentan <green>aktiviert <grey>und wird deaktiviert in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Wartungsmodus ist momentan <red>deaktiviert <grey>und wird aktiviert in <gold>%TIME%."
alreadyEnabled: "<prefix><red>Der Wartungsmodus ist bereits aktiviert!"
alreadyDisabled: "<prefix><red>Der Wartungsmodus ist bereits deaktiviert!"
endtimerBroadcast: "<prefix><gray>Der Wartungsmodus wird deaktiviert in: <gold>%TIME%<gray>."
@@ -26,9 +30,10 @@ joinNotification: "<prefix><yellow>%PLAYER% <red>hat versucht dem Server beizutr
motdList: "<prefix><gray>Liste deiner Motds, die während Maintenance angezeigt werden:"
motdListEmpty: "<prefix><red>Du hast keine Maintenance MOTDs gesetzt!"
reload: "<prefix><green>Die Config, die Liste der Spieler die während Maintenance beitreten können, die Sprachdateien und das Icon, das während der Maintenance angezeigt wird, wurden reloaded."
removedMotd: "<prefix><green>Die %INDEX%. Wartungs-MOTD wurde entfernt."
removedMotd: "<prefix><green>Die %INDEX%. Maintenance MOTD wurde entfernt."
removeMotdError: "<prefix><red>Du hast nur eine Maintenance MOTD eingestellt, daher kannst du auch keine entfernen!"
setMotd: "<prefix><green>Die %LINE%. Zeile der %INDEX%. Maintenance MOTD wurde geändert zu: %MOTD%"
setReason: "<prefix><green>Grund für die Wartung gesetzt auf: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Momentan gibt es %MOTDS% MOTDs, du musst also eine Zahl, die zwischen 1 und %NEWAMOUNT% liegt, wählen."
setMotdLineError: "<prefix><red>Das zweite Argument des Befehls muss die Zeilennummer sein (1 oder 2)!"
updateDownloading: "<prefix><red><bold>Das Update wird heruntergeladen..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>Der Wartungsmodus ist bereits aus auf
singleServerMaintenanceList: "<prefix><gray>Unterserver, die im Wartungsmodus sind:"
singleServerMaintenanceListEmpty: "<prefix><gray>Es befindet sich momentan kein Unterserver im Wartungsmodus."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>startet in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>endet in <gold>%TIME%"
second: "Sekunde"
seconds: "Sekunden"
minute: "Minute"
@@ -84,21 +91,34 @@ helpScheduleTimer: "<gold>/maintenance scheduletimer <Aktiviere in> <Wartungsdau
helpDebug: "<gold>/maintenance debug <gray>(Aktiviert Debug Nachrichten in der Konsole)"
helpDump: "<gold>/maintenance dump <gray>(Sammelt wichtige Informationen für Bugreports)"
helpHelp: "<gold>/maintenance help [Seite] <gray>(Zeigt diese wunderschöne Hilfe)"
helpMotd: "<gold>/maintenance motd [Timer] <gray>(Zeigt die momentan gesetzten Maintenance Motds an. Mit dem 'timer' Argument werden die timerspezifischen Motds angezeigt)"
helpRemoveMotd: "<gold>/maintenance removemotd [Timer] <Index> <gray>(Entfernt eine Maintenance Motd. Mit dem 'timer' Argument wird eine timerspezifischen Motd entfernt)"
helpSetMotd: "<gold>/maintenance setmotd [Timer] <Index> <1/2> <Nachricht> <gray>(Setzte eine Motd für den Wartungsmodus. Mit dem 'timer' Argument wird eine timerspezifische Motd gesetzt)"
helpMotd: "<gold>/maintenance motd [Timer] [Modus] <gray>(Zeigt die momentan gesetzten Maintenance Motds an. Mit dem 'timer' Argument werden die timerspezifischen Motds angezeigt)"
helpRemoveMotd: "<gold>/maintenance removemotd [Timer] [Modus] <Index> <gray>(Entfernt eine Maintenance Motd. Mit dem 'timer' Argument wird eine timerspezifischen Motd entfernt)"
helpSetMotd: "<gold>/maintenance setmotd [Timer] [Modus] <Index> <1/2> <Nachricht> <gray>(Setzte eine Motd für den Wartungsmodus. Mit dem 'timer' Argument wird eine timerspezifische Motd gesetzt)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Setzt oder löscht den %REASON% Platzhalter)"
helpReload: "<gold>/maintenance reload <gray>(Lädt Config, Language und Whitelist Dateien sowie das Maintenance Icon neu)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Aktiviert/deaktiviert den Wartungsmodus)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Zeigt den aktuellen Wartungsstatus und alle laufenden Timer an)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Aktiviert/deaktiviert den Wartungsmodus mit einem optionalen Anzeigemodus beim Aktivieren)"
helpUpdate: "<gold>/maintenance update <gray>(Sucht nach neuen Updates für das Plugin und lädt diese auf den Server)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Zeigt alle gewhitelisteten Spieler für den Wartungsmodus an)"
helpWhitelistAdd: "<gold>/maintenance add <Name/UUID> <gray>(Whitelistet einen Spieler für den Wartungsmodus, sodass dieser während Wartungen den Server betreten kann)"
helpWhitelistRemove: "<gold>/maintenance remove <Name/UUID> <gray>(Entfernt einen Spieler aus der Wartungsmodus Whitelist)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [Server] <gray>(Falls momentan ein Timer läuft, wird dieser abgebrochen)"
helpSingleEndtimer: "<gold>/maintenance endtimer [Server] <Deaktiviere in> <gray>(Sobald der Timer, bspw. 1h5m, abläuft wird der Wartungsmodus deaktiviert)"
helpSingleStarttimer: "<gold>/maintenance starttimer [Server] <Aktiviere in> <gray>(Sobald der Timer, bspw. 1h5m, abläuft wird der Wartungsmodus aktiviert)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [Server] <Starte in> <Wartungsdauer> <gray>(Sobald der Timer, bspw. 1h5m, abläuft wird der Wartungsmodus für die gegebene Länge aktiviert)"
helpSingleToggle: "<gold>/maintenance <on/off> [Server] <gray>(Aktiviert/deaktiviert den Wartungsmodus)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Aktiviert/deaktiviert den Wartungsmodus global oder auf einem bestimmten Server mit optionalem Anzeigemodus beim Aktivieren)"
helpStatus: "<gold>/maintenance status <gray>(Zeigt alle Server an, auf denen momentan der Wartungsmodus aktiviert ist)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Wartungsmodus aktiviert.**"
webhookMaintenanceDeactivated: "**Wartungsmodus deaktiviert.**"
webhookStarttimerStarted: "Der Wartungsmodus wird aktiviert in <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Der Wartungsmodus wird deaktiviert in <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Beitritt gesperrt: `%PLAYER%` hat versucht, während den Wartungsarbeiten beizutreten."
webhookSingleMaintenanceActivated: "Der Wartungsmodus wurde aktiviert auf `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Der Wartungsmodus wurde deaktiviert auf `%SERVER%`."
webhookSingleStarttimerStarted: "Der Wartungsmodus wird aktiviert auf `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Der Wartungsmodus wird deaktiviert auf `%SERVER%` <t:%TIMESTAMP%:R>(`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 4
+32 -12
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>You do not have the permission to execute that command."
kickmessage: "<red>The server is currently under maintenance!<br>Try again later!"
maintenanceActivated: "<prefix><gold>Maintenance mode is now activated."
maintenanceDeactivated: "<prefix><gold>Maintenance mode is now deactivated."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>Maintenance is already enabled!"
alreadyDisabled: "<prefix><red>Maintenance is already disabled!"
endtimerBroadcast: "<prefix><gray>Maintenance mode will be disabled in <gold>%TIME%<gray>."
@@ -29,6 +33,7 @@ reload: "<prefix><green>Reloaded config, whitelistedplayers, language file and t
removedMotd: "<prefix><green>Removed motd number %INDEX%."
removeMotdError: "<prefix><red>You only have one motd, so you cannot remove any!"
setMotd: "<prefix><green>Set line %LINE% of the %INDEX%. maintenance motd to %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>You currently have %MOTDS% motds, so you have to pick a number between 1 and %NEWAMOUNT%."
setMotdLineError: "<prefix><red>The second argument has to be the line number (1 or 2)!"
updateDownloading: "<prefix><red><bold>Downloading update..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>Maintenance is already disabled on se
singleServerMaintenanceList: "<prefix><gray>Proxied servers, that have maintenance enabled:"
singleServerMaintenanceListEmpty: "<prefix><gray>There are no proxied servers that are under maintenance."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "second"
seconds: "seconds"
minute: "minute"
@@ -84,21 +91,34 @@ helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance du
helpDebug: "<gold>/maintenance debug <gray>(Enables some debug logging)"
helpDump: "<gold>/maintenance dump <gray>(Dumps some server information, used for bug reports)"
helpHelp: "<gold>/maintenance help [page] <gray>(Shows this beautiful help window)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(Lists the currently set maintenance motds. If specifying 'timer', the timer motds are shown)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Removes a maintenance motd. If using 'timer' as an argument, a timerspecific pingmessage will be removed)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(Sets a motd for maintenance mode. If using 'timer' as an argument, a timerspecific pingmessage will be set)"
helpMotd: "<gold>/maintenance motd [timer] [mode] <gray>(Lists maintenance motds. If 'timer' is used, timer-specific motds are shown)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] [mode] <index> <gray>(Removes a maintenance motd in the selected mode)"
helpSetMotd: "<gold>/maintenance setmotd [timer] [mode] <index> <1/2> <message> <gray>(Sets a maintenance motd in the selected mode)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Reloads the config file, whitelist file and the server-icon)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Enables/disables maintenance mode)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Remotely downloads the newest version of the plugin onto your server)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Shows all whitelisted players for the maintenance mode)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(Adds the player to the maintenance whitelist, so they can join the server even though maintenance is enabled)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Removes the player from the maintenance whitelist)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(If running, the current timer will be aborted)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Enables/disables maintenance mode)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Lists all proxied servers, that are currently under maintenance)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+47 -27
View File
@@ -1,39 +1,44 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<prefix><dark_red>No tienes permiso para ejecutar este comando."
kickmessage: "<red>¡El servidor se encuentra en mantenimiento!<br>¡Intentalo de nuevo más tarde!"
maintenanceActivated: "<prefix><gold>Mantenimiento <gray>activado."
maintenanceDeactivated: "<prefix><gold>Modo mantenimiento desactivado."
alreadyEnabled: "<prefix><gray>¡Modo mantenimiento se encuentra habilitado!"
alreadyDisabled: "<prefix><gray>¡Modo mantenimiento se encuentra deshabilitado!"
maintenanceActivated: "<prefix><gold>Modo Mantenimiento activado."
maintenanceDeactivated: "<prefix><gold>Modo Mantenimiento desactivado."
maintenanceStatusOn: "<prefix><gold>El modo de mantenimiento está actualmente <green>activado<gold>."
maintenanceStatusOff: "<prefix><gold>El modo de mantenimiento está actualmente <red>desactivado<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>El modo de mantenimiento está actualmente <green>habilitado <grey>y se desactivará en <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>El modo de mantenimiento está actualmente <red>desactivado <grey>y se activará en <gold>%TIME%."
alreadyEnabled: "<prefix><red>¡Modo mantenimiento ya está activado!"
alreadyDisabled: "<prefix><red>¡Modo mantenimiento ya está desactivado!"
endtimerBroadcast: "<prefix><gray>El modo mantenimiento se desactivará en <gold>%TIME%<gray>."
endtimerStarted: "<prefix><gray>Temporizador iniciado: El modo mantenimiento se desactivará en <gold>%TIME%<gray>."
starttimerBroadcast: "<prefix><gray>El modo mantenimiento se habilitara en <gold>%TIME%<gray>."
starttimerStarted: "<prefix><gray>Temporizador iniciado: El modo mantenimiento se activará en <gold>%TIME%<gray>."
scheduletimerBroadcast: "<prefix><gray>Maintenance mode will be enabled in <gold>%TIME% <gray>and will last for <gold>%DURATION%<gray>."
endtimerStarted: "<prefix><green>Temporizador iniciado: <gray>El modo mantenimiento se desactivará en <gold>%TIME%<gray>."
starttimerBroadcast: "<prefix><gray>El modo mantenimiento se activará en <gold>%TIME%<gray>."
starttimerStarted: "<prefix><green>Temporizador iniciado: <gray>El modo mantenimiento se activará en <gold>%TIME%<gray>."
scheduletimerBroadcast: "<prefix><gray>Temporizador iniciado: El modo mantenimiento se activará en <gold>%TIME% <gray>y durará <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><gray>Temporizador iniciado: El modo mantenimiento se activará en <gold>%TIME% <gray>y durará <gold>%DURATION%<gray>."
timerAlreadyRunning: "<prefix><red>Ya hay un temporizador programado!"
timerNotRunning: "<prefix><gray>Actualmente no hay temporizador."
timerAlreadyRunning: "<prefix><red>¡Ya hay un temporizador programado!"
timerNotRunning: "<prefix><red>Actualmente no hay ningún temporizador."
timerCancelled: "<prefix><red>El Temporizador actual ha sido deshabilitado."
timerTooLong: "<prefix><gray>El numero debe ser menor a 40320 <dark_gray>(<gold>28 días<dark_gray>)"
timerMotdDisabled: "<prefix><red>Tienes que configurar <green>'enable-timerspecific-messages'<red> en la configuración en <green>'true'<red>, si quieres usar / editar motdspecers específicos."
timerTooLong: "<prefix><red>¡La duración debe ser menor a 28 días!"
timerMotdDisabled: "<prefix><red>Debes configurar 'enable-timerspecific-messages' en la configuración a 'true', si quieres usar/editar motds por temporizador."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
joinNotification: "<prefix><red>%PLAYER% <gray>Intenta unirse al servidor."
joinNotification: "<prefix><yellow>%PLAYER% <red>intentó unirse al servidor."
motdList: "<prefix><gray>Lista de motds de mantenimiento:"
motdListEmpty: "<prefix><red>¡No tiene ningún motd(s<dark_gray>) de mantenimiento establecido!"
motdListEmpty: "<prefix><red>¡No tienes ningún motd(s) de mantenimiento establecido!"
reload: "<prefix><green>Configuracion recargada"
removedMotd: "<prefix><green>Numero de motd eliminado <gold>%INDEX%<green>."
removeMotdError: "<prefix>&Solo tienes un motd, por lo que no puedes eliminar ninguno!"
setMotd: "<prefix><green>Establecer linea <gold>%LINE%<green> sobre <gold>%INDEX%<green>. Para mantenimiento para %MOTD%"
setReason: "<prefix><green>Se estableció el motivo de mantenimiento a: <yellow>%REASON%"
setMotdIndexError: "<prefix><green>Actualmente tiene <gold>%MOTDS%<green> Motd(s<dark_gray>), por lo que debe elegir un número entre 1 y %NEWAMOUNT%."
setMotdLineError: "<prefix><gray>El segundo argumento tiene que ser el numero de línea.(1 ó 2<dark_gray>)!"
updateDownloading: "<prefix><green>Descargando actualización..."
updateFailed: "<prefix><red>Actualizacion fallida!"
updateFinished: "<prefix><green>La actualización fue exitosa!, debes reiniciar el servidor!"
updateFinished: "<prefix><green><bold>The update was successful! To prevent issues with tasks and to complete the update, you have to restart the server!"
whitelistedPlayers: "<prefix><gold>Jugadores en lista blanca:"
whitelistedPlayersFormat: "<gray>- <green>%NAME% <dark_gray>(<gray>%UUID%<dark_gray>)"
whitelistAdded: "<prefix><green>%PLAYER% <gray>Añadido a la lista de mantenimiento!"
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><gray>El modo mantenimiento se encuentra d
singleServerMaintenanceList: "<prefix><gray>Servidores proxy, que tienen habilitado el mantenimiento:"
singleServerMaintenanceListEmpty: "<prefix><gray>No hay servidores proxy que están en mantenimiento."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <yellow>Abriendo <grey>%SERVER% <yellow>en <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <yellow>Cerrando <aqua>%SERVER% <yellow>en <gold>%TIME%"
second: "Segundo"
seconds: "Segundos"
minute: "Minuto"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><gray>No hay pagina con ese número."
helpNextPage: "<gray>Utilice <aqua>/maintenance help %PAGE% <gray>Para pasar a la siguiente ventana de ayuda."
helpAbortTimer: "<gold>/maintenance aborttimer <dark_gray>(<gray>Si se ejecuta, se abortará el temporizador actual<dark_gray>)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray> (Después del tiempo establecido, ej. 1h5m, el modo de mantenimiento será desactivado)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(Después del tiempo indicado, ej: 1h5m, El modo mantenimiento se habilita.)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(Después del tiempo indicado, ej. 1h5m, el modo mantenimiento se habilitará.)"
helpDebug: "<gold>/maintenance debug <gray>(Activa algunos registros de depuración)"
helpDump: "<gold>/maintenance dump <dark_gray>(<gray>Descarga información del servidor, usada para informes de errores<dark_gray>)"
helpHelp: "<gold>/maintenance help [page] <dark_gray>(<gray>Muestra la pagina de ayuda<dark_gray>)"
helpMotd: "<gold>/maintenance motd [timer] <dark_gray>(<gray>Enumera los motivos de mantenimiento establecidos actualmente. Si se especifica el 'temporizador', se muestran los motivos del temporizador<dark_gray>)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <dark_gray>(<gray>Elimina un mantenimiento motd. Si se usa 'timer' como argumento, se eliminará un pingmessage de temporizador específico<dark_gray>)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <dark_gray>(<gray>Establece un motd para el modo de mantenimiento. Si usa 'timer' como argumento, se establecerá un mensaje de ping específico para el tiempo<dark_gray>)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Configura o reestablece el valor por defecto de %REASON%)"
helpReload: "<gold>/maintenance reload <dark_gray>(<gray>Vuelve a cargar el archivo de configuración, el archivo de la lista blanca y el ícono del servidor<dark_gray>)"
helpToggle: "<gold>/maintenance <on/off> <dark_gray>(<gray>Habilita / deshabilita el modo de mantenimiento<dark_gray>)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Muestra el estado de mantenimiento actual y cualquier temporizador activo)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Habilita/desactiva el modo de mantenimiento con un modo de visualización opcional al habilitar)"
helpUpdate: "<gold>/maintenance update <dark_gray>(<gray>Descarga de forma remota la versión más reciente del complemento en su servidor<dark_gray>)"
helpWhitelist: "<gold>/maintenance whitelist <dark_gray>(<gray>Muestra todos los jugadores en lista blanca para el modo de mantenimiento<dark_gray>)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <dark_gray>(<gray>Agrega al jugador a la lista blanca de mantenimiento, para que puedan unirse al servidor aunque el mantenimiento esté habilitado<dark_gray>)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <dark_gray>(<gray>Quita al jugador de la lista blanca de mantenimiento<dark_gray>)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [servidor] <dark_gray>(<gray>Si está funcionando, se abortará el temporizador actual<dark_gray>)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <dark_gray>(<gray>Habilita / deshabilita el modo de mantenimiento<dark_gray>)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [servidor|global] [mode] <gray> (Habilita/desactiva el modo de mantenimiento globalmente o en un servidor específico con un modo de visualización opcional al habilitar)"
helpStatus: "<gold>/maintenance status <dark_gray>(<gray>Enumera todos los servidores proxy, que están actualmente en mantenimiento<dark_gray>)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Modo de mantenimiento activado.**"
webhookMaintenanceDeactivated: "**Modo de mantenimiento desactivado.**"
webhookStarttimerStarted: "<prefix><gray>El modo mantenimiento se activará en <gold>%TIME%<gray>`)."
webhookEndtimerStarted: "<prefix><gray>El modo mantenimiento se desactivará en <gold>%TIME%<gray>`)."
webhookJoinNotification: "Bloqueado: `%PLAYER%` intentó unirse durante el mantenimiento."
webhookSingleMaintenanceActivated: "Modo de mantenimiento activado en %SERVER%`."
webhookSingleMaintenanceDeactivated: "Modo de mantenimiento desactivado en `%SERVER%`."
webhookSingleStarttimerStarted: "El modo de mantenimiento se activará en `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "El modo de mantenimiento se desactivará en `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+32 -12
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>Vous n'avez pas la permission d'exécuter cette commande."
kickmessage: "<red>Le serveur est actuellement en maintenance !<br>Réessayez plus tard !"
maintenanceActivated: "<prefix><gold>Le mode maintenance est maintenant activé."
maintenanceDeactivated: "<prefix><gold>Le mode maintenance est maintenant désactivé."
maintenanceStatusOn: "<prefix><gold>Le mode maintenance est actuellement <green>activé<gold>."
maintenanceStatusOff: "<prefix><gold>Le mode maintenance est actuellement <red>désactivé<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Le mode maintenance est actuellement <green>activé<grey> et sera désactivé dans <gold>%TIME%"
maintenanceStatusOffWithStartTimer: "<prefix><gold>Le mode maintenance est actuellement <red>désactivé<grey> et sera activé dans <gold>%TIME%"
alreadyEnabled: "<prefix><red>La maintenance est déjà activée !"
alreadyDisabled: "<prefix><red>La maintenance est déjà désactivée !"
endtimerBroadcast: "<prefix><gray>Le mode maintenance sera désactivé dans <gold>%TIME%<gray>."
@@ -29,6 +33,7 @@ reload: "<prefix><green>Fichier de configuration, liste blanche (whitelist) des
removedMotd: "<prefix><green>Numéro de motd supprimé %INDEX%."
removeMotdError: "<prefix><red>Vous n’avez qu’un motd, vous ne pouvez donc pas en supprimer !"
setMotd: "<prefix><green>Ligne %LINE% du motd %INDEX%. fixée à %MOTD%"
setReason: "<prefix><green>Raison de la maintenance configurée à : <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Vous avez actuellement %MOTDS% motd(s), vous devez donc choisir un nombre compris entre 1 et %NEWAMOUNT%."
setMotdLineError: "<prefix><red>Le deuxième argument doit être le numéro de ligne (1 ou 2) !"
updateDownloading: "<prefix><red><bold>Téléchargement de la mise à jour..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>La maintenance est déjà désactivé
singleServerMaintenanceList: "<prefix><gray>Serveurs proxy ayant la maintenance activée :"
singleServerMaintenanceListEmpty: "<prefix><gray>Il n'y a pas de serveurs proxy en maintenance."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>comme dans <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>termine dans <gold>%TIME%"
second: "seconde"
seconds: "secondes"
minute: "minute"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>Il n'y a pas cette page !"
helpNextPage: "<gray>Utilisez <aqua>/maintenance help %PAGE% <gray>pour accéder à la fenêtre d’aide suivante."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Si en cours d'exécution, le minuteur actuel sera abandonné)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <désactiver dans> <gray>(Après le temps imparti, ex. 1h5m, le mode maintenance sera désactiver)"
helpStarttimer: "<gold>/maintenance starttimer <activer dans> <gray>(Après le temps imparti, ex. 1h5m, le mode maintenance sera activé)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <activer dans> <durée de maintenance> <gray>(Après le temps imparti, ex. 1h5m, le mode maintenance sera activé)"
helpDebug: "<gold>/maintenance debug <gray>(Active certains enregistrements de débogage)"
helpDump: "<gold>/maintenance dump <gray>(Enregistre certaines informations sur le serveur, utilisées pour les rapports de bug)"
helpHelp: "<gold>/maintenance help [page] <gray>(Montre cette belle fenêtre d'aide)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(Répertorie les paramètres de maintenance actuellement définis. Si vous spécifiez 'timer', les mots de la minuterie seront affichés)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Supprime un motd de maintenance. Si vous utilisez 'timer' comme argument, un message ping spécifique au temporisateur sera supprimé)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(Définit un motd pour le mode maintenance. Si vous utilisez 'timer' comme argument, un message ping spécifique à un temporisateur sera défini)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(définit ou efface la valeur du placeholder %REASON%)"
helpReload: "<gold>/maintenance reload <gray>(Recharge le fichier de configuration, la liste blanche, le fichier de langue et l'icône du serveur)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Active/désactive le mode maintenance)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Affiche les états de maintenance et les minuteurs actifs)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Active/désactive le mode maintenance)"
helpUpdate: "<gold>/maintenance update <gray>(Télécharge la dernière version du plugin sur votre serveur)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Affiche les joueurs de la liste blanche pour le mode maintenance)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(Ajoute un joueur à la liste blanche de maintenance afin qu'il puisse rejoindre le serveur même si la maintenance est activée)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Supprime un joueur de la liste blanche de maintenance)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(Si en cours d'exécution, le minuteur actuel sera abandonné)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Active/désactive le mode maintenance)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Active/désactive le mode maintenance globalement, ou sur un serveur spécifique avec un mode d'affichage facultatif lors de l'activation)"
helpStatus: "<gold>/maintenance status <gray>(Répertorie tous les serveurs mandatés actuellement en maintenance)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "\n\n**Mode maintenance activé.**"
webhookMaintenanceDeactivated: "\n\n**Mode maintenance désactivé.**"
webhookStarttimerStarted: "Le mode maintenance sera activé dans <t:%TIMESTAMP%:R>(`%TIME%`)."
webhookEndtimerStarted: "Le mode maintenance sera désactivé dans <t:%TIMESTAMP%:R>(`%TIME%`)."
webhookJoinNotification: "Connexion bloquée : `%PLAYER%` a cherché à rejoindre durant une maintenance."
webhookSingleMaintenanceActivated: "Mode maintenance activé sur `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Mode maintenance désactivé sur `%SERVER%`."
webhookSingleStarttimerStarted: "Le mode maintenance sera activé sur `%SERVER%` <t:%TIMESTAMP%:R>(`%TIME%`)."
webhookSingleEndtimerStarted: "Le mode maintenance sera désactivé sur `%SERVER%` <t:%TIMESTAMP%:R>(`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+32 -12
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>Nincs engedélyed a parancs használatához."
kickmessage: "<red>A szerver karbantartás alatt áll!<br>Próbáld újra később!"
maintenanceActivated: "<prefix><gold>Karbantartás elindítva."
maintenanceDeactivated: "<prefix><gold>Karbantartás befejezve."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>Már engedélyezve van!"
alreadyDisabled: "<prefix><red>Már le van tiltva!"
endtimerBroadcast: "<prefix><gray>Felléphetsz <gold>%TIME%<gray> múlva."
@@ -29,6 +33,7 @@ reload: "<prefix><green>Újratöltve."
removedMotd: "<prefix><green>Motd szám törölve: %INDEX%."
removeMotdError: "<prefix><red>Csak egy motd van,nem törölheted!"
setMotd: "<prefix><green>Sor %LINE% beállítva %INDEX%-re. motd %MOTD%-ra"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Csak %MOTDS%-d van, úgyhogy válassz egy számot 1 és %NEWAMOUNT% között."
setMotdLineError: "<prefix><red>Ennek egy sornak kell lennie (1 vagy 2)!"
updateDownloading: "<prefix><red><bold>Frissítés letöltése..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>A karbantartás már le van tiltva a(
singleServerMaintenanceList: "<prefix><gray>Proxy szerverek,karbantartási móddal engedélyezve:"
singleServerMaintenanceListEmpty: "<prefix><gray>Nincsenek proxy szerverek karbantartás alatt."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "másodperc"
seconds: "másodperc"
minute: "perc"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>Nincs ilyen oldal!"
helpNextPage: "<gray>Használd a <aqua>/maintenance help %PAGE% <gray>parancsot,hogy lapozz."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Az időzítő,ha fut nem lesz figelembe vévve)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(Azután a megadott időben pl. 1h5m, a karbantartásnak vége lesz)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(Azután a megadott időben pl. 1h5m, karbantartás elkezdödik)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(Azután a megadott időben pl. 1h5m, a karbantartás elkezdödik)"
helpDebug: "<gold>/maintenance debug <gray>(Hibakeresés bekapcsolása)"
helpDump: "<gold>/maintenance dump <gray>(A hibajelentési előzményeket törli)"
helpHelp: "<gold>/maintenance help [page] <gray>(Megjelenítí a segítség ablakot)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(Megjeleníti a motd-ket)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Törli a megadott motd-t)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(Beállít egy motd-t)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Újratölti a plugint)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Karbantartás mód ki-be kapcsolása)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Frissíti a plugin verziót)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Megmutatja a karbantartás fehérlistázottak listáját)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(Hozzáadja a játékost a karbantartás fehérlistához)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Kitörli a játékost a karbantartás fehérlistáról)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(Ha van karbantartás,akkor figyelmen kívül veszi az időzítőt)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Ki-be kapcsolja a karbantartás módot)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Kiírja az összes proxy szervert, ami karbantartás alatt van)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+54 -34
View File
@@ -1,19 +1,23 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>Non hai i permessi necessari per eseguire quel comando."
noPermission: "<red>Non hai il permesso per eseguire questo comando."
kickmessage: "<red>Il server è momentaneamente in manutenzione!<br>Riprova più tardi!"
maintenanceActivated: "<prefix><gold>Modalità manutenzione ora attivata."
maintenanceDeactivated: "<prefix><gold>Modalità manutenzione ora disattivata."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>La modalità manutenzione è già attiva!"
alreadyDisabled: "<prefix><red>La modalità mantenzione è già disattivata!"
alreadyDisabled: "<prefix><red>La modalità manutenzione è già disattivata!"
endtimerBroadcast: "<prefix><gray>La modalità manutenzione verrà disattivata tra <gold>%TIME%<gray>."
endtimerStarted: "<prefix><green>Timer avviato: <gray>La modalità manutenzione verrà disattivata tra <gold>%TIME%<gray>."
starttimerBroadcast: "<prefix><gray>La modalità manutenzione verrà attivata tra <gold>%TIME%<gray>."
starttimerStarted: "<prefix><green>Timer avviato: <gray>La modalità manutenzione verrà attivata tra <gold>%TIME%<gray>."
scheduletimerBroadcast: "<prefix><gray>Maintenance mode will be enabled in <gold>%TIME% <gray>and will last for <gold>%DURATION%<gray>."
scheduletimerBroadcast: "<prefix><gray>La modalità manutenzione sarà abilitata tra <gold>%TIME% <gray>e durerà per <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><green>Timer avviato: la modalità di manutenzione <gray>sarà abilitata in <gold>%TIME% <gray>e durerà per <gold>%DURATION%<gray>."
timerAlreadyRunning: "<prefix><red>C'è già un timer programmato!"
timerNotRunning: "<prefix><red>Non c'è nessun timer attivo al momento."
@@ -23,18 +27,19 @@ timerMotdDisabled: "<prefix><red>Devi impostare 'enable-timerspecific-messages'
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
joinNotification: "<prefix><yellow>%PLAYER% <red>ha tentato di accedere al server."
motdList: "<prefix><gray>Lista delle tue motds di manutenzione:"
motdListEmpty: "<prefix><red>Non hai ancora nessuna motds di manutenzione!"
reload: "<prefix><green>Ricaricata config, whitelistedplayers, file dei linguaggi e la icona di manutenzione."
removedMotd: "<prefix><green>Rimossa motd numero %INDEX%."
removeMotdError: "<prefix><red>Hai soltanto una motd, quindi non puoi rimuoverla!"
setMotd: "<prefix><green>Impostata linea %LINE% in %INDEX%. motd manutenzione a %MOTD%"
setMotdIndexError: "<prefix><red>Hai attualmente %MOTDS% motds, quindi devi scegliere un numero fra 1 e %NEWAMOUNT%."
motdList: "<prefix><gray>Lista delle tue MOTD di manutenzione:"
motdListEmpty: "<prefix><red>Non hai ancora nessuna MOTD di manutenzione!"
reload: "<prefix><green>Ricaricata config, whitelistedplayers, file dei linguaggi e l'icona di manutenzione."
removedMotd: "<prefix><green>Rimossa MOTD numero %INDEX%."
removeMotdError: "<prefix><red>Hai soltanto una MOTD, quindi non puoi rimuoverla!"
setMotd: "<prefix><green>Impostata MOTD di manutenzione numero %INDEX%, linea %LINE% a: %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Hai attualmente %MOTDS% MOTD, devi scegliere un numero tra 1 e %NEWAMOUNT%."
setMotdLineError: "<prefix><red>Il secondo argomento deve corrispondere alla linea (1 o 2)!"
updateDownloading: "<prefix><red><bold>Scaricando aggiornamento..."
updateDownloading: "<prefix><red><bold> Sto scaricando l'aggiornamento..."
updateFailed: "<prefix><dark_red><bold>Aggiornamento non riuscito!"
updateFinished: "<prefix><green><bold>L'aggiornamento è completato! Per prevenire problemi e completare l'aggiornamento dovresti riavviare il server!"
whitelistedPlayers: "<prefix><gold>Player in whitelist per la manutenzione:"
whitelistedPlayers: "<prefix><gold>Giocatori in whitelist per la manutenzione:"
whitelistedPlayersFormat: "<dark_gray>- <yellow>%NAME% <dark_gray>(<gray>%UUID%<dark_gray>)"
whitelistAdded: "<prefix><green>Aggiunto <aqua>%PLAYER% <green>alla whitelist della manutenzione!"
whitelistAlreadyAdded: "<prefix><aqua>%PLAYER% <red>è già nella whitelist della manutenzione!"
@@ -42,32 +47,34 @@ whitelistRemoved: "<prefix><green>Rimosso <aqua>%PLAYER% <green>dalla lista dell
whitelistNotFound: "<prefix><red>Questo giocatore non fa parte della whitelist della manutenzione!"
whitelistEmpty: "<prefix><red>La whitelist di manutenzione è vuota! Usa <yellow>/maintenance add <player/uuid> <red>per aggiungere qualcuno!"
playerNotFound: "<prefix><red>Nessun giocatore con questo username è mai entrato nel server."
playerNotFoundUuid: "<prefix><red>Nessun player con quell'uuid è stato trovato."
playerNotFoundUuid: "<prefix><red>Nessun giocatore con quell'uuid è stato trovato."
playerNotOnline: "<prefix><red>Non c'è nessun giocatore online con quell'username."
offlinePlayerFetchError: "<prefix><red>C'è stato un errore cercando il player offline. Riprova più tardi."
offlinePlayerFetchError: "<prefix><red>C'è stato un errore cercando il giocatore offline. Riprova più tardi."
invalidUuid: "<prefix><red>L'uuid presenta un formato invalido!"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
sentToWaitingServer: "<prefix><red>Sei stato inviato su un server d'attesa!"
forceWaitingServer: "<prefix><red>Non puoi abbandonare il server d'attesa durante una manutenzione!"
serverNotFound: "<prefix><red>Nessun server con questo nome è registrato sulla proxy!"
serverNotFound: "<prefix><red>Nessun server con questo nome è registrato sul proxy server!"
singleTimerAlreadyRunning: "<prefix><red>È già presente un timer pianificato per quel server!"
singleTimerCancelled: "<prefix><red>Il timer attuale per il server <yellow>%SERVER% <red>è stato rimosso."
singleTimerNotRunning: "<prefix><red>Non c'è nessun timer correntemente attivo per quel server."
singleEndtimerBroadcast: "<prefix><gray>La modalità manutenzione per il server %SERVER% verrà disabilitata tra <gold>%TIME%<gray>."
singleEndtimerStarted: "<prefix><green>Timer avviato: <gray>La modalità manutenzione per il server %SERVER% verrà disattivata tra <gold>%TIME%<gray>."
singleStarttimerBroadcast: "<prefix><gray>La modalità manutenzione per il server %SERVER% verrà disabilitata tra <gold>%TIME%<gray>."
singleStarttimerBroadcast: "<prefix><gray>La modalità manutenzione per il server %SERVER% verrà attivata tra <gold>%TIME%<gray>."
singleStarttimerStarted: "<prefix><green>Timer avviato: <gray>La modalità manutenzione per il server %SERVER% verrà attivata tra <gold>%TIME%<gray>."
singleScheduletimerBroadcast: "<prefix><gray>Maintenance mode on server %SERVER% will be enabled in <gold>%TIME% <gray>and will last for <gold>%DURATION%<gray>."
singleScheduletimerBroadcast: "<prefix><gray>La modalità manutenzione sul server %SERVER% sarà abilitata tra <gold>%TIME% <gray>e durerà per <gold>%DURATION%<gray>."
singleScheduletimerStarted: "<prefix><green>Timer avviato: <gray>Modalità di manutenzione sul server %SERVER% sarà attivato in <gold>%TIME% <gray>e durerà per <gold>%DURATION%<gray>."
singleMaintenanceKick: "<prefix><red>Il server %SERVER% è in manutenzione al momento! Ritenta più tardi!"
singleMaintenanceKickComplete: "<red>Il server %SERVER% è in manutenzione!<br>Prova a rientrare per accedere ad un'altro server!"
singleMaintenanceKickComplete: "<red>Il server %SERVER% è in manutenzione!<br>Prova a rientrare per accedere a un altro server!"
singleMaintenanceActivated: "<prefix><gold>La modalità manutenzione è ora attiva sul server <yellow>%SERVER%<gold>."
singleMaintenanceDeactivated: "<prefix><gold>La modalità manutenzione è ora disattivata sul server <yellow>%SERVER%<gold>."
singleServerAlreadyEnabled: "<prefix><red>La modalità manutenzione è già attiva sul server <yellow>%SERVER%<red>!"
singleServerAlreadyDisabled: "<prefix><red>La modalità manutenzione è già disattivata sul server <yellow>%SERVER%<red>!"
singleServerMaintenanceList: "<prefix><gray>Servers sotto proxy che hanno la modalità manutenzione attiva:"
singleServerMaintenanceListEmpty: "<prefix><gray>Nessun server sotto proxy ha la modalità manutenzione attiva."
singleServerMaintenanceList: "<prefix><gray>Server collegati con manutenzione attiva:"
singleServerMaintenanceListEmpty: "<prefix><gray>Nessun server sotto proxy server ha la modalità manutenzione attiva."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "secondo"
seconds: "secondi"
minute: "minuto"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>Non c'è alcuna pagina con quel numero!"
helpNextPage: "<gray>Usa <aqua>/maintenance help %PAGE% <gray>per andare alla prossima pagina d'aiuto."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Se attivo, l'attuale timer verrà fermato)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpDebug: "<gold>/maintenance debug <gray>(Enables some debug logging)"
helpEndtimer: "<gold>/maintenance endtimer <tempo di disattivazione> <gray>(Dopo il tempo specificato, es. 1h5m, la modalità manutenzione verrà disattivata)"
helpStarttimer: "<gold>/maintenance starttimer <tempo di attivazione> <gray>(Dopo il tempo specificato, es. 1h5m, la modalità manutenzione verrà attivata)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <tempo di attivazione> <durata manutenzione> <gray>(Dopo il tempo specificato, es. 1h5m, la modalità manutenzione verrà attivata e durerà per il tempo indicato)"
helpDebug: "<gold>/maintenance debug <gray>(Attiva la registrazione di debug)"
helpDump: "<gold>/maintenance dump <gray>(Copia alcune informazioni dal server, usato per segnalare problemi)"
helpHelp: "<gold>/maintenance help [page] <gray>(Mostra questa fantastica pagina d'aiuto)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(Elenca le attuali motds di manutenzione impostate. Se specifichi 'timer', verranno mostrati i motds dei timer)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Rimuove una motd di manutenzione. Se specifichi 'timer' come argomento, un messaggio specifico al timer verrà rimosso)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(Imposta il motd per la manutenzione. Se specifichi 'timer' come argomento, un messaggio specifico al timer verrà impostato)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Ricarica il file di configurazione, whitelist server e icona)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Abilita/Disabilita modalità manutenzione)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Scarica dall'esterno l'ultima versione del plugin nel server)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Elenca tutti i giocatori nella whitelist per la manutenzione)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(Aggiunge il giocatore alla whitelist di manutenzione, così possono entrare in un server durante la manutenzione)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Rimuovi il player dalla whitelist della manutenzione)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Rimuovi il giocatore dalla whitelist della manutenzione)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(Se attivo, il timer attuale verrà fermato)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Abilita/disabilita la modalità di manutenzione)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Elenca tutti i server sotto proxy che hanno la modalità manutenzione attiva)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+32 -12
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>このコマンドを実行する権限がありません。"
kickmessage: "<red>メンテナンス中です。<br>しばらくたってから、もう一度お試しください"
maintenanceActivated: "<prefix><green>メンテナンスを有効にしました。"
maintenanceDeactivated: "<prefix><yellow>メンテナンスを解除しました。"
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>メンテナンスは既に有効です。"
alreadyDisabled: "<prefix><red>メンテナンスは既に解除されています。"
endtimerBroadcast: "<prefix><gray>メンテナンスモードは、<gold>%TIME%<gray>で無効になります。"
@@ -29,6 +33,7 @@ reload: "<prefix><green>再読み込みが完了しました。"
removedMotd: "<prefix><green>motd番号%INDEX%を削除しました。"
removeMotdError: "<prefix><red>motdは1つしかないので、削除することはできません。"
setMotd: "<prefix><greenINDEX%.の行 %LINE% を %MOTD% に設定します。"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>現在、%MOTDS%のMOTDを持っているので、1から%NEWAMOUNT%の間の数字を選ぶ必要があります。"
setMotdLineError: "<prefix><red>第2引数には、行番号(1または2)を指定する必要があります。"
updateDownloading: "<prefix><red><bold>アップデートをダウンロード中です..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><yellow>%SERVER%<red> のメンテナン
singleServerMaintenanceList: "<prefix><gray>プロキシサーバーで、メンテナンスが有効になっているもの: "
singleServerMaintenanceListEmpty: "<prefix><gray>メンテナンス中のプロキシサーバーはありません。"
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "秒"
seconds: "秒"
minute: "分"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>その番号のページがありません。"
helpNextPage: "<gray>Use <aqua>/maintenance help %PAGE% <gray>で次のヘルプウィンドウに移動します。"
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(タイマーを実行していた場合、タイマーの実行を中止します。)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(指定された時間(例えば1時間5分)が経過すると、メンテナンス・モードは無効になる)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(指定された時間後、例えば1時間5分後にメンテナンス・モードが有効になる)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(指定された時間(例えば1時間5分)が経過すると、メンテナンスモードが有効になる)"
helpDebug: "<gold>/maintenance debug <gray>(デバッグロギングを有効にします。)"
helpDump: "<gold>/maintenance dump <gray>(バグレポートに使用される、いくつかのサーバ情報をダンプします。)"
helpHelp: "<gold>/maintenance help [page] <gray>(このようなヘルプページが表示されます。)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(現在設定されているメンテナンスMOTDを一覧表示します。'timer'を指定した場合は、タイマーの場合のMOTDを表示します。)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(メンテナンスの場合に使用されるMOTDを削除します。引数として'timer'を使用した場合にはタイマー固有のピングメッセージが表示されます。)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(メンテナンスの場合に使用されるMOTDを設定します。引数として'timer'を使用した場合にはタイマー固有のピングメッセージが表示されます。)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(設定ファイル、ホワイトリストファイル、サーバーアイコンを再読み込みします。)"
helpToggle: "<gold>/maintenance <on/off> <gray>(メンテナンスモードの有効/解除を設定します。)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(最新版のプラグインをサーバーにリモートでダウンロードします。)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(メンテナンスモードのホワイトリストに登録されているすべてのプレーヤーを表示します。)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(メンテナンスのホワイトリストにユーザーを追加し、メンテナンスが有効な状態でもサーバーに参加できるようにします。)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(メンテナンスのホワイトリストからプレーヤーを削除します。)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(もし、タイマーが実行中の場合、現在のタイマーを中止します。)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(メンテナンスモードの有効化/無効化を設定します。)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(現在メンテナンス中のプロキシサーバーをすべてリストアップします。)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+29 -9
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>권한이 없습니다."
kickmessage: "<red>서버가 현재 점검 중입니다!<br>나중에 다시 시도하세요!"
maintenanceActivated: "<prefix><gold>유지 관리 모드가 활성화되었습니다."
maintenanceDeactivated: "<prefix><gold>유지 관리 모드가 비활성화되었습니다."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>유지 관리가 이미 활성화되었습니다!"
alreadyDisabled: "<prefix><red>유지보수가 이미 비활성화되었습니다!"
endtimerBroadcast: "<prefix><gray>유지 관리 모드는<gold>%TIME%<gray>에 비활성화됩니다.."
@@ -29,6 +33,7 @@ reload: "<prefix><green>설정, 화이트리스트 플레이어, 언어 파일
removedMotd: "<prefix><green>%INDEX%번째 motd를 삭제했습니다."
removeMotdError: "<prefix><red>motd가 하나만 있으므로 제거할 수 없습니다!"
setMotd: "<prefix><green>INDEX%의 %LINE% 줄을 %MOTD%로 설정합니다."
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>현재 %MOTDS% motd가 있으므로 1에서 %NEWAMOUNT% 사이의 숫자를 선택해야 합니다."
setMotdLineError: "<prefix><red>두 번째 인수는 줄 번호(1 또는 2)여야 합니다!"
updateDownloading: "<prefix><red><bold>업데이트 다운로드 중..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>Maintenance is already disabled on se
singleServerMaintenanceList: "<prefix><gray>Proxied servers, that have maintenance enabled:"
singleServerMaintenanceListEmpty: "<prefix><gray>There are no proxied servers that are under maintenance."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "second"
seconds: "seconds"
minute: "minute"
@@ -87,18 +94,31 @@ helpHelp: "<gold>/maintenance help [page] <gray>(Shows this beautiful help windo
helpMotd: "<gold>/maintenance motd [timer] <gray>(Lists the currently set maintenance motds. If specifying 'timer', the timer motds are shown)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Removes a maintenance motd. If using 'timer' as an argument, a timerspecific pingmessage will be removed)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <message> <gray>(Sets a motd for maintenance mode. If using 'timer' as an argument, a timerspecific pingmessage will be set)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Reloads the config file, whitelist file and the server-icon)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Enables/disables maintenance mode)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Remotely downloads the newest version of the plugin onto your server)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Shows all whitelisted players for the maintenance mode)"
helpWhitelistAdd: "<gold>/maintenance add <name/uuid> <gray>(Adds the player to the maintenance whitelist, so they can join the server even though maintenance is enabled)"
helpWhitelistRemove: "<gold>/maintenance remove <name/uuid> <gray>(Removes the player from the maintenance whitelist)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(If running, the current timer will be aborted)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Enables/disables maintenance mode)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Lists all proxied servers, that are currently under maintenance)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+36 -16
View File
@@ -1,14 +1,18 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Prace techniczne<dark_gray>] "
noPermission: "<red>Nie posiadasz permisji na użycie tej komendy."
kickmessage: "<red>Serwer jest w trakcie przerwy technicznej!<br>Spróbuj ponownie później!"
maintenanceActivated: "<prefix><gold>Tryb konserwacji włączony."
maintenanceDeactivated: "<prefix><gold>Tryb konserwacji wyłączony."
alreadyEnabled: "<prefix><red>Przerwa techniczna już jest włącznona!"
alreadyDisabled: "<prefix><red>Przerwa techniczna już jest wyłącznona!"
maintenanceStatusOn: "<prefix><gold>Tryb konserwacji jest obecnie <green>włączony<gold>."
maintenanceStatusOff: "<prefix><gold>Tryb konserwacji jest obecnie <red>wyłączony<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Tryb konserwacji jest obecnie <green>włączony <grey>i zostanie wyłączony za <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Tryb konserwacji jest obecnie <red>wyłączony <grey>i zostanie włączony za <gold>%TIME%."
alreadyEnabled: "<prefix><red>Tryb konserwacji jest już włączony!"
alreadyDisabled: "<prefix><red>Tryb konserwacji jest już wyłączony!"
endtimerBroadcast: "<prefix><gray>Przerwa techniczna zostanie zakończona za <gold>%TIME%<gray>."
endtimerStarted: "<prefix><green>Rozpoczęto timer: <gray>Tryb konserwacji zostanie wyłączony za <gold>%TIME%<gray>."
starttimerBroadcast: "<prefix><gray>Przerwa techniczna zacznie się za <gold>%TIME%<gray>."
@@ -18,7 +22,7 @@ scheduletimerStarted: "<prefix><green>Rozpoczęto timer: <gray>Przerwa techniczn
timerAlreadyRunning: "<prefix><red>Timer został już zaplanowany!"
timerNotRunning: "<prefix><red>Aktualnie nie ma żadnego działającego timera."
timerCancelled: "<prefix><red>Bieżący timer został anulowany."
timerTooLong: "<prefix><red>Liczba musi byc mniejsz niż 40320 (28 dni)!"
timerTooLong: "<prefix><red>Podany czas musi być krótszy niż 28 dni!"
timerMotdDisabled: "<prefix><red>Musisz ustawić 'enable-timerspecific-messages' w konfiguracji na 'true', jeśli chcesz używać motd z timerem."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
@@ -29,6 +33,7 @@ reload: "<prefix><green>Ponownie załadowano plik konfiguracji, białą listę,
removedMotd: "<prefix><green>Usunięto motd numer %INDEX%."
removeMotdError: "<prefix><red>Masz tylko jedno motd, którego nie możesz usunąć!"
setMotd: "<prefix><green>Ustawiono wiersz %LINE% od %INDEX%. motd konserwacji na %MOTD%"
setReason: "<prefix><green>Ustawiono powód konserwacji na: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Masz aktualnie %MOTDS% motd, więc musisz wybrać liczbę między 1 a %NEWAMOUNT%."
setMotdLineError: "<prefix><red>Drugi argument musi być numerem lini (1 lub 2)!"
updateDownloading: "<prefix><red><bold>Pobieranie aktualizacji..."
@@ -44,7 +49,7 @@ whitelistEmpty: "<prefix><red>Biała lista konserwacji jest pusta! Użyj <yellow
playerNotFound: "<prefix><red>Ten gracz nigdy nie grał na serwerze."
playerNotFoundUuid: "<prefix><red>Nie można znaleźć gracza z tym uuid."
playerNotOnline: "<prefix><red>Nie ma gracza online z tym nickiem."
offlinePlayerFetchError: "<prefix><red>There was an error while fetching offline player. Please try again later."
offlinePlayerFetchError: "<prefix><red>Wystąpił błąd podczas pobierania gracza offline. Spróbuj ponownie później."
invalidUuid: "<prefix><red>Nieprawidłowy format uuid!"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
sentToWaitingServer: "<prefix><red>Zostałeś wysłany do poczekalni!"
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>Tryb konserwacji jest już wyłączon
singleServerMaintenanceList: "<prefix><gray>Serwery z włączonym trybem konserwacji:"
singleServerMaintenanceListEmpty: "<prefix><gray>Brak serwerów pod nadzorem."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>zaczyna się za <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>kończy się za <gold>%TIME%"
second: "sekunda"
seconds: "sekund"
minute: "minuta"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>Strona z tym numerem nie istnieje!"
helpNextPage: "<gray>Użyj <aqua>/maintenance help %PAGE% <gray>aby przejść do następnej strony pomocy."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Jeśli aktywny, timer zostanie anulowany)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <czas> <gray>(Po podanym czasie, np. 1h5m, tryb konserwacji zostanie wyłączony)"
helpStarttimer: "<gold>/maintenance starttimer <czas> <gray>(Po podanym czasie, np. 1h5m, tryb konserwacji zostanie włączony)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <czas do włączenia> <czas trwania> <gray>(Po podanym czasie, np. 1h5m, tryb konserwacji zostanie włączony)"
helpDebug: "<gold>/maintenance debug <gray>(Włacza logowanie dodatkowych informcji do debugowania)"
helpDump: "<gold>/maintenance dump <gray>(Daje informacje o serwerze, używane do raportowania błędów)"
helpHelp: "<gold>/maintenance help [strona] <gray>(Pokazuje tą piękną stronę pomocy)"
helpMotd: "<gold>/maintenance motd [timer] <gray>(Wyświetla aktualnie ustawione motd. Jeśli wstawiono 'timer', motd z timerem zostaną pokazane)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Usuwa motd konserwacji. Jeśli wstawiono 'timer', motd z timerem zostanie usunięte)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <wiadomość> <gray>(Ustawia motd konserwacji. Jeśli wstawiono 'timer', motd z timerem zostanie ustawione)"
helpSetReason: "<gold>/maintenance setreason <powód/none> <gray>(Ustawia albo usuwa powód trybu konserwacji w wiadomościach. Wpisz \"none\" jako powód, aby wyczyścić)"
helpReload: "<gold>/maintenance reload <gray>(Przeładowuje plik konfiguracji, białej listy i ikonę serwera)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Włącza/wyłącza tryb konserwacji)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Pokazuje status trybu konserwacji i wszystkie minutniki)"
helpToggle: "<gold>/maintenance <on/off> [tryb] <gray>(Włącza/Wyłącza tryb konserwacji, opcjonalnie z trybem podanym podczas włączania)"
helpUpdate: "<gold>/maintenance update <gray>(Pobiera najnowszą wersję plugina)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Pokazuje białą listę trybu konserwacji)"
helpWhitelistAdd: "<gold>/maintenance add <nazwa/uuid> <gray>(Dodaje gracza do listy konserwacji, aby mogli dołączyć do serwera w trakcie konserwacji)"
helpWhitelistRemove: "<gold>/maintenance remove <nazwa/uuid> <gray>(Usuwa gracza z listy trybu konserwacji)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [serwer] <gray>(Jeśli aktywny, timer zostanie anulowany)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [serwer] <gray>(Włącza/wyłącza tryb konserwacji)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [serwer/global] [tryb] <gray>(Włącza/Wyłącza tryb konserwacji globalnie lub na podanym serwerze, opcjonalnie z trybem podanym podczas włączania)"
helpStatus: "<gold>/maintenance status <gray>(Wyświetla wszystkie serwery w trybie konserwacji)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Tryb konserwacji włączony.**"
webhookMaintenanceDeactivated: "**Tryb konserwacji wyłączony.**"
webhookStarttimerStarted: "Tryb konserwacji zostanie włączony <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Tryb konserwacji zostanie wyłączony <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Zablokowano dołączenie: `%PLAYER%` próbował(a) dołączyć podczas konserwacji."
webhookSingleMaintenanceActivated: "Tryb konserwacji włączony dla `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Tryb konserwacji wyłączony dla `%SERVER%`."
webhookSingleStarttimerStarted: "Tryb konserwacji zostanie włączony dla `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Tryb konserwacji zostanie wyłączony dla `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+32 -12
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>Você não tem permissão para executar esse comando."
kickmessage: "<red>O servidor está em manutenção!<br>Tente novamente mais tarde!"
maintenanceActivated: "<prefix><gold>O modo de manutenção agora está ativado."
maintenanceDeactivated: "<prefix><gold>O modo de manutenção agora está desativado."
maintenanceStatusOn: "<prefix><gold>O modo de manutenção está atualmente <green>ativo<gold>."
maintenanceStatusOff: "<prefix><gold>O modo de manutenção está atualmente <red>desativado<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>O modo de manutenção está atualmente <green>habilitado <grey>e será desabilitado em <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>O modo de manutenção está atualmente <red>desabilitado <grey>e será habilitado em <gold>%TIME%."
alreadyEnabled: "<prefix><red>A manutenção já está ativada!"
alreadyDisabled: "<prefix><red>A manutenção já está desativada!"
endtimerBroadcast: "<prefix><gray>O modo de manutenção será desativado em <gold>%TIME%<gray>."
@@ -29,6 +33,7 @@ reload: "<prefix><green>Recarregou a configuração, lista de jogadores com perm
removedMotd: "<prefix><green>Motd número %INDEX% removida."
removeMotdError: "<prefix><red>Você possui apenas uma motd, portanto, não pode remover nenhuma!"
setMotd: "<prefix><green>Definiu a linha %LINE% da motd de manutenção %INDEX% para %MOTD%"
setReason: "<prefix><green>Definir motivo de manutenção para: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>No momento, você possui %MOTDS% motds, portanto, deve escolher um número entre 1 e %NEWAMOUNT%."
setMotdLineError: "<prefix><red>O segundo argumento deve ser o número da linha (1 ou 2)!"
updateDownloading: "<prefix><red><bold>Baixando atualização..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>A manutenção já está desativada n
singleServerMaintenanceList: "<prefix><gray>Servidores proxy com manutenção ativada:"
singleServerMaintenanceListEmpty: "<prefix><gray>Não há servidores proxy em manutenção no momento."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>iniciando em <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>terminando em <gold>%TIME%"
second: "segundo"
seconds: "segundos"
minute: "minuto"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>Não há página com esse número!"
helpNextPage: "<gray>Use <aqua>/maintenance help %PAGE% <gray>para ir para a próxima janela de ajuda."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Se estiver em execução, o temporizador atual será abortado)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <desabilitar em> <gray>(Depois do tempo fornecido, exemplo: 1h5m, o modo manutenção será desabilitado)"
helpStarttimer: "<gold>/maintenance starttimer <habilitar em> <gray>(Depois do tempo fornecido, exemplo: 1h5m, o modo de manutenção será habilitado)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <habilitar em> <duração da manutenção> <gray>(Depois do tempo fornecido, exemplo: 1h5m, o modo de manutenção será habilitado)"
helpDebug: "<gold>/maintenance debug <gray>(Ativa alguns registros de depuração)"
helpDump: "<gold>/maintenance dump <gray>(Exibe algumas informações do servidor, usadas para relatórios de bugs)"
helpHelp: "<gold>/maintenance help [página] <gray>(Mostra esta bela janela de ajuda)"
helpMotd: "<gold>/maintenance motd [temporizador] <gray>(Lista as motds de manutenção atualmente definidas. Se 'temporizador' for especificado, as motds de temporizador serão mostradas)"
helpRemoveMotd: "<gold>/maintenance removemotd [temporizador] <índice> <gray>(Remove uma motd de manutenção. Se usar 'temporizador' como argumento, uma mensagem de ping específica do temporizador será removida)"
helpSetMotd: "<gold>/maintenance setmotd [temporizador] <índice> <1/2> <mensagem> <gray>(Define uma motd para o modo de manutenção. Se usar 'temporizador' como argumento, uma mensagem de ping específica do temporizador será definida)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Define ou limpa o valor %REASON% comum)"
helpReload: "<gold>/maintenance reload <gray>(Recarrega o arquivo de configuração, arquivo de lista branca e o ícone do servidor)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Ativa/desativa o modo de manutenção)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Mostra o estado atual da manutenção e qualquer timer em execução)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Ativa/desativa modo de manutenção com modo de exibição opcional quando ativado)"
helpUpdate: "<gold>/maintenance update <gray>(Baixa remotamente a versão mais recente do plugin para o seu servidor)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Mostra todos os jogadores na lista branca para o modo de manutenção)"
helpWhitelistAdd: "<gold>/maintenance add <nome/uuid> <gray>(Adiciona o jogador à lista branca de manutenção, permitindo que eles entrem no servidor mesmo com a manutenção ativada)"
helpWhitelistRemove: "<gold>/maintenance remove <nome/uuid> <gray>(Remove o jogador da lista branca de manutenção)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [servidor] <gray>(Se estiver em execução, o temporizador atual será abortado)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [servidor] <gray>(Ativa/desativa o modo de manutenção)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Ativa/desativa modo de manutenção globalmente ou em um servidor especifico com um modo de exibição opcional quando ativado)"
helpStatus: "<gold>/maintenance status <gray>(Lista todos os servidores proxy que estão atualmente em manutenção)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Modo de manutenção ativado.**"
webhookMaintenanceDeactivated: "**Modo de manutenção desativado.**"
webhookStarttimerStarted: "Manutenção será ativada em <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Manutenção será desativada em <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Entrada bloqueada: `%PLAYER%` tentou conectar durante a manutenção."
webhookSingleMaintenanceActivated: "Manutenção será ativada em `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Manutenção será desativada em `%SERVER%`."
webhookSingleStarttimerStarted: ""
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+76 -56
View File
@@ -1,62 +1,67 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>У вас недостаточно прав."
noPermission: "<red>У вас нет разрешения на выполнение этой команды."
kickmessage: "<red>Сервер закрыт на технические работы!<br>Попробуйте позже!"
maintenanceActivated: "<prefix><gold>Режим технических работ включен."
maintenanceDeactivated: "<prefix><gold>Режим технических работ выключен."
alreadyEnabled: "<prefix><red>Режим технических работ уже включен!"
maintenanceStatusOn: "<prefix><gold>Режим обслуживания уже <green>включён<gold>."
maintenanceStatusOff: "<prefix><gold>Режим обслуживания уже <red>выключен<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Режим обслуживания уже <green>включён<grey> и будет выключен через - <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Режим обслуживания уже <red>Выключен<grey> и будет включен через - <gold>%TIME%."
alreadyEnabled: "<prefix><red>Режим технических работ уже включён!"
alreadyDisabled: "<prefix><red>Режим технических работ не был включен!"
endtimerBroadcast: "<prefix><gray>Сервер будет открыт через <gold>%TIME%<gray>."
endtimerStarted: "<prefix><green>Таймер запущен: <gray>Сервер будет открыт через <gold>%TIME%<gray>."
starttimerBroadcast: "<prefix><gray>Сервер будет закрыт на тех. работы через <gold>%TIME%<gray>."
starttimerStarted: "<green>Таймер успешно запущен! Сервер будет закрыт на технические работы через <white>%TIME%<green>!"
scheduletimerBroadcast: "<prefix><gray>Maintenance mode will be enabled in <gold>%TIME% <gray>and will last for <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><green>Начат таймер: <gray>Тех. работы будут включены <gold>%TIME% <gray>и будут последние для <gold>%DURATION%<gray>."
timerAlreadyRunning: "<red>Таймер уже запущен!"
timerNotRunning: "<red>Таймер не был запущен."
timerCancelled: "<gray>Таймер был остановлен."
timerTooLong: "<red>Число должно быть не больше чем <white>40320 <red>(<white>28 <red>дней)."
timerMotdDisabled: "<red>Сообщения по таймеру выключены в настройках плагина."
starttimerBroadcast: "<prefix><gray>Сервер будет закрыт на технические работы через <gold>%TIME%<gray>."
starttimerStarted: "<prefix><green>Таймер запущен: <gray>Режим обслуживания будет включен через <gold>%TIME%<gray>."
scheduletimerBroadcast: "<prefix><gray>Режим обслуживания будет включён через <gold>%TIME% <gray>и продлится <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><green>Таймер запущен: <gray>Режим обслуживания будет включен через <gold>%TIME% <gray>и продлится <gold>%DURATION%<gray>."
timerAlreadyRunning: "<prefix><red>Таймер уже запланирован!"
timerNotRunning: "<prefix><red>В данный момент нет активного таймера."
timerCancelled: "<prefix><red>Таймер остановлен."
timerTooLong: "<prefix><red>Длительность должна быть меньше 28 дней!"
timerMotdDisabled: "<prefix><red>Вы должны установить 'enable-timerspecific-messages' в конфигурации как 'true', если вы хотите использовать/редактировать титульные строки таймера."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
joinNotification: "<red>Игрок <white>%PLAYER% <red>пытался зайти на сервер."
motdList: "<gray>Список титульных строк в режиме технических работ:"
motdListEmpty: "<red>Список пуст."
reload: "<green>Плагин успешно перезагружен!"
removedMotd: "<green>Титульная строка с индексом <white>%INDEX% <green>успешно удалена!"
removeMotdError: "<red>Вы не можете удалить последние 2 титульные строки."
setMotd: "<green>Титульная строка <white>%LINE% <green>с индексом <white>%INDEX% <green>успешно изменена: <white>%MOTD%"
setMotdIndexError: "<red>Неверный индекс, допустимые значения: 1 - %NEWAMOUNT%."
setMotdLineError: "<red>Второй аргумент должен быть номером строки (1 или 2)!"
updateDownloading: "<green>Обновление загружается..."
updateFailed: "<red>Обновление не удалось!"
updateFinished: "<green>Обновление успешно пройдено! Перезагрузите сервер, чтобы избежать возможных проблем."
whitelistedPlayers: "<gray>Белый лист в режиме технических работ:"
joinNotification: "<prefix><yellow>%PLAYER% <red>попытался зайти на сервер."
motdList: "<prefix><gray>Список титульных строк для режима обслуживания:"
motdListEmpty: "<prefix><red>Список пуст."
reload: "<prefix><green>Плагин успешно перезагружен!"
removedMotd: "<prefix><green>Удалена титульная строка с индексом %INDEX%."
removeMotdError: "<prefix><red>У вас должна оставаться хотя бы одна титульная строка!"
setMotd: "<prefix><green>Титульная строка %LINE% с индексом %INDEX% успешно изменена на %MOTD%"
setReason: "<prefix><green> Проводятся технические работы: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Неверный индекс, допустимые значения: 1 - %NEWAMOUNT%."
setMotdLineError: "<prefix><red>Вторым аргументом должен быть номер строки (1 или 2)!"
updateDownloading: "<prefix><red><bold>Загрузка обновления..."
updateFailed: "<prefix><dark_red><bold>Ошибка обновления!"
updateFinished: "<prefix><green><bold>Обновление успешно установлено! Перезагрузите сервер, чтобы избежать возможных проблем."
whitelistedPlayers: "<prefix><gold>Игроки допущенные к серверу во время технических работ:"
whitelistedPlayersFormat: "<gray>- <white>%NAME% <gray>(<white>%UUID%<gray>)"
whitelistAdded: "<green>Игрок <white>%PLAYER% <green>успешно добавлен в белый лист!"
whitelistAlreadyAdded: "<red>Игрок <white>%PLAYER% <red>уже в белом листе!"
whitelistRemoved: "<green>Игрок <white>%PLAYER% <green>успешно удалён из белого листа!"
whitelistNotFound: "<red>Данный игрок не находится в белом листе!"
whitelistEmpty: "<red>Белый лист пуст! Используйте /maintenance add [игрок|uuid] чтобы добавить кого-нибудь."
playerNotFound: "<red>Игрок не найден!"
playerNotFoundUuid: "<red>Игрок с таким UUID не найден!"
playerNotOnline: "<red>Игрок должен быть онлайн!"
offlinePlayerFetchError: "<prefix><red>Произошла ошибка при получении игрока в автономном режиме. Пожалуйста, повторите попытку позже."
invalidUuid: "<red>Неверный формат UUID!"
whitelistAdded: "<prefix><green>Игрок <aqua>%PLAYER% <green>успешно добавлен в вайтлист!"
whitelistAlreadyAdded: "<prefix><aqua>Игрок <white>%PLAYER% <red>уже в вайтлисте!"
whitelistRemoved: "<prefix><green>Игрок <aqua>%PLAYER% <green> удалён из вайтлиста!"
whitelistNotFound: "<prefix><red>Данный игрок не находится в вайтлисте!"
whitelistEmpty: "<prefix><red>Вайтлист пуст! Используйте <yellow>/maintenance add <player/uuid> <red>чтобы добавить кого-нибудь."
playerNotFound: "<prefix><red>Игрок не найден!"
playerNotFoundUuid: "<prefix><red>Игрок с таким UUID не найден!"
playerNotOnline: "<prefix><red>Игрок должен быть онлайн!"
offlinePlayerFetchError: "<prefix><red>Произошла ошибка при получении оффлайн игрока. Пожалуйста, повторите попытку позже."
invalidUuid: "<prefix><red>Неверный формат UUID!"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
sentToWaitingServer: "<red>Вы были перемещены в лобби!"
forceWaitingServer: "<red>Вы не можете покинуть лобби во время технических работ!"
serverNotFound: "<red>Сервер не найден!"
singleTimerAlreadyRunning: "<red>Таймер сервера <white>%SERVER% <red>уже запущен!"
singleTimerCancelled: "<gray>Таймер сервера <white>%SERVER% <gray>был остановлен."
singleTimerNotRunning: "<red>Таймер сервера <white>%SERVER% <red>не был запущен."
singleEndtimerBroadcast: "<green>Сервер <white>%SERVER% <green>будет открыт через <white>%TIME%<green>!"
singleEndtimerStarted: "<prefix><green>Начат таймер: <gray>Тех. работы будут включены <gold>%TIME% <gray>и будут последние для <gold>%DURATION%<gray>."
singleStarttimerBroadcast: "<red>Внимание! Сервер <white>%SERVER% <red>будет закрыт на технические работы через <white>%TIME%<red>!"
singleStarttimerStarted: "<prefix><red>Внимание! <gray>Сервер <green>%SERVER%<gray> будет закрыт на технические работы через <gold>%TIME%<gray>."
sentToWaitingServer: "<prefix><red>Вы были перемещены в лобби!"
forceWaitingServer: "<prefix><red>Вы не можете покинуть лобби во время технических работ!"
serverNotFound: "<prefix><red>Сервер с таким именем не зарегистрирован на прокси сервере!"
singleTimerAlreadyRunning: "<prefix><red>Таймер для этого сервера уже запланирован!"
singleTimerCancelled: "<prefix><red>Таймер сервера <yellow>%SERVER% <red>был остановлен."
singleTimerNotRunning: "<prefix><red>Для этого сервера нет запущенных таймеров."
singleEndtimerBroadcast: "<prefix><gray>Технические работы на сервере %SERVER% завершатся через <gold>%TIME%<gray>."
singleEndtimerStarted: "<prefix><green>Таймер запущен: <gray>Режим обслуживания на сервере %SERVER% будет выключен спустя <gold>%TIME%<gray>."
singleStarttimerBroadcast: "<prefix><gray>Технические работы на сервере %SERVER% начнутся через <gold>%TIME%<gray>."
singleStarttimerStarted: "<prefix><green>Таймер запущен: <gray>Режим обслуживания на сервере %SERVER% будет включен через <gold>%TIME%<gray>."
singleScheduletimerBroadcast: "<prefix><gray>Maintenance mode on server %SERVER% will be enabled in <gold>%TIME% <gray>and will last for <gold>%DURATION%<gray>."
singleScheduletimerStarted: "<prefix><green>Таймер начат: <gray>Тех. работы на сервере %SERVER% будут начаты в <gold>%TIME% <gray>и продлятся <gold>%DURATION%<gray>."
singleMaintenanceKick: "<red>Данный сервер закрыт на технические работы! Попробуйте позже."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<red>Режим технических работ
singleServerMaintenanceList: "<gray>Сервера на которых включен режим технических работ:"
singleServerMaintenanceListEmpty: "<gray>Сервера на которых включен режим технических работ: <white>пусто"
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow> Запуститься через <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER%<yellow> Закончится через <gold>%TIME%"
second: "секунду"
seconds: "секунд"
minute: "минуту"
@@ -78,27 +85,40 @@ helpHeader: "<gray>========[ <white>%NAME% <gray>| <gold>%PAGE%<white>/<gold>%MA
helpPageNotFound: "<red>Страница не найдена."
helpNextPage: "<white>Используйте <gold>/maintenance help %PAGE% <white>чтобы перейти на следующую страницу."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>- <white>Остановить таймер включения/выключения режима технических работ."
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(Через данное время, например 1ч5мин, режим обслуживания выключится)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(По истечению заданного времени, например 1h5m, режим технических работ будет включен)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(По истечению заданного времени, например 1h5m, режим технических работ будет включен)"
helpDebug: "<gold>/maintenance debug <gray>(Enables some debug logging)"
helpDump: "<gold>/maintenance dump <gray>- <white>Создать дамп."
helpHelp: "<gold>/maintenance help [страница] <gray>- <white>Отобразить список доступных команд."
helpMotd: "<gold>/maintenance motd <timer> <gray>- <white>Отобразить список титульных сообщений."
helpRemoveMotd: "<gold>/maintenance removemotd <timer> [индекс] <gray>- <white>Удалить титульное сообщение."
helpSetMotd: "<gold>/maintenance setmotd <timer> [индекс] [1|2] [сообщение] <gray>- <white>Добавить новое титульное сообщение."
helpSetReason: "<gold>/maintenance setreason <причина/ничего> <gray>(Установить или убрать %REASON% для отображения)"
helpReload: "<gold>/maintenance reload <gray>- <white>Перезагрузить плагин."
helpToggle: "<gold>/maintenance [on|off] <gray>- <white>Включить/выключить режим технических работ."
helpGlobalStatus: "<gold>/maintenance status <gray>(Показывает текущее состояние технического обслуживания и все запущенные таймеры)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray> (Включает/выключает режим обслуживания с дополнительным режимом отображения при включении)"
helpUpdate: "<gold>/maintenance update <gray>- <white>Обновить плагин."
helpWhitelist: "<gold>/maintenance whitelist <gray>- <white>Отобразить всех игроков из белого списка."
helpWhitelistAdd: "<gold>/maintenance add [ник|uuid] <gray>- <white>Добавить игрока в белый список."
helpWhitelistRemove: "<gold>/maintenance remove [ник|uuid] <gray>- <white>Удалить игрока из белого списка."
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [сервер] <gray>- <white>Остановить таймер включения/выключения режима технических работ."
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance [on|off] [сервер] <gray>- <white>Включить/выключить режим технических работ."
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray> (включает/отключает режим обслуживания глобально или на определенном сервере с дополнительным режимом отображения при включении)"
helpStatus: "<gold>/maintenance status <gray>- <white>Список всех серверов закрытых на технические работы."
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Режим обслуживания включён.**"
webhookMaintenanceDeactivated: "**Режим обслуживания выключен**"
webhookStarttimerStarted: "Режим обслуживания будет включен через <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Режим обслуживания будет отключён через <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Заблокировано соединение: `%PLAYER%` пытался присоединиться во время обслуживания."
webhookSingleMaintenanceActivated: "Режим обслуживания включен на `%SERVER% `."
webhookSingleMaintenanceDeactivated: "Режим обслуживания отключен на `%SERVER%`."
webhookSingleStarttimerStarted: "Режим обслуживания будет включен на `%SERVER%` в <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Режим обслуживания будет выключен на `%SERVER%` в <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+30 -10
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Underhållsarbete<dark_gray>] "
noPermission: "<red>Du har inte rättigheterna att utfärda det kommandot."
kickmessage: "<red>Servern är just nu under underhåll!<br>Försök igen senare!"
maintenanceActivated: "<prefix><gold>Underhållsläge är aktiverat."
maintenanceDeactivated: "<prefix><gold>Underhållsläge är avaktiverat."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>Underhållsläge är redan aktiverat!"
alreadyDisabled: "<prefix><red>Underhållsläge är redan avaktiverat!"
endtimerBroadcast: "<prefix><gray>Underhållsläget kommer att vara avstängt om <gold>%TIME%<gray>."
@@ -18,7 +22,7 @@ scheduletimerStarted: "<prefix><green>Startade timer: <gray>Underhållsläge kom
timerAlreadyRunning: "<prefix><red>Det finns redan en timer schemalagd!"
timerNotRunning: "<prefix><red>Ingen timer är aktiverad för tillfället."
timerCancelled: "<prefix><red>Den nuvarande timern har blivit inaktiverad."
timerTooLong: "<prefix><red>Talet måste vara mindre än 40320 (28 days)!"
timerTooLong: "<prefix><red>Talet måste vara mindre än 40320 (28 dagar)!"
timerMotdDisabled: "<prefix><red>Du måste sätta värdet för 'enable-timerspecific-messages' i konfigurationsfilen filen till 'true', om du vill använda/redigera tidsspecifika motds."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
@@ -29,6 +33,7 @@ reload: "<prefix><green>Laddade om konfigurationsfilen, vitlistade spelare, spr
removedMotd: "<prefix><green>Tog bort motd nummer %INDEX%."
removeMotdError: "<prefix><red>Du har bara en motd, så du kan inte ta bort några!"
setMotd: "<prefix><green>Satte linjen %LINE% av %INDEX%. underhålls motd till %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Du har just nu %MOTDS% motds, så du måste välja ett nummer mellan 1 och %NEWAMOUNT%."
setMotdLineError: "<prefix><red>Det andra argumentet måste vara linjenumret (1 or 2)!"
updateDownloading: "<prefix><red><bold>Laddar ner uppdatering..."
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix><red>Underhåll är redan avaktiverat på
singleServerMaintenanceList: "<prefix><gray>Proxiade servrar, som är under underhåll:"
singleServerMaintenanceListEmpty: "<prefix><gray>Det finns inga proxiade servrar som har underhåll aktiverat."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "sekund"
seconds: "sekunder"
minute: "minut"
@@ -87,18 +94,31 @@ helpHelp: "<gold>/maintenance help [sida] <gray>(Visar denna magiskt fina hjälp
helpMotd: "<gold>/maintenance motd [timer] <gray>(Listar alla för närvarande aktiva underhålls motds. Om argumentet 'timer' specificeras, kommer alla timer motds visas)"
helpRemoveMotd: "<gold>/maintenance removemotd [timer] <index> <gray>(Tar bort ett motd. Om 'timer' används som argument, kommer ett timerspecifikt pingmeddelande bli borttaget)"
helpSetMotd: "<gold>/maintenance setmotd [timer] <index> <1/2> <meddelande> <gray>(Sätter ett specifikt motd. If using. Om argumentet 'timer' specificeras, kommer ett timerspecifikt pingmeddelande bli satt)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Laddar om konfigurationsfilen, vitlistan and server ikonen)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Aktiverar/avaktiverar underhållsläget)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Avlägset laddar ner senaste versionen av insticksprogrammet på din server)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Visar alla vitlistade spelare för underhållsläget)"
helpWhitelistAdd: "<gold>/maintenance add <namn/uuid> <gray>(Lägger till en spelare på vitlistan för underhållsläget, så de kan logga in även om underhåll är aktiverat)"
helpWhitelistRemove: "<gold>/maintenance remove <namn/uuid> <gray>(Tar bort spelaren från vitlistan för underhållsläget)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [server] <gray>(Vid utfärdandet av detta kommando kommer den nuvarande timern bli avbruten)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server] <gray>(Aktiverar/avaktiverar underhållsläge)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Listar alla proxiade servrar som är i underhåll)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+38 -18
View File
@@ -1,12 +1,16 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<yellow><bold>Sunucu<bold> <dark_gray>▸ "
noPermission: "<red>Bunun için iznin yok!"
kickmessage: "<red>Sunucuda bakım çalışması vardır!"
maintenanceActivated: "<prefix><gold>Bakım modu aktif."
maintenanceDeactivated: "<prefix><gold>Bakım modu deaktif."
noPermission: "<red>Bunun için iznin yok."
kickmessage: "<red>Sunucuda bakım çalışması vardır!<br>Lütfen daha sonra tekrar deneyin!"
maintenanceActivated: "<prefix><gold>Bakım modu aktifleştirildi."
maintenanceDeactivated: "<prefix><gold>Bakım modu deaktifleştirildi."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<prefix><red>Bakım modu zaten aktif!"
alreadyDisabled: "<prefix><red>Bakım modu zaten deaktif!"
endtimerBroadcast: "<prefix><gray>Bakım modu <gold>%TIME%<gray> içerisinde devre dışı olacak."
@@ -18,7 +22,7 @@ scheduletimerStarted: "<prefix><green>Başlatılan zamanlayıcı: <gray>Bakım m
timerAlreadyRunning: "<prefix><red>Zaten planlanmış bir zamanlayıcı var!"
timerNotRunning: "<prefix><red>Şu anda çalışan bir zamanlayıcı yok."
timerCancelled: "<prefix><red>Geçerli zamanlayıcı devre dışı bırakıldı."
timerTooLong: "<prefix><red>Belirtilen sayı 40320'den küçük olmalıdır (28 güne tekabül)!"
timerTooLong: "<prefix><red>Belirtilen sayı 28 günden küçük olmalıdır!"
timerMotdDisabled: "<prefix><red>Zamanlayıcıya özgü motd'ları kullanmak/düzenlemek istiyorsanız, konfigürasyonda 'enable-timerspecial-messages' kısmını 'true' olarak ayarlamalısınız."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
@@ -29,8 +33,9 @@ reload: "<prefix><green>Konfigürasyon dosyaları yeniden yüklendi."
removedMotd: "<prefix><green>%INDEX% numaralı MOTD kaldırıldı."
removeMotdError: "<prefix><red>Yalnızca bir MOTD'niz var, bu nedenle hiçbirini kaldıramazsınız!"
setMotd: "<prefix><green>%LINE% satırını %INDEX% ayarlayın. Bakım MOTD'si %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>Şu anda %MOTDS% motd'unuz var, bu yüzden 1 ile %NEWAMOUNT% arasında bir sayı seçmeniz gerekiyor."
setMotdLineError: "<prefix><red>2. argüman satır numarası (1 veya 2) olmalıdır.!"
setMotdLineError: "<prefix><red>2. argüman satır numarası (1 veya 2) olmalıdır!"
updateDownloading: "<prefix><red><bold>Güncelleme indiriliyor..."
updateFailed: "<prefix><dark_red><bold>Güncelleme hataya uğradı!"
updateFinished: "<prefix><green><bold>Güncelleme başarılı oldu! Işleyişle ilgili sorunları önlemek ve güncellemeyi tamamlamak için sunucuyu yeniden başlatmanız gerekir!"
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<prefix>%SERVER%<red> sunucusunda zaten bakım mod
singleServerMaintenanceList: "<prefix><gray>Bakımı etkinleştirilmiş sunucular:"
singleServerMaintenanceListEmpty: "<prefix><gray>Bakımda olan sunucu yok."
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "saniye"
seconds: "saniye"
minute: "dakika"
@@ -78,27 +85,40 @@ helpHeader: "<dark_gray>========[ <yellow>%NAME% <dark_gray>| <yellow>%PAGE%/%MA
helpPageNotFound: "<prefix><red>Bu numaraya sahip bir sayfa yok!"
helpNextPage: "<aqua>/maintenance help %PAGE% <gray>yazarak bir sonraki yardım sayfasına geçebilirsiniz."
helpAbortTimer: "<gold>/maintenance aborttimer <gray>(Eğer çalışıyorsa mevcut zamanlayıcı iptal edilecektir.)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpEndtimer: "<gold>/maintenance endtimer <disable in> <gray>(Belirtilen süreden sonra, Örnek: 1h5m, bakım modu deaktif olacaktır)"
helpStarttimer: "<gold>/maintenance starttimer <enable in> <gray>(Belirtilen süreden sonra, Örnek: 1h5m, bakım modu aktif olacaktır)"
helpScheduleTimer: "<gold>/maintenance scheduletimer <enable in> <maintenance duration> <gray>(Belirtilen süreden sonra, Örnek: 1h5m, bakım modu aktif olacaktır)"
helpDebug: "<gold>/maintenance debug <gray>(Bazı hata ayıklama kaydını etkinleştirir)"
helpDump: "<gold>/maintenance dump <gray>(Hata raporları için kullanılan bazı sunucu bilgilerini atar)"
helpHelp: "<gold>/maintenance help [sayfa] <gray>(Bu güzel yardım penceresini gösterir)"
helpMotd: "<gold>/maintenance motd [zamanlayıcı] <gray>(Halihazırda ayarlanmış bakım MOTD'lerini listeler. Zamanlayıcı belirtilirse, zamanlayıcı modları gösterilir)"
helpRemoveMotd: "<gold>/maintenance removemotd [zamanlayıcı] <dizin> <gray>(Bir bakım MOTD'sini kaldırır. Argüman olarak zamanlayıcı kullanılıyorsa, zamanlayıcıya özel bir MOTD mesajı kaldırılacaktır)"
helpSetMotd: "<gold>/maintenance setmotd [zamanlayıcı] <dizin> <1/2> <mesaj> <gray>(Bakım modu için bir MOTD ayarlar. Argüman olarak zamanlayıcı kullanılıyorsa, zamanlayıcıya özel MOTD ping mesajı ayarlanır)"
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>(Konfigürasyon dosyasını, beyaz liste dosyasını ve sunucu simgesini yeniden yükler)"
helpToggle: "<gold>/maintenance <on/off> <gray>(Bakım modunu etkinleştirir/devre dışı bırakır)"
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>(Eklentinin en yeni sürümünü sunucunuza uzaktan indirir)"
helpWhitelist: "<gold>/maintenance whitelist <gray>(Bakım modu için beyaz listedeki tüm oyuncuları gösterir)"
helpWhitelistAdd: "<gold>/maintenance add <isim/uuid> <gray>(Oyuncuyu bakım beyaz listesine ekler, böylece bakım etkin olsa bile sunucuya katılabilir)"
helpWhitelistRemove: "<gold>/maintenance remove <isim/uuid> <gray>(Oyuncuyu bakım beyaz listesinden kaldırır)"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [sunucu] <gray>(Çalışıyorsa, mevcut zamanlayıcı iptal edilecektir.)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [sunucu] <gray>(Bakım modunu etkinleştirir/devre dışı bırakır)"
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>(Şu anda bakımda olan tüm sunucuları listeler)"
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
+61 -41
View File
@@ -1,51 +1,56 @@
#Maintenance Plugin by kennytv
#You can use https://webui.adventure.kyori.net/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.adventure.kyori.net/minimessage/format.html
#You can use https://webui.advntr.dev/ to edit and preview the formatted text
#For a full list of formats and fancy examples, see https://docs.papermc.io/adventure/minimessage/format/
#If you want to contribute changes to the default messages, you can do so via https://crowdin.com/project/maintenance
#Messages containing the placeholder "<prefix>" will have it replaced with the following string
prefix: "<dark_gray>[<yellow>Maintenance<dark_gray>] "
noPermission: "<red>У вас недостатньо прав!"
kickmessage: "<red>Сервер зачинено на технічні роботи!"
maintenanceActivated: "<green>Режим технічних робіт увімкнено!"
maintenanceDeactivated: "<gray>Режим технічних робіт вимкнено!"
alreadyEnabled: "<red>Режим технічних робіт вже увімкнено!"
alreadyDisabled: "<red>Режим технічних робіт був включений."
endtimerBroadcast: "<green>Сервер буде відкритий через<white>%TIME%<green>!"
noPermission: "<red>У вас недостатньо прав для виконання цієї команди."
kickmessage: "<red>Сервер перебуває на технічному обслуговуванні!<br>Спробуйте пізніше!"
maintenanceActivated: "<green>Режим технічних робіт увімкнено."
maintenanceDeactivated: "<gray>Режим технічних робіт вимкнено."
maintenanceStatusOn: "<prefix><gold>Maintenance mode is currently <green>enabled<gold>."
maintenanceStatusOff: "<prefix><gold>Maintenance mode is currently <red>disabled<gold>."
maintenanceStatusOnWithEndTimer: "<prefix><gold>Maintenance mode is currently <green>enabled <grey>and will be disabled in <gold>%TIME%."
maintenanceStatusOffWithStartTimer: "<prefix><gold>Maintenance mode is currently <red>disabled <grey>and will be enabled in <gold>%TIME%."
alreadyEnabled: "<red>Режим технічних робіт вже увімкнений!"
alreadyDisabled: "<red>Режим технічних робіт був вимкнений."
endtimerBroadcast: "<green>Режим технічних робіт буде вимкнений через<gold>%TIME%<gray>."
endtimerStarted: "<green>Таймер успішно запущено! Сервер буде відкритий через<white>%TIME%<green>!"
starttimerBroadcast: "<green>Сервер буде закрито на технічні роботи через <white>%TIME%<green>!"
starttimerStarted: "<green>Таймер успішно запущено! Сервер буде закрито на технічні роботи через <white>%TIME%<green>!"
scheduletimerBroadcast: "<prefix><gray>Технічні роботи розпочнуться в<gold>%TIME% <gray>і триватимуть <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><green>Started timer: <gray>Maintenance mode will be enabled in <gold>%TIME% <gray>and will last for <gold>%DURATION%<gray>."
scheduletimerStarted: "<prefix><green>стартований таймер: <gray>режим підтримки буде ввімкнуто в <gold>%TIME% <gray>і буде тривати <gold>%DURATION%<gray>."
timerAlreadyRunning: "<red>Таймер вже запущено!"
timerNotRunning: "<red>Таймер не було запущено."
timerCancelled: "<gray>Таймер було зупинено."
timerTooLong: "<red>Число має бути не більше ніж <white>40320 <red>(<white>28 <red>днів)."
timerTooLong: "<red>Число має бути не більше ніж <white>40320 <red>(<white>28 <red>днів)!"
timerMotdDisabled: "<red>Повідомлення про таймер вимкнено в налаштуваннях плагіна."
motdTimer: "%HOURS%:%MINUTES%:%SECONDS%"
motdTimerNotRunning: "-"
joinNotification: "<red>Гравець <white>%PLAYER% <red>намагався зайти на сервер."
motdList: "<gray>Лист титульних рядків у режимі технічних робіт:"
motdListEmpty: "<red>Лист порожній."
reload: "<green>Плагін успішно перезавантажено!"
removedMotd: "<green>Титульний рядок з індексом <white>%INDEX% <green>успішно видалено!"
removeMotdError: "<red>Ви не можете видалити останні 2 титульні рядки."
setMotd: "<green>Титульний рядок <white>%LINE% <green>з індексом<white>%INDEX% <green>успішно змінено: <white>%MOTD%"
setMotdIndexError: "<red>Неправильний індекс, допустимі значення: 1 - %NEWAMOUNT%."
setMotdLineError: "<red>Другий аргумент має бути номером рядка (1 или 2)!"
updateDownloading: "<green>Оновлення завантажується..."
updateFailed: "<red>Оновлення не вдалось!"
updateFinished: "<green>Оновлення успішно пройдено! Перезавантажте сервер, щоб уникнути можливих проблем."
whitelistedPlayers: "<gray>Білий лист у режимі технічних робіт:"
joinNotification: "<prefix><yellow>%PLAYER% <red>Намагався приєднатися до сервера."
motdList: "<prefix><gray>Список motds для технічних робіт:"
motdListEmpty: "<prefix><red>У вас немає жодного motds!"
reload: "<prefix><green>Перезавантажено конфігурацію, білий список, мовні файли та іконку технічних робіт."
removedMotd: "<prefix><green>Видалено motd номер %INDEX%."
removeMotdError: "<prefix><red>У вас лише один motd, тому ви не можете його видалити!"
setMotd: "<prefix><green>Set line %LINE% of the %INDEX%. maintenance motd to %MOTD%"
setReason: "<prefix><green>Set maintenance reason to: <yellow>%REASON%"
setMotdIndexError: "<prefix><red>У вас %MOTDS% motds, тому ви можете обрати число між 1 і %NEWAMOUNT%."
setMotdLineError: "<red>Другий аргумент має бути числом (1 або 2)!"
updateDownloading: "<prefix><red><bold>Оновлення завантажується..."
updateFailed: "<prefix><dark_red><bold>Оновлення невдале!"
updateFinished: "<green>Оновлення успішне! Перезавантажте сервер, щоб уникнути можливих проблем."
whitelistedPlayers: "<gray>Білий список технічних робіт:й"
whitelistedPlayersFormat: "<gray>- <white>%NAME% <gray>(<white>%UUID%<gray>)"
whitelistAdded: "<green>Гравець <white>%PLAYER% <green>успішно доданий у білий лист!"
whitelistAlreadyAdded: "<red>Гравець <white>%PLAYER% <red>вже у білому листі!"
whitelistRemoved: "<green>Гравця <white>%PLAYER% <green>успішно видалено з білого листа!"
whitelistNotFound: "<red>Цей гравець не знаходиться у білому листі!"
whitelistEmpty: "<red>Білий лист порожній! Використовуйте /maintenance add [Гравець|uuid] щоб додати когось."
playerNotFound: "<red>Гравець не знайден!"
playerNotFoundUuid: "<red>Гравець з таким UUID не знайден!"
playerNotOnline: "<red>Гравець повинен бути онлайн!"
offlinePlayerFetchError: "<prefix><red>There was an error while fetching offline player. Please try again later."
invalidUuid: "<red>Невірний формат UUID!"
whitelistAdded: "<green>Гравець <white>%PLAYER% <green>успішно доданий у білий список технічних робіт!"
whitelistAlreadyAdded: "<red>Гравець <white>%PLAYER% <red>вже у білому листі технічних робіт!"
whitelistRemoved: "<green>Гравця <white>%PLAYER% <green>успішно видалено з білого листа технічних робіт!"
whitelistNotFound: "<red>Цей гравець не знаходиться у білому листі технічних робіт!"
whitelistEmpty: "<red>Білий список порожній! Використовуйте /maintenance add [Гравець|uuid] щоб додати когось!"
playerNotFound: "<red>Жоден гравець з таким іменем раніше не грав на цьому сервері."
playerNotFoundUuid: "<red>Гравець з таким UUID не знайдений!"
playerNotOnline: "<red>Гравець повинен бути онлайн."
offlinePlayerFetchError: "<prefix><red>Помилка при отриманні автономного гравця. Будь ласка, спробуйте ще раз."
invalidUuid: "<red>Неправильний uuid формат!"
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
sentToWaitingServer: "<red>Ви були переміщені у лобі!"
forceWaitingServer: "<red>Ви не можете залишити лобі під час технічних робіт!"
@@ -68,6 +73,8 @@ singleServerAlreadyDisabled: "<red>Режим технічних робіт на
singleServerMaintenanceList: "<gray>Сервера на яких включено режим технічних робіт:"
singleServerMaintenanceListEmpty: "<gray>Сервера на яких включено режим технічних робіт:<white>порожньо"
singleServerMaintenanceListEntry: "<dark_grey>- <aqua>%SERVER%"
singleServerMaintenanceListEntryWithStartTimer: "<dark_grey>- <grey>%SERVER% <yellow>starting in <gold>%TIME%"
singleServerMaintenanceListEntryWithEndTimer: "<dark_grey>- <aqua>%SERVER% <yellow>ending in <gold>%TIME%"
second: "секунду"
seconds: "секунд"
minute: "хвилину"
@@ -87,18 +94,31 @@ helpHelp: "<gold>/maintenance help [сторінка] <gray>- <white>Відоб
helpMotd: "<gold>/maintenance motd <timer> <gray>- <white>Відобразити список титульних повідомлень."
helpRemoveMotd: "<gold>/maintenance removemotd <timer> [індекс] <gray>- <white>Видалити титульне повідомлення."
helpSetMotd: "<gold>/maintenance setmotd <timer> [індекс] [1|2] [сообщение] <gray>- <white>Додати нове титульне повідомлення."
helpSetReason: "<gold>/maintenance setreason <reason/none> <gray>(Sets or clears the %REASON% placeholder value)"
helpReload: "<gold>/maintenance reload <gray>- <white>Перезавантажити плагін."
helpToggle: "<gold>/maintenance [on|off] <gray>- <white>Увімкнути/вимкнути режим технічних робіт."
helpGlobalStatus: "<gold>/maintenance status <gray>(Shows the current maintenance status and any running timers)"
helpToggle: "<gold>/maintenance <on/off> [mode] <gray>(Enables/disables maintenance mode with an optional display mode when enabling)"
helpUpdate: "<gold>/maintenance update <gray>- <white>Оновити плагін."
helpWhitelist: "<gold>/maintenance whitelist <gray>- <white>Відобразити всіх гравців із білого листа."
helpWhitelistAdd: "<gold>/maintenance add [ник|uuid] <gray>- <white>Додати гравця до білого листа."
helpWhitelistRemove: "<gold>/maintenance remove [ник|uuid] <gray>- <white>Видалити гравця із білого листа."
#Messages for the Bungee/Velocity part, you can ignore them if you use the plugin on Paper/Sponge
helpAbortSingleTimer: "<gold>/maintenance aborttimer [сервер] <gray>- <white>Зупинити таймер увімкнення/вимкнення режиму технічних робіт."
helpSingleEndtimer: "<gold>/maintenance endtimer [server] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance [on|off] [сервер] <gray>- <white>Увімкнути/вимкнути режим технічних робіт."
helpSingleEndtimer: "<gold>/maintenance endtimer [server|global] <disable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be disabled)"
helpSingleStarttimer: "<gold>/maintenance starttimer [server|global] [mode] <enable in> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleScheduleTimer: "<gold>/maintenance scheduletimer [server|global] [mode] <enable in> <maintenance duration> <gray>(After the given time, e.g. 1h5m, maintenance mode will be enabled)"
helpSingleToggle: "<gold>/maintenance <on/off> [server|global] [mode] <gray>(Enables/disables maintenance mode globally or on a specific server with an optional display mode when enabling)"
helpStatus: "<gold>/maintenance status <gray>- <white>Список всіх серверів, закритих на технічні роботи."
#Discord webhook messages. These use Discord markdown, not MiniMessage.
#For timer webhooks, %TIMESTAMP% is a Unix timestamp for use in Discord timestamp tags, e.g. <t:%TIMESTAMP%:R>.
webhookMaintenanceActivated: "**Maintenance mode enabled.**"
webhookMaintenanceDeactivated: "**Maintenance mode disabled.**"
webhookStarttimerStarted: "Maintenance mode will be enabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookEndtimerStarted: "Maintenance mode will be disabled <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookJoinNotification: "Blocked join: `%PLAYER%` tried to join during maintenance."
webhookSingleMaintenanceActivated: "Maintenance mode enabled on `%SERVER%`."
webhookSingleMaintenanceDeactivated: "Maintenance mode disabled on `%SERVER%`."
webhookSingleStarttimerStarted: "Maintenance mode will be enabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
webhookSingleEndtimerStarted: "Maintenance mode will be disabled on `%SERVER%` <t:%TIMESTAMP%:R> (`%TIME%`)."
#Used for autoupdating the language file, do not change this value.
language-version: 2
language-version: 5
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more