Upload 11.1.3

This commit is contained in:
Matej Pacan 2025-01-17 18:21:20 +01:00
parent e619da5efd
commit dd66d55fdc
203 changed files with 39421 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

44
.gitignore vendored Normal file
View File

@ -0,0 +1,44 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
upsell/
chatcontrol-bungeecord/library
chatcontrol-velocity/library
# Node artifact files
node_modules/
dist/
.classpath
.externalToolBuilders/
.project
.settings/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
ftp-settings.yml

View File

@ -0,0 +1,30 @@
<?xml version="1.0" ?>
<project name="ChatControl" default="Build">
<target name="Build">
<jar jarfile="/Users/kangarko/Test Servers/1.8.8/plugins/${ant.project.name}.jar" basedir="./target/classes/" includes="**/*">
<fileset dir="../chatcontrol-core/target/classes" />
<fileset dir="../../Foundation/foundation-bukkit/target/classes" />
<fileset dir="../../Foundation/foundation-core/target/classes" />
</jar>
<jar jarfile="/Users/kangarko/Test Servers/Second 1.8.8/plugins/${ant.project.name}.jar" basedir="./target/classes/" includes="**/*">
<fileset dir="../chatcontrol-core/target/classes" />
<fileset dir="../../Foundation/foundation-bukkit/target/classes" />
<fileset dir="../../Foundation/foundation-core/target/classes" />
</jar>
<jar jarfile="/Users/kangarko/Test Servers/1.21/plugins/${ant.project.name}.jar" basedir="./target/classes/" includes="**/*">
<fileset dir="../chatcontrol-core/target/classes" />
<fileset dir="../../Foundation/foundation-bukkit/target/classes" />
<fileset dir="../../Foundation/foundation-core/target/classes" />
</jar>
<jar jarfile="/Users/kangarko/Test Servers/Second 1.21/plugins/${ant.project.name}.jar" basedir="./target/classes/" includes="**/*">
<fileset dir="../chatcontrol-core/target/classes" />
<fileset dir="../../Foundation/foundation-bukkit/target/classes" />
<fileset dir="../../Foundation/foundation-core/target/classes" />
</jar>
<!--<jar jarfile="/Users/kangarko/Test Servers/Non-Standard/1.21.1/plugins/${ant.project.name}.jar" basedir="./target/classes/" includes="**/*">
<fileset dir="../chatcontrol-core/target/classes" />
<fileset dir="../../Foundation/foundation-bukkit/target/classes" />
<fileset dir="../../Foundation/foundation-core/target/classes" />
</jar>-->
</target>
</project>

128
chatcontrol-bukkit/pom.xml Normal file
View File

@ -0,0 +1,128 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mineacademy</groupId>
<artifactId>chatcontrol-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>chatcontrol-bukkit</artifactId>
<name>ChatControl</name>
<version>11.1.3</version>
<properties>
<main.class>org.mineacademy.chatcontrol.ChatControl</main.class>
</properties>
<dependencies>
<dependency>
<groupId>org.mineacademy</groupId>
<artifactId>chatcontrol-core</artifactId>
<version>LATEST</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mineacademy</groupId>
<artifactId>foundation-bukkit</artifactId>
<version>LATEST</version>
<scope>compile</scope>
</dependency>
<!-- MineAcademy repo -->
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>DiscordSRV</artifactId>
<version>1.28.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>dynmap</artifactId>
<version>3.7b7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>SimpleClans</artifactId>
<version>2.19.3-SNAPSHOT-418</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>TownyChat</artifactId>
<version>0.115</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>mcMMO</artifactId>
<version>2.1.231</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>EssentialsX</artifactId>
<version>2.21.0-SNAPSHOT-1565</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mineacademy.plugin</groupId>
<artifactId>AuthMe</artifactId>
<version>5.6.0-SNAPSHOT-2622</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.name}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactSet>
<includes>
<include>org.mineacademy:foundation-bukkit*</include>
<include>org.mineacademy:chatcontrol-core*</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.mineacademy.fo</pattern>
<shadedPattern>org.mineacademy.chatcontrol.lib</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,401 @@
package org.mineacademy.chatcontrol;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.command.CommandDummy;
import org.mineacademy.chatcontrol.command.CommandIgnore;
import org.mineacademy.chatcontrol.command.CommandList;
import org.mineacademy.chatcontrol.command.CommandMail;
import org.mineacademy.chatcontrol.command.CommandMe;
import org.mineacademy.chatcontrol.command.CommandMotd;
import org.mineacademy.chatcontrol.command.CommandMute;
import org.mineacademy.chatcontrol.command.CommandRealName;
import org.mineacademy.chatcontrol.command.CommandReply;
import org.mineacademy.chatcontrol.command.CommandSay;
import org.mineacademy.chatcontrol.command.CommandSpy;
import org.mineacademy.chatcontrol.command.CommandTag;
import org.mineacademy.chatcontrol.command.CommandTell;
import org.mineacademy.chatcontrol.command.CommandToggle;
import org.mineacademy.chatcontrol.listener.BookListener;
import org.mineacademy.chatcontrol.listener.CommandListener;
import org.mineacademy.chatcontrol.listener.PlayerListener;
import org.mineacademy.chatcontrol.listener.TabListener;
import org.mineacademy.chatcontrol.listener.ThirdPartiesListener;
import org.mineacademy.chatcontrol.listener.chat.AdventureChatListener;
import org.mineacademy.chatcontrol.listener.chat.LegacyChatListener;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.Colors;
import org.mineacademy.chatcontrol.model.Format;
import org.mineacademy.chatcontrol.model.Migrator;
import org.mineacademy.chatcontrol.model.Newcomer;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.ProxyChat;
import org.mineacademy.chatcontrol.model.WarningPoints;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.Database;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.operator.Groups;
import org.mineacademy.chatcontrol.operator.PlayerMessages;
import org.mineacademy.chatcontrol.operator.Rules;
import org.mineacademy.chatcontrol.operator.Tag;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.FileUtil;
import org.mineacademy.fo.MinecraftVersion;
import org.mineacademy.fo.MinecraftVersion.V;
import org.mineacademy.fo.RandomUtil;
import org.mineacademy.fo.command.DebugSubCommand;
import org.mineacademy.fo.debug.Debugger;
import org.mineacademy.fo.model.HookManager;
import org.mineacademy.fo.model.SimpleBook;
import org.mineacademy.fo.model.Variable;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.platform.BukkitPlugin;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.region.DiskRegion;
import org.mineacademy.fo.remain.Remain;
import org.mineacademy.fo.settings.YamlConfig;
import org.mineacademy.fo.visual.VisualizedRegion;
import github.scarsz.discordsrv.DiscordSRV;
/**
* ChatControl is a simple chat management plugin.
*
* @since last major code audit November 2024
*/
public final class ChatControl extends BukkitPlugin {
@Override
public String[] getStartupLogo() {
return new String[] {
"&c ____ _ _ ____ ___ ____ ____ _ _ ___ ____ ____ _ ",
"&4 | |__| |__| | | | | |\\ | | |__/ | | | ",
"&4 |___ | | | | | |___ |__| | \\| | | \\ |__| |___",
};
}
@Override
protected void onPluginLoad() {
Variable.PROTOTYPE_PATH = fileName -> {
// Return different prototypes for different variable types since MESSAGE
// variables do not support all keys
final File file = FileUtil.createIfNotExists("variables/" + fileName + ".yml");
final YamlConfig config = YamlConfig.fromFile(file);
final String type = config.getString("Type", "FORMAT").toLowerCase();
return "prototype/" + "variable-" + ("format".equals(type) ? "format" : "message") + ".yml";
};
// Add Sentry tags
Debugger.addSentryTag(() -> {
final Map<String, String> tags = new HashMap<>();
tags.put("db_type", Settings.Database.TYPE.getKey());
tags.put("db_player_type", Settings.UUID_LOOKUP ? "uuid" : "name");
tags.put("proxy_enabled", Settings.Proxy.ENABLED ? "true" : "false");
return tags;
});
// Set how to get the region for tools
DiskRegion.setCreatedPlayerRegionGetter(player -> SenderCache.from(player).getCreatedRegion());
DiskRegion.setCreatedPlayerRegionResetter(player -> SenderCache.from(player).setCreatedRegion(new VisualizedRegion()));
}
@Override
protected void onPluginPreStart() {
Migrator.migrateV10Settings(this);
}
@Override
protected void onPluginStart() {
this.checkSecureProfile();
// Update and create tables
Database.getInstance().prepareTables();
// Reload database cache for online players
for (final Player player : Remain.getOnlinePlayers()) {
final SenderCache senderCache = SenderCache.from(player);
if (!senderCache.isDatabaseLoaded() && senderCache.getCacheLoadingTask() == null)
Database.getInstance().loadAndStoreCache(player, senderCache, cache -> {
});
}
// Register third party early to prevent duplication on reload
ThirdPartiesListener.registerEvents();
if (Settings.Ignore.ENABLED)
this.registerCommand(new CommandIgnore());
if (Settings.Mail.ENABLED)
this.registerCommand(new CommandMail());
if (Settings.Me.ENABLED)
this.registerCommand(new CommandMe());
if (Settings.Say.ENABLED)
this.registerCommand(new CommandSay());
if (Settings.ListPlayers.ENABLED)
this.registerCommand(new CommandList());
if (Settings.Motd.ENABLED)
this.registerCommand(new CommandMotd());
if (Settings.Mute.ENABLED)
this.registerCommand(new CommandMute());
if (!Settings.Tag.APPLY_ON.isEmpty())
this.registerCommand(new CommandTag());
if (Settings.Tag.APPLY_ON.contains(Tag.Type.NICK))
this.registerCommand(new CommandRealName());
if (!Settings.Spy.APPLY_ON.isEmpty())
this.registerCommand(new CommandSpy());
if (!Settings.Toggle.APPLY_ON.isEmpty()) {
final boolean deregister = !Platform.isPluginInstalled("PvPManager");
new CommandToggle().register(deregister, deregister);
}
if (Settings.PrivateMessages.ENABLED) {
this.registerCommand(new CommandReply());
new CommandTell().register(!Platform.isPluginInstalled("Towny"));
}
this.registerCommand(new CommandDummy());
if (Remain.hasAdventureChatEvent() && Settings.CHAT_LISTENER_PRIORITY.getValue())
AdventureChatListener.register();
else
LegacyChatListener.register();
this.registerEvents(CommandListener.getInstance());
this.registerEvents(PlayerListener.getInstance());
if (Settings.TabComplete.ENABLED && MinecraftVersion.atLeast(V.v1_13))
this.registerEvents(TabListener.getInstance());
if (Remain.hasBookEvent())
this.registerEvents(BookListener.getInstance());
this.loadData();
// Run tasks
WarningPoints.scheduleTask();
Newcomer.scheduleTask();
ProxyChat.scheduleTask();
// Add more info to debug zip feature
DebugSubCommand.addDebugLines(
"Proxy: " + Settings.Proxy.ENABLED,
"Database: " + Settings.Database.TYPE);
if (HookManager.isLiteBansLoaded() && !HookManager.isVaultLoaded())
CommonCore.warning("Please install Vault plugin to enable prefix/suffix/group variables since you have LiteBans installed.");
if (Remain.isFolia() && !HookManager.isProtocolLibLoaded())
CommonCore.warning("Please install ProtocolLib when on Folia otherwise parts of the plugin might not work.");
if (Platform.isPluginInstalled("TAB"))
CommonCore.warning("TAB detected. Use %chatcontrol_player_nick_section%, %chatcontrol_player_prefix_section% and %chatcontrol_player_suffix_section% variables in groups.yml to ensure compatibility.");
if (Platform.isPluginInstalled("InteractiveChat")) {
CommonCore.warning("InteractiveChat detected. If having issues, adjust our Chat_Listener_Priority key in settings.yml (try setting it to LOWEST, without -MODERN prefix).");
if (Settings.Colors.APPLY_ON.contains(Colors.Type.CHAT)) {
final File interactiveChatFile = new File(this.getDataFolder().getParent(), "InteractiveChat/config.yml");
if (interactiveChatFile.exists()) {
final YamlConfig interactiveChatConfig = YamlConfig.fromFile(interactiveChatFile);
final List<String> formats = interactiveChatConfig.getStringList("Settings.FormattingTags.AdditionalRGBFormats");
if (formats.contains("#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])"))
CommonCore.logFramed(
"INCOMPATIBILITY WITH INTERACTIVECHAT DETECTED",
"",
"ChatControl hex colors are incompatible with the",
"Settings.FormattingTags.AdditionalRGBFormats key in",
"InteractiveChat's config.yml containing:",
"#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])",
"",
"Either remove that key from InteractiveChat's config.yml",
"or remove 'chat' (and possibly others) from Colors.Apply_On",
"in ChatControl's settings.yml.");
}
}
}
if (HookManager.isDiscordSRVLoaded() && Settings.Channels.ENABLED && Settings.Discord.ENABLED)
if (DiscordSRV.config().getBoolean("DiscordChatChannelMinecraftToDiscord"))
CommonCore.logFramed(
"Warning: The key DiscordChatChannelMinecraftToDiscord",
"is set on true in your DiscordSRV/config.yml file.",
"",
"Since you have Channels and Discord integration enabled",
"in ChatControl, this will produce duplicated messages.",
"Set the key to false to resolve this.");
if (Settings.SHOW_TIPS)
CommonCore.log("",
"For documentation & tutorial, see:",
"&chttps://github.com/kangarko/chatcontrol/wiki",
"",
"Loaded! Random joke: " + this.getRandomSplash(),
"");
Platform.runTask(() -> {
for (final String fileName : Arrays.asList("usermap.csv", "blocked-commands.log")) {
final File file = new File(this.getDataFolder(), fileName);
if (file.exists()) {
file.delete();
CommonCore.log("Deleted " + fileName + " file. It's no longer used by ChatControl.");
}
}
});
}
/*
* Warn if secure profile is enabled
*/
private void checkSecureProfile() {
final File serverProperties = new File("server.properties");
final Properties properties = new Properties();
try {
properties.load(new FileInputStream(serverProperties));
} catch (final IOException ex) {
ex.printStackTrace();
}
final boolean enforceSecureProfile = Boolean.parseBoolean(properties.getProperty("enforce-secure-profile", "false"));
if (enforceSecureProfile)
CommonCore.warning("It is advised you set 'enforce-secure-profile' to false in server.properties for best performance and improved player privacy.");
}
@Override
protected void onPluginPreReload() {
// Reload database before its instance is replaced by settings being reloaded
Database.getInstance().disconnect();
}
@Override
protected void onPluginReload() {
Variables.setDoubleParse(Settings.Performance.SUPPORT_VARIABLES_IN_VARIABLES);
this.loadData();
for (final Player online : Remain.getOnlinePlayers()) {
final SenderCache senderCache = SenderCache.from(online);
if (senderCache.isDatabaseLoaded()) {
final WrappedSender wrapped = WrappedSender.fromPlayerCaches(online, PlayerCache.fromCached(online), senderCache);
if (Settings.Channels.ENABLED)
Channel.autoJoin(online, wrapped.getPlayerCache());
Players.setTablistName(wrapped);
}
}
}
/*
* A common call for startup and reloading
*/
private void loadData() {
SimpleBook.copyDefaults();
// Load parts of the plugin
Variable.loadVariables();
Channel.loadChannels();
Format.loadFormats();
// Load rule system
Rules.getInstance().load();
Groups.getInstance().load();
PlayerMessages.getInstance().load();
// Copy sample image but only if folder doesn't exist so people can remove it
if (!FileUtil.getFile("images").exists())
FileUtil.extractRaw("images/creeper-head.png");
}
/*
* Time for some fun!
*/
private String getRandomSplash() {
return RandomUtil.nextItem(
"Requires at least 32Gb of RAM! #unfortunatelyNotAJoke #joke",
"Never closes database connections #dailyLeaks #patched",
"Uses outdated log4j! #joke",
"Try '/me is the best' today! #seriously",
"Cracked by kangarko (MineAcademy.org) #joke",
"Censored for stating the obvious! #elonMusk");
}
@Override
public Set<String> getConsoleFilter() {
return Settings.ConsoleFilter.ENABLED ? Settings.ConsoleFilter.MESSAGES : new HashSet<>();
}
@Override
public boolean isRegexCaseInsensitive() {
return Settings.RULES_CASE_INSENSITIVE;
}
@Override
public boolean isRegexUnicode() {
return Settings.RULES_UNICODE;
}
/**
* The inception year -- whoa long time ago!
*
* @return the year
*/
@Override
public int getFoundedYear() {
return 2013;
}
@Override
public String getSentryDsn() {
return "https://f3e0e6f4236a18360bf321211866ae6f@o4508048573661184.ingest.us.sentry.io/4508052468269056";
}
@Override
public int getBStatsPluginId() {
return 13100;
}
@Override
public int getBuiltByBitId() {
return 18217;
}
@Override
protected boolean useFullPlaceholderAPIParser() {
return Settings.Performance.SUPPORT_FULL_PLACEHOLDERAPI_SYNTAX;
}
}

View File

@ -0,0 +1,475 @@
package org.mineacademy.chatcontrol;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.LogType;
import org.mineacademy.chatcontrol.model.Mute;
import org.mineacademy.chatcontrol.model.PlayerMessageType;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.Mail;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.operator.PlayerMessages;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.PlayerUtil;
import org.mineacademy.fo.ValidCore;
import org.mineacademy.fo.model.LimitedQueue;
import org.mineacademy.fo.model.SimpleBook;
import org.mineacademy.fo.model.Task;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.visual.VisualizedRegion;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
/**
* Represents a cache that can work for any command sender,
* such as those coming from Discord too.
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class SenderCache {
/**
* The internal sender map by unique ID.
*/
private static final Map<UUID, SenderCache> uniqueCacheMap = new HashMap<>();
/**
* The sender name
*/
@Getter
private final String senderName;
/**
* Sender's last communication
*/
private final Map<LogType, Queue<Output>> lastCommunication = new HashMap<>();
/**
* Stores last packets sent, caught by ProtocolLib
*
* 100 is the maximum chat line count you can view in history
* This is used to delete messages
*/
private final LimitedQueue<String> lastChatPackets = new LimitedQueue<>(100);
/**
* The last time the sender has joined the server or -1 if not set
*/
@Getter
@Setter
private long lastLogin = -1;
/**
* The last time the sender used sound notify successfuly
*/
@Getter
@Setter
private long lastSoundNotify = -1;
/**
* If sender is player - his join location
*/
@Setter
private Location joinLocation;
/**
* Did the sender move from his {@link #joinLocation}
*/
@Getter
@Setter
private boolean movedFromJoin;
/**
* The last sign test, null if not yet set
*/
@Getter
@Setter
@Nullable
private String[] lastSignText;
/**
* Represents a region the player is currently creating
*/
@Getter
@Setter
private VisualizedRegion createdRegion = new VisualizedRegion();
/**
* Represents an unfinished mail the player writes
*/
@Getter
@Setter
private SimpleBook pendingMail;
/**
* The mail this player is replying to
*/
@Getter
@Setter
private Mail pendingMailReply;
/**
* Recent warning messages the sender has received
* Used to prevent duplicate warning messages
*/
@Getter
private final Map<UUID, Long> recentWarningMessages = new HashMap<>();
/**
* Was the related {@link PlayerCache} loaded from the database?
*/
@Getter
@Setter
private boolean databaseLoaded = false;
/**
* Is the database currently being queried?
*/
@Getter
@Setter
private boolean queryingDatabase = false;
/**
* Used for AuthMe to delay join message
*/
@Getter
@Setter
private String joinMessage;
/**
* Get last reply player
*/
@Getter
@Setter
private String replyPlayerName;
/**
* If conversation mode is enabled this holds the player the
* sender is conversing with, otherwise null as bull
*/
@Getter
@Setter
private String conversingPlayerName;
/**
* When did the player chat in automode last time?
*/
@Getter
@Setter
private long lastAutoModeChat;
/**
* Did we already triggered join flood feature for this player?
* Used to limit running commands only once.
*/
@Getter
@Setter
private boolean joinFloodActivated;
/**
* The database loading task which might hang on slow db and we need to clean it manually
*/
@Getter
@Setter
private Task cacheLoadingTask;
/**
* Ping proxy back that db has loaded and we can now send join message.
*/
@Getter
@Setter
private boolean pendingProxyJoinMessage;
/**
* Proxy to show join message
*
* @param wrapped
*/
public void sendJoinMessage(final WrappedSender wrapped) {
ValidCore.checkBoolean(this.databaseLoaded, "Cannot send join message for " + wrapped.getName() + " since db was not loaded yet!");
ValidCore.checkNotNull(this.joinMessage, "Join message must be set!");
if (Settings.Messages.APPLY_ON.contains(PlayerMessageType.JOIN) && (!Mute.isSomethingMutedIf(Settings.Mute.HIDE_JOINS, wrapped) || Settings.Mute.SOFT_HIDE) && !PlayerUtil.isVanished(wrapped.getPlayer()))
Platform.runTask(Settings.Messages.DEFER_JOIN_MESSAGE_BY.getTimeTicks(), () -> PlayerMessages.broadcast(PlayerMessageType.JOIN, wrapped, this.joinMessage));
if (Settings.Proxy.ENABLED)
this.pendingProxyJoinMessage = true;
}
/**
* Return the last chat message, or null if not yet registered
*
* @return
*/
@Nullable
public String getLastChatMessage() {
final Output lastChatOutput = this.getLastChat();
return lastChatOutput == null ? null : lastChatOutput.getOutput();
}
/**
* Return the last chat output
* @return
*/
@Nullable
public Output getLastChat() {
final List<Output> lastOutputs = this.getLastOutputs(LogType.CHAT, 1, null);
return lastOutputs.isEmpty() ? null : lastOutputs.get(0);
}
/**
* Retrieve a list of the last X amount of outputs the sender has issued
*
* @param type
* @param amountInHistory
* @param channel
* @return
*/
public List<Output> getLastOutputs(final LogType type, final int amountInHistory, @Nullable final Channel channel) {
return this.filterOutputs(type, channel, amountInHistory, null);
}
/**
* Retrieve a list of all outputs issued on or after the given date
*
* @param type
* @param timestamp
* @param channel
* @return
*/
public List<Output> getOutputsAfter(final LogType type, final long timestamp, @Nullable final Channel channel) {
return this.filterOutputs(type, channel, -1, output -> output.getTime() >= timestamp);
}
/*
* Return a list of inputs by the given type, if the type is chat then also from the given channel,
* maximum of the given limit and matching the given filter
*/
private List<Output> filterOutputs(final LogType type, @Nullable final Channel channel, final int limit, @Nullable final Predicate<Output> filter) {
final Queue<Output> allOutputs = this.lastCommunication.get(type);
final List<Output> listedOutputs = new ArrayList<>();
if (allOutputs != null) {
final Output[] outputArray = allOutputs.toArray(new Output[allOutputs.size()]);
// Start from the last output
for (int i = allOutputs.size() - 1; i >= 0; i--) {
final Output output = outputArray[i];
// Return if channels set but not equal
if (output == null)
continue;
if (output.getChannel() != null && channel != null && !output.getChannel().equals(channel.getName()))
continue;
if (limit != -1 && listedOutputs.size() >= limit)
break;
if (filter != null && !filter.test(output))
break;
listedOutputs.add(output);
}
}
// Needed to reverse the entire list now
Collections.reverse(listedOutputs);
return listedOutputs;
}
/**
* Cache the given chat message from the given channel
*
* @param input
* @param channel
*/
public void cacheMessage(final String input, final Channel channel) {
this.record(LogType.CHAT, input, channel);
}
/**
* Cache the given command
*
* @param input
*/
public void cacheCommand(final String input) {
this.record(LogType.COMMAND, input, null);
}
/*
* Internal caching handler method
*/
private void record(final LogType type, final String input, @Nullable final Channel channel) {
final Queue<Output> queue = this.lastCommunication.getOrDefault(type, new LimitedQueue<>(100));
final Output record = new Output(System.currentTimeMillis(), input, channel == null ? null : channel.getName());
queue.add(record);
this.lastCommunication.put(type, queue);
}
/**
* Get the last chat packets
*
* @return
*/
public LimitedQueue<String> getLastChatPackets() {
synchronized (this.lastChatPackets) {
return this.lastChatPackets;
}
}
/**
* Get the join location, throwing exception if not set
*
* @return the joinLocation
*/
public Location getJoinLocation() {
ValidCore.checkBoolean(this.hasJoinLocation(), "Join location has not been set!");
return this.joinLocation;
}
/**
* Return if the join location has been set
* @return
*/
public boolean hasJoinLocation() {
return this.joinLocation != null;
}
/**
* Return if the sender is conversing with another player
*
* @return
*/
public boolean hasConversingPlayer() {
return this.conversingPlayerName != null;
}
/**
* Convert the sender name to a player is online
*
* @return
*/
public Player toPlayer() {
return Bukkit.getPlayerExact(this.senderName);
}
// ------------------------------------------------------------------------------------------------------------
// Static
// ------------------------------------------------------------------------------------------------------------
/**
* Return all caches stored in memory
*
* @return
*/
public static Iterator<SenderCache> getCaches() {
synchronized (uniqueCacheMap) {
return uniqueCacheMap.values().iterator();
}
}
/**
* Retrieve (or create) a sender cache
*
* @param sender
* @return
*/
public static SenderCache from(final CommandSender sender) {
return from(Platform.toPlayer(sender).getUniqueId(), sender.getName());
}
/**
* Retrieve (or create) a sender cache
*
* @param wrapped
* @return
*/
public static SenderCache from(final WrappedSender wrapped) {
return from(wrapped.getUniqueId(), wrapped.getName());
}
/**
* Retrieve (or create) a sender cache
*
* @param senderUid
* @param senderName
* @return
*/
public static SenderCache from(final UUID senderUid, final String senderName) {
synchronized (uniqueCacheMap) {
SenderCache cache = uniqueCacheMap.get(senderUid);
if (cache == null) {
cache = new SenderCache(senderName);
uniqueCacheMap.put(senderUid, cache);
}
return cache;
}
}
// ------------------------------------------------------------------------------------------------------------
// Classes
// ------------------------------------------------------------------------------------------------------------
/**
* Represents a given senders output
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static final class Output {
/**
* The default output with -1 time and a blank message
*/
public static final Output NO_OUTPUT = new Output(-1, "", "");
/**
* The time the message was sent
*/
private final long time;
/**
* The message content
*/
private final String output;
/**
* Message channel or null if not associated (such as for commands)
*/
@Nullable
private final String channel;
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.time + " '" + this.output + "' " + this.channel;
}
}
}

View File

@ -0,0 +1,56 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is executed when a player joins a channel.
*/
@Getter
public final class ChannelJoinEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The cache of the Dude himself
*/
private final PlayerCache cache;
/**
* The channel player is joining into
*/
private final Channel channel;
/**
* The mode player is joining with
*/
private final ChannelMode mode;
/**
* Enable joining?
*/
@Setter
private boolean cancelled;
public ChannelJoinEvent(final PlayerCache cache, final Channel channel, final ChannelMode mode) {
this.cache = cache;
this.channel = channel;
this.mode = mode;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,56 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is executed when a player leaves a channel.
*/
@Getter
public final class ChannelLeaveEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The cache of the Dude himself
*/
private final PlayerCache cache;
/**
* The channel leaving
*/
private final Channel channel;
/**
* The mode player had
*/
private final ChannelMode mode;
/**
* Prevent dude leaving?
*/
@Setter
private boolean cancelled;
public ChannelLeaveEvent(final PlayerCache cache, final Channel channel, final ChannelMode mode) {
this.cache = cache;
this.channel = channel;
this.mode = mode;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,78 @@
package org.mineacademy.chatcontrol.api;
import java.util.Set;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.fo.event.SimpleEvent;
import org.mineacademy.fo.model.SimpleComponent;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
* Event for when players send a message to a chat channel.
*
* This is fired after checker, rules, colors, sound notify etc. has fired.
* Cancelling has thus only partial effect and is not recommended.
*/
@Getter
@Setter
@AllArgsConstructor
public final class ChannelPostChatEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The chat channel
*/
private final Channel channel;
/**
* The player who issued the message
*/
private final CommandSender sender;
/**
* A list of players receiving this message
*/
private final Set<Player> recipients;
/**
* The message
*/
private final String message;
/**
* The formatted message
*/
private SimpleComponent format;
/**
* The message
*/
private String consoleFormat;
/**
* Is the event cancelled?
*/
private final boolean cancelledSilently;
/**
* Is the event cancelled?
*/
private boolean cancelled;
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,65 @@
package org.mineacademy.chatcontrol.api;
import java.util.Set;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* Event for when players send a message to a chat channel.
* This is fired before any modifications are made to the message.
*/
@Getter
@Setter
public final class ChannelPreChatEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The chat channel
*/
private final Channel channel;
/**
* The player who issued the message
*/
private final CommandSender sender;
/**
* A list of players receiving this message
*/
private final Set<Player> recipients;
/**
* The message
*/
private String message;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public ChannelPreChatEvent(final Channel channel, final CommandSender sender, final String message, final Set<Player> recipients) {
this.channel = channel;
this.sender = sender;
this.message = message;
this.recipients = recipients;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,72 @@
package org.mineacademy.chatcontrol.api;
import java.util.Set;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.fo.event.SimpleEvent;
import org.mineacademy.fo.model.SimpleComponent;
import lombok.Getter;
import lombok.Setter;
/**
* Event for when players send a message to a chat channel from another server.
*/
@Getter
@Setter
public final class ChatChannelProxyEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The chat channel
*/
private final Channel channel;
/**
* The sender name of whom issued the message
*/
private final String senderName;
/**
* The sender UUID of whom issued the message
*/
private final UUID senderUid;
/**
* A list of players receiving this message
*/
private final Set<Player> recipients;
/**
* The formatted message
*/
@Setter
private SimpleComponent formattedMessage;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public ChatChannelProxyEvent(final Channel channel, final String senderName, final UUID senderUid, final SimpleComponent formattedMessage, final Set<Player> recipients) {
this.channel = channel;
this.senderName = senderName;
this.senderUid = senderUid;
this.formattedMessage = formattedMessage;
this.recipients = recipients;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,115 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.Checker;
import org.mineacademy.chatcontrol.model.Newcomer;
import org.mineacademy.chatcontrol.model.RuleType;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.model.db.ServerSettings;
import org.mineacademy.chatcontrol.operator.Rule;
import org.mineacademy.chatcontrol.operator.Rule.RuleCheck;
import org.mineacademy.fo.ValidCore;
/**
* The main class of the ChatControl's API.
*
* @author kangarko
*/
public final class ChatControlAPI {
/**
* Get if the chat has globally been muted via /mute.
*
* @return if the chat has been globally muted
*/
public static boolean isChatMuted() {
return ServerSettings.getInstance().isMuted();
}
/**
* Get the player cache or complains if the player is not online.
*
* @param player the player
* @return
*/
public static PlayerCache getCache(final Player player) {
ValidCore.checkBoolean(player.isOnline(), "Player " + player.getName() + " must be online to get his cache!");
return PlayerCache.fromCached(player);
}
/**
* Run antispam, anticaps, time and delay checks as well as rules for the given message
*
* @param sender
* @param message
* @return
*/
public static Checker checkMessage(final Player sender, final String message) {
final Checker checker = Checker.filterChannel(WrappedSender.fromPlayer(sender), message, null);
return checker;
}
/**
* Apply the given rules type for the message sent by the player
*
* @param type
* @param sender
* @param message
* @return
*/
public static RuleCheck<?> checkRules(final RuleType type, final Player sender, final String message) {
return checkRules(type, sender, message, null);
}
/**
* Apply the given rules type for the message sent by the player
*
* @param type
* @param sender
* @param message
* @param channel
* @return
*/
public static RuleCheck<?> checkRules(final RuleType type, final CommandSender sender, final String message, final Channel channel) {
return Rule.filter(type, WrappedSender.fromSender(sender), message, channel);
}
/**
* Return if the player is newcomer according to the Newcomer settings.
*
* @param player the player
* @return if ChatControl considers the player a newcomer
*/
public static boolean isNewcomer(final Player player) {
return Newcomer.isNewcomer(player);
}
/**
* Returns true if the given channel is available and loaded.
*
* @param channelName
* @return
*/
public static boolean isChannelInstalled(final String channelName) {
return Channel.isChannelLoaded(channelName);
}
/**
* Sends a message to the given channel through the given sender.
*
* @param sender act as if the given sender issued the chat message
* @param channelName
* @param message
*/
public static void sendMessage(final CommandSender sender, final String channelName, final String message) {
final Channel channel = Channel.findChannel(channelName);
ValidCore.checkNotNull(channel, "Channel '" + channelName + "' is not installed. Use ChatControlAPI#isChannelInstalled to check that first!");
channel.sendMessage(sender, message);
}
}

View File

@ -0,0 +1,162 @@
package org.mineacademy.chatcontrol.api;
import java.util.UUID;
import javax.annotation.Nullable;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.fo.event.SimpleEvent;
import org.mineacademy.fo.model.SimpleTime;
import lombok.Getter;
import lombok.Setter;
/**
* Event for when something gets muted, either a channel, a player or the server.
*
* Channel mute = targetChannel is not null
* Player mute = targetPlayerName and UUID are not null
* Server mute = everything is null
*/
@Getter
public final class MuteEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The type of the mute.
*/
private final Type type;
/**
* The duration of the mute, null if we are UNmuting
*/
@Nullable
@Setter
private SimpleTime duration;
/**
* The channel that got muted, or null if the event did not fire for a channel
*/
@Nullable
private Channel targetChannel;
/**
* The player name who got muted, or null if the event did not fire for a player
*/
@Nullable
private String targetPlayerName;
/**
* The player UUID who got muted, or null if the event did not fire for a player
*/
@Nullable
private UUID targetPlayerUniqueId;
/**
* Is the event cancelled?
*/
@Setter
private boolean cancelled;
/*
* Server mute
*/
private MuteEvent(final Type type, final SimpleTime duration) {
this.type = type;
this.duration = duration;
}
/*
* Channel mute
*/
private MuteEvent(final Type type, final Channel targetChannel, final SimpleTime duration) {
this.type = type;
this.targetChannel = targetChannel;
this.duration = duration;
}
/*
* Player mute
*/
private MuteEvent(final Type type, final String targetPlayerName, final UUID targetPlayerUniqueId, final SimpleTime duration) {
this.type = type;
this.targetPlayerName = targetPlayerName;
this.targetPlayerUniqueId = targetPlayerUniqueId;
this.duration = duration;
}
/**
* Return true if this is a mute
*
* @return
*/
public boolean isMute() {
return this.duration != null;
}
/**
* Return true if this is an unmute
*
* @return
*/
public boolean isUnmute() {
return this.duration == null;
}
/**
* Return true if this is a player mute
*
* @return
*/
public boolean isPlayerMute() {
return this.type == Type.PLAYER;
}
/**
* Return true if this is a channel mute
*
* @return
*/
public boolean isChannelMute() {
return this.type == Type.CHANNEL;
}
/**
* Return true if this is a server mute
*
* @return
*/
public boolean isServerMute() {
return this.type == Type.SERVER;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
public static MuteEvent server(final SimpleTime duration) {
return new MuteEvent(Type.SERVER, duration);
}
public static MuteEvent channel(final Channel targetChannel, final SimpleTime duration) {
return new MuteEvent(Type.CHANNEL, targetChannel, duration);
}
public static MuteEvent player(final String targetPlayerName, final UUID targetPlayerUniqueId, final SimpleTime duration) {
return new MuteEvent(Type.PLAYER, targetPlayerName, targetPlayerUniqueId, duration);
}
public enum Type {
SERVER,
CHANNEL,
PLAYER;
}
}

View File

@ -0,0 +1,252 @@
package org.mineacademy.chatcontrol.api;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiFunction;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.listener.ThirdPartiesListener;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.ValidCore;
import org.mineacademy.fo.model.HookManager;
import lombok.Getter;
/**
* Represents a party for a channel.
*/
public abstract class Party {
/**
* Registration by name
*/
private static final Map<String, Party> byName = new HashMap<>();
/**
* This is a channel shown only to players in the same Faction as the sender.
*/
public static final Party FACTION = new Party("factions-faction") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return HookManager.getOnlineFactionPlayers(sender).contains(receiver);
};
};
/**
* Chat for PlotSquared - only shown to players inside the plot.
*/
public static final Party PLOT = new Party("plotsquared-plot") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return HookManager.getPlotPlayers(sender).contains(receiver);
}
};
/**
* This is a channel shown only to players in the same Town as the sender.
*/
public static final Party TOWN = new Party("towny-town") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return HookManager.getTownResidentsOnline(sender).contains(receiver);
}
};
/**
* This is a channel shown only to players in the same Nation as the sender.
*/
public static final Party NATION = new Party("towny-nation") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return HookManager.getNationPlayersOnline(sender).contains(receiver);
}
};
/**
* This is a channel shown only to same ally players as the sender.
*/
public static final Party ALLY = new Party("towny-ally") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return HookManager.getAllyPlayersOnline(sender).contains(receiver);
}
};
/**
* Chat for mcMMO - only shown to players within the same party.
*/
public static final Party MCMMO = new Party("mcmmo-party") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return ThirdPartiesListener.getMcMMOPartyRecipients(sender).contains(receiver);
}
};
/**
* Only chat with other players in the same land
*/
public static final Party LAND = new Party("lands-land") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return HookManager.getLandPlayers(sender).contains(receiver);
}
};
/**
* Only chat with players belonging to the same island as you
* and having the given (or higher rank, from top to bottom)
*/
public static final Party ISLAND_VISITOR = new Party("bentobox-island-visitor") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> visitors = HookManager.getBentoBoxVisitors(sender);
return visitors.contains(sender.getUniqueId()) && visitors.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_COOP = new Party("bentobox-island-coop") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> coops = HookManager.getBentoBoxCoops(sender);
return coops.contains(sender.getUniqueId()) && coops.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_TRUSTED = new Party("bentobox-island-trusted") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> trustees = HookManager.getBentoBoxTrustees(sender);
return trustees.contains(sender.getUniqueId()) && trustees.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_MEMBER = new Party("bentobox-island-member") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> members = HookManager.getBentoBoxMembers(sender);
return members.contains(sender.getUniqueId()) && members.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_SUBOWNER = new Party("bentobox-island-subowner") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> subowners = HookManager.getBentoBoxSubOwners(sender);
return subowners.contains(sender.getUniqueId()) && subowners.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_OWNER = new Party("bentobox-island-owner") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> owners = HookManager.getBentoBoxOwners(sender);
return owners.contains(sender.getUniqueId()) && owners.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_MOD = new Party("bentobox-island-mod") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> mods = HookManager.getBentoBoxMods(sender);
return mods.contains(sender.getUniqueId()) && mods.contains(receiver.getUniqueId());
}
};
public static final Party ISLAND_ADMIN = new Party("bentobox-island-admin") {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
final Set<UUID> admins = HookManager.getBentoBoxAdmins(sender);
return admins.contains(sender.getUniqueId()) && admins.contains(receiver.getUniqueId());
}
};
/**
* The unique party name
*/
@Getter
private final String name;
private Party(final String name) {
this.name = name;
byName.put(name, this);
}
/**
* Return true if the receiver is in the sender's party.
*
* @param receiver
* @param sender
* @return
*/
public abstract boolean isInParty(Player receiver, Player sender);
/**
* Get the unique party name
*/
@Override
public final String toString() {
return this.name;
}
/**
* Register a new party into our API.
*
* @param name
* @param isInParty return true or false depending if the first argument (the receiver) is in the second argument's (the sender's) party
*/
public static void register(final String name, final BiFunction<Player, Player, Boolean> isInParty) {
ValidCore.checkBoolean(!byName.containsKey(name), "Party named " + name + " already exists!");
byName.put(name, new Party(name) {
@Override
public boolean isInParty(final Player receiver, final Player sender) {
return isInParty.apply(receiver, sender);
}
});
}
/**
* Finds the party from the given name, throwing exception if failed.
*
* @param name
* @return
*/
public static Party fromKey(final String name) {
for (final Map.Entry<String, Party> entry : byName.entrySet())
if (entry.getKey().equalsIgnoreCase(name))
return entry.getValue();
throw new IllegalArgumentException("No such channel party: " + name + ". Available: " + CommonCore.join(byName.keySet()));
}
/**
*
* @return
*/
public static Set<String> getPartiesNames() {
return Collections.unmodifiableSet(byName.keySet());
}
/**
*
* @return
*/
public static Collection<Party> getParties() {
return Collections.unmodifiableCollection(byName.values());
}
}

View File

@ -0,0 +1,49 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is when a player is mentioned in chat.
*/
@Getter
@Setter
public final class PlayerMentionEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The player that mentioned the other player
*/
private final CommandSender mentioner;
/**
* The player that was mentioned
*/
private final SyncedCache mentioned;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public PlayerMentionEvent(final CommandSender mentioner, final SyncedCache mentioned) {
this.mentioner = mentioner;
this.mentioned = mentioned;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,62 @@
package org.mineacademy.chatcontrol.api;
import javax.annotation.Nullable;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.PlayerMessageType;
import org.mineacademy.chatcontrol.operator.Operator.OperatorCheck;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
/**
* An event that is executed when a player joins a channel.
*/
@Getter
@RequiredArgsConstructor
public final class PlayerMessageEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The Dude himself, null if timed or demo run
*/
@Nullable
private final Player player;
/**
* The channel player is joining into
*/
private final PlayerMessageType type;
/**
* The associated check with this
*/
private final OperatorCheck<?> check;
/**
* The original message if any, can be null
* For example the death event has its own native MC messages returned here
*/
@Nullable
private final String originalMessage;
/**
* Enable joining?
*/
@Setter
private boolean cancelled;
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,48 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is when a player is mentioned in chat.
*/
@Getter
@Setter
public final class PlayerPreMentionEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The matched player
*/
private final SyncedCache player;
/**
* The prefix look for when matching
*/
private String prefix;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public PlayerPreMentionEvent(final SyncedCache player, final String prefix) {
this.player = player;
this.prefix = prefix;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,74 @@
package org.mineacademy.chatcontrol.api;
import javax.annotation.Nullable;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is executed when players send a message via /reply or /tell.
* <p>
* This is fired before all checks are done so you can just cancel the event and manually force the
* message to be sent.
*/
@Getter
@Setter
public final class PrePrivateMessageEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The command sender
*/
private final WrappedSender sender;
/**
* The recipient... may be on this server or on proxy
*/
private final SyncedCache receiverCache;
/**
* The recipient, null if he's on proxy
*/
@Nullable
private final Player receiver;
/**
* The message being sent
*/
private String message;
/**
* Should we play the sound?
*/
private boolean sound;
/**
* Should we allow this event to occur?
*/
private boolean cancelled;
public PrePrivateMessageEvent(final WrappedSender sender, final SyncedCache receiverCache, final Player receiver, final String message, final boolean sound) {
this.sender = sender;
this.receiverCache = receiverCache;
this.receiver = receiver;
this.message = message;
this.sound = sound;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,57 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.operator.Rule;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is executed when a rule or a handler matches a message.
* <p>
* The event is fired before the rule edits the message in any way.
*/
@Getter
@Setter
public final class PreRuleMatchEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The sender checked
*/
private final CommandSender sender;
/**
* The full message
*/
private final String message;
/**
* The matching rule
*/
private final Rule rule;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public PreRuleMatchEvent(final CommandSender sender, final String message, final Rule rule) {
this.sender = sender;
this.message = message;
this.rule = rule;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,72 @@
package org.mineacademy.chatcontrol.api;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.operator.RuleOperator;
import org.mineacademy.fo.event.SimpleEvent;
import lombok.Getter;
import lombok.Setter;
/**
* An event that is executed when a rule replaces a message
* either via "then replace" or "then rewrite (in)" operators
*/
@Getter
@Setter
public final class RuleReplaceEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The sender checked
*/
private final CommandSender sender;
/**
* The message being matched
*/
private final String message;
/**
* The replaced match
*/
private String replacedMatch;
/**
* The matching rule operator
*/
private final RuleOperator rule;
/**
* Did the rule replace the entire message? True for "then rewrite",
* false for "then replace"
*/
private final boolean wholeMessage;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public RuleReplaceEvent(final CommandSender sender, final String message, final String replacedMatch, final RuleOperator rule, final boolean wholeMessage) {
super(!Bukkit.isPrimaryThread());
this.sender = sender;
this.message = message;
this.replacedMatch = replacedMatch;
this.rule = rule;
this.wholeMessage = wholeMessage;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,48 @@
/*package org.mineacademy.chatcontrol.api;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.SimpleExpansion;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.platform.FoundationPlayer;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
// This class is automatically registered if your plugin uses Foundation,
// if not then call {@link Variables#addExpansion(SimpleExpansion)} in your onEnable()
// manually.
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SampleCustomPlaceholders extends SimpleExpansion {
@Getter
private static final SimpleExpansion instance = new SampleCustomPlaceholders();
@Override
protected SimpleComponent onReplace(FoundationPlayer audience, String identifier) {
// You can get the Bukkit sender and Players from the audience object
final CommandSender sender = audience.getSender();
final Player player = audience.isPlayer() ? audience.getPlayer() : null;
// use {my_variable} or {chatcontrol_my_variable} if using PlaceholderAPI in your messages
if ("my_variable".equals(identifier))
return SimpleComponent.fromMiniAmpersand("<red>My &evariable");
else if ("my_section_variable".equals(identifier))
return SimpleComponent.fromAmpersand("&cMy variable");
else if ("my_adventure_variable".equals(identifier))
return SimpleComponent.fromAdventure(Component.text("My variable").color(NamedTextColor.RED));
else if ("my_plaintext_variable".equals(identifier))
return SimpleComponent.fromPlain("My variable");
return null;
}
}*/

View File

@ -0,0 +1,68 @@
package org.mineacademy.chatcontrol.api;
import java.util.Set;
import javax.annotation.Nullable;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.mineacademy.chatcontrol.model.Spy;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.fo.event.SimpleEvent;
import org.mineacademy.fo.model.SimpleComponent;
import lombok.Getter;
import lombok.Setter;
/**
* Event for when spying players receive a /spy message.
*/
@Getter
@Setter
public final class SpyEvent extends SimpleEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
/**
* The spy type
*/
private final Spy.Type type;
/**
* The player who issued the message, may be null if coming from proxy for example
*/
@Nullable
private final WrappedSender sender;
/**
* A list of players receiving this message
*/
private final Set<Player> recipients;
/**
* The message
*/
private final SimpleComponent message;
/**
* Is the event cancelled?
*/
private boolean cancelled;
public SpyEvent(final Spy.Type type, final WrappedSender sender, final SimpleComponent message, final Set<Player> recipients) {
this.type = type;
this.sender = sender;
this.message = message;
this.recipients = recipients;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,31 @@
package org.mineacademy.chatcontrol.command;
import java.util.List;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
public final class CommandDummy extends ChatControlCommand {
public CommandDummy() {
super("dummy");
this.setMinArguments(0);
this.setPermission(null);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
// This command does nothing, we let people route to it in commands.yml to register it in tab-complete
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,133 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.db.Database;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.HookManager;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.remain.Remain;
import org.mineacademy.fo.settings.Lang;
public final class CommandIgnore extends ChatControlCommand {
public CommandIgnore() {
super(Settings.Ignore.COMMAND_ALIASES);
this.setUsage(Lang.component("command-ignore-usage"));
this.setDescription(Lang.component("command-ignore-description"));
this.setMinArguments(1);
this.setPermission(Permissions.Command.IGNORE);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
usages.add(Lang.component("command-ignore-usages"));
if (this.hasPerm(Permissions.Command.IGNORE_OTHERS))
usages.add(Lang.component("command-ignore-usages-others"));
if (this.hasPerm(Permissions.Command.IGNORE_LIST))
usages.add(Lang.component("command-ignore-usages-list"));
if (this.hasPerm(Permissions.Command.IGNORE_LIST) && this.hasPerm(Permissions.Command.IGNORE_OTHERS))
usages.add(Lang.component("command-ignore-usages-list-others"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkUsage(this.args.length <= 2);
final String param = this.args[0];
final boolean otherPlayer = this.args.length == 2;
this.checkBoolean(this.isPlayer() || otherPlayer, Lang.component("command-console-missing-player-name"));
if (otherPlayer)
this.checkPerm(Permissions.Command.IGNORE_OTHERS);
if ("list".equals(param)) {
this.checkPerm(Permissions.Command.IGNORE_LIST);
this.pollCache(otherPlayer ? this.args[1] : this.audience.getName(), listPlayer -> {
this.checkBoolean(!listPlayer.getIgnoredPlayers().isEmpty(), Lang.component("command-ignore-not-ignoring" + (otherPlayer ? "-other" : ""), "player", listPlayer.getPlayerName()));
final List<String> ignoredPlayerNames = Database.getInstance().getPlayerNamesSync(listPlayer.getIgnoredPlayers());
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-ignore-list-header", "player", listPlayer.getPlayerName()))
.setPages(CommonCore.convertList(ignoredPlayerNames, name -> SimpleComponent
.fromMiniNative(" <gray>- " + name)
.onHover(Lang.component("command-ignore-list-tooltip-stop", "player", name))
.onClickRunCmd("/" + this.getLabel() + " " + (otherPlayer ? listPlayer.getPlayerName() + " " : "") + name)))
.send(this.audience);
});
return;
}
this.pollCache(otherPlayer ? this.args[0] : this.audience.getName(), forCache -> {
this.pollCache(this.args[otherPlayer ? 1 : 0], targetCache -> {
this.checkBoolean(!forCache.getPlayerName().equals(targetCache.getPlayerName()), Lang.component("command-ignore-cannot-ignore-self"));
final UUID targetId = targetCache.getUniqueId();
final boolean ignored = forCache.isIgnoringPlayer(targetId);
final Player otherOnline = Remain.getPlayerByUUID(targetId);
if (!ignored && otherOnline != null && otherOnline.isOnline() && otherOnline.hasPermission(Permissions.Bypass.REACH))
this.returnTell(Lang.component("command-ignore-cannot-ignore-admin"));
forCache.setIgnoredPlayer(targetId, !ignored);
// Hook into CMI/Essentials async to prevent server freeze
Platform.runTask(() -> HookManager.setIgnore(forCache.getUniqueId(), targetId, !ignored));
this.tellSuccess(Lang.component(
ignored ? "command-ignore-disable" : "command-ignore-enable" + (otherPlayer ? "-other" : ""),
"player", forCache.getPlayerName(),
"target", targetCache.getPlayerName()));
this.updateProxyData(forCache);
});
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord(
Players.getPlayerNamesForTabComplete(this.getSender()).stream().filter(name -> !name.equals(this.audience.getName())).collect(Collectors.toList()),
Arrays.asList(this.hasPerm(Permissions.Command.IGNORE_LIST) ? "list" : ""));
if (this.args.length == 2 && this.hasPerm(Permissions.Command.IGNORE_OTHERS))
return this.completeLastWordPlayerNames();
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,137 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.PlaceholderPrefix;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.chatcontrol.settings.Settings.ListPlayers.SortBy;
import org.mineacademy.fo.exception.FoException;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.platform.FoundationPlayer;
import org.mineacademy.fo.settings.Lang;
public final class CommandList extends ChatControlCommand {
public CommandList() {
super(Settings.ListPlayers.COMMAND_ALIASES);
this.setUsage(Settings.Proxy.ENABLED ? Lang.component("command-list-usage-server") : SimpleComponent.empty());
this.setDescription(Lang.component("command-list-description"));
this.setPermission(Permissions.Command.LIST);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
usages.add(Lang.component("command-list-usage-1-" + (Settings.Proxy.ENABLED ? "network" : "local")));
if (Settings.Proxy.ENABLED)
usages.add(Lang.component("command-list-usage-2"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkUsage(this.args.length <= 1);
final List<SimpleComponent> pages = this.compilePlayers(this.audience);
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-list-header-" + (Settings.Proxy.ENABLED ? "network" : "local"), "pages", pages.size()))
.setPages(pages)
.send(this.audience);
}
private List<SimpleComponent> compilePlayers(final FoundationPlayer audience) {
// Group all players by the sorting type
final Map<String, List<SyncedCache>> grouppedPlayers = new TreeMap<>(String::compareTo);
// Cache if sender sees vanished players for best performance
final boolean senderSeeVanished = audience.hasPermission(Permissions.Bypass.VANISH);
// Compile a list of caches and group them
for (final SyncedCache syncedCache : SyncedCache.getCaches()) {
if (syncedCache.isVanished() && !senderSeeVanished)
continue;
final SortBy sorter = Settings.ListPlayers.SORT_BY;
String key;
// Group by the given key
if (sorter == SortBy.GROUP)
key = syncedCache.getGroup();
else if (sorter == SortBy.NAME)
key = syncedCache.getPlayerName();
else if (sorter == SortBy.NICK)
key = syncedCache.getNameOrNickColorless();
else if (sorter == SortBy.PREFIX)
key = syncedCache.getPrefix();
else
throw new FoException("Sorting by " + Settings.ListPlayers.SORT_BY + " is unsupported for the list command!");
// Add to our group
if (key == null)
key = "";
final List<SyncedCache> caches = grouppedPlayers.getOrDefault(key, new ArrayList<>());
caches.add(syncedCache);
// Save to main group
grouppedPlayers.put(key, caches);
}
final List<SimpleComponent> sortedPlayers = new ArrayList<>();
int count = 1;
for (final List<SyncedCache> caches : grouppedPlayers.values()) {
Collections.sort(caches, Comparator.comparing(SyncedCache::getPlayerName));
for (final SyncedCache cache : caches) {
final Variables variables = Variables.builder().placeholders(cache.getPlaceholders(PlaceholderPrefix.PLAYER)).placeholder("count", count++);
final String format = variables.replaceLegacy(Settings.ListPlayers.FORMAT);
final List<String> formatHover = variables.replaceLegacyList(Settings.ListPlayers.FORMAT_HOVER);
sortedPlayers.add(SimpleComponent.fromMiniAmpersand(format).onHoverLegacy(formatHover));
}
}
return sortedPlayers;
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return Settings.Proxy.ENABLED ? this.completeLastWord(SyncedCache.getServers()) : NO_COMPLETE;
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,568 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.Prompt;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.mineacademy.chatcontrol.SenderCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Spy;
import org.mineacademy.chatcontrol.model.ToggleType;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.Database;
import org.mineacademy.chatcontrol.model.db.Log;
import org.mineacademy.chatcontrol.model.db.Mail;
import org.mineacademy.chatcontrol.model.db.Mail.Recipient;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ChatUtil;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.TimeUtil;
import org.mineacademy.fo.conversation.SimplePrompt;
import org.mineacademy.fo.exception.FoException;
import org.mineacademy.fo.menu.model.ItemCreator;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.SimpleBook;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.Tuple;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.platform.FoundationPlayer;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.remain.CompMaterial;
import org.mineacademy.fo.remain.CompMetadata;
import org.mineacademy.fo.settings.Lang;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
public final class CommandMail extends ChatControlCommand {
public CommandMail() {
super(Settings.Mail.COMMAND_ALIASES);
this.setUsage(Lang.component("command-mail-usage"));
this.setDescription(Lang.component("command-mail-description"));
this.setMinArguments(1);
this.setPermission(Permissions.Command.MAIL);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-mail-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkConsole();
final String param = this.args[0];
final Player player = this.getPlayer();
final Database database = Database.getInstance();
final WrappedSender wrapped = WrappedSender.fromPlayer(player);
final UUID uuid = wrapped.getUniqueId();
final SenderCache senderCache = wrapped.getSenderCache();
final PlayerCache playerCache = wrapped.getPlayerCache();
final SimpleBook pendingBody = CommonCore.getOrDefault(senderCache.getPendingMail(), SimpleBook.newEmptyBook());
if ("send".equals(param) || "s".equals(param)) {
this.checkNotNull(senderCache.getPendingMail(), Lang.component("command-mail-no-pending"));
this.checkBoolean(pendingBody.isSigned(), Lang.component("command-mail-no-subject"));
this.checkArgs(2, Lang.component("command-mail-no-recipients"));
this.checkUsage(this.args.length == 2);
this.sendMail(wrapped, this.args[1], pendingBody);
return;
}
else if ("autoreply".equals(param) || "autor".equals(param) || "ar".equals(param)) {
this.checkArgs(2, Lang.component("command-mail-autoresponder-usage"));
final String timeRaw = this.joinArgs(1);
if ("off".equals(timeRaw) || "view".equals(timeRaw)) {
this.checkBoolean(playerCache.isAutoResponderValid(), Lang.component("command-mail-autoresponder-disabled"));
final Tuple<SimpleBook, Long> autoResponder = playerCache.getAutoResponder();
if ("off".equals(timeRaw)) {
playerCache.removeAutoResponder();
this.tellSuccess(Lang.component("command-mail-autoresponder-removed"));
} else {
autoResponder.getKey().openPlain(wrapped.getAudience());
this.tellSuccess(Lang.component("command-mail-autoresponder",
"title", autoResponder.getKey().getTitle(),
"date", TimeUtil.getFormattedDateShort(autoResponder.getValue())));
}
return;
}
long futureTime;
try {
futureTime = System.currentTimeMillis() + this.findTime(timeRaw).getTimeSeconds() * 1000;
} catch (final FoException ex) {
this.returnTell(Lang.component("command-mail-autoresponder-invalid-time", "time", timeRaw));
return;
}
// If has no email, try updating current auto-responder's date
if (senderCache.getPendingMail() == null) {
this.checkBoolean(playerCache.hasAutoResponder(), Lang.component("command-mail-autoresponder-missing"));
playerCache.setAutoResponderDate(futureTime);
this.tellSuccess(Lang.component("command-mail-autoresponder-updated", "date", TimeUtil.getFormattedDateShort(futureTime)));
return;
}
this.checkBoolean(pendingBody.isSigned(), Lang.component("command-mail-no-subject"));
// Save
playerCache.setAutoResponder(senderCache.getPendingMail(), futureTime);
// Remove draft from cache because it was finished
senderCache.setPendingMail(null);
this.tellSuccess(Lang.component("command-mail-autoresponder-set", "date", TimeUtil.getFormattedDateShort(futureTime)));
return;
}
else if ("open".equals(param) || "forward".equals(param) || "reply".equals(param) || "delete-sender".equals(param) || "delete-recipient".equals(param)) {
this.checkArgs(2, "Unique mail ID has not been provided. If this is a bug, please report it. If you are playing hard, play harder!");
final UUID mailId = UUID.fromString(this.args[1]);
this.syncCallback(() -> database.findMail(mailId), mail -> {
this.checkNotNull(mail, Lang.component("command-mail-delete-invalid"));
if ("open".equals(param)) {
mail.displayTo(this.audience);
if (!String.join(" ", this.args).contains("-donotmarkasread"))
mail.markOpen(player);
} else if ("forward".equals(param)) {
// No recipients
if (this.args.length == 2)
new PromptForwardRecipients(mailId).show(player);
else if (this.args.length == 3)
this.sendMail(wrapped, this.args[2], SimpleBook.clone(mail.getBody(), player.getName()));
else
this.returnInvalidArgs(this.joinArgs(1));
}
else if ("reply".equals(param)) {
this.checkConsole();
senderCache.setPendingMailReply(mail);
this.getPlayer().chat("/" + this.getLabel() + " new");
}
else if ("delete-sender".equals(param)) {
this.checkBoolean(!mail.isSenderDeleted(), Lang.component("command-mail-delete-invalid"));
mail.setSenderDeleted(true);
this.tellSuccess(Lang.component("command-mail-delete-sender"));
}
else if ("delete-recipient".equals(param)) {
this.checkArgs(3, Lang.component("command-mail-delete-no-recipient"));
final UUID recipientId = UUID.fromString(this.args[2]);
final Recipient recipient = mail.findRecipient(recipientId);
this.checkNotNull(recipient, Lang.component("command-mail-delete-invalid-recipient", "uuid", recipientId));
this.checkBoolean(!recipient.isMarkedDeleted(), Lang.component("command-mail-delete-invalid"));
mail.setDeletedBy(recipient);
this.tellSuccess(Lang.component("command-mail-delete-recipient"));
}
});
return;
}
this.checkUsage(this.args.length == 1);
if ("new".equals(param) || "n".equals(param)) {
this.checkUsage(this.args.length == 1);
this.checkBoolean(CompMaterial.isAir(player.getItemInHand().getType()), Lang.component("command-mail-hand-full"));
for (final ItemStack stack : player.getInventory().getContents())
if (stack != null && CompMetadata.hasMetadata(stack, SimpleBook.TAG))
this.returnTell(Lang.component("command-mail-already-drafting"));
final ItemStack bookItem = ItemCreator
.fromBookPlain(pendingBody, true)
.name(Lang.legacy("command-mail-item-title"))
.lore(Lang.legacy("command-mail-item-tooltip"))
.make();
player.setItemInHand(bookItem);
if (senderCache.getPendingMailReply() != null)
this.tellInfo(Lang.component("command-mail-reply-usage", "player", senderCache.getPendingMailReply().getSenderName()));
else
this.tellInfo(Lang.component("command-mail-new-usage-" + (senderCache.getPendingMail() == null ? "new" : "pending")));
} else if ("inbox".equals(param) || "i".equals(param) || "read".equals(param)) {
this.syncCallback(() -> database.findMailsTo(uuid), mails -> {
final List<SimpleComponent> pages = new ArrayList<>();
for (final Mail incoming : mails) {
final Recipient recipient = incoming.findRecipient(uuid);
final boolean read = recipient.isOpened();
// Hide deleted emails
if (recipient.isMarkedDeleted())
continue;
final List<SimpleComponent> openHover = new ArrayList<>();
openHover.add(Lang.component("command-mail-open-tooltip"));
openHover.add(Lang.component("command-mail-open-tooltip-script-" + (read ? "read" : "unread"),
"date", TimeUtil.getFormattedDateShort(recipient.getOpenTime())));
pages.add(SimpleComponent
.fromMiniNative("<dark_gray>[" + (read ? "<gray>" : "<dark_green>") + "O<dark_gray>]")
.onHover(openHover)
.onClickRunCmd("/" + this.getLabel() + " open " + incoming.getUniqueId())
.appendPlain(" ")
.appendMiniNative("<dark_gray>[<gold>R<dark_gray>]")
.onHover(Lang.component("command-mail-reply-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " reply " + incoming.getUniqueId())
.appendPlain(" ")
.appendMiniNative("<dark_gray>[<dark_aqua>F<dark_gray>]")
.onHover(Lang.component("command-mail-forward-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " forward " + incoming.getUniqueId())
.appendPlain(" ")
.appendMiniNative("<dark_gray>[<red>X<dark_gray>]<white>")
.onHover(Lang.component("command-mail-delete-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " delete-recipient " + incoming.getUniqueId() + " " + player.getUniqueId())
.append(Lang.component("command-mail-inbox-line",
"subject", CommonCore.getOrDefault(incoming.getSubject(), ChatUtil.capitalize(Lang.plain("part-blank"))),
"sender", CommonCore.getOrDefault(incoming.getSenderName(), ChatUtil.capitalize(Lang.plain("part-unknown"))),
"date", TimeUtil.getFormattedDateMonth(incoming.getDate()))));
}
this.checkBoolean(!pages.isEmpty(), Lang.component("command-mail-no-incoming-mail"));
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-mail-inbox-header"))
.setPages(pages)
.send(this.audience);
});
} else if ("archive".equals(param) || "a".equals(param) || "sent".equals(param)) {
this.syncCallback(() -> database.findMailsFrom(uuid), mails -> {
final List<SimpleComponent> pages = new ArrayList<>();
for (final Mail outgoing : mails) {
// Hide deleted emails
if (outgoing.isSenderDeleted() || outgoing.isAutoReply())
continue;
final List<SimpleComponent> statusHover = new ArrayList<>();
statusHover.add(Lang.component("command-mail-archive-recipients-tooltip"));
final List<Recipient> recipients = outgoing.getRecipients();
int index = 0;
for (final String recipientName : database.getPlayerNamesSync(CommonCore.convertList(recipients, Recipient::getUniqueId))) {
final Recipient recipient = recipients.get(index);
statusHover.add(Lang.component("command-mail-archive-tooltip-" + (recipient.isOpened() ? "read" : "unread"), "recipient", recipientName));
index++;
}
pages.add(SimpleComponent.empty()
.appendMiniNative("<dark_gray>[<gold>O<dark_gray>]")
.onHover(Lang.component("command-mail-open-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " open " + outgoing.getUniqueId() + " -donotmarkasread")
.appendPlain(" ")
.appendMiniNative("<dark_gray>[<dark_aqua>F<dark_gray>]")
.onHover(Lang.component("command-mail-forward-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " forward " + outgoing.getUniqueId())
.appendPlain(" ")
.appendMiniNative("<dark_gray>[<red>X<dark_gray>]")
.onHover(Lang.component("command-mail-delete-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " delete-sender " + outgoing.getUniqueId())
.append(Lang.component("command-mail-archive-line",
"subject", outgoing.getSubject(),
"recipients", Lang.numberFormat("case-recipient", outgoing.getRecipients().size()),
"date", TimeUtil.getFormattedDateMonth(outgoing.getDate())))
.onHover(statusHover));
}
this.checkBoolean(!pages.isEmpty(), Lang.component("command-mail-archive-no-mail"));
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-mail-archive-header"))
.setPages(pages)
.send(this.audience);
});
} else
this.returnInvalidArgs(param);
}
/*
* Parses the recipients and sends the book email
*/
private void sendMail(final WrappedSender wrapped, final String recipientsLine, final SimpleBook body) {
String[] split = recipientsLine.split("\\|");
// Check for special cases
if (split.length == 1) {
final String param = split[0].toLowerCase();
// Send to offline receivers
if ("all".equals(param)) {
this.checkPerm(Permissions.Command.MAIL_SEND_ALL);
this.pollCaches(caches -> {
this.sendMail0(wrapped, body, caches);
});
return;
}
// Send to all online network players
else if ("online".equals(param)) {
this.checkPerm(Permissions.Command.MAIL_SEND_ONLINE);
split = CommonCore.toArray(Common.getPlayerNames());
}
}
final List<PlayerCache> tempRecipients = new ArrayList<>();
for (int i = 0; i < split.length; i++) {
final String recipientName = split[i];
if (!recipientName.isEmpty()) {
final boolean last = i + 1 == split.length;
this.pollCache(recipientName, recipientCache -> {
tempRecipients.add(recipientCache);
if (last)
this.sendMail0(wrapped, body, tempRecipients);
});
}
}
}
private void sendMail0(final WrappedSender wrapped, final SimpleBook body, final List<PlayerCache> recipients) {
final Set<String> uniqueNames = new HashSet<>();
boolean warned = false;
// Remove invalid recipients
for (final Iterator<PlayerCache> it = recipients.iterator(); it.hasNext();) {
final PlayerCache recipient = it.next();
final String playerName = recipient.getPlayerName();
final boolean isSelf = this.getPlayer().getUniqueId().equals(recipient.getUniqueId());
// Strip duplicates
if (uniqueNames.contains(playerName)) {
it.remove();
continue;
}
// Disallow when recipients ignores sender
if (!this.hasPerm(Permissions.Bypass.REACH) && recipient.isIgnoringPlayer(this.getPlayer().getUniqueId())) {
this.tellError(Lang.component("command-mail-send-fail-ignore", "player", playerName));
warned = true;
it.remove();
continue;
}
// Disallow when recipient has turned mails off
if (Settings.Toggle.APPLY_ON.contains(ToggleType.MAIL) && recipient.hasToggledPartOff(ToggleType.MAIL) && !isSelf) {
this.tellError(Lang.component("command-mail-send-fail-toggle", "player", playerName));
warned = true;
it.remove();
continue;
}
// Auto responder > send auto response back to sender
if (recipient.isAutoResponderValid() && !isSelf)
Platform.runTask(20, () -> Mail.sendAutoReply(recipient.getUniqueId(), this.getPlayer().getUniqueId(), recipient.getAutoResponder().getKey()));
uniqueNames.add(playerName);
}
// Prepare mail
final Mail mail = Mail.send(this.getPlayer(), recipients, body);
// Broadcast to spying players
Spy.broadcastMail(wrapped, recipients, mail);
// Log
Log.logMail(this.getPlayer(), mail);
// Remove draft from sender because it was finished
SenderCache.from(this.getSender()).setPendingMail(null);
// Try removing the item if it still exists
final Player player = this.getPlayer();
final ItemStack[] content = player.getInventory().getContents();
for (int itemIndex = 0; itemIndex < content.length; itemIndex++) {
final ItemStack item = content[itemIndex];
if (item != null && CompMetadata.hasMetadata(player, SimpleBook.TAG))
content[itemIndex] = new ItemStack(CompMaterial.AIR.getMaterial());
}
player.getInventory().setContents(content);
player.updateInventory();
if (recipients.isEmpty()) {
if (!warned)
this.tellError(Lang.component("command-mail-send-fail"));
} else
this.tellSuccess(Lang.component("command-mail-send-success"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord("new", "n", "send", "s", "autor", "inbox", "i", "read", "archive", "a", "sent");
if (this.args.length == 2)
if ("autor".equals(this.args[0]))
return this.completeLastWord("view", "off", "3 hours", "7 days");
else if ("send".equals(this.args[0])) {
final List<String> tab = new ArrayList<>();
if (this.hasPerm(Permissions.Command.MAIL_SEND_ONLINE))
tab.add("online");
if (this.hasPerm(Permissions.Command.MAIL_SEND_ALL))
tab.add("all");
tab.addAll(this.completeLastWordPlayerNames());
return tab;
}
return NO_COMPLETE;
}
// ------------------------------------------------------------------------------------------------------------
// Classes
// ------------------------------------------------------------------------------------------------------------
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
private static class PromptForwardRecipients extends SimplePrompt {
/**
* The mail that is being forwarded
*/
private final UUID mailId;
/**
* @see org.mineacademy.fo.conversation.SimplePrompt#getPrompt(org.bukkit.conversations.ConversationContext)
*/
@Override
protected String getPrompt(final ConversationContext ctx) {
return Lang.legacy("command-mail-forward-recipients");
}
/**
* @see org.mineacademy.fo.conversation.SimplePrompt#isInputValid(org.bukkit.conversations.ConversationContext, java.lang.String)
*/
@Override
protected boolean isInputValid(final ConversationContext context, final String input) {
if (input.isEmpty() || input.equals("|")) {
this.tell(Lang.component("command-mail-forward-recipients-invalid", "input", input));
return false;
}
return true;
}
/**
* @see org.bukkit.conversations.ValidatingPrompt#acceptValidatedInput(org.bukkit.conversations.ConversationContext, java.lang.String)
*/
@Override
protected Prompt acceptValidatedInput(final ConversationContext context, final String input) {
Platform.runTaskAsync(() -> {
for (final String playerName : input.split("\\|"))
if (!Bukkit.getOfflinePlayer(playerName).hasPlayedBefore()) {
this.tell(Lang.component("player-not-stored", "player", playerName));
return;
}
// Send later so that the player is not longer counted as "conversing"
Platform.runTask(5, () -> {
final FoundationPlayer audience = Platform.toPlayer(this.getPlayer(context));
final Variables variables = Variables.builder(audience);
audience.dispatchCommand(variables.replaceLegacy(Settings.Mail.COMMAND_ALIASES.get(0) + " forward " + this.mailId + " " + input));
this.tell(context, variables.replaceLegacy(Lang.legacy("command-mail-forward-success")));
});
});
return null;
}
}
}

View File

@ -0,0 +1,84 @@
package org.mineacademy.chatcontrol.command;
import java.util.List;
import java.util.UUID;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.ChatControlProxyMessage;
import org.mineacademy.chatcontrol.model.Colors;
import org.mineacademy.chatcontrol.model.Format;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ChatUtil;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.ProxyUtil;
import org.mineacademy.fo.RandomUtil;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.settings.Lang;
public final class CommandMe extends ChatControlCommand {
public CommandMe() {
super(Settings.Me.COMMAND_ALIASES);
this.setUsage(Lang.component("command-me-usage"));
this.setDescription(Lang.component("command-me-description"));
this.setMinArguments(1);
this.setPermission(Permissions.Command.ME);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-me-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
String message = this.joinArgs(0);
if (message.equals("is the best")) {
this.tell("&k | &r &6You are not the best. Kangarko with his MineAcademy.org thing is the best. Our trained personal has been sent to help you. Expected arrival: " + (5 + RandomUtil.nextInt(10)) + " minutes.");
return;
}
message = Colors.removeColorsNoPermission(this.getSender(), message, Colors.Type.ME);
if (Settings.MAKE_CHAT_LINKS_CLICKABLE && this.getSender().hasPermission(Permissions.Chat.LINKS))
message = ChatUtil.addMiniMessageUrlTags(message);
final WrappedSender wrappedSender = WrappedSender.fromAudience(this.audience);
final SimpleComponent messageComponent = Variables.builder(this.audience).replaceMessageVariables(SimpleComponent.fromMiniSection(message));
final Format format = Format.parse(Settings.Me.FORMAT);
final SimpleComponent formattedMessage = format.build(wrappedSender, CommonCore.newHashMap("message", messageComponent));
final UUID senderId = this.isPlayer() ? this.getPlayer().getUniqueId() : CommonCore.ZERO_UUID;
final boolean bypassReach = this.hasPerm(Permissions.Bypass.REACH);
Players.showMe(senderId, bypassReach, formattedMessage);
if (!this.isPlayer())
this.tell(formattedMessage);
if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.ME, senderId, bypassReach, formattedMessage);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return this.completeLastWordPlayerNames();
}
}

View File

@ -0,0 +1,81 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SenderCache;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.ChatControlProxyMessage;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ProxyUtil;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
public final class CommandMotd extends ChatControlCommand {
public CommandMotd() {
super(Settings.Motd.COMMAND_ALIASES);
this.setMaxArguments(1);
this.setUsage(Lang.component("command-motd-usage"));
this.setDescription(Lang.component("command-motd-description"));
this.setPermission(Permissions.Command.MOTD);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
usages.add(Lang.component("command-motd-usages-self"));
if (this.hasPerm(Permissions.Command.MOTD_OTHERS))
usages.add(Lang.component("command-motd-usages-others"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkBoolean(this.isPlayer() || this.args.length == 1, Lang.component("command-console-missing-player-name"));
this.pollCache(this.args.length == 1 ? this.args[0] : this.audience.getName(), cache -> {
this.checkBoolean(SyncedCache.isPlayerConnected(cache.getUniqueId()), Lang.component("player-not-online", "player", cache.getPlayerName()));
final boolean self = cache.getPlayerName().equals(this.audience.getName());
if (!self)
this.checkPerm(Permissions.Command.MOTD_OTHERS);
final Player player = Bukkit.getPlayerExact(cache.getPlayerName());
if (player != null)
Players.showMotd(WrappedSender.fromPlayerCaches(player, cache, SenderCache.from(player)), false);
else if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.MOTD, cache.getUniqueId());
if (!self)
this.tellSuccess(Lang.component("command-motd-success"));
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return this.completeLastWordPlayerNames();
}
}

View File

@ -0,0 +1,317 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChatControlProxyMessage;
import org.mineacademy.chatcontrol.model.MuteType;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.model.db.ServerSettings;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ChatUtil;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.Messenger;
import org.mineacademy.fo.ProxyUtil;
import org.mineacademy.fo.TimeUtil;
import org.mineacademy.fo.exception.FoException;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.HookManager;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.SimpleTime;
import org.mineacademy.fo.model.Tuple;
import org.mineacademy.fo.remain.Remain;
import org.mineacademy.fo.settings.Lang;
public final class CommandMute extends ChatControlCommand {
public CommandMute() {
super(Settings.Mute.COMMAND_ALIASES);
this.setMinArguments(1);
this.setPermission(Permissions.Command.MUTE);
this.setUsage(Lang.component("command-mute-usage"));
this.setDescription(Lang.component("command-mute-description"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-mute-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
final MuteType type = this.findEnum(MuteType.class, this.args[0]);
// Print status
if (this.args.length == 1) {
final List<SimpleComponent> messages = new ArrayList<>();
if (type == MuteType.SERVER) {
final boolean muted = ServerSettings.getInstance().isMuted();
final String remainingTime = TimeUtil.formatTimeShort(ServerSettings.getInstance().getUnmuteTimeRemaining() / 1000);
messages.add(Lang
.component("command-mute-server-" + (muted ? "on" : "off"), "remaining_time", remainingTime)
.onHover(Lang.component("command-mute-change-status-tooltip-" + (muted ? "unmute" : "mute"), "remaining_time", remainingTime))
.onClickRunCmd("/" + this.getLabel() + " server " + (muted ? "off" : "3m")));
} else if (type == MuteType.PROXY) {
final boolean muted = ServerSettings.isProxyLoaded() ? ServerSettings.getProxy().isMuted() : false;
final String remainingTime = ServerSettings.isProxyLoaded() ? TimeUtil.formatTimeShort(ServerSettings.getProxy().getUnmuteTimeRemaining() / 1000) : "0";
messages.add(Lang
.component("command-mute-proxy-" + (muted ? "on" : "off"), "remaining_time", remainingTime)
.onHover(Lang.component("command-mute-change-status-tooltip-" + (muted ? "unmute" : "mute"), "remaining_time", remainingTime))
.onClickRunCmd("/" + this.getLabel() + " proxy " + (muted ? "off" : "3m")));
} else if (type == MuteType.CHANNEL)
for (final Channel channel : Channel.getChannels()) {
final boolean muted = channel.isMuted();
final String remainingTime = TimeUtil.formatTimeShort(channel.getUnmuteTimeRemaining() / 1000);
messages.add(Lang
.component("command-mute-player-or-channel-" + (muted ? "on" : "off"), "player_or_channel", channel.getName(), "remaining_time", remainingTime)
.onHover(Lang.component("command-mute-change-status-tooltip-" + (muted ? "unmute" : "mute"), "remaining_time", remainingTime))
.onClickRunCmd("/" + this.getLabel() + " channel " + channel.getName() + " " + (muted ? "off" : "3m")));
}
else {
this.pollCaches(caches -> {
for (final PlayerCache otherCache : caches) {
boolean muted = otherCache.isMuted();
long remainingTimeLong = Common.getOrDefault(otherCache.getUnmuteTimeRemaining(), 0L);
boolean externalMute = false;
if (!muted) {
final Tuple<Boolean, Long> muteTuple = HookManager.getUnmuteTime(otherCache.getUniqueId());
if (muteTuple.getKey()) {
muted = true;
remainingTimeLong = muteTuple.getValue() == 0 ? 0 : (muteTuple.getValue() - System.currentTimeMillis());
externalMute = true;
}
}
final String remainingTime;
if (muted) {
if (remainingTimeLong != 0)
remainingTime = TimeUtil.formatTimeShort((remainingTimeLong / 1000) + 1);
else
remainingTime = externalMute ? Lang.legacy("command-mute-external") : Lang.plain("part-unknown");
} else
remainingTime = Lang.plain("part-none");
String command;
if (externalMute) {
if (HookManager.isEssentialsLoaded())
command = "/essentials:mute " + otherCache.getPlayerName();
else
command = muted ? "/unmute " + otherCache.getPlayerName() : "/tempmute " + otherCache.getPlayerName() + " 3m";
} else
command = "/" + this.getLabel() + " player " + otherCache.getPlayerName() + " " + (muted ? "off" : "3m");
messages.add(Lang
.component("command-mute-player-or-channel-" + (muted ? "on" : "off"), "player_or_channel", otherCache.getPlayerName(), "remaining_time", remainingTime)
.onHover(Lang.component("command-mute-change-status-tooltip-" + (muted ? "unmute" : "mute"), "remaining_time", remainingTime))
.onClickRunCmd(command));
}
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-mute-player-status"))
.setPages(messages)
.send(this.audience);
});
return;
}
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-mute-status").replace("{type}", type.getLangKey()))
.setPages(messages)
.send(this.audience);
}
else if (this.args.length >= 2) {
final boolean requiresNoExtraArgument = type == MuteType.PROXY || type == MuteType.SERVER;
this.checkBoolean(this.args.length >= (requiresNoExtraArgument ? 2 : 3), Lang.component("command-mute-no-duration"));
final String name = requiresNoExtraArgument ? "" : this.args[1];
final String reason = CommonCore.joinRange(requiresNoExtraArgument ? 2 : 3, this.args);
final String rawDuration = this.args[requiresNoExtraArgument ? 1 : 2];
final boolean isOff = "off".equals(rawDuration);
SimpleTime duration = null;
if (!isOff)
try {
final long seconds = TimeUtil.toMilliseconds(rawDuration) / 1000L;
duration = SimpleTime.fromSeconds((int) seconds);
} catch (final NumberFormatException ex) {
this.returnTell(Lang.component("command-mute-invalid-duration", "input", rawDuration));
} catch (final IllegalArgumentException ex) {
this.returnTell(ex.getMessage());
}
final SimpleComponent muteMessage = Lang.component("command-mute-mute-success-" + (reason.isEmpty() ? "plain" : "reason"),
"type", type.getLangKey() + (requiresNoExtraArgument ? "" : " " + name),
"duration", rawDuration,
"reason", reason,
"player", this.audience.getName());
final SimpleComponent unmuteMessage = Lang.component("command-mute-unmute-success",
"type", type.getLangKey() + (requiresNoExtraArgument ? "" : " " + name),
"player", this.audience.getName());
final Set<CommandSender> recipients = new HashSet<>();
if (type == MuteType.CHANNEL) {
final Channel channel = this.findChannel(name);
this.checkBoolean(isOff || !channel.isMuted(), Lang.component("command-mute-already-muted", "type", ChatUtil.capitalize(type.getKey()), "name", name));
this.checkBoolean(!isOff || channel.isMuted(), Lang.component("command-mute-not-muted", "type", ChatUtil.capitalize(type.getKey()), "name", name));
channel.setMuted(duration);
recipients.addAll(channel.getOnlinePlayers().keySet());
if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.MUTE, type.getKey(), channel.getName(), isOff ? "off" : duration.toString(), (isOff ? unmuteMessage : muteMessage));
} else if (type == MuteType.PLAYER) {
final SimpleTime finalDuration = duration;
this.pollCache(name, playerCache -> {
final SyncedCache syncedCache = SyncedCache.fromUniqueId(playerCache.getUniqueId());
this.checkBoolean(isOff || !playerCache.isMuted(), Lang.component("command-mute-already-muted", "type", ChatUtil.capitalize(type.getKey()), "name", name));
this.checkBoolean(!isOff || playerCache.isMuted(), Lang.component("command-mute-not-muted", "type", ChatUtil.capitalize(type.getKey()), "name", name));
final Player playerInstance = playerCache.toPlayer();
final String targetPlayerName = playerCache.getPlayerName();
// Check not self
if (playerInstance != null) {
this.checkBoolean(isOff || !this.isPlayer() || !playerInstance.equals(this.getPlayer()), Lang.component("command-mute-cannot-mute-yourself"));
recipients.add(playerInstance);
}
if (syncedCache != null)
this.checkBoolean(!syncedCache.hasMuteBypass(), Lang.component("command-mute-cannot-mute-admin"));
// Send to LiteBans
if (isOff)
HookManager.setLiteBansUnmute(targetPlayerName);
else
HookManager.setLiteBansMute(targetPlayerName, rawDuration, reason);
playerCache.setMuted(finalDuration);
if (Settings.Mute.BROADCAST)
recipients.addAll(Remain.getOnlinePlayers());
final SimpleComponent broadcastMessage = (isOff ? unmuteMessage : muteMessage);
for (final CommandSender recipient : recipients)
Messenger.announce(recipient, broadcastMessage);
if (Settings.Mute.BROADCAST)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.BROADCAST, Messenger.getAnnouncePrefix().appendPlain(" ").append(broadcastMessage));
else
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.MESSAGE, playerCache.getUniqueId(), Messenger.getAnnouncePrefix().appendPlain(" ").append(broadcastMessage));
if (!this.isPlayer())
this.tellInfo(broadcastMessage);
this.updateProxyData(playerCache);
});
return;
} else if (type == MuteType.SERVER || type == MuteType.PROXY) {
if (type == MuteType.PROXY && !Settings.Proxy.ENABLED)
this.returnTell(Lang.component("command-mute-cannot-mute-proxy-not-enabled"));
if (type == MuteType.PROXY && !ServerSettings.isProxyLoaded())
this.returnTell("Proxy settings not loaded yet.");
final ServerSettings serverCache = type == MuteType.PROXY ? ServerSettings.getProxy() : ServerSettings.getInstance();
this.checkBoolean(isOff || !serverCache.isMuted(), Lang.component("command-mute-already-muted-server", "type", ChatUtil.capitalize(type.getKey())));
this.checkBoolean(!isOff || serverCache.isMuted(), Lang.component("command-mute-not-muted-server", "type", ChatUtil.capitalize(type.getKey())));
serverCache.setMuted(duration);
recipients.addAll(Remain.getOnlinePlayers());
if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.MUTE, type.getKey(), "", isOff ? "off" : duration.toString(), (isOff ? unmuteMessage : muteMessage));
} else
throw new FoException("Unknown mute type: " + type);
recipients.add(this.getSender());
for (final CommandSender recipient : recipients)
Messenger.announce(recipient, isOff ? unmuteMessage : muteMessage);
}
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
final List<String> timeSuggestions = Arrays.asList("off", "1m", "15m");
if (this.args.length == 1) {
final List<String> keys = new ArrayList<>();
for (final MuteType type : MuteType.values()) {
if (type == MuteType.PROXY && !Settings.Proxy.ENABLED)
continue;
keys.add(type.getKey());
}
return this.completeLastWord(keys);
}
if (this.args.length == 2)
if ("server".equals(this.args[0]) || "proxy".equals(this.args[0]))
return this.completeLastWord(timeSuggestions);
else if ("channel".equals(this.args[0]))
return this.completeLastWord(Channel.getChannelNames());
else
return this.completeLastWordPlayerNames();
if (this.args.length == 3 && !"server".equals(this.args[0]))
return this.completeLastWord(timeSuggestions);
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,50 @@
package org.mineacademy.chatcontrol.command;
import java.util.List;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.operator.Tag.Type;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.settings.Lang;
public final class CommandRealName extends ChatControlCommand {
public CommandRealName() {
super(Settings.RealName.COMMAND_ALIASES);
this.setValidArguments(0, 1);
this.setUsage(Lang.component("command-real-name-usage"));
this.setDescription(Lang.component("command-real-name-description"));
this.setPermission(Permissions.Command.REAL_NAME);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkBoolean(this.args.length == 1 || this.isPlayer(), Lang.component("command-console-missing-player-name"));
this.pollCache(this.args.length == 1 ? this.args[0] : this.audience.getName(), cache -> {
final boolean hasNick = Settings.Tag.APPLY_ON.contains(Type.NICK) && cache.hasTag(Type.NICK);
this.tellInfo(Lang.component("command-real-name-" + (hasNick ? "" : "no-") + "nick",
"player", cache.getPlayerName(),
"nick", CommonCore.getOrEmpty(cache.getTag(Type.NICK))));
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWordPlayerNames();
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,71 @@
package org.mineacademy.chatcontrol.command;
import java.util.List;
import java.util.Map;
import org.bukkit.Bukkit;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Format;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.PrivateMessage;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.settings.Lang;
public final class CommandReply extends ChatControlCommand {
public CommandReply() {
super(Settings.PrivateMessages.REPLY_ALIASES);
this.setMinArguments(1);
this.setPermission(Permissions.Command.REPLY);
this.setUsage(Lang.component("command-reply-dosage"));
this.setDescription(Lang.component("command-reply-prescription"));
this.setAutoHandleHelp(false);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkConsole();
final String message = this.joinArgs(0);
final WrappedSender wrapped = WrappedSender.fromAudience(this.audience);
final String replyPlayer = wrapped.getSenderCache().getReplyPlayerName();
this.checkNotNull(replyPlayer, Lang.component("command-reply-alone"));
// Handle replying to console
if (replyPlayer.equalsIgnoreCase("CONSOLE")) {
final Map<String, Object> placeholders = CommonCore.newHashMap(
"message", message,
"receiver", Lang.legacy("part-console"),
"player", Lang.legacy("part-console"),
"sender", this.audience.getName());
this.tell(Format.parse(Settings.PrivateMessages.FORMAT_SENDER).build(wrapped, placeholders));
Common.tell(Bukkit.getConsoleSender(), Format.parse(Settings.PrivateMessages.FORMAT_RECEIVER).build(wrapped, placeholders));
return;
}
final SyncedCache syncedCache = SyncedCache.fromPlayerName(replyPlayer);
this.checkNotNull(syncedCache, Lang.component("player-not-online", "player", replyPlayer));
PrivateMessage.send(wrapped, syncedCache, message);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return this.completeLastWordPlayerNames();
}
}

View File

@ -0,0 +1,80 @@
package org.mineacademy.chatcontrol.command;
import java.util.List;
import java.util.UUID;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Colors;
import org.mineacademy.chatcontrol.model.Format;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.ToggleType;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ChatUtil;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.settings.Lang;
public final class CommandSay extends ChatControlCommand {
public CommandSay() {
super(Settings.Say.COMMAND_ALIASES);
this.setUsage(Lang.component("command-say-usage"));
this.setDescription(Lang.component("command-say-description"));
this.setMinArguments(1);
this.setPermission(Permissions.Command.SAY);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-say-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
String message = Colors.removeColorsNoPermission(this.getSender(), this.joinArgs(0), Colors.Type.SAY);
if (Settings.MAKE_CHAT_LINKS_CLICKABLE && this.getSender().hasPermission(Permissions.Chat.LINKS))
message = ChatUtil.addMiniMessageUrlTags(message);
final WrappedSender wrappedSender = WrappedSender.fromAudience(this.audience);
final SimpleComponent messageComponent = Variables.builder(this.audience).replaceMessageVariables(SimpleComponent.fromMiniSection(message));
final SimpleComponent formattedMessage = Format.parse(Settings.Say.FORMAT).build(wrappedSender, CommonCore.newHashMap("message", messageComponent));
final UUID senderId = this.isPlayer() ? this.getPlayer().getUniqueId() : CommonCore.ZERO_UUID;
final boolean bypassReach = this.hasPerm(Permissions.Bypass.REACH);
for (final Player online : Players.getOnlinePlayersWithLoadedDb()) {
final PlayerCache cache = PlayerCache.fromCached(online);
if (Settings.Toggle.APPLY_ON.contains(ToggleType.BROADCAST) && cache.hasToggledPartOff(ToggleType.BROADCAST) && !senderId.equals(online.getUniqueId()))
continue;
if (!bypassReach && Settings.Ignore.HIDE_SAY && cache.isIgnoringPlayer(senderId))
continue;
Common.tell(online, formattedMessage);
}
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return this.completeLastWordPlayerNames();
}
}

View File

@ -0,0 +1,223 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.menu.SpyMenu;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Spy;
import org.mineacademy.chatcontrol.model.Spy.Type;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
public final class CommandSpy extends ChatControlCommand {
public CommandSpy() {
super(Settings.Spy.COMMAND_ALIASES);
this.setMinArguments(1);
this.setPermission(Permissions.Command.SPY);
this.setUsage(Lang.component("command-spy-usage"));
this.setDescription(Lang.component("command-spy-description"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
usages.add(Lang.component("command-spy-usages-1"));
if (Settings.Channels.ENABLED && Settings.Spy.APPLY_ON.contains(Spy.Type.CHAT))
usages.add(Lang.component("command-spy-usages-2"));
usages.add(Lang.component("command-spy-usages-3"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
final String param = this.args[0];
if ("list".equals(param)) {
this.pollCaches(caches -> {
this.checkBoolean(!caches.isEmpty(), Lang.component("command-spy-no-stored"));
final List<SimpleComponent> list = new ArrayList<>();
for (final PlayerCache diskCache : caches) {
final boolean spyingSomething = diskCache.isSpyingSomethingEnabled();
final String playerName = diskCache.getPlayerName();
if (spyingSomething)
list.add(SimpleComponent
.fromMiniNative(" <gray>- ")
.appendMiniNative("<dark_gray>[<dark_red>X<dark_gray>]")
.onHover(Lang.component("command-spy-list-tooltip", "player", playerName))
.onClickRunCmd("/" + this.getLabel() + " off " + playerName)
.appendMiniNative(" <white>" + playerName)
.onHover(this.getSpyingStatus(diskCache))
);
}
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-spy-list-header"))
.setPages(list)
.send(this.audience);
});
return;
}
if ("toggle".equals(param)) {
this.checkArgs(2, Lang.component("command-spy-toggle-no-type", "available", Settings.Spy.APPLY_ON));
final Spy.Type type = this.findEnum(Spy.Type.class, this.args[1], condition -> Settings.Spy.APPLY_ON.contains(condition), Lang.component("command-invalid-type",
"type", "spy type",
"value", this.args[1],
"available", Settings.Spy.APPLY_ON));
this.checkPerm(Permissions.Spy.TYPE + type.getKey());
final boolean isChat = type == Type.CHAT;
Channel channel = null;
if (isChat) {
this.checkArgs(3, Lang.component("command-spy-toggle-no-channel", "available", Channel.getChannelNames()));
channel = this.findChannel(this.args[2]);
}
final Channel finalChannel = channel;
this.checkBoolean(this.isPlayer() || this.args.length == (isChat ? 4 : 3), Lang.component("command-console-missing-player-name"));
this.pollCache(this.args.length == (isChat ? 4 : 3) ? this.args[isChat ? 3 : 2] : this.audience.getName(), cache -> {
final boolean isSpying = isChat ? cache.isSpyingChannel(finalChannel) : cache.isSpying(type);
if (isChat)
cache.setSpyingChannel(finalChannel, !isSpying);
else
cache.setSpying(type, !isSpying);
this.tellSuccess(Lang.component("command-spy-" + (isSpying ? "disable" : "enable"),
"player", cache.getPlayerName(),
"type", isChat ? Lang.component("command-spy-type-channel").appendPlain(" " + finalChannel.getName()) : type.getLangKey()));
});
return;
}
if (this.args.length > 2)
this.returnInvalidArgs(this.joinArgs(3));
this.checkBoolean(this.isPlayer() || this.args.length == 2, Lang.component("command-console-missing-player-name"));
this.pollDiskCacheOrSelf(this.args.length == 2 ? this.args[1] : this.audience.getName(), cache -> {
if ("status".equals(param)) {
this.checkBoolean(cache.isSpyingSomething(), Lang.component("command-spy-no-spying", "player", cache.getPlayerName()));
this.tellNoPrefix(Lang.component("command-spy-status-1", "player", cache.getPlayerName()));
for (final SimpleComponent component : this.getSpyingStatus(cache))
this.tellNoPrefix(component);
this.tellNoPrefix(Lang.component("command-spy-status-2", "player", cache.getPlayerName()));
}
else if ("menu".equals(param)) {
this.checkConsole();
SpyMenu.showTo(cache, this.getPlayer());
}
else if ("off".equals(param)) {
this.checkBoolean(cache.isSpyingSomethingEnabled(), Lang.component("command-spy-no-spying", "player", cache.getPlayerName()));
cache.setSpyingOff();
this.updateProxyData(cache);
this.tellSuccess(Lang.component("command-spy-toggle-off", "player", cache.getPlayerName()));
} else if ("on".equals(param)) {
final boolean atLeastOne = cache.setSpyingOn(cache.toPlayer());
this.updateProxyData(cache);
if (atLeastOne)
this.tellSuccess(Lang.component("command-spy-toggle-on", "player", cache.getPlayerName()));
else
this.tellError(Lang.component("command-spy-cannot-toggle-anything-no-perms", "perm", Permissions.Spy.TYPE + "{type}"));
} else
this.returnInvalidArgs(param);
});
}
private List<SimpleComponent> getSpyingStatus(final PlayerCache diskCache) {
final List<SimpleComponent> hover = new ArrayList<>();
if (!diskCache.getSpyingSectors().isEmpty()) {
hover.add(Lang.component("command-spy-status-sectors"));
for (final Spy.Type type : diskCache.getSpyingSectors())
if (type != Type.CHAT && Settings.Spy.APPLY_ON.contains(type))
hover.add(SimpleComponent.fromMiniAmpersand(" <gray>-<white> " + type.getLangKey()));
}
if (Settings.Spy.APPLY_ON.contains(Spy.Type.CHAT))
if (!diskCache.getSpyingChannels().isEmpty()) {
if (!hover.isEmpty())
hover.add(SimpleComponent.fromMiniNative("<reset>"));
hover.add(Lang.component("command-spy-status-channel"));
for (final String channelName : diskCache.getSpyingChannels())
hover.add(SimpleComponent.fromMiniNative(" <gray>-<white> " + channelName));
}
return hover;
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord("status", "menu", "off", "toggle", "list");
if (this.args.length == 2 && Arrays.asList("status", "menu", "off", "on").contains(this.args[0]))
return this.completeLastWordPlayerNames();
if (this.args.length > 1 && "toggle".equals(this.args[0])) {
if (this.args.length == 2)
return this.completeLastWord(Settings.Spy.APPLY_ON);
if (this.args.length == 3)
return "chat".equals(this.args[1]) && Settings.Spy.APPLY_ON.contains(Spy.Type.CHAT) ? this.completeLastWord(Channel.getChannelNames()) : this.completeLastWordPlayerNames();
if (this.args.length == 4 && "chat".equals(this.args[1]))
return this.completeLastWordPlayerNames();
}
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,142 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.List;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.ChatControlProxyMessage;
import org.mineacademy.chatcontrol.model.Colors;
import org.mineacademy.chatcontrol.model.Migrator;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.operator.Tag;
import org.mineacademy.chatcontrol.operator.Tag.TagCheck;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ChatUtil;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.ProxyUtil;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.settings.Lang;
public final class CommandTag extends ChatControlCommand {
public CommandTag() {
super(Settings.Tag.COMMAND_ALIASES);
this.setMinArguments(1);
this.setPermission(Permissions.Command.TAG_TYPE.substring(0, Permissions.Command.TAG_TYPE.length() - 1));
this.setUsage(Lang.component("command-tag-usage"));
this.setDescription(Lang.component("command-tag-description"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-tag-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkConsole();
final WrappedSender wrapped = WrappedSender.fromPlayer(this.getPlayer());
final PlayerCache cache = wrapped.getPlayerCache();
if ("list".equals(this.args[0])) {
if (this.args.length > 1)
this.returnInvalidArgs(this.joinArgs(2));
this.tellInfo(Lang.component("command-tag-your-tags"));
boolean shownAtLeastOne = false;
for (final Tag.Type tag : Settings.Tag.APPLY_ON)
if (this.hasPerm(Permissions.Command.TAG_TYPE + tag.getKey())) {
this.tellNoPrefix(" &7- " + ChatUtil.capitalize(tag.getKey()) + ": &f" + CommonCore.getOrDefault(cache.getTag(tag), Lang.plain("part-none") + "."));
shownAtLeastOne = true;
}
if (!shownAtLeastOne)
this.tellNoPrefix("&7 - " + Lang.plain("part-none"));
return;
}
else if ("off".equals(this.args[0])) {
this.checkBoolean(!cache.getTags().isEmpty(), Lang.component("command-tag-off-no-tags"));
for (final Tag.Type tag : Settings.Tag.APPLY_ON)
cache.setTag(tag, null);
Players.setTablistName(wrapped);
final SimpleComponent message = Lang.component("command-tag-off-all");
if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.TAG_UPDATE, Platform.getCustomServerName(), cache.getUniqueId(), cache.toMap(), message);
this.tellSuccess(message);
return;
}
final Tag.Type type = this.findTag(this.args[0]);
this.checkPerm(Permissions.Command.TAG_TYPE + type.getKey());
if (this.args.length == 1) {
this.tellInfo(Lang.component("command-tag-status-self",
"type", type.getKey(),
"tag", CommonCore.getOrDefault(cache.getTag(type), Lang.plain("part-none"))));
return;
}
String tag = this.joinArgs(1);
if (tag.contains("&#"))
tag = Migrator.convertAmpersandToMiniHex(tag);
if (tag.contains("#"))
tag = Migrator.convertHexToMiniHex(tag);
// Colorize according to senders permissions
tag = Colors.removeColorsNoPermission(this.getSender(), tag, type.getColorType());
// Apply rules!
final TagCheck check = Tag.filter(type, wrapped, tag);
tag = check.getMessage();
this.checkBoolean(!check.isCancelledSilently(), Lang.component("command-tag-not-allowed"));
this.setTag(type, cache, tag);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1) {
final List<String> tags = new ArrayList<>();
for (final Tag.Type type : Settings.Tag.APPLY_ON)
if (this.hasPerm(Permissions.Command.TAG_TYPE + type.getKey()))
tags.add(type.getKey());
return this.completeLastWord(tags, "list");
}
if (this.args.length == 2)
return this.completeLastWord("off");
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,110 @@
package org.mineacademy.chatcontrol.command;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mineacademy.chatcontrol.SenderCache;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.PlaceholderPrefix;
import org.mineacademy.chatcontrol.model.PrivateMessage;
import org.mineacademy.chatcontrol.model.ToggleType;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
public final class CommandTell extends ChatControlCommand {
public CommandTell() {
super(Settings.PrivateMessages.TELL_ALIASES);
this.setMinArguments(1);
this.setPermission(Permissions.Command.TELL);
this.setUsage(Lang.component("command-tell-usage"));
this.setDescription(Lang.component("command-tell-description"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-tell-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
final boolean isOff = "off".equalsIgnoreCase(this.args[0]);
final String message = this.joinArgs(1);
final WrappedSender wrapped = WrappedSender.fromAudience(this.audience);
final SyncedCache syncedSenderCache = SyncedCache.fromUniqueId(wrapped.getUniqueId());
final SyncedCache syncedReceiverCache = SyncedCache.fromNickColorless(this.args[0]);
final SenderCache senderCache = wrapped.getSenderCache();
if (isOff || message.isEmpty()) {
this.checkBoolean(Settings.PrivateMessages.AUTOMODE, Lang.component("command-rule-conversation-disabled"));
this.checkConsole();
final String conversingPlayer = senderCache.getConversingPlayerName();
if (isOff) {
this.checkNotNull(conversingPlayer, Lang.component("command-tell-conversation-mode-not-conversing"));
final SyncedCache conversingCache = SyncedCache.fromPlayerName(conversingPlayer);
this.tellSuccess(Lang.component("command-tell-conversation-mode-off", conversingCache == null ? null : conversingCache.getPlaceholders(PlaceholderPrefix.RECEIVER)));
senderCache.setConversingPlayerName(null);
senderCache.setLastAutoModeChat(0);
} else {
this.checkNotNull(syncedReceiverCache, Lang.component("command-tell-receiver-offline", "receiver_name", this.args[0]));
final String receiverName = syncedReceiverCache.getPlayerName();
final boolean isConversing = receiverName.equals(conversingPlayer);
if (syncedReceiverCache.isVanished() && !this.hasPerm(Permissions.Bypass.VANISH))
this.returnTell(Lang.component("command-tell-receiver-offline", "receiver_name", receiverName));
final Map<String, Object> placeholders = new HashMap<>();
placeholders.put("message", message);
placeholders.putAll(syncedReceiverCache.getPlaceholders(PlaceholderPrefix.RECEIVER));
placeholders.putAll(syncedSenderCache.getPlaceholders(PlaceholderPrefix.SENDER));
if (this.isPlayer() && !this.hasPerm(Permissions.Bypass.REACH)) {
if (Settings.Ignore.ENABLED && Settings.Ignore.STOP_PRIVATE_MESSAGES && syncedReceiverCache.isIgnoringPlayer(this.getPlayer().getUniqueId()))
this.returnTell(Lang.component("command-ignore-cannot-pm", placeholders));
if (Settings.Toggle.APPLY_ON.contains(ToggleType.PRIVATE_MESSAGE) && !this.audience.getName().equals(receiverName) && syncedReceiverCache.hasToggledPartOff(ToggleType.PRIVATE_MESSAGE))
this.returnTell(Lang.component("command-toggle-cannot-pm", placeholders));
}
senderCache.setConversingPlayerName(isConversing ? null : receiverName);
senderCache.setLastAutoModeChat(0);
this.tellSuccess(Lang.component("command-tell-conversation-mode-" + (isConversing ? "off" : "on"), placeholders));
}
return;
}
this.checkNotNull(syncedReceiverCache, Lang.component("command-tell-receiver-offline", "receiver_name", this.args[0]));
PrivateMessage.send(wrapped, syncedReceiverCache, message);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return this.args.length == 1 ? this.completeLastWordPlayerNames() : NO_COMPLETE;
}
}

View File

@ -0,0 +1,237 @@
package org.mineacademy.chatcontrol.command;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.ChatControlCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.PlayerMessageType;
import org.mineacademy.chatcontrol.model.ToggleType;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.operator.PlayerMessage;
import org.mineacademy.chatcontrol.operator.PlayerMessages;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.ChatUtil;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
public final class CommandToggle extends ChatControlCommand {
public CommandToggle() {
super(Settings.Toggle.COMMAND_ALIASES);
this.setValidArguments(1, 2);
this.setPermission(Permissions.Command.TOGGLE_TYPE.substring(0, Permissions.Command.TOGGLE_TYPE.length() - 1));
this.setUsage(Lang.component("command-toggle-usage"));
this.setDescription(Lang.component("command-toggle-description"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
Collections.addAll(usages, Lang.component("command-toggle-usages-1",
"label", this.getLabel(),
"toggle_available", Settings.Toggle.APPLY_ON,
"messages_available", Settings.Messages.APPLY_ON));
if (this.canToggle(ToggleType.CHAT))
usages.add(Lang.component("command-toggle-usages-chat"));
if (this.canToggle(PlayerMessageType.JOIN))
usages.add(Lang.component("command-toggle-usages-join"));
if (this.canToggle(PlayerMessageType.TIMED))
usages.add(Lang.component("command-toggle-usages-timed"));
if (this.canToggle(ToggleType.PRIVATE_MESSAGE))
usages.add(Lang.component("command-toggle-usages-private-message"));
if (this.canToggle(ToggleType.SOUND_NOTIFY))
usages.add(Lang.component("command-toggle-usages-sound-notify"));
usages.add(Lang.component("command-toggle-usages-2"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
this.checkConsole();
final String typeRaw = this.args[0];
final PlayerCache cache = PlayerCache.fromCached(this.getPlayer());
ToggleType toggleType = null;
PlayerMessageType messageType = null;
try {
toggleType = ToggleType.fromKey(typeRaw);
if (toggleType != null)
if (!Settings.Toggle.APPLY_ON.contains(toggleType))
toggleType = null;
else
this.checkPerm(Permissions.Command.TOGGLE_TYPE + toggleType.getKey());
} catch (final IllegalArgumentException ex) {
}
try {
messageType = PlayerMessageType.fromKey(typeRaw);
if (!this.canToggle(messageType))
messageType = null;
} catch (final IllegalArgumentException ex) {
}
if ("list".equals(typeRaw)) {
final List<SimpleComponent> pages = new ArrayList<>();
if (!Settings.Messages.APPLY_ON.isEmpty()) {
for (final Entry<PlayerMessageType, Set<String>> entry : cache.getIgnoredMessages().entrySet())
if (this.canToggle(entry.getKey())) {
pages.add(Lang.component("command-toggle-list-line", "type", ChatUtil.capitalize(entry.getKey().getToggleLangKey())));
for (final String groupName : entry.getValue())
pages.add(SimpleComponent
.fromMiniNative(" <gray>-<white> ")
.appendPlain(groupName)
.onHover(Lang.component("command-toggle-list-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " " + entry.getKey() + " " + groupName));
}
if (!pages.isEmpty())
pages.add(SimpleComponent.empty());
}
if (!Settings.Toggle.APPLY_ON.isEmpty()) {
pages.add(Lang.component("command-toggle-list-plugin-part-title"));
for (final ToggleType playerToggle : ToggleType.values())
if (this.canToggle(playerToggle))
pages.add(SimpleComponent
.fromMiniAmpersand(" <gray>-<white> " + ChatUtil.capitalize(playerToggle.getDescription()) + "<gray>: ")
.append(Lang.component("command-toggle-list-plugin-part-" + (cache.hasToggledPartOff(playerToggle) ? "ignoring" : "receiving")))
.onHover(Lang.component("command-toggle-list-plugin-part-toggle"))
.onClickRunCmd("/" + this.getLabel() + " " + playerToggle.getKey() + " " + cache.getPlayerName())
);
}
new ChatPaginator()
.setPages(pages)
.setFoundationHeader(Lang.legacy("command-toggle-list-plugin-part-header", "player", cache.getPlayerName()))
.send(this.audience);
return;
}
this.checkBoolean(toggleType != null || messageType != null, Lang.component("command-toggle-invalid-type",
"available_toggles", Common.join(Settings.Toggle.APPLY_ON),
"available_messages", Common.join(Settings.Messages.APPLY_ON)));
if (messageType != null) {
if (this.args.length == 2) {
final PlayerMessage message = this.findMessage(messageType, this.args[1]);
final boolean ignoring = cache.isIgnoringMessage(message);
this.checkToggle(ignoring);
cache.setIgnoringMessage(message, !ignoring);
this.tellSuccess(Lang.component("command-toggle-ignore-group-" + (ignoring ? "off" : "on"), "type", messageType.getToggleLangKey(), "group", message.getGroup()));
}
else {
final boolean ignoring = cache.isIgnoringMessages(messageType);
this.checkToggle(ignoring);
cache.setIgnoringMessages(messageType, !ignoring);
this.tellSuccess(Lang.component("command-toggle-ignore-all-" + (ignoring ? "off" : "on"), "type", messageType.getToggleLangKey()));
}
} else {
final boolean ignoring = cache.hasToggledPartOff(toggleType);
this.checkToggle(ignoring);
if (toggleType != null && toggleType.equals(ToggleType.PRIVATE_MESSAGE) && !cache.hasManuallyToggledPMs())
cache.setManuallyToggledPMs(true);
cache.setToggledPart(toggleType, !ignoring);
this.tellSuccess(Lang.component("command-ignore-part-" + (ignoring ? "off" : "on"), "type", toggleType.getDescription()));
}
}
private void checkToggle(final boolean ignoring) {
this.checkPerm(ignoring ? Permissions.Command.TOGGLE_STATE_OFF : Permissions.Command.TOGGLE_STATE_ON);
}
/*
* Return true if the toggle is enabled from settings
*/
private boolean canToggle(final ToggleType type) {
return Settings.Toggle.APPLY_ON.contains(type) && this.hasPerm(Permissions.Command.TOGGLE_TYPE + type.getKey());
}
/*
* Return true if the player message is enabled from settings
*/
private boolean canToggle(final PlayerMessageType type) {
return (Settings.Messages.APPLY_ON.contains(type) || (type == PlayerMessageType.SWITCH && Settings.Proxy.ENABLED)) && this.hasPerm(Permissions.Command.TOGGLE_TYPE + type.getKey());
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1) {
final Set<String> completed = CommonCore.newSet("list");
for (final PlayerMessageType type : Settings.Messages.APPLY_ON)
if (this.canToggle(type))
completed.add(type.getKey());
for (final ToggleType type : Settings.Toggle.APPLY_ON)
if (this.canToggle(type))
completed.add(type.getKey());
if (Settings.Proxy.ENABLED)
if (this.canToggle(PlayerMessageType.SWITCH))
completed.add("switch");
return this.completeLastWord(completed);
}
if (this.args.length == 2) {
PlayerMessageType type;
try {
type = PlayerMessageType.fromKey(this.args[0]);
if (type != null && !this.canToggle(type))
return NO_COMPLETE;
} catch (final IllegalArgumentException ex) {
return NO_COMPLETE;
}
return this.completeLastWord(PlayerMessages.getInstance().getMessageNames(type));
}
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,459 @@
package org.mineacademy.chatcontrol.command;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SenderCache;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.ChatControlProxyMessage;
import org.mineacademy.chatcontrol.model.PlayerMessageType;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.model.RuleType;
import org.mineacademy.chatcontrol.model.WrappedSender;
import org.mineacademy.chatcontrol.model.db.Database;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.operator.Group;
import org.mineacademy.chatcontrol.operator.Groups;
import org.mineacademy.chatcontrol.operator.PlayerMessage;
import org.mineacademy.chatcontrol.operator.PlayerMessages;
import org.mineacademy.chatcontrol.operator.Rule;
import org.mineacademy.chatcontrol.operator.Rules;
import org.mineacademy.chatcontrol.operator.Tag;
import org.mineacademy.chatcontrol.operator.Tag.Type;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.Common;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.Messenger;
import org.mineacademy.fo.ProxyUtil;
import org.mineacademy.fo.command.SharedBukkitCommandCore;
import org.mineacademy.fo.command.SimpleCommandCore;
import org.mineacademy.fo.exception.CommandException;
import org.mineacademy.fo.model.CompChatColor;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.settings.Lang;
import lombok.NonNull;
public interface SharedChatControlCommandCore extends SharedBukkitCommandCore {
/**
* Return a channel by name or send error message to player if wrong name
*
* @param channelName
* @return
*/
default Channel findChannel(@NonNull final String channelName) {
final Channel channel = Channel.findChannel(channelName);
this.checkBoolean(channel != null, Lang.component("command-invalid-channel", "channel", channelName, "available", CommonCore.joinAnd(Channel.getChannelNames())));
return channel;
}
/**
* Return channel mode by name or send error to player if wrong mode
*
* @param modeName
* @return
*/
default ChannelMode findMode(@NonNull final String modeName) {
try {
return ChannelMode.fromKey(modeName);
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-invalid-mode", "mode", modeName, "available", ChannelMode.values()));
return null;
}
}
/**
* Return a rule by the name or send error message to the player
*
* @param ruleName
* @return
*/
default Rule findRule(final String ruleName) {
final List<Rule> namedRules = Rules.getInstance().getRulesWithName();
final Rule rule = Rules.getInstance().findRule(ruleName);
final String available = namedRules.isEmpty() ? Lang.plain("part-none") : CommonCore.join(namedRules, ", ", Rule::getName);
this.checkBoolean(ruleName != null && !ruleName.isEmpty(), Lang.component("command-no-rule-name", "available", available));
this.checkBoolean(rule != null, Lang.component("command-invalid-rule", "rule", ruleName, "available", available));
return rule;
}
/**
* Parse rule type from name or send error message to the player
*
* @param typeName
* @return
*/
default RuleType findRuleType(final String typeName) {
try {
return RuleType.fromKey(typeName);
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-invalid-type", "type", "rule", "value", typeName, "available", RuleType.values()));
return null;
}
}
/**
* Return a rule group by the name or send error message to the player
*
* @param groupName
* @return
*/
default Group findRuleGroup(final String groupName) {
final List<String> groups = Groups.getInstance().getGroupNames();
final Group group = Groups.getInstance().findGroup(groupName);
final String available = groups.isEmpty() ? Lang.plain("part-none") : CommonCore.join(groups);
this.checkBoolean(groupName != null && !groupName.isEmpty(), Lang.component("command-no-group-name", "available", available));
this.checkBoolean(group != null, Lang.component("command-invalid-group", "group", groupName, "available", available));
return group;
}
/**
* Return a rule by the name or send error message to the player
*
* @param type
* @param groupName
* @return
*/
default PlayerMessage findMessage(final PlayerMessageType type, final String groupName) {
final Collection<String> itemNames = PlayerMessages.getInstance().getMessageNames(type);
final PlayerMessage items = PlayerMessages.getInstance().findMessage(type, groupName);
final String available = itemNames.isEmpty() ? Lang.plain("part-none") : CommonCore.join(itemNames);
this.checkBoolean(groupName != null && !groupName.isEmpty(), Lang.component("command-no-message-name", "available", available));
this.checkBoolean(items != null, Lang.component("command-invalid-type", "type", "message", "value", groupName, "available", available));
return items;
}
/**
* Parse rule type from name or send error message to the player
*
* @param typeName
* @return
*/
default PlayerMessageType findMessageType(final String typeName) {
try {
final PlayerMessageType type = PlayerMessageType.fromKey(typeName);
if (!Settings.Messages.APPLY_ON.contains(type))
throw new IllegalArgumentException();
return type;
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-invalid-type", "type", "message", "value", typeName, "available", PlayerMessageType.values()));
return null;
}
}
/**
* Return the disk cache for name or if name is null and we are not console then return it for the sender
*
* @param nameOrNick
* @param syncCallback
*
* @throws CommandException
*/
default void pollDiskCacheOrSelf(@Nullable final String nameOrNick, final Consumer<PlayerCache> syncCallback) throws CommandException {
if (nameOrNick == null) {
this.checkBoolean(this.isPlayer(), Lang.component("command-console-missing-player-name"));
syncCallback.accept(PlayerCache.fromCached(this.getPlayer()));
return;
}
this.pollCache(nameOrNick, syncCallback);
}
/**
* Attempts to get a player cache by his name or nick from either
* data.db or database. Since this is a blocking operation, we have a synced
* callback here.
*
* @param nameOrNick
* @param syncCallback
*/
default void pollCache(final String nameOrNick, final Consumer<PlayerCache> syncCallback) {
final SenderCache senderCache = SenderCache.from(this.getSender());
// Prevent calling this again when loading
senderCache.setQueryingDatabase(true);
PlayerCache.poll(nameOrNick, cache -> {
handleCallbackCommand(this.getSender(),
() -> {
this.checkBoolean(cache != null, Lang.component("player-not-stored", "player", nameOrNick));
syncCallback.accept(cache);
}, () -> {
senderCache.setQueryingDatabase(false);
});
});
}
/**
* Get certain information async and then process this information in the sync callback
*
* @param <T>
* @param asyncGetter
* @param syncCallback
*/
default <T> void syncCallback(final Supplier<T> asyncGetter, final Consumer<T> syncCallback) {
final SenderCache senderCache = SenderCache.from(this.getSender());
// Prevent calling this again when loading
senderCache.setQueryingDatabase(true);
// Run the getter async
asyncCallbackCommand(this.getSender(), () -> {
final T value = asyncGetter.get();
// Then run callback on the main thread
syncCallbackCommand(this.getSender(),
() -> syncCallback.accept(value),
() -> senderCache.setQueryingDatabase(false));
}, () -> senderCache.setQueryingDatabase(false));
}
/**
* Handles callback for all caches on disk or database
*
* @param syncCallback
*/
default void pollCaches(final Consumer<List<PlayerCache>> syncCallback) {
final SenderCache senderCache = SenderCache.from(this.getSender());
this.tellInfo(Lang.component("command-compiling-data"));
// Prevent calling this again when loading
senderCache.setQueryingDatabase(true);
PlayerCache.pollAll(caches -> handleCallbackCommand(this.getSender(),
() -> syncCallback.accept(caches),
() -> senderCache.setQueryingDatabase(false)));
}
/**
* Parse dog tag or send error message to the player
*
* @param name
* @return
*/
default Tag.Type findTag(final String name) {
try {
final Tag.Type tag = Tag.Type.fromKey(name);
if (!Settings.Tag.APPLY_ON.contains(tag))
throw new IllegalArgumentException();
return tag;
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-invalid-tag", "tag", name, "available", Common.join(Settings.Tag.APPLY_ON)));
return null;
}
}
/**
* Special method used for nicks/prefixes/suffixes
*
* @param type
* @param cache
* @param tag
*/
default void setTag(final Tag.Type type, final PlayerCache cache, String tag) {
final boolean remove = "off".equals(tag) || tag.equals(cache.getPlayerName());
final boolean self = this.getAudience().getName().equals(cache.getPlayerName());
this.checkBoolean(!remove || cache.hasTag(type), Lang.component("command-tag-no-tag-" + (self ? "self" : "other"),
"target", cache.getPlayerName(),
"type", type.getKey()));
final char firstChar = tag.charAt(0);
final char lastChar = tag.charAt(tag.length() - 1);
if (firstChar == '"' || firstChar == '\'')
tag = tag.substring(1);
if (lastChar == '"' || lastChar == '\'')
tag = tag.substring(0, tag.length() - 1);
// Prevent "legacy chars found" error if not parsed
if (!Settings.Colors.APPLY_ON.contains(type.getColorType()))
tag = CompChatColor.convertLegacyToMini(tag, true);
final String colorlessTag = SimpleComponent.fromMiniAmpersand(tag).toPlain();
if (type == Type.NICK)
this.checkBoolean(!tag.contains(" "), Lang.component("command-tag-no-spaces"));
this.checkBoolean(!colorlessTag.isEmpty(), Lang.component("command-tag-no-colors-only"));
if (type == Tag.Type.NICK && !colorlessTag.equalsIgnoreCase(this.getAudience().getName())) {
final String newTagFinal = tag;
asyncCallbackCommand(this.getSender(), () -> {
this.checkBoolean(colorlessTag.length() <= Settings.Tag.MAX_NICK_LENGTH, Lang.component("command-tag-exceeded-length",
"length", colorlessTag.length(),
"max", Settings.Tag.MAX_NICK_LENGTH));
if (Settings.Tag.NICK_DISABLE_IMPERSONATION) {
final OfflinePlayer offline = Bukkit.getOfflinePlayer(colorlessTag);
this.checkBoolean(!offline.hasPlayedBefore(), Lang.component("command-tag-no-impersonation"));
}
if (!cache.getPlayerName().equals(this.getSender().getName()))
this.checkBoolean(!Database.getInstance().isNickUsed(colorlessTag), Lang.component("command-tag-already-used"));
syncCallbackCommand(this.getSender(), () -> {
this.setTag0(type, cache, newTagFinal, remove, self);
}, () -> {
// No final callback
});
}, () -> {
// No final callback
});
}
else
this.setTag0(type, cache, tag, remove, self);
}
default void setTag0(final Tag.Type type, final PlayerCache cache, final String newTag, final boolean remove, final boolean self) {
cache.setTag(type, remove ? null : newTag);
final Player cachePlayer = cache.toPlayer();
if (cachePlayer != null)
Players.setTablistName(WrappedSender.fromPlayerCaches(cachePlayer, cache, SenderCache.from(cachePlayer)));
final SimpleComponent component = Lang.component("command-tag-" + (remove ? "remove" : "set") + (self ? "-self" : ""),
"type", type.getKey(),
"tag", newTag,
"target", cache.getPlayerName());
this.tellInfo(component);
// Notify proxy so that players connected on another server get their tablist updated
if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.TAG_UPDATE, Platform.getCustomServerName(), cache.getUniqueId(), cache.toDataSectionOfMap(), component);
}
/**
* Makes all other servers on the network get database updated for
* the given player cache
*
* @param cache
* @throws CommandException
*/
default void updateProxyData(final PlayerCache cache) throws CommandException {
this.updateProxyData(cache, SimpleComponent.empty());
}
/**
* Makes all other servers on the network get database updated for
* the given player cache, sending the player the specified message
*
* @param cache
* @param message
* @throws CommandException
*/
default void updateProxyData(@NonNull final PlayerCache cache, @NonNull SimpleComponent message) throws CommandException {
final Player messageTarget = cache.toPlayer();
if (messageTarget != null && !message.isEmpty()) {
Messenger.info(messageTarget, message);
message = SimpleComponent.empty();
}
if (Settings.Proxy.ENABLED)
ProxyUtil.sendPluginMessage(ChatControlProxyMessage.DATABASE_UPDATE, Platform.getCustomServerName(), cache.getUniqueId(), cache.toDataSectionOfMap(), message);
}
/**
* @see SimpleCommandCore#tellInfo(String)
*
* @param message
*/
void tellInfo(String message);
/**
* @see SimpleCommandCore#tellInfo(SimpleComponent)
*
* @param message
*/
void tellInfo(SimpleComponent message);
/*
* See below, but everything is wrapped and run async
*/
static void asyncCallbackCommand(final CommandSender sender, final Runnable callback, final Runnable finallyCallback) {
Platform.runTaskAsync(() -> handleCallbackCommand(sender, callback, finallyCallback));
}
/*
* See below, but everything is wrapped and run on the main thread
*/
static void syncCallbackCommand(final CommandSender sender, final Runnable callback, final Runnable finallyCallback) {
Platform.runTask(() -> handleCallbackCommand(sender, callback, finallyCallback));
}
/*
* Wraps the given callback in try-catch clause and sends all CommandExceptions to the sender,
* then runs the finally clause with the finallyCallback
*/
static void handleCallbackCommand(final CommandSender sender, final Runnable callback, final Runnable finallyCallback) {
try {
callback.run();
} catch (final CommandException ex) {
ex.sendErrorMessage(Platform.toPlayer(sender));
} catch (final Throwable t) {
Messenger.error(sender, Lang.component("command-error").replaceBracket("error", t.toString()));
t.printStackTrace();
throw t;
} finally {
try {
finallyCallback.run();
} catch (final Throwable t) {
t.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,175 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.GenericSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.chatcontrol.settings.Settings.Channels;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.annotation.AutoRegister;
import org.mineacademy.fo.command.SimpleCommandGroup;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
import lombok.Getter;
/**
* Stores all /channel commands
*/
@AutoRegister
public final class ChannelCommands extends SimpleCommandGroup {
/**
* The singleton of this class
*/
@Getter
private final static SimpleCommandGroup instance = new ChannelCommands();
/**
* Initiaze this class, this must be called after settings have loaded
*/
private ChannelCommands() {
super(Settings.Channels.COMMAND_ALIASES);
}
/**
* @see org.mineacademy.fo.command.SimpleCommandGroup#getHelpHeader()
*/
@Override
protected String[] getHelpHeader() {
return new String[] {
"&8",
"&8" + CommonCore.chatLineSmooth(),
this.getHeaderPrefix() + " Channel Commands",
" ",
" &2[] &7= " + Lang.plain("command-label-optional-args"),
" &6<> &7= " + Lang.plain("command-label-required-args"),
" "
};
}
/**
* @see org.mineacademy.fo.command.SimpleCommandGroup#getHeaderPrefix()
*/
@Override
protected String getHeaderPrefix() {
return "<gradient:#db0000:#fb00ff><bold>";
}
/**
* @see org.mineacademy.fo.command.SimpleCommandGroup#getNoParamsHeader(org.bukkit.command.CommandSender)
*/
@Override
protected List<String> getNoParamsHeader() {
final List<String> messages = new ArrayList<>();
final boolean isPlayer = this.getAudience().isPlayer();
final Player player = isPlayer ? (Player) this.getAudience().getPlayer() : null;
final PlayerCache cache = isPlayer ? PlayerCache.fromCached(player) : null;
messages.add("&8" + CommonCore.chatLineSmooth());
messages.add(this.getHeaderPrefix() + " " + Lang.plain("channel-header"));
messages.add("");
if (!Settings.Channels.ENABLED) {
messages.add(" " + Lang.plain("channel-disabled"));
return messages;
}
if (isPlayer && Channels.IGNORE_WORLDS.contains(player.getWorld().getName())) {
messages.add(" " + Lang.plain("channel-disabled-world").replace("{world}", player.getWorld().getName()));
return messages;
}
if (Channel.getChannels().isEmpty()) {
messages.add(" " + Lang.plain("channel-no-channels"));
return messages;
}
// Fill in the channels player is in
{
boolean atLeastOneJoined = false;
for (final Channel channel : Channel.getChannels()) {
final String name = channel.getName();
final ChannelMode mode = isPlayer ? cache.getChannelMode(channel) : null;
SimpleComponent component = SimpleComponent.fromMiniNative("<gray> - " + (mode != null ? "<green>" : "<white>") + name + " ");
if (mode == null) {
if (this.getAudience().hasPermission(Permissions.Channel.JOIN.replace("{channel}", name).replace("{mode}", "write")))
component = component
.onHover(Lang.component("channel-tooltip-join"))
.onClickRunCmd("/" + Settings.Channels.COMMAND_ALIASES.get(0) + " join " + name + " write");
} else {
if (this.getAudience().hasPermission(Permissions.Channel.LEAVE.replace("{channel}", name)))
component = component
.onHover(Lang.component("channel-tooltip-leave"))
.onClickRunCmd("/" + Settings.Channels.COMMAND_ALIASES.get(0) + " leave " + name);
}
if (mode != null) {
component = component.appendMiniNative("<dark_gray>(" + (mode == ChannelMode.WRITE ? "<gold>✎" : "<dark_green>▲") + "<dark_gray>) ");
if (this.getAudience().hasPermission(Permissions.Channel.JOIN.replace("{channel}", name).replace("{mode}", mode.getKey())))
component = component
.onHover(Lang.component("channel-tooltip-switch-to-" + (mode == ChannelMode.WRITE ? "read" : "write")))
.onClickRunCmd("/" + Settings.Channels.COMMAND_ALIASES.get(0) + " join " + name + " " + (mode == ChannelMode.WRITE ? "read" : "write"));
}
messages.add(component.toMini(null));
atLeastOneJoined = true;
}
if (!atLeastOneJoined)
messages.add(" &7- &o" + Lang.plain("part-none"));
}
messages.add("&8" + CommonCore.chatLineSmooth());
return messages;
}
/**
* @see org.mineacademy.fo.command.SimpleCommandGroup#registerSubcommands()
*/
@Override
protected void registerSubcommands() {
this.registerSubcommand(ChannelSubCommand.class);
}
/**
* A helper class used by all channel commands
*/
public static abstract class ChannelSubCommand extends GenericSubCommand {
protected ChannelSubCommand(final String sublabel) {
super(ChannelCommands.getInstance(), sublabel);
}
@Override
public final void onCommand() {
if (!Channels.ENABLED)
this.returnTell(Lang.component("channel-disabled"));
if (this.isPlayer() && Channels.IGNORE_WORLDS.contains(this.getPlayer().getWorld().getName()))
this.returnTell(Lang.component("channel-disabled-world", "world", this.getPlayer().getWorld().getName()));
this.onChannelCommand();
}
/**
* Same as onCommand but we already checked if channels are enabled
*/
protected abstract void onChannelCommand();
}
}

View File

@ -0,0 +1,184 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.channel.ChannelCommands.ChannelSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.chatcontrol.settings.Settings.Channels;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.Messenger;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.remain.Remain;
import org.mineacademy.fo.settings.Lang;
public final class JoinChannelSubCommand extends ChannelSubCommand {
public JoinChannelSubCommand() {
super("join/j");
this.setPermission(Permissions.Channel.JOIN.replace(".{channel}.{mode}", ""));
this.setUsage(Lang.component("channel-join-usage"));
this.setDescription(Lang.component("channel-join-description"));
this.setMinArguments(1);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
usages.add(Lang.component("channel-join-usages-self"));
if (this.hasPerm(Permissions.Channel.JOIN_OTHERS))
usages.add(Lang.component("channel-join-usages-others"));
usages.add(Lang.component("channel-join-usages-2"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onChannelCommand() {
this.checkBoolean(this.args.length < 4, this.getUsage());
final Channel channel = this.findChannel(this.args[0]);
final ChannelMode pickedMode = this.args.length >= 2 ? this.findMode(this.args[1]) : ChannelMode.WRITE;
this.checkBoolean(this.isPlayer() || this.args.length == 3, Lang.component("command-console-missing-player-name"));
this.pollCache(this.args.length == 3 ? this.args[2] : this.getSender().getName(), cache -> {
final boolean self = cache.getPlayerName().equals(this.getSender().getName());
final ChannelMode previousMode = cache.getChannelMode(channel);
final Channel previousWriteChannel = cache.getWriteChannel();
final Player targetPlayerOrNull = Remain.getPlayerByUUID(cache.getUniqueId());
ChannelMode mode = pickedMode;
// If no mode is specified and player can only read the channel, default to reading
if (this.args.length < 2 && !Channel.canWriteInto(this.getSender(), channel) && Channel.canRead(this.getSender(), channel))
mode = ChannelMode.READ;
this.checkPerm(Permissions.Channel.JOIN.replace("{channel}", channel.getName()).replace("{mode}", mode.getKey()));
// Check if joining oneself
if (!self)
this.checkPerm(Permissions.Channel.JOIN_OTHERS);
// Check if player connected
if (Settings.Proxy.ENABLED) {
this.checkBoolean(Settings.Database.isRemote(), Lang.component("command-remote-database-required"));
this.checkBoolean(SyncedCache.isPlayerConnected(cache.getUniqueId()), Lang.component("player-not-connected-proxy", "player", cache.getPlayerName()));
} else
this.checkNotNull(targetPlayerOrNull, Lang.component("player-not-online", "player", cache.getPlayerName()));
this.checkBoolean(previousMode != mode, Lang.component("channel-join-already-joined" + (self ? "-self" : ""), "mode", previousMode == null ? "" : previousMode.getKey()));
// Start compiling message for player
SimpleComponent component = previousMode != null
? Lang.component("channel-join-switch-success", "channel", channel.getName(), "old_mode", previousMode == null ? "" : previousMode.getKey(), "new_mode", mode.getKey())
: Lang.component("channel-join-success", "channel", channel.getName(), "mode", mode.getKey());
final int readLimit = Settings.Channels.MAX_READ_CHANNELS.getForUUID(cache.getUniqueId());
this.checkBoolean(mode != ChannelMode.READ || readLimit > 0, Lang.component("channel-join-read-disabled"));
boolean save = false;
// Check limits
if (mode == ChannelMode.READ) {
int readingChannelsAmount = 0;
final List<String> channelsPlayerLeft = new ArrayList<>();
for (final Channel otherReadChannel : cache.getChannels(ChannelMode.READ))
if (++readingChannelsAmount >= readLimit) {
cache.updateChannelMode(otherReadChannel, null, false);
channelsPlayerLeft.add(otherReadChannel.getName());
save = true;
}
if (!channelsPlayerLeft.isEmpty())
component = component.append(Lang.component("channel-join-leave-reading-" + (self ? "you" : "other"),
"channels", CommonCore.joinAnd(channelsPlayerLeft),
"target", cache.getPlayerName()));
}
// If player has another mode for that channel, remove it first
if (previousMode != null) {
cache.updateChannelMode(channel, null, false);
save = true;
}
// Remove the player from old write channel early to avoid errors
if (previousWriteChannel != null && mode == ChannelMode.WRITE) {
cache.updateChannelMode(previousWriteChannel, null, false);
save = true;
}
channel.joinPlayer(cache, mode, false);
save = true;
// If player was writing in another write channel, leave him or change mode if set
if (previousWriteChannel != null && mode == ChannelMode.WRITE)
if (Channels.JOIN_READ_OLD && cache.getChannels(ChannelMode.READ).size() + 1 <= readLimit) {
cache.updateChannelMode(previousWriteChannel, ChannelMode.READ, false);
save = true;
} else if (previousMode == null)
component = component.append(Lang.component("channel-join-leave-reading-" + (self ? "you" : "other"),
"channels", previousWriteChannel.getName(),
"target", cache.getPlayerName()));
if (!Settings.Proxy.ENABLED || !self)
this.tellSuccess(component);
if (!self && targetPlayerOrNull != null && this.isPlayer())
Messenger.success(targetPlayerOrNull, component);
if (save)
cache.upsert();
// Notify proxy so that players connected on another server get their channel updated
this.updateProxyData(cache, component);
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord(this.isPlayer() ? Channel.getChannelsWithJoinPermission(this.getPlayer()) : Channel.getChannels(), Channel::getName);
if (this.args.length == 2) {
final List<String> modesPlayerHasPermissionFor = new ArrayList<>();
for (final ChannelMode mode : ChannelMode.values())
if (this.hasPerm(Permissions.Channel.JOIN.replace("{channel}", this.args[0]).replace("{mode}", mode.getKey())))
modesPlayerHasPermissionFor.add(mode.getKey());
return this.completeLastWord(modesPlayerHasPermissionFor);
}
if (this.args.length == 3 && this.hasPerm(Permissions.Channel.JOIN_OTHERS))
return this.completeLastWordPlayerNames();
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,143 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.channel.ChannelCommands.ChannelSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.Messenger;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.remain.Remain;
import org.mineacademy.fo.settings.Lang;
public final class LeaveChannelSubCommand extends ChannelSubCommand {
public LeaveChannelSubCommand() {
super("leave/l");
this.setPermission(Permissions.Channel.LEAVE.replace(".{channel}", ""));
this.setUsage(Lang.component("channel-leave-usage"));
this.setDescription(Lang.component("channel-leave-description"));
this.setValidArguments(1, 2);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
usages.add(Lang.component("channel-leave-usages-self"));
if (this.hasPerm(Permissions.Channel.LEAVE_OTHERS))
usages.add(Lang.component("channel-leave-usages-others"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onChannelCommand() {
final Channel channel = this.findChannel(this.args[0]);
this.checkBoolean(this.isPlayer() || this.args.length == 2, Lang.component("command-console-missing-player-name"));
this.pollCache(this.args.length == 2 ? this.args[1] : this.audience.getName(), cache -> {
final boolean self = cache.getPlayerName().equals(this.audience.getName());
final Set<Channel> oldChannels = cache.getChannels().keySet();
final Player playerMaybe = Remain.getPlayerByUUID(cache.getUniqueId());
this.checkPerm(Permissions.Channel.LEAVE.replace("{channel}", channel.getName()));
// Check if joining oneself
if (!self)
this.checkPerm(Permissions.Channel.LEAVE_OTHERS);
// Check if player connected
if (Settings.Proxy.ENABLED) {
this.checkBoolean(Settings.Database.isRemote(), Lang.component("command-remote-database-required"));
this.checkBoolean(SyncedCache.isPlayerConnected(cache.getUniqueId()), Lang.component("player-not-online", "player", cache.getPlayerName()));
} else
this.checkNotNull(playerMaybe, Lang.component("player-not-online", "player", cache.getPlayerName()));
if (oldChannels.isEmpty())
this.returnTell(Lang.component("channel-leave-no-channel" + (self ? "" : "-other")));
final List<Channel> channelsPlayerCanLeave = Channel.filterChannelsPlayerCanLeave(cache.getChannels().keySet(), playerMaybe);
if (!cache.isInChannel(channel.getName())) {
if (channelsPlayerCanLeave.isEmpty())
this.returnTell(Lang.component("channel-leave-not-joined" + (self ? "" : "-other"), "name", cache.getPlayerName()));
else
this.returnTell(Lang.component("channel-leave-not-joined-suggest-alt" + (self ? "" : "-other"),
"name", cache.getPlayerName(),
"channels", CommonCore.join(channelsPlayerCanLeave, ", ", Channel::getName)));
}
final int readLimit = Settings.Channels.MAX_READ_CHANNELS.getForUUID(cache.getUniqueId());
final boolean hasJoinReadPerm = playerMaybe == null || playerMaybe.hasPermission(Permissions.Channel.JOIN.replace("{channel}", channel.getName()).replace("{mode}", ChannelMode.READ.getKey()));
// If leaving channel player is not reading and he can read,
// turn into reading channel first before leaving completely
if (Settings.Channels.JOIN_READ_OLD && hasJoinReadPerm && cache.getChannels(ChannelMode.READ).size() < readLimit && cache.getChannelMode(channel) != ChannelMode.READ) {
cache.updateChannelMode(channel, ChannelMode.READ, true);
this.tellSuccess(Lang.component("channel-leave-switch-to-reading" + (self ? "" : "-other"),
"channel", channel.getName(),
"target", cache.getPlayerName()));
if (!self && playerMaybe != null)
Messenger.success(playerMaybe, Lang.component("channel-leave-switch-to-reading", "channel", channel.getName()));
return;
} else {
channel.leavePlayer(cache, true);
// Mark as manually left
cache.markLeftChannel(channel);
}
if (!Settings.Proxy.ENABLED || !self)
this.tellSuccess(Lang.component("channel-leave-success" + (self ? "" : "-other"),
"channel", channel.getName(), "target", cache.getPlayerName()));
if (!self && playerMaybe != null && !Settings.Proxy.ENABLED)
Messenger.success(playerMaybe, Lang.component("channel-leave-success", "channel", channel.getName()));
// Notify proxy so that players connected on another server get their channel updated
this.updateProxyData(cache, Lang.component("channel-leave-success", "channel", channel.getName()));
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord((this.isPlayer() ? Channel.getChannelsWithLeavePermission(this.getPlayer()) : Channel.getChannels())
.stream()
.filter(channel -> !this.isPlayer() || channel.isInChannel(this.getPlayer()))
.collect(Collectors.toList()), Channel::getName);
if (this.args.length == 2 && this.hasPerm(Permissions.Channel.LEAVE_OTHERS))
return this.completeLastWordPlayerNames();
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,149 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.channel.ChannelCommands.ChannelSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.db.PlayerCache;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
public final class ListChannelSubCommand extends ChannelSubCommand {
public ListChannelSubCommand() {
super("list/ls");
this.setPermission(Permissions.Channel.LIST);
this.setMaxArguments(1);
this.setDescription(Lang.component("channel-list-description"));
this.setUsage(Lang.component("channel-list-usage"));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("channel-list-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onChannelCommand() {
final String selectedChannelName = this.args.length == 1 ? this.args[0] : null;
final Channel selectedChannel = selectedChannelName != null ? Channel.findChannel(selectedChannelName) : null;
final boolean canSeeOptions = this.hasPerm(Permissions.Channel.LIST_OPTIONS);
if (selectedChannelName != null)
this.checkNotNull(selectedChannel, Lang.component("channel-no-channel", "channel", selectedChannelName, "available", Channel.getChannelNames()));
this.pollCaches(caches -> {
final List<SimpleComponent> messages = new ArrayList<>();
final Map<String, Map<String /*player*/, ChannelMode>> allChannelPlayers = new LinkedHashMap<>();
for (final PlayerCache cache : caches) {
final SyncedCache synced = SyncedCache.fromUniqueId(cache.getUniqueId());
if (synced == null)
continue;
for (final Map.Entry<String, ChannelMode> entry : synced.getChannels().entrySet()) {
final String channelName = entry.getKey();
final ChannelMode mode = entry.getValue();
final Map<String /*player*/, ChannelMode> playersInChannel = allChannelPlayers.getOrDefault(channelName, new LinkedHashMap<>());
playersInChannel.put(cache.getPlayerName(), mode);
allChannelPlayers.put(channelName, playersInChannel);
}
}
for (final Channel channel : Channel.getChannels()) {
// Filter channel if parameter is given
if (selectedChannel != null && !selectedChannel.equals(channel))
continue;
// Allow one-click un/mute
final boolean muted = channel.isMuted();
final boolean joined = this.isPlayer() ? channel.isInChannel(this.getPlayer()) : false;
SimpleComponent channelNameComponent = SimpleComponent.fromMiniNative(" <white>" + channel.getName());
if (canSeeOptions && this.isPlayer()) {
if (Settings.Mute.ENABLED)
channelNameComponent = channelNameComponent
.append(Lang.component("channel-list-" + (muted ? "unmute" : "mute")))
.onHover(Lang.component("channel-list-" + (muted ? "unmute-tooltip" : "mute-tooltip")))
.onClickRunCmd("/" + Settings.Mute.COMMAND_ALIASES.get(0) + " channel " + channel.getName() + " " + (muted ? "off" : "15m"));
channelNameComponent = channelNameComponent
.append(Lang.component("channel-list-" + (joined ? "leave" : "join")))
.onHover(Lang.component("channel-list-" + (joined ? "leave-tooltip" : "join-tooltip")))
.onClickRunCmd("/" + Settings.Channels.COMMAND_ALIASES.get(0) + " " + (joined ? "leave" : "join") + " " + channel.getName());
}
messages.add(channelNameComponent);
final Map<String /*player*/, ChannelMode> channelPlayers = allChannelPlayers.getOrDefault(channel.getName(), new LinkedHashMap<>());
if (channelPlayers.isEmpty())
messages.add(Lang.component("channel-list-no-players"));
else
for (final Entry<String, ChannelMode> entry : channelPlayers.entrySet()) {
final String playerName = entry.getKey();
final ChannelMode mode = entry.getValue();
SimpleComponent playerComponent = SimpleComponent.fromMiniNative(" <gray>- ");
if (canSeeOptions && this.isPlayer())
playerComponent = playerComponent
.append(Lang.component("channel-list-remove"))
.onHover(Lang.component("channel-list-remove-tooltip", "player", playerName))
.onClickRunCmd("/" + Settings.Channels.COMMAND_ALIASES.get(0) + " leave " + channel.getName() + " " + playerName);
playerComponent = playerComponent.append(Lang.component("channel-list-line",
"mode_color", mode.getColor().toString(),
"mode", mode.getKey(),
"mode_colorized", mode.getColor().toString() + mode.getKey(),
"player", playerName));
messages.add(playerComponent);
}
messages.add(SimpleComponent.fromPlain(" "));
}
new ChatPaginator()
.setFoundationHeader(Lang.legacy("channel-list-header" + (Settings.Proxy.ENABLED ? "-network" : "")))
.setPages(messages)
.send(this.audience);
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord(Channel.getChannelNames());
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,69 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.command.channel.ChannelCommands.ChannelSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.fo.exception.EventHandledException;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.settings.Lang;
public final class SendAsChannelSubCommand extends ChannelSubCommand {
public SendAsChannelSubCommand() {
super("sendas/sa");
this.setPermission(Permissions.Channel.SEND_AS.replace(".{channel}", ""));
this.setUsage(Lang.component("channel-send-as-usage"));
this.setDescription(Lang.component("channel-send-as-description"));
this.setMinArguments(3);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onChannelCommand() {
final Player player = this.findPlayer(this.args[0]);
final Channel channel = this.findChannel(this.args[1]);
final String message = this.joinArgs(2);
this.checkPerm(Permissions.Channel.SEND_AS.replace("{channel}", channel.getName()));
try {
channel.sendMessage(player, message);
} catch (final EventHandledException ex) {
final StringBuilder builder = new StringBuilder();
for (final SimpleComponent component : ex.getComponents())
builder.append(component.toLegacySection(null));
this.tellError(Lang.component("channel-send-as-error", "player", player.getName(), "error", builder.toString()));
}
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
// Remove proxy players, no support yet
return this.completeLastWordPlayerNames()
.stream()
.filter(name -> Bukkit.getPlayerExact(name) != null)
.collect(Collectors.toList());
if (this.args.length == 2)
return this.completeLastWord(Channel.getChannelNames());
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,46 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.List;
import org.mineacademy.chatcontrol.command.channel.ChannelCommands.ChannelSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.fo.settings.Lang;
public final class SendChannelSubCommand extends ChannelSubCommand {
public SendChannelSubCommand() {
super("send/s");
this.setPermission(Permissions.Channel.SEND.replace(".{channel}", ""));
this.setUsage(Lang.component("channel-send-usage"));
this.setDescription(Lang.component("channel-send-description"));
this.setMinArguments(2);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onChannelCommand() {
final Channel channel = this.findChannel(this.args[0]);
final String message = this.joinArgs(1);
this.checkPerm(Permissions.Channel.SEND.replace("{channel}", channel.getName()));
channel.sendMessage(this.getSender(), message);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord(Channel.getChannelNames());
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,100 @@
package org.mineacademy.chatcontrol.command.channel;
import java.util.List;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.channel.ChannelCommands.ChannelSubCommand;
import org.mineacademy.chatcontrol.model.Channel;
import org.mineacademy.chatcontrol.model.ChannelMode;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.remain.Remain;
import org.mineacademy.fo.settings.Lang;
public final class SetChannelSubCommand extends ChannelSubCommand {
public SetChannelSubCommand() {
super("set");
this.setPermission(Permissions.Channel.SET);
this.setUsage(Lang.component("channel-set-usage"));
this.setDescription(Lang.component("channel-set-description"));
this.setMinArguments(3);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("channel-set-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onChannelCommand() {
this.pollCache(this.args[0], cache -> {
final Player playerMaybe = Remain.getPlayerByUUID(cache.getUniqueId());
// Check if player connected
if (Settings.Proxy.ENABLED) {
this.checkBoolean(Settings.Database.isRemote(), Lang.component("command-remote-database-required"));
this.checkBoolean(SyncedCache.isPlayerConnected(cache.getUniqueId()), Lang.component("player-not-connected-proxy", "player", cache.getPlayerName()));
} else
this.checkNotNull(playerMaybe, Lang.component("player-not-online", "player", cache.getPlayerName()));
for (final String channelAndMode : this.joinArgs(1).split("\\|")) {
final String[] split = channelAndMode.split(" ");
this.checkBoolean(split.length == 2, "Invalid syntax! Usage: /{label} {sublabel} <player> <channel> <mode>|<anotherChannel> <anotherMode>");
final Channel channel = this.findChannel(split[0]);
final ChannelMode mode = "none".equals(split[1]) ? null : this.findMode(split[1]);
// Cannot have more than 1 write channel
if (mode == ChannelMode.WRITE && cache.getWriteChannel() != null)
cache.updateChannelMode(cache.getWriteChannel(), null, false);
// Remove old channel forcefully
cache.updateChannelMode(channel, null, false);
// Then rejoin
if (mode != null)
channel.joinPlayer(cache, mode, false);
cache.upsert();
final SimpleComponent message = mode != null
? Lang.component("channel-set-success", "channel", channel.getName(), "player", cache.getPlayerName(), "mode", mode.getKey())
: Lang.component("channel-set-success-remove", "channel", channel.getName(), "player", cache.getPlayerName());
this.tellSuccess(message);
}
// Notify proxy so that players connected on another server get their channel updated
this.updateProxyData(cache);
});
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWordPlayerNames();
if (this.args.length == 2)
return this.completeLastWord(Channel.getChannels(), Channel::getName);
if (this.args.length == 3)
return this.completeLastWord(ChannelMode.values(), "none");
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,314 @@
package org.mineacademy.chatcontrol.command.chatcontrol;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.mineacademy.chatcontrol.SyncedCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.MainSubCommand;
import org.mineacademy.chatcontrol.model.Announce;
import org.mineacademy.chatcontrol.model.Announce.AnnounceType;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.FileUtil;
import org.mineacademy.fo.MinecraftVersion;
import org.mineacademy.fo.MinecraftVersion.V;
import org.mineacademy.fo.ReflectionUtil;
import org.mineacademy.fo.collection.SerializedMap;
import org.mineacademy.fo.exception.CommandException;
import org.mineacademy.fo.model.CompToastStyle;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.remain.CompMaterial;
import org.mineacademy.fo.settings.Lang;
import net.kyori.adventure.bossbar.BossBar;
public final class AnnounceSubCommand extends MainSubCommand {
/**
* The pattern to match key:value pairs in the message
*/
private static final Pattern ANNOUNCE_PARAM_PATTERN = Pattern.compile("\\b[a-zA-Z]+:[a-zA-Z0-9_]+\\b(?!(?=[^<]*>))");
public AnnounceSubCommand() {
super("announce/a/broadcast/bc");
this.setUsage(Lang.component("command-announce-usage"));
this.setDescription(Lang.component("command-announce-description"));
this.setMinArguments(1);
this.setPermission(Permissions.Command.ANNOUNCE_TYPE.substring(0, Permissions.Command.ANNOUNCE_TYPE.length() - 1));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
final List<SimpleComponent> usages = new ArrayList<>();
if (this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + AnnounceType.CHAT.getKey()))
usages.add(Lang.component("command-announce-usage-chat"));
if (this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + AnnounceType.IMAGE.getKey()))
usages.add(Lang.component("command-announce-usage-image"));
if (this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + AnnounceType.TITLE.getKey()))
usages.add(Lang.component("command-announce-usage-title"));
if (this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + AnnounceType.ACTIONBAR.getKey()))
usages.add(Lang.component("command-announce-usage-actionbar"));
if (this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + AnnounceType.BOSSBAR.getKey()))
usages.add(Lang.component("command-announce-usage-bossbar"));
if (MinecraftVersion.atLeast(V.v1_12) && this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + AnnounceType.TOAST.getKey()))
usages.add(Lang.component("command-announce-usage-toast"));
if (usages.isEmpty())
usages.add(Lang.component("command-announce-usage-no-perms"));
usages.add(Lang.component("command-announce-usage-footer"));
return SimpleComponent.join(usages);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
final AnnounceType type = this.findParam(this.args[0]);
this.checkPerm(Permissions.Command.ANNOUNCE_TYPE + type.getKey());
if (type == AnnounceType.IMAGE)
this.checkArgs(3, Lang.component("command-announce-usage-image"));
else
this.checkArgs(2, Lang.component("command-announce-no-type"));
final SerializedMap params = new SerializedMap();
final String message = this.mapParams(type, params, this.joinArgs(1));
if (type != AnnounceType.IMAGE)
this.checkBoolean(!message.isEmpty(), Lang.component("command-announce-empty"));
Announce.send(this.getSender(), type, message, params);
}
/*
* Map chat key:value pairs parameters
*/
private String mapParams(final AnnounceType type, final SerializedMap map, String line) {
final Matcher matcher = ANNOUNCE_PARAM_PATTERN.matcher(line);
if (type == AnnounceType.IMAGE) {
final int height = this.findNumber(2, Lang.component("command-announce-image-lines"));
this.checkBoolean(height >= 2 && height <= 35, Lang.component("command-announce-invalid-image-height", "min", 2, "max", 35));
final File imageFile = FileUtil.getFile("images/" + this.args[1]);
this.checkBoolean(imageFile.exists(), Lang.component("command-announce-invalid-image", "file", imageFile.toPath().toString()));
map.put("height", height);
map.put("imageFile", imageFile.getName());
line = CommonCore.joinRange(3, this.args);
}
while (matcher.find()) {
final String word = matcher.group();
final String[] split = word.split("\\:");
if (split.length != 2)
continue;
final String key = split[0];
Object value = CommonCore.joinRange(1, split);
if (!type.isCompatible())
continue;
if ("server".equals(key)) {
// ok
}
else if (type == AnnounceType.CHAT) {
if ("type".equals(key))
this.checkBoolean("raw".equals(value), Lang.component("command-announce-invalid-raw-type"));
else
this.returnTell(Lang.component("command-invalid-param-short", "param", word));
}
else if (type == AnnounceType.TITLE) {
if ("stay".equals(key) || "fadein".equals(key) || "fadeout".equals(key))
try {
value = Integer.parseInt(value.toString());
} catch (final NumberFormatException ex) {
this.returnTell(Lang.component("command-announce-invalid-time-ticks"));
}
else
this.returnTell(Lang.component("command-invalid-param-short", "param", word));
}
else if (type == AnnounceType.BOSSBAR) {
if ("time".equals(key))
try {
value = Integer.parseInt(value.toString());
} catch (final NumberFormatException ex) {
this.returnTell(Lang.component("command-announce-invalid-time-seconds"));
}
else if ("color".equals(key))
try {
value = ReflectionUtil.lookupEnum(BossBar.Color.class, value.toString());
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-announce-invalid-key",
"key", key,
"value", value,
"available", BossBar.Color.values()));
}
else if ("overlay".equals(key))
try {
value = ReflectionUtil.lookupEnum(BossBar.Overlay.class, value.toString());
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-announce-invalid-key",
"key", key,
"value", value,
"available", BossBar.Overlay.values()));
}
else
this.returnTell(Lang.component("command-invalid-param-short", "param", word));
} else if (type == AnnounceType.TOAST) {
if ("icon".equals(key)) {
final CompMaterial material = CompMaterial.fromString(value.toString());
this.checkNotNull(material, Lang.component("command-invalid-material", "material", key));
value = material;
} else if ("style".equals(key))
try {
value = CompToastStyle.fromKey(value.toString());
} catch (final IllegalArgumentException ex) {
this.returnTell(Lang.component("command-announce-invalid-key",
"key", key,
"value", value,
"available", CompToastStyle.values()));
}
else
this.returnTell(Lang.component("command-invalid-param-short", "param", word));
} else if (type == AnnounceType.IMAGE) {
// ok
} else
this.returnTell(Lang.component("command-invalid-param-short", "param", word));
map.put(key, value);
line = line.replace(word + (line.contains(word + " ") ? " " : ""), "").trim();
}
return line.trim();
}
/*
* Lookup a parameter by its label and automatically return on error
*/
private AnnounceType findParam(final String label) {
for (final AnnounceType param : AnnounceType.values())
if (param.getLabels().contains(label)) {
this.checkBoolean(MinecraftVersion.atLeast(param.getMinimumVersion()), "Sending " + param.getKey() + " messages requires Minecraft " + param.getMinimumVersion() + " or greater.");
return param;
}
this.returnTell(Lang.component("command-invalid-type", "type", "announcement", "value", label, "available", AnnounceType.getAvailableParams()));
return null;
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord(AnnounceType
.getAvailableParams()
.stream()
.filter(type -> this.hasPerm(Permissions.Command.ANNOUNCE_TYPE + type.getKey()))
.collect(Collectors.toList()));
if (this.args.length >= 2) {
final String lastWord = this.args[this.args.length - 1];
AnnounceType param;
try {
param = this.findParam(this.args[0]);
} catch (final CommandException ex) {
// Do not send tab complete error message to player
return NO_COMPLETE;
}
if (!param.isCompatible())
return NO_COMPLETE;
if (lastWord.startsWith("server:"))
return this.completeLastWord(SyncedCache.getServers());
if (param == AnnounceType.TITLE)
return this.completeLastWord("stay:", "fadein:", "fadeout:", "server:");
else if (param == AnnounceType.BOSSBAR) {
if (lastWord.startsWith("color:"))
return this.completeLastWord(CommonCore.convertArrayToList(BossBar.Color.values(), color -> "color:" + color.toString().toLowerCase()));
else if (lastWord.startsWith("overlay:"))
return this.completeLastWord(CommonCore.convertArrayToList(BossBar.Overlay.values(), color -> "overlay:" + color.toString().toLowerCase()));
return this.completeLastWord("time:", "color:", "overlay:", "server:");
}
else if (param == AnnounceType.TOAST) {
if (lastWord.startsWith("icon:"))
return this.completeLastWord(CommonCore.convertArrayToList(CompMaterial.values(), mat -> "icon:" + mat.toString()));
else if (lastWord.startsWith("style:"))
return this.completeLastWord(CommonCore.convertArrayToList(CompToastStyle.values(), mat -> "style:" + mat.toString()));
return this.completeLastWord("icon:", "style:", "server:");
}
else if (param == AnnounceType.ACTIONBAR)
return this.completeLastWord("server:");
else if (param == AnnounceType.IMAGE)
if (this.args.length == 2) {
File file = FileUtil.getFile("images/" + this.args[1]);
String[] files = file.list();
if (files == null) {
final String path = file.toPath().toString();
final int lastDir = path.lastIndexOf('/');
file = new File(lastDir == -1 ? path : path.substring(0, lastDir));
files = file.list();
}
if (file != null)
return this.completeLastWord(file.list());
}
else if (this.args.length == 3)
return this.completeLastWord("6", "10", "20", "server:");
}
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,188 @@
package org.mineacademy.chatcontrol.command.chatcontrol;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.SenderCache;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.MainSubCommand;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.FileUtil;
import org.mineacademy.fo.TimeUtil;
import org.mineacademy.fo.menu.model.ItemCreator;
import org.mineacademy.fo.model.ChatPaginator;
import org.mineacademy.fo.model.SimpleBook;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.platform.Platform;
import org.mineacademy.fo.settings.Lang;
public final class BookSubCommand extends MainSubCommand {
public BookSubCommand() {
super("book");
this.setUsage(Lang.component("command-book-usage"));
this.setDescription(Lang.component("command-book-description"));
this.setMinArguments(1);
this.setPermission(Permissions.Command.BOOK);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-book-usages");
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#onCommand()
*/
@Override
protected void onCommand() {
final String param = this.args[0];
final SenderCache senderCache = SenderCache.from(this.getSender());
if ("new".equals(param)) {
this.checkConsole();
this.checkBoolean(Settings.Mail.ENABLED, Lang.component("command-book-mail-disabled"));
this.audience.dispatchCommand(Settings.Mail.COMMAND_ALIASES.get(0) + " new");
this.tellSuccess(Lang.component("command-book-new"));
}
else if ("save".equals(param)) {
final SimpleBook pendingBook = senderCache.getPendingMail();
this.checkNotNull(pendingBook, Lang.component("command-book-save-no-book"));
this.checkBoolean(pendingBook.isSigned(), Lang.component("command-book-save-not-signed"));
this.checkArgs(2, Lang.component("command-book-save-no-name"));
this.checkUsage(this.args.length == 2);
try {
pendingBook.save(this.args[1]);
senderCache.setPendingMail(null);
this.tellSuccess(Lang.component("command-book-save", "name", this.args[1]));
} catch (final IOException ex) {
ex.printStackTrace();
this.tellError(Lang.component("command-book-save-error", "name", this.args[1], "error", ex.toString()));
}
return;
}
else if ("delete".equals(param)) {
this.checkArgs(2, Lang.component("command-book-no-book", "available", SimpleBook.getBookNames()));
final String bookName = this.args[1];
final File bookFile = FileUtil.getFile("books/" + bookName + (bookName.endsWith(".yml") ? "" : ".yml"));
this.checkBoolean(bookFile.exists(), Lang.component("command-book-invalid", "name", bookName, "available", SimpleBook.getBookNames()));
final boolean success = bookFile.delete();
if (success)
this.tellSuccess(Lang.component("command-book-delete", "name", bookName));
else
this.tellError(Lang.component("command-book-delete-fail", "name", bookName));
}
else if ("open".equals(param) || "give".equals(param) || "load".equals(param)) {
this.checkArgs(2, Lang.component("command-book-no-book", "available", SimpleBook.getBookNames()));
final SimpleBook book;
try {
book = SimpleBook.fromFile(this.args[1]);
} catch (final IllegalArgumentException ex) {
this.returnTell(ex.getMessage());
return;
}
final Player player = this.findPlayerOrSelf(this.args.length == 3 ? this.args[2] : null);
final boolean self = player.getName().equals(this.audience.getName());
if ("open".equals(param)) {
book.openColorized(Platform.toPlayer(player));
if (!self)
this.tell(Lang.component("command-book-open", "player", player.getName()));
}
else if ("give".equals(param)) {
ItemCreator.fromBookColorized(book, false).give(player);
this.tell(Lang.component("command-book-give", "player", player.getName()));
}
else if ("load".equals(param)) {
senderCache.setPendingMail(book);
this.tell(Lang.component("command-book-loaded",
"name", book.getFileName().replace(".yml", ""),
"label", Settings.Mail.COMMAND_ALIASES.get(0)));
}
}
else if ("list".equals(param)) {
final List<SimpleComponent> pages = new ArrayList<>();
for (final SimpleBook book : SimpleBook.getBooks())
pages.add(SimpleComponent.empty()
.append(Lang.component("command-book-list-open"))
.onHover(Lang.component("command-book-list-open-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " " + this.getSublabel() + " open " + book.getFileName() + " " + this.audience.getName())
.appendPlain(" ")
.append(Lang.component("command-book-list-delete"))
.onHover(Lang.component("command-book-list-delete-tooltip"))
.onClickRunCmd("/" + this.getLabel() + " " + this.getSublabel() + " delete " + book.getFileName())
.append(Lang.component("command-book-list",
"title", book.getTitle(),
"author", book.getAuthor(),
"date", TimeUtil.getFormattedDateMonth(book.getLastModified())))
.onHover(Lang.component("command-book-list-tooltip", "name", book.getFileName()))
);
this.checkBoolean(!pages.isEmpty(), Lang.component("command-book-list-none"));
new ChatPaginator()
.setFoundationHeader(Lang.legacy("command-book-list-header"))
.setPages(pages)
.send(this.audience);
} else
this.returnInvalidArgs(param);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
if (this.args.length == 1)
return this.completeLastWord("new", "save", "delete", "open", "give", "list", "load");
if (this.args.length >= 2 && ("save".equals(this.args[0]) || "delete".equals(this.args[0]) || "open".equals(this.args[0]) || "give".equals(this.args[0]) || "load".equals(this.args[0])))
if (this.args.length == 2)
return this.completeLastWord(SimpleBook.getBookNames());
else if (this.args.length == 3 && ("open".equals(this.args[0]) || "give".equals(this.args[0])))
return this.completeLastWordPlayerNames();
return NO_COMPLETE;
}
}

View File

@ -0,0 +1,187 @@
package org.mineacademy.chatcontrol.command.chatcontrol;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.entity.Player;
import org.mineacademy.chatcontrol.command.SharedChatControlCommandCore;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.annotation.AutoRegister;
import org.mineacademy.fo.command.PermsSubCommand;
import org.mineacademy.fo.command.SimpleCommand;
import org.mineacademy.fo.command.SimpleCommandGroup;
import org.mineacademy.fo.command.SimpleSubCommand;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.platform.BukkitPlugin;
import org.mineacademy.fo.settings.Lang;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* Holds all /chc main commands
*/
@AutoRegister
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ChatControlCommands extends SimpleCommandGroup {
/**
* The singleton of this class
*/
@Getter
private final static SimpleCommandGroup instance = new ChatControlCommands();
/**
* @see org.mineacademy.fo.command.SimpleCommandGroup#getHeaderPrefix()
*/
@Override
protected String getHeaderPrefix() {
return "<bold><gradient:#db0000:#fb00ff>";
}
/**
* @see org.mineacademy.fo.command.SimpleCommandGroup#registerSubcommands()
*/
@Override
protected void registerSubcommands() {
this.registerSubcommand(MainSubCommand.class);
this.registerSubcommand(new PermsSubCommand(Permissions.class));
this.registerDefaultSubcommands();
}
// ------------------------------------------------------------------------------------------------------------
// Classes
// ------------------------------------------------------------------------------------------------------------
/**
* Represents the core for a bunch of other commands having same flags
* such as -proxy -silent etc.
*/
public static abstract class CommandFlagged extends MainSubCommand {
protected CommandFlagged(final String label, final SimpleComponent usage, final SimpleComponent description) {
super(label);
this.setUsage(usage);
this.setDescription(description);
}
@Override
public final void onCommand() {
final List<String> params = Arrays.asList(this.args);
final boolean console = params.contains("-console") || params.contains("-c");
final boolean silent = params.contains("-silent") || params.contains("-s");
final boolean anonymous = params.contains("-anonymous") || params.contains("-a");
final boolean force = params.contains("-force") || params.contains("-f");
final String reason = String.join(" ", params.stream().filter(param -> param.charAt(0) != '-').collect(Collectors.toList()));
if (console && (silent || anonymous || force))
this.returnTell(Lang.component("command-clear-no-arguments-while-console"));
if (!console && !silent && !anonymous && !force && !params.isEmpty() && reason.isEmpty())
this.returnInvalidArgs(this.joinArgs(1));
if (silent && !reason.isEmpty())
this.returnTell(Lang.component("command-no-reason-while-silent"));
if (silent && anonymous)
this.returnTell(Lang.component("command-no-silent-and-anonymous"));
this.execute(console, anonymous, silent, force, reason);
}
/**
* Execute this command
*
* @param console
* @param anonymous
* @param silent
* @param forced
*
* @param reason
*/
protected abstract void execute(boolean console, boolean anonymous, boolean silent, boolean forced, String reason);
/**
* @see org.mineacademy.fo.command.SimpleCommand#tabComplete()
*/
@Override
protected List<String> tabComplete() {
return NO_COMPLETE;
}
}
/**
* Represents the foundation for plugin commands
*/
public static abstract class ChatControlCommand extends SimpleCommand implements SharedChatControlCommandCore {
protected ChatControlCommand(final String label) {
super(label);
}
protected ChatControlCommand(final List<String> labels) {
super(labels);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#completeLastWordPlayerNames()
*/
@Override
protected final List<String> completeLastWordPlayerNames() {
return CommonCore.tabComplete(this.getLastArg(), Players.getPlayerNamesForTabComplete(this.getSender()));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#findPlayerInternal(java.lang.String)
*/
@Override
public final Player findPlayerInternal(final String name) {
return Players.findPlayer(name);
}
}
/**
* Represents the foundation for plugin commands
* used for /channel and /chc subcommands
*/
public static abstract class MainSubCommand extends GenericSubCommand {
protected MainSubCommand(final String sublabel) {
super(BukkitPlugin.getInstance().getDefaultCommandGroup(), sublabel);
}
}
/**
* Represents the foundation for plugin commands
* used for /channel and /chc subcommands
*/
public static abstract class GenericSubCommand extends SimpleSubCommand implements SharedChatControlCommandCore {
protected GenericSubCommand(final SimpleCommandGroup group, final String sublabel) {
super(group, sublabel);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#completeLastWordPlayerNames()
*/
@Override
protected final List<String> completeLastWordPlayerNames() {
return CommonCore.tabComplete(this.getLastArg(), Players.getPlayerNamesForTabComplete(this.getSender()));
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#findPlayerInternal(java.lang.String)
*/
@Override
public final Player findPlayerInternal(final String name) {
return Players.findPlayer(name);
}
}
}

View File

@ -0,0 +1,87 @@
package org.mineacademy.chatcontrol.command.chatcontrol;
import java.util.List;
import org.mineacademy.chatcontrol.command.chatcontrol.ChatControlCommands.CommandFlagged;
import org.mineacademy.chatcontrol.model.ChatControlProxyMessage;
import org.mineacademy.chatcontrol.model.Permissions;
import org.mineacademy.chatcontrol.model.Players;
import org.mineacademy.chatcontrol.settings.Settings;
import org.mineacademy.fo.CommonCore;
import org.mineacademy.fo.Messenger;
import org.mineacademy.fo.ProxyUtil;
import org.mineacademy.fo.model.SimpleComponent;
import org.mineacademy.fo.model.Variables;
import org.mineacademy.fo.settings.Lang;
public final class ClearSubCommand extends CommandFlagged {
public ClearSubCommand() {
super("clear/cl", Lang.component("command-clear-usage"), Lang.component("command-clear-description"));
this.setPermission(Permissions.Command.CLEAR);
}
/**
* @see org.mineacademy.fo.command.SimpleCommand#getMultilineUsageMessage()
*/
@Override
protected SimpleComponent getMultilineUsage() {
return Lang.component("command-clear-usages");
}
@Override
protected void execute(final boolean console, final boolean anonymous, final boolean silent, final boolean force, final String reason) {
// Compile message
final SimpleComponent announceMessage;
if (silent)
announceMessage = SimpleComponent.empty();
else {
announceMessage = Lang.component(
"command-clear-success" + (Settings.Proxy.ENABLED ? "-network" : "") + (anonymous ? "-anonymous" : "") + (reason.isEmpty() ? "-no-reason" : ""),
"player", this.audience.getName(),
"reason", reason);
}
// Do the actual clear
if (console)
for (int i = 0; i < 5000; i++)
System.out.println(" ");
else
Players.clearChat(this.getSender(), announceMessage.isEmpty(), force);
if (console) {
this.checkPerm(Permissions.Command.CLEAR_CONSOLE);
final SimpleComponent message = Lang.component("command-clear-success-console" + (reason.isEmpty() ? "-no-reason" : ""),
"reason", reason,