i18n: per-player EN/PT via Adventure GlobalTranslator #1
@@ -25,7 +25,7 @@ All player-facing text is Portuguese (pt-BR).
|
|||||||
| `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. |
|
| `ranking` | `/ranking mineracao` and friends. Covers **offline players too**. |
|
||||||
| `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. |
|
| `marcos` | Announces round milestones — 100 km walked, 24 hours played — the first time someone crosses one. |
|
||||||
| `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player on respawn (the death screen swallows chat sent during the event), so they can run back to their dropped items. Respects `keepInventory`. No storage, no command. |
|
| `mortes` | Replaces each death message with a comic pt-BR line (cause-based flavor + a death counter) and sends the death coordinates **privately** to the dead player on respawn (the death screen swallows chat sent during the event), so they can run back to their dropped items. Respects `keepInventory`. No storage, no command. |
|
||||||
| `zoacao` | A bare `f`/`F` in chat (trimmed, nothing else) is swapped for a random line from `zoacao.mensagens` — a chat gag. The player's name still prefixes it. Pure chat swap; the `luto` tribute is unaffected (paying respects still needs the `[F]` button or `/f`). Affects Bedrock chat too (it's a chat event, not a click). |
|
| `zoacao` | A chat message matching the trigger (a pattern + a match mode) is swapped for a random line from `zoacao.mensagens` — a chat gag. The player's name still prefixes it. Default: a bare `f`/`F` (trimmed) → a random gag line. Match modes: `igual` (equals), `contem` (contains), `comeca` (starts with), `termina` (ends with), `regex`. The mode, the pattern, and the message list are all editable in-game with `/canalhandia zoacao ...`. Pure chat swap; the `luto` tribute is unaffected (paying respects still needs the `[F]` button or `/f`). Affects Bedrock chat too (it's a chat event, not a click). |
|
||||||
| `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Optional wiki grounding, per-player memory, operator corrections. |
|
| `ia` | `/ia <pergunta>` asks an OpenAI-compatible model in chat. Optional wiki grounding, per-player memory, operator corrections. |
|
||||||
|
|
||||||
Toggle any of them: `/canalhandia modulo <nome> <on|off>`
|
Toggle any of them: `/canalhandia modulo <nome> <on|off>`
|
||||||
@@ -164,6 +164,12 @@ Admin (`canalhandia.admin`):
|
|||||||
/canalhandia modulo <nome> <on|off> liga/desliga um módulo
|
/canalhandia modulo <nome> <on|off> liga/desliga um módulo
|
||||||
/canalhandia marcos força uma verificação de marcos
|
/canalhandia marcos força uma verificação de marcos
|
||||||
/canalhandia limpar [cooldown|historico|tudo]
|
/canalhandia limpar [cooldown|historico|tudo]
|
||||||
|
/canalhandia zoacao listar mostra a regra e as frases da zoação
|
||||||
|
/canalhandia zoacao modo <m> igual | contem | comeca | termina | regex
|
||||||
|
/canalhandia zoacao padrao <texto> texto/regex que dispara a zoação
|
||||||
|
/canalhandia zoacao adicionar <frase> adiciona uma frase de zoação
|
||||||
|
/canalhandia zoacao remover <n|texto> remove uma frase de zoação
|
||||||
|
/canalhandia zoacao limpar volta para as frases padrão
|
||||||
/canalhandia reload
|
/canalhandia reload
|
||||||
/curiosidade modo <entrada|intervalo|ambos|manual>
|
/curiosidade modo <entrada|intervalo|ambos|manual>
|
||||||
/curiosidade intervalo <min> intervalo do modo temporizado
|
/curiosidade intervalo <min> intervalo do modo temporizado
|
||||||
|
|||||||
@@ -480,17 +480,20 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat gag: a bare "f" (or "F", trimmed, nothing else) gets replaced with a
|
* Chat gag: a message matching the {@code zoacao} trigger (pattern + match
|
||||||
* random line from {@code zoacao.mensagens}. Pure chat swap — the player's
|
* mode, default a bare "f") gets replaced with a random line from
|
||||||
* name still prefixes it as normal. Independent of {@code luto}: paying
|
* {@code zoacao.mensagens}. Pure chat swap — the player's name still
|
||||||
* respects still needs the {@code [F]} button (Java) or {@code /f} command.
|
* prefixes it as normal. Independent of {@code luto}: paying respects still
|
||||||
|
* needs the {@code [F]} button (Java) or {@code /f} command. Editable
|
||||||
|
* in-game via {@code /canalhandia zoacao ...}.
|
||||||
*/
|
*/
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onChatF(AsyncPlayerChatEvent event) {
|
public void onChatF(AsyncPlayerChatEvent event) {
|
||||||
if (!settings.moduleEnabled(Module.ZOACAO)) {
|
if (!settings.moduleEnabled(Module.ZOACAO)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String gag = Zoacao.replace(event.getMessage(), settings.zoacaoMessages(), random);
|
String gag = Zoacao.replace(event.getMessage(), settings.zoacaoMode(),
|
||||||
|
settings.zoacaoPattern(), settings.zoacaoMessages(), random);
|
||||||
if (gag != null) {
|
if (gag != null) {
|
||||||
event.setMessage(gag);
|
event.setMessage(gag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "limpar" -> clear(sender, rest);
|
case "limpar" -> clear(sender, rest);
|
||||||
|
case "zoacao" -> zoacaoEdit(sender, rest);
|
||||||
case "reload" -> {
|
case "reload" -> {
|
||||||
if (admin(sender)) {
|
if (admin(sender)) {
|
||||||
plugin.reloadConfig();
|
plugin.reloadConfig();
|
||||||
@@ -619,6 +620,97 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-game editing of the {@code zoacao} chat gag: the match mode, the
|
||||||
|
* trigger pattern, and the replacement message list. Admin-only; every
|
||||||
|
* change writes through to config.yml immediately.
|
||||||
|
*/
|
||||||
|
private void zoacaoEdit(CommandSender sender, String[] args) {
|
||||||
|
if (!admin(sender)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Settings settings = plugin.settings();
|
||||||
|
String sub = args.length > 0 ? args[0].toLowerCase(Locale.ROOT) : "listar";
|
||||||
|
switch (sub) {
|
||||||
|
case "listar", "status" -> {
|
||||||
|
Msg.header(sender, "Zoação (gag de chat)");
|
||||||
|
Msg.line(sender, "correspondência", settings.zoacaoMode().key()
|
||||||
|
+ " (igual | contem | comeca | termina | regex)");
|
||||||
|
Msg.line(sender, "padrão", settings.zoacaoPattern());
|
||||||
|
List<String> messages = settings.zoacaoMessages();
|
||||||
|
Msg.line(sender, "mensagens", "(" + messages.size() + ")");
|
||||||
|
for (int i = 0; i < messages.size(); i++) {
|
||||||
|
sender.sendMessage(Component.text(" " + (i + 1) + ". ", NamedTextColor.DARK_AQUA)
|
||||||
|
.append(Component.text(messages.get(i), NamedTextColor.WHITE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "modo" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
Msg.error(sender, "Uso: /canalhandia zoacao modo <igual|contem|comeca|termina|regex>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Zoacao.Mode mode = Zoacao.Mode.byKey(args[1]);
|
||||||
|
if (mode == null) {
|
||||||
|
Msg.error(sender, "Modo inválido. Use: igual, contem, comeca, termina, regex.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings.zoacaoMode(mode);
|
||||||
|
Msg.ok(sender, "Correspondência da zoação: " + mode.key() + ".");
|
||||||
|
}
|
||||||
|
case "padrao" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
Msg.error(sender, "Uso: /canalhandia zoacao padrao <texto> (atual: " + settings.zoacaoPattern() + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String pattern = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||||
|
settings.zoacaoPattern(pattern);
|
||||||
|
Msg.ok(sender, "Padrão da zoação: " + pattern + ".");
|
||||||
|
}
|
||||||
|
case "adicionar", "add" -> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
Msg.error(sender, "Uso: /canalhandia zoacao adicionar <frase>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String line = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||||
|
List<String> messages = new ArrayList<>(settings.zoacaoMessages());
|
||||||
|
messages.add(line);
|
||||||
|
settings.zoacaoMessages(messages);
|
||||||
|
Msg.ok(sender, "Adicionado: " + line + " (agora " + messages.size() + " mensagens).");
|
||||||
|
}
|
||||||
|
case "remover", "remove" -> {
|
||||||
|
List<String> messages = new ArrayList<>(settings.zoacaoMessages());
|
||||||
|
if (args.length < 2) {
|
||||||
|
Msg.error(sender, "Uso: /canalhandia zoacao remover <número|texto>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String target = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
|
||||||
|
int index = parse(target, Integer.MIN_VALUE);
|
||||||
|
boolean removed;
|
||||||
|
if (index != Integer.MIN_VALUE) {
|
||||||
|
int idx = index - 1;
|
||||||
|
removed = idx >= 0 && idx < messages.size();
|
||||||
|
if (removed) {
|
||||||
|
messages.remove(idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removed = messages.removeIf(m -> m.equalsIgnoreCase(target));
|
||||||
|
}
|
||||||
|
if (!removed) {
|
||||||
|
Msg.error(sender, "Não encontrei '" + target + "' na lista.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings.zoacaoMessages(messages);
|
||||||
|
Msg.ok(sender, "Removido. Restam " + messages.size() + " mensagens.");
|
||||||
|
}
|
||||||
|
case "limpar" -> {
|
||||||
|
// Clear the configured list so the built-in defaults come back.
|
||||||
|
settings.zoacaoMessages(List.of());
|
||||||
|
Msg.ok(sender, "Lista limpa — voltou para as mensagens padrão.");
|
||||||
|
}
|
||||||
|
default -> Msg.error(sender, "Uso: /canalhandia zoacao <listar|modo|padrao|adicionar|remover|limpar>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void clear(CommandSender sender, String[] args) {
|
private void clear(CommandSender sender, String[] args) {
|
||||||
if (!admin(sender)) {
|
if (!admin(sender)) {
|
||||||
return;
|
return;
|
||||||
@@ -666,6 +758,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg.line(sender, "categorias ativas", enabled.isEmpty() ? "nenhuma" : String.join(", ", enabled));
|
Msg.line(sender, "categorias ativas", enabled.isEmpty() ? "nenhuma" : String.join(", ", enabled));
|
||||||
|
Msg.line(sender, "zoação", settings.zoacaoMode().key() + " '" + settings.zoacaoPattern()
|
||||||
|
+ "' (" + settings.zoacaoMessages().size() + " mensagens)");
|
||||||
Msg.line(sender, "ia", plugin.ai().configured()
|
Msg.line(sender, "ia", plugin.ai().configured()
|
||||||
? settings.aiModel() + " · perfil " + settings.aiProfile().name().toLowerCase(Locale.ROOT)
|
? settings.aiModel() + " · perfil " + settings.aiProfile().name().toLowerCase(Locale.ROOT)
|
||||||
+ " · " + plugin.ai().askedToday() + "/" + settings.aiDailyLimit() + " hoje"
|
+ " · " + plugin.ai().askedToday() + "/" + settings.aiDailyLimit() + " hoje"
|
||||||
@@ -724,6 +818,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
commands.put("/curiosidade reacao add <chave> <rótulo>", "cria ou muda uma reação");
|
commands.put("/curiosidade reacao add <chave> <rótulo>", "cria ou muda uma reação");
|
||||||
commands.put("/curiosidade reacao remover <chave>", "remove uma reação");
|
commands.put("/curiosidade reacao remover <chave>", "remove uma reação");
|
||||||
commands.put("/curiosidade categoria <nome> <on|off>", "liga/desliga uma categoria");
|
commands.put("/curiosidade categoria <nome> <on|off>", "liga/desliga uma categoria");
|
||||||
|
commands.put("/canalhandia zoacao listar", "mostra a regra e as frases da zoação");
|
||||||
|
commands.put("/canalhandia zoacao modo <m>", "igual | contem | comeca | termina | regex");
|
||||||
|
commands.put("/canalhandia zoacao padrao <texto>", "texto/regex que dispara a zoação");
|
||||||
|
commands.put("/canalhandia zoacao adicionar <frase>", "adiciona uma frase de zoação");
|
||||||
|
commands.put("/canalhandia zoacao remover <n|texto>", "remove uma frase de zoação");
|
||||||
|
commands.put("/canalhandia zoacao limpar", "volta para as frases padrão");
|
||||||
}
|
}
|
||||||
commands.forEach((cmd, description) -> sender.sendMessage(
|
commands.forEach((cmd, description) -> sender.sendMessage(
|
||||||
Component.text(" " + cmd, NamedTextColor.AQUA)
|
Component.text(" " + cmd, NamedTextColor.AQUA)
|
||||||
@@ -893,7 +993,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
List<String> options = new ArrayList<>(List.of("status", "modulos", "plataformas"));
|
List<String> options = new ArrayList<>(List.of("status", "modulos", "plataformas"));
|
||||||
if (sender.hasPermission(ADMIN)) {
|
if (sender.hasPermission(ADMIN)) {
|
||||||
options.addAll(List.of("modulo", "marcos", "limpar", "reload"));
|
options.addAll(List.of("modulo", "marcos", "limpar", "zoacao", "reload"));
|
||||||
}
|
}
|
||||||
return filter(options, args[0]);
|
return filter(options, args[0]);
|
||||||
}
|
}
|
||||||
@@ -907,6 +1007,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
if (args.length == 2 && args[0].equalsIgnoreCase("limpar")) {
|
if (args.length == 2 && args[0].equalsIgnoreCase("limpar")) {
|
||||||
return filter(List.of("cooldown", "historico", "tudo"), args[1]);
|
return filter(List.of("cooldown", "historico", "tudo"), args[1]);
|
||||||
}
|
}
|
||||||
|
if (args.length == 2 && args[0].equalsIgnoreCase("zoacao")) {
|
||||||
|
return filter(List.of("listar", "modo", "padrao", "adicionar", "remover", "limpar"), args[1]);
|
||||||
|
}
|
||||||
|
if (args.length == 3 && args[0].equalsIgnoreCase("zoacao") && args[1].equalsIgnoreCase("modo")) {
|
||||||
|
return filter(List.of("igual", "contem", "comeca", "termina", "regex"), args[2]);
|
||||||
|
}
|
||||||
if (args.length == 3 && args[0].equalsIgnoreCase("modulo")) {
|
if (args.length == 3 && args[0].equalsIgnoreCase("modulo")) {
|
||||||
return filter(List.of("on", "off"), args[2]);
|
return filter(List.of("on", "off"), args[2]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,9 +347,9 @@ final class Settings {
|
|||||||
// --- zoacao (f-gag) -----------------------------------------------------
|
// --- zoacao (f-gag) -----------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lines a bare "f" in chat gets replaced with, picked at random. Defaults
|
* Lines a matching chat message gets replaced with, picked at random.
|
||||||
* to a small built-in list if unset/empty so the feature works out of the
|
* Defaults to a small built-in list if unset/empty so the feature works out
|
||||||
* box; operators edit {@code zoacao.mensagens} in config.yml to customise.
|
* of the box; operators edit {@code zoacao.mensagens} in-game to customise.
|
||||||
*/
|
*/
|
||||||
List<String> zoacaoMessages() {
|
List<String> zoacaoMessages() {
|
||||||
List<String> messages = plugin.getConfig().getStringList("zoacao.mensagens");
|
List<String> messages = plugin.getConfig().getStringList("zoacao.mensagens");
|
||||||
@@ -365,6 +365,31 @@ final class Settings {
|
|||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Writes the full message list through to config immediately. */
|
||||||
|
void zoacaoMessages(List<String> messages) {
|
||||||
|
plugin.getConfig().set("zoacao.mensagens", messages);
|
||||||
|
plugin.saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** How a chat message is tested against the trigger pattern. */
|
||||||
|
Zoacao.Mode zoacaoMode() {
|
||||||
|
return Zoacao.Mode.byKeyOrDefault(plugin.getConfig().getString("zoacao.correspondencia", "igual"),
|
||||||
|
Zoacao.Mode.IGUAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void zoacaoMode(Zoacao.Mode mode) {
|
||||||
|
set("zoacao.correspondencia", mode.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The trigger text (or regex for the {@code regex} mode). */
|
||||||
|
String zoacaoPattern() {
|
||||||
|
return plugin.getConfig().getString("zoacao.padrao", "f");
|
||||||
|
}
|
||||||
|
|
||||||
|
void zoacaoPattern(String pattern) {
|
||||||
|
set("zoacao.padrao", pattern);
|
||||||
|
}
|
||||||
|
|
||||||
// --- content ------------------------------------------------------------
|
// --- content ------------------------------------------------------------
|
||||||
|
|
||||||
boolean categoryEnabled(Category category) {
|
boolean categoryEnabled(Category category) {
|
||||||
|
|||||||
@@ -1,37 +1,110 @@
|
|||||||
package dev.marcospaulo.canalhandia;
|
package dev.marcospaulo.canalhandia;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pure logic for the {@code zoacao} chat gag: when a player sends a bare
|
* Pure logic for the {@code zoacao} chat gag: when a player's chat message
|
||||||
* {@code "f"} (or {@code "F"}, trimmed, nothing else) their message is swapped
|
* matches a configurable trigger (a pattern + a match mode), it is swapped
|
||||||
* for a random line from a configurable list. Extracted so the trigger rule is
|
* for a random line from a configurable list. Extracted so the match rule is
|
||||||
* unit-testable without a server.
|
* unit-testable without a server.
|
||||||
*
|
*
|
||||||
|
* <p>Match modes:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link Mode#IGUAL} — message equals the pattern (ignoring case, trimmed)</li>
|
||||||
|
* <li>{@link Mode#CONTEM} — message contains the pattern (case-insensitive)</li>
|
||||||
|
* <li>{@link Mode#COMECA} — message starts with the pattern</li>
|
||||||
|
* <li>{@link Mode#TERMINA} — message ends with the pattern</li>
|
||||||
|
* <li>{@link Mode#REGEX} — pattern is a case-insensitive regex, matched anywhere</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* <p>This is a standalone chat gag. It does not interact with the {@code luto}
|
* <p>This is a standalone chat gag. It does not interact with the {@code luto}
|
||||||
* tribute — paying respects still happens via the {@code [F]} button (Java) or
|
* tribute — paying respects still happens via the {@code [F]} button (Java) or
|
||||||
* the {@code /f} command (Bedrock), neither of which is a chat message.
|
* the {@code /f} command (Bedrock), neither of which is a chat message.
|
||||||
*/
|
*/
|
||||||
final class Zoacao {
|
final class Zoacao {
|
||||||
|
|
||||||
|
/** How a chat message is tested against the trigger pattern. */
|
||||||
|
enum Mode {
|
||||||
|
IGUAL("igual"),
|
||||||
|
CONTEM("contem"),
|
||||||
|
COMECA("comeca"),
|
||||||
|
TERMINA("termina"),
|
||||||
|
REGEX("regex");
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
Mode(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Mode byKey(String key) {
|
||||||
|
for (Mode mode : values()) {
|
||||||
|
if (mode.key.equalsIgnoreCase(key)) {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Mode byKeyOrDefault(String key, Mode fallback) {
|
||||||
|
Mode mode = byKey(key);
|
||||||
|
return mode == null ? fallback : mode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Zoacao() {
|
private Zoacao() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message the chat message as sent by the player
|
* @param message the chat message as sent by the player
|
||||||
|
* @param mode how to test the message against the pattern
|
||||||
|
* @param pattern the trigger text (or regex for {@link Mode#REGEX})
|
||||||
* @param gags the configured replacement lines; if null/empty, no gag
|
* @param gags the configured replacement lines; if null/empty, no gag
|
||||||
* @param random shared random used to pick a line
|
* @param random shared random used to pick a line
|
||||||
* @return the replacement line if the message is a bare "f", otherwise null
|
* @return the replacement line if the message matches, otherwise null
|
||||||
* (meaning "leave the message alone")
|
* (meaning "leave the message alone")
|
||||||
*/
|
*/
|
||||||
static String replace(String message, List<String> gags, Random random) {
|
static String replace(String message, Mode mode, String pattern, List<String> gags, Random random) {
|
||||||
if (message == null || gags == null || gags.isEmpty()) {
|
if (message == null || gags == null || gags.isEmpty() || pattern == null || pattern.isBlank()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!message.trim().equalsIgnoreCase("f")) {
|
if (!matches(message, mode, pattern)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return gags.get(random.nextInt(gags.size()));
|
return gags.get(random.nextInt(gags.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pure test of one message against one pattern under one mode. */
|
||||||
|
static boolean matches(String message, Mode mode, String pattern) {
|
||||||
|
if (message == null || mode == null || pattern == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String trimmed = message.trim();
|
||||||
|
String needle = pattern.toLowerCase(Locale.ROOT);
|
||||||
|
switch (mode) {
|
||||||
|
case IGUAL:
|
||||||
|
return trimmed.equalsIgnoreCase(pattern);
|
||||||
|
case CONTEM:
|
||||||
|
return trimmed.toLowerCase(Locale.ROOT).contains(needle);
|
||||||
|
case COMECA:
|
||||||
|
return trimmed.toLowerCase(Locale.ROOT).startsWith(needle);
|
||||||
|
case TERMINA:
|
||||||
|
return trimmed.toLowerCase(Locale.ROOT).endsWith(needle);
|
||||||
|
case REGEX:
|
||||||
|
try {
|
||||||
|
return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(trimmed).find();
|
||||||
|
} catch (java.util.regex.PatternSyntaxException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -71,9 +71,20 @@ janela-reacao-segundos: 90
|
|||||||
luto:
|
luto:
|
||||||
cabeca: true
|
cabeca: true
|
||||||
|
|
||||||
# Frases que substituem um "f" sozinho no chat (módulo zoacao). Uma é sorteada
|
# Zoa de quem manda uma mensagem que bate com o padrão, trocando por uma frase
|
||||||
# por mensagem. Edite à vontade — a graça é ser inesperado.
|
# engraçada. Tudo editável em jogo com /canalhandia zoacao ...
|
||||||
zoacao:
|
zoacao:
|
||||||
|
# Como comparar a mensagem do chat com o padrão:
|
||||||
|
# igual - mensagem inteira igual ao padrão (ignora maiúsculas e espaços)
|
||||||
|
# contem - mensagem contém o padrão em qualquer lugar
|
||||||
|
# comeca - mensagem começa com o padrão
|
||||||
|
# termina - mensagem termina com o padrão
|
||||||
|
# regex - padrão é uma expressão regular (maiúsculas ignoradas)
|
||||||
|
correspondencia: igual
|
||||||
|
# Texto (ou regex) que dispara a zoação. Padrão: só um "f" sozinho.
|
||||||
|
padrao: "f"
|
||||||
|
# Frases que substituem a mensagem. Uma é sorteada por vez. Edite à vontade —
|
||||||
|
# a graça é ser inesperado.
|
||||||
mensagens:
|
mensagens:
|
||||||
- "Sou gay"
|
- "Sou gay"
|
||||||
- "Gosto de anime"
|
- "Gosto de anime"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dev.marcospaulo.canalhandia;
|
package dev.marcospaulo.canalhandia;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@@ -13,48 +14,129 @@ class ZoacaoTest {
|
|||||||
private static final List<String> GAGS = List.of(
|
private static final List<String> GAGS = List.of(
|
||||||
"Sou gay", "Gosto de anime", "Jogo no celular");
|
"Sou gay", "Gosto de anime", "Jogo no celular");
|
||||||
|
|
||||||
|
// --- Mode.byKey ---------------------------------------------------------
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void bareLowercaseFIsReplacedWithAGagFromTheList() {
|
void modeByKeyParsesEachMode() {
|
||||||
String gag = Zoacao.replace("f", GAGS, new Random(0L));
|
assertEquals(Zoacao.Mode.IGUAL, Zoacao.Mode.byKey("igual"));
|
||||||
|
assertEquals(Zoacao.Mode.CONTEM, Zoacao.Mode.byKey("contem"));
|
||||||
|
assertEquals(Zoacao.Mode.COMECA, Zoacao.Mode.byKey("comeca"));
|
||||||
|
assertEquals(Zoacao.Mode.TERMINA, Zoacao.Mode.byKey("termina"));
|
||||||
|
assertEquals(Zoacao.Mode.REGEX, Zoacao.Mode.byKey("regex"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void modeByKeyIsCaseInsensitive() {
|
||||||
|
assertEquals(Zoacao.Mode.CONTEM, Zoacao.Mode.byKey("CONTEM"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void modeByKeyReturnsNullForUnknown() {
|
||||||
|
assertNull(Zoacao.Mode.byKey("exato"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void modeByKeyOrDefaultFallsBack() {
|
||||||
|
assertEquals(Zoacao.Mode.IGUAL, Zoacao.Mode.byKeyOrDefault("xx", Zoacao.Mode.IGUAL));
|
||||||
|
assertEquals(Zoacao.Mode.CONTEM, Zoacao.Mode.byKeyOrDefault("contem", Zoacao.Mode.IGUAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- matches / replace: IGUAL ------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void igualBareLowercaseFMatches() {
|
||||||
|
assertTrue(Zoacao.matches("f", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void igualBareUppercaseFMatches() {
|
||||||
|
assertTrue(Zoacao.matches("F", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void igualSurroundingWhitespaceStillMatches() {
|
||||||
|
assertTrue(Zoacao.matches(" f ", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
assertTrue(Zoacao.matches("\tF\n", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void igualFWithAnythingElseDoesNotMatch() {
|
||||||
|
assertFalse(Zoacao.matches("f lol", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
assertFalse(Zoacao.matches("ff", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
assertFalse(Zoacao.matches("pra você f", Zoacao.Mode.IGUAL, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- matches: CONTEM / COMECA / TERMINA ---------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contemMatchesAnywhere() {
|
||||||
|
assertTrue(Zoacao.matches("aaaffffaaa", Zoacao.Mode.CONTEM, "fff"));
|
||||||
|
assertTrue(Zoacao.matches("morte do f cara", Zoacao.Mode.CONTEM, "f"));
|
||||||
|
assertFalse(Zoacao.matches("oi", Zoacao.Mode.CONTEM, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void comecaMatchesAtStart() {
|
||||||
|
assertTrue(Zoacao.matches("f para o morto", Zoacao.Mode.COMECA, "f"));
|
||||||
|
assertTrue(Zoacao.matches("FFFreak", Zoacao.Mode.COMECA, "f"));
|
||||||
|
assertFalse(Zoacao.matches("oi f", Zoacao.Mode.COMECA, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void terminaMatchesAtEnd() {
|
||||||
|
assertTrue(Zoacao.matches("press f", Zoacao.Mode.TERMINA, "f"));
|
||||||
|
assertTrue(Zoacao.matches("mais F", Zoacao.Mode.TERMINA, "f"));
|
||||||
|
assertFalse(Zoacao.matches("f oi", Zoacao.Mode.TERMINA, "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- matches: REGEX -----------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void regexMatchesAnywhereCaseInsensitive() {
|
||||||
|
assertTrue(Zoacao.matches("drop f na fogueira", Zoacao.Mode.REGEX, "\\bf\\b"));
|
||||||
|
assertTrue(Zoacao.matches("FFFFFFFF", Zoacao.Mode.REGEX, "f+"));
|
||||||
|
assertFalse(Zoacao.matches("floresta", Zoacao.Mode.REGEX, "^f$"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void regexInvalidPatternDoesNotMatch() {
|
||||||
|
assertFalse(Zoacao.matches("f", Zoacao.Mode.REGEX, "(["));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- replace -----------------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void replaceReturnsAGagWhenMatch() {
|
||||||
|
String gag = Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", GAGS, new Random(0L));
|
||||||
assertTrue(GAGS.contains(gag), "expected a gag from the list, got " + gag);
|
assertTrue(GAGS.contains(gag), "expected a gag from the list, got " + gag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void bareUppercaseFIsReplaced() {
|
void replaceSingleElementListIsDeterministic() {
|
||||||
// A single-element list makes the random pick deterministic.
|
assertEquals("alvo",
|
||||||
String gag = Zoacao.replace("F", List.of("alvo"), new Random(0L));
|
Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", List.of("alvo"), new Random(0L)));
|
||||||
assertEquals("alvo", gag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void surroundingWhitespaceStillCountsAsBareF() {
|
void replaceReturnsNullWhenNoMatch() {
|
||||||
assertEquals("alvo", Zoacao.replace(" f ", List.of("alvo"), new Random(0L)));
|
assertNull(Zoacao.replace("oi", Zoacao.Mode.IGUAL, "f", GAGS, new Random(0L)));
|
||||||
assertEquals("alvo", Zoacao.replace("\tF\n", List.of("alvo"), new Random(0L)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void fWithAnythingElseIsLeftAlone() {
|
void replaceReturnsNullForEmptyOrNullGags() {
|
||||||
assertNull(Zoacao.replace("f lol", GAGS, new Random(0L)));
|
assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", List.of(), new Random(0L)));
|
||||||
assertNull(Zoacao.replace("ff", GAGS, new Random(0L)));
|
assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, "f", null, new Random(0L)));
|
||||||
assertNull(Zoacao.replace("f.", GAGS, new Random(0L)));
|
|
||||||
assertNull(Zoacao.replace("pra você f", GAGS, new Random(0L)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void nonFMessagesAreLeftAlone() {
|
void replaceReturnsNullForBlankOrNullPattern() {
|
||||||
assertNull(Zoacao.replace("oi", GAGS, new Random(0L)));
|
assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, "", GAGS, new Random(0L)));
|
||||||
assertNull(Zoacao.replace("", GAGS, new Random(0L)));
|
assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, " ", GAGS, new Random(0L)));
|
||||||
assertNull(Zoacao.replace("F para o morto", GAGS, new Random(0L)));
|
assertNull(Zoacao.replace("f", Zoacao.Mode.IGUAL, null, GAGS, new Random(0L)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void emptyOrNullGagsLeaveMessageAlone() {
|
void replaceReturnsNullForNullMessage() {
|
||||||
assertNull(Zoacao.replace("f", List.of(), new Random(0L)));
|
assertNull(Zoacao.replace(null, Zoacao.Mode.IGUAL, "f", GAGS, new Random(0L)));
|
||||||
assertNull(Zoacao.replace("f", null, new Random(0L)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void nullMessageLeftAlone() {
|
|
||||||
assertNull(Zoacao.replace(null, GAGS, new Random(0L)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user