package com.example.addon.modules;
import com.example.addon.QuinnAddon;
import meteordevelopment.meteorclient.events.game.ReceiveMessageEvent;
import meteordevelopment.meteorclient.settings.BoolSetting;
import meteordevelopment.meteorclient.settings.EnumSetting;
import meteordevelopment.meteorclient.settings.IntSetting;
import meteordevelopment.meteorclient.settings.Setting;
import meteordevelopment.meteorclient.settings.SettingGroup;
import meteordevelopment.meteorclient.settings.StringListSetting;
import meteordevelopment.meteorclient.settings.StringSetting;
import meteordevelopment.meteorclient.systems.friends.Friend;
import meteordevelopment.meteorclient.systems.friends.Friends;
import meteordevelopment.meteorclient.systems.modules.Module;
import meteordevelopment.meteorclient.utils.player.ChatUtils;
import meteordevelopment.orbit.EventHandler;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.text.Text;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Accepts teleport requests.
*
*
Changes from the version this was built from:
*
* - It referenced an {@code Addon} class and a {@code utils.Utils} helper that do not exist
* here, so the category comes from {@link QuinnAddon} and the toggle messages are left to
* Meteor, which already prints them.
* - The accept command is a setting. {@code /tpy} is not universal.
* - Another player can no longer trigger it by typing. Matching the phrase anywhere in
* chat meant anyone could say "Someone has requested to teleport to you" and have you run the
* accept command at them. Messages that look like player chat are now ignored, and the name
* has to belong to someone actually on the server.
* - A per-player cooldown. Servers re-announce a pending request every so often, and the
* original answered every announcement, which reads as chat spam and gets you kicked.
* - The name is matched against the tab list rather than assumed to be the first word, so a
* rank prefix in the message no longer breaks it.
*
*/
public class AutoAccept extends Module {
private final SettingGroup sgGeneral = settings.getDefaultGroup();
public enum Mode {
Friends,
Whitelist,
Blacklist,
All
}
private final Setting mode = sgGeneral.add(new EnumSetting.Builder()
.name("mode")
.description("Which players to accept requests from.")
.defaultValue(Mode.Friends)
.build());
private final Setting> whitelist = sgGeneral.add(new StringListSetting.Builder()
.name("whitelist")
.description("List of players to accept.")
.visible(() -> mode.get() == Mode.Whitelist)
.build());
private final Setting> blacklist = sgGeneral.add(new StringListSetting.Builder()
.name("blacklist")
.description("List of players to ignore.")
.visible(() -> mode.get() == Mode.Blacklist)
.build());
private final Setting acceptCommand = sgGeneral.add(new StringSetting.Builder()
.name("accept-command")
.description("Command sent to accept, with the player name appended. Servers differ: /tpy, /tpaccept, /tpayes.")
.defaultValue("/tpy")
.build());
private final Setting cooldown = sgGeneral.add(new IntSetting.Builder()
.name("cooldown")
.description("Seconds to wait before answering the same player again. Servers re-announce pending requests.")
.defaultValue(30)
.min(0)
.sliderRange(0, 120)
.build());
private final Setting requireOnline = sgGeneral.add(new BoolSetting.Builder()
.name("require-online-player")
.description("Only accept when the name in the message belongs to someone on the player list. Turn this off only if the server hides the list.")
.defaultValue(true)
.build());
private final Setting ignoreChat = sgGeneral.add(new BoolSetting.Builder()
.name("ignore-player-chat")
.description("Ignore messages that look like somebody talking. Without this, anyone can make you accept by typing the request text.")
.defaultValue(true)
.build());
private final Map lastAccepted = new HashMap<>();
public AutoAccept() {
super(QuinnAddon.CATEGORY, "auto-accept", "Automatically accepts teleport requests.");
}
@Override
public void onActivate() {
lastAccepted.clear();
}
@EventHandler
private void onMessageReceive(ReceiveMessageEvent event) {
if (mc.player == null) return;
Text message = event.getMessage();
String text = message.getString();
if (!isRequest(text)) return;
if (ignoreChat.get() && looksLikePlayerChat(text)) return;
String playerName = extractPlayerName(text);
if (playerName == null) return;
if (playerName.equalsIgnoreCase(mc.player.getGameProfile().name())) return;
if (!shouldAccept(playerName)) return;
long now = System.currentTimeMillis();
Long last = lastAccepted.get(playerName.toLowerCase(Locale.ROOT));
if (last != null && now - last < cooldown.get() * 1000L) return;
lastAccepted.put(playerName.toLowerCase(Locale.ROOT), now);
info("Accepting teleport request from " + playerName);
ChatUtils.sendPlayerMsg(acceptCommand.get() + " " + playerName);
}
private boolean isRequest(String text) {
return text.contains("has requested to teleport")
|| text.contains("to teleport to you")
|| text.contains("has invited you to join them");
}
/**
* Server announcements are not spoken by anyone, so the usual chat shapes are a good enough
* tell: {@code ...}, {@code Name: ...} and {@code [Rank] Name » ...}. A server with an
* unusual chat format could still slip through, which is why the name is checked as well.
*/
private boolean looksLikePlayerChat(String text) {
int phrase = text.indexOf("has requested");
if (phrase < 0) phrase = text.indexOf("to teleport to you");
if (phrase < 0) phrase = text.indexOf("has invited you");
if (phrase < 0) phrase = text.length();
String before = text.substring(0, phrase);
return before.contains("<")
|| before.contains(":")
|| before.contains("»")
|| before.contains(">");
}
private boolean shouldAccept(String playerName) {
switch (mode.get()) {
case Friends:
for (Friend friend : Friends.get()) {
if (friend.name.equalsIgnoreCase(playerName)) return true;
}
return false;
case Whitelist:
for (String name : whitelist.get()) {
if (name.equalsIgnoreCase(playerName)) return true;
}
return false;
case Blacklist:
for (String name : blacklist.get()) {
if (name.equalsIgnoreCase(playerName)) return false;
}
return true;
case All:
return true;
default:
return false;
}
}
/**
* Picks the name out of the announcement by looking for a word that belongs to someone on the
* server, which survives rank prefixes and colour codes. Falls back to the first word when the
* player list cannot be trusted.
*/
private String extractPlayerName(String text) {
String[] parts = text.split("[^A-Za-z0-9_]+");
if (mc.getNetworkHandler() != null) {
for (String part : parts) {
if (part.length() < 3 || part.length() > 16) continue;
for (PlayerListEntry entry : mc.getNetworkHandler().getPlayerList()) {
String name = entry.getProfile().name();
if (name != null && name.equalsIgnoreCase(part)) return name;
}
}
}
if (requireOnline.get()) return null;
for (String part : parts) {
if (!part.isEmpty()) return part;
}
return null;
}
}