You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 07:26:18 +00:00
Merge latest duckie main with UI login
This commit is contained in:
@@ -338,6 +338,8 @@ public class CatalogItem implements ISerialize, Runnable, Comparable<CatalogItem
|
||||
message.appendBoolean(haveOffer(this));
|
||||
message.appendBoolean(false); //unknown
|
||||
message.appendString(this.name + ".png");
|
||||
message.appendString(this.itemId == null ? "" : this.itemId);
|
||||
message.appendBoolean(this.haveOffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1136,7 +1136,7 @@ public class CatalogManager {
|
||||
if (extradata.length() > Emulator.getConfig().getInt("hotel.trophies.length.max", 300)) {
|
||||
extradata = extradata.substring(0, Emulator.getConfig().getInt("hotel.trophies.length.max", 300));
|
||||
}
|
||||
|
||||
|
||||
extradata = habbo.getClient().getHabbo().getHabboInfo().getUsername() + (char) 9 + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + "-" + (Calendar.getInstance().get(Calendar.MONTH) + 1) + "-" + Calendar.getInstance().get(Calendar.YEAR) + (char) 9 + Emulator.getGameEnvironment().getWordFilter().filter(extradata.replace(((char) 9) + "", ""), habbo);
|
||||
}
|
||||
|
||||
@@ -1205,7 +1205,7 @@ public class CatalogManager {
|
||||
|
||||
if (badgeFound && item.getBaseItems().size() == 1) {
|
||||
habbo.getClient().sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.ALREADY_HAVE_BADGE));
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
UserCatalogItemPurchasedEvent purchasedEvent = new UserCatalogItemPurchasedEvent(habbo, item, itemsList, totalCredits, totalPoints, badges);
|
||||
|
||||
@@ -74,8 +74,9 @@ public abstract class CatalogPage implements Comparable<CatalogPage>, ISerialize
|
||||
this.textDetails = set.getString("page_text_details");
|
||||
this.textTeaser = set.getString("page_text_teaser");
|
||||
|
||||
if (!set.getString("includes").isEmpty()) {
|
||||
for (String id : set.getString("includes").split(";")) {
|
||||
String includes = set.getString("includes");
|
||||
if (includes != null && !includes.isEmpty()) {
|
||||
for (String id : includes.split(";")) {
|
||||
try {
|
||||
this.included.add(Integer.valueOf(id));
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class AboutCommand extends Command {
|
||||
}
|
||||
public static String credits = "Arcturus Morningstar is an opensource project based on Arcturus By TheGeneral \n" +
|
||||
"The Following people have all contributed to this emulator:\n" +
|
||||
"TheGeneral\n Beny\n Alejandro\n Capheus\n Skeletor\n Harmonic\n Mike\n Remco\n zGrav \n Quadral \n Harmony\n Swirny\n ArpyAge\n Mikkel\n Rodolfo\n Rasmus\n Kitt Mustang\n Snaiker\n nttzx\n necmi\n Dome\n Jose Flores\n Cam\n Oliver\n Narzo\n Tenshie\n MartenM\n Ridge\n SenpaiDipper\n Snaiker\n Thijmen\n DuckieTM\n simoleo89\n Medievalshell\n Lorenzune";
|
||||
"TheGeneral\n Beny\n Alejandro\n Capheus\n Skeletor\n Harmonic\n Mike\n Remco\n zGrav \n Quadral \n Harmony\n Swirny\n ArpyAge\n Mikkel\n Rodolfo\n Rasmus\n Kitt Mustang\n Snaiker\n nttzx\n necmi\n Dome\n Jose Flores\n Cam\n Oliver\n Narzo\n Tenshie\n MartenM\n Ridge\n SenpaiDipper\n Snaiker\n Thijmen\n DuckieTM\n simoleo89\n Medievalshell\n Lorenzo (the wired master)";
|
||||
@Override
|
||||
public boolean handle(GameClient gameClient, String[] params) {
|
||||
|
||||
|
||||
+4
@@ -177,6 +177,10 @@ public class WiredConditionFurniHaveFurni extends InteractionWiredCondition {
|
||||
int count = settings.getFurniIds().length;
|
||||
if (count > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) return false;
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -164,6 +164,10 @@ public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition {
|
||||
this.all = (params.length > 0) && (params[0] == 1);
|
||||
this.furniSource = (params.length > 1) ? params[1] : ((params.length > 0 && params[0] > 1) ? params[0] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -240,6 +240,10 @@ public class WiredConditionFurniTypeMatch extends InteractionWiredCondition {
|
||||
this.quantifier = (params.length > 2) ? this.normalizeQuantifier(params[2]) : QUANTIFIER_ALL;
|
||||
}
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
return false;
|
||||
|
||||
+4
@@ -171,6 +171,10 @@ public class WiredConditionHasAltitude extends InteractionWiredCondition {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -177,6 +177,10 @@ public class WiredConditionNotFurniHaveFurni extends InteractionWiredCondition {
|
||||
int count = settings.getFurniIds().length;
|
||||
if (count > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) return false;
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -163,6 +163,10 @@ public class WiredConditionNotFurniHaveHabbo extends InteractionWiredCondition {
|
||||
this.all = (params.length > 0) && (params[0] == 1);
|
||||
this.furniSource = (params.length > 1) ? params[1] : ((params.length > 0 && params[0] > 1) ? params[0] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -186,6 +186,10 @@ public class WiredConditionTriggerOnFurni extends InteractionWiredCondition {
|
||||
this.userSource = (params.length > 1) ? params[1] : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.quantifier = (params.length > 2) ? this.normalizeQuantifier(params[2]) : QUANTIFIER_ALL;
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -133,6 +133,10 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -85,6 +85,10 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -290,6 +290,10 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
THashMap<HabboItem, WiredChangeDirectionSetting> newItems = new THashMap<>();
|
||||
|
||||
for (int i = 0; i < itemsCount; i++) {
|
||||
|
||||
+4
@@ -263,6 +263,10 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+5
@@ -59,6 +59,11 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect {
|
||||
this.furniSource = settings.getIntParams()[2];
|
||||
|
||||
int count = settings.getFurniIds().length;
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
this.items.add(room.getHabboItem(settings.getFurniIds()[i]));
|
||||
|
||||
+4
@@ -415,6 +415,10 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -261,6 +261,10 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement
|
||||
int count = settings.getFurniIds().length;
|
||||
if (count > Emulator.getConfig().getInt("hotel.wired.furni.selection.count", 5)) return false;
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
+6
-7
@@ -7,16 +7,11 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.pets.RideablePet;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTileState;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitType;
|
||||
import com.eu.habbo.habbohotel.rooms.*;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.WiredEffectType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
@@ -189,6 +184,10 @@ public class WiredEffectTeleport extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -169,6 +169,10 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
for (int i = 0; i < itemsCount; i++) {
|
||||
|
||||
+4
@@ -153,6 +153,10 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -101,6 +101,10 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (itemsCount > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
List<HabboItem> newItems = new ArrayList<>();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -275,6 +275,10 @@ public abstract class WiredEffectUserFurniBase extends InteractionWiredEffect {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (settings.getFurniIds().length > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
|
||||
+4
@@ -199,6 +199,10 @@ public class WiredEffectUserToFurni extends WiredEffectUserFurniBase {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
if (settings.getFurniIds().length > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
|
||||
+4
@@ -92,6 +92,10 @@ public class WiredEffectFurniOnFurni extends InteractionWiredEffect {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+4
@@ -79,6 +79,10 @@ public class WiredEffectUsersOnFurni extends InteractionWiredEffect {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count > 0 && this.furniSource == WiredSourceUtil.SOURCE_TRIGGER) {
|
||||
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
|
||||
}
|
||||
|
||||
this.items.clear();
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger {
|
||||
|
||||
private static final long ACTIVATION_PULSE_MS = 300L;
|
||||
private static final String ANTENNA_INTERACTION = "antenna";
|
||||
private static final String REQUIRE_ANTENNA_ERROR = "Puoi selezionare solo furni antenna.";
|
||||
private static final String REQUIRE_ANTENNA_ERROR = "You can only select antenna furni.";
|
||||
|
||||
private int channel = 0; // signal channel (0-based)
|
||||
private THashSet<HabboItem> items;
|
||||
|
||||
@@ -194,7 +194,6 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
private volatile boolean wiredSettingsLoaded;
|
||||
private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
|
||||
private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
|
||||
|
||||
private boolean youtubeEnabled = false;
|
||||
private String youtubeCurrentVideo = "";
|
||||
private String youtubeSenderName = "";
|
||||
|
||||
@@ -12,11 +12,11 @@ import com.eu.habbo.habbohotel.users.DanceType;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboGender;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.WiredUserActionType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredMoveCarryHelper;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper;
|
||||
import com.eu.habbo.habbohotel.wired.WiredUserActionType;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.GenericErrorMessagesComposer;
|
||||
import com.eu.habbo.messages.outgoing.inventory.AddPetComposer;
|
||||
import com.eu.habbo.messages.outgoing.rooms.pets.RoomPetComposer;
|
||||
@@ -1264,9 +1264,14 @@ public class RoomUnitManager {
|
||||
if (habbo == null || habbo.getRoomUnit() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasIdle = habbo.getRoomUnit().isIdle();
|
||||
habbo.getRoomUnit().resetIdleTimer();
|
||||
this.room.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose());
|
||||
WiredManager.triggerUserUnidles(this.room, habbo.getRoomUnit());
|
||||
|
||||
if (wasIdle) {
|
||||
this.room.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose());
|
||||
WiredManager.triggerUserUnidles(this.room, habbo.getRoomUnit());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1448,11 +1453,6 @@ public class RoomUnitManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DISPOSAL ====================
|
||||
|
||||
/**
|
||||
* Disposes the unit manager.
|
||||
*/
|
||||
public void dispose() {
|
||||
this.currentHabbos.clear();
|
||||
this.currentBots.clear();
|
||||
|
||||
@@ -292,6 +292,8 @@ public class PacketManager {
|
||||
this.registerHandler(Incoming.CatalogAdminMoveOfferEvent, CatalogAdminMoveOfferEvent.class);
|
||||
this.registerHandler(Incoming.CatalogAdminMovePageEvent, CatalogAdminMovePageEvent.class);
|
||||
this.registerHandler(Incoming.CatalogAdminPublishEvent, CatalogAdminPublishEvent.class);
|
||||
this.registerHandler(Incoming.CatalogAdminSavePageImagesEvent, CatalogAdminSavePageImagesEvent.class);
|
||||
this.registerHandler(Incoming.CatalogAdminSavePageIconEvent, CatalogAdminSavePageIconEvent.class);
|
||||
}
|
||||
|
||||
private void registerEvent() throws Exception {
|
||||
|
||||
@@ -442,6 +442,8 @@ public class Incoming {
|
||||
public static final int CatalogAdminMoveOfferEvent = 10056;
|
||||
public static final int CatalogAdminMovePageEvent = 10057;
|
||||
public static final int CatalogAdminPublishEvent = 10058;
|
||||
public static final int CatalogAdminSavePageImagesEvent = 10060;
|
||||
public static final int CatalogAdminSavePageIconEvent = 10061;
|
||||
|
||||
// Custom Prefixes
|
||||
public static final int RequestUserPrefixesEvent = 7011;
|
||||
|
||||
+7
-6
@@ -21,7 +21,7 @@ public class CatalogAdminCreateOfferEvent extends MessageHandler {
|
||||
}
|
||||
|
||||
int pageId = this.packet.readInt();
|
||||
int itemId = this.packet.readInt();
|
||||
String itemIds = this.packet.readString();
|
||||
String catalogName = this.packet.readString();
|
||||
int costCredits = this.packet.readInt();
|
||||
int costPoints = this.packet.readInt();
|
||||
@@ -39,12 +39,13 @@ public class CatalogAdminCreateOfferEvent extends MessageHandler {
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
(pageType == CatalogPageType.BUILDER)
|
||||
? "INSERT INTO catalog_items_bc (page_id, item_ids, catalog_name, order_number, extradata) VALUES (?, ?, ?, ?, ?)"
|
||||
: "INSERT INTO catalog_items (page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, club_only, extradata, have_offer, offer_id, limited_stack, order_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
Statement.RETURN_GENERATED_KEYS)) {
|
||||
(pageType == CatalogPageType.BUILDER)
|
||||
? "INSERT INTO catalog_items_bc (page_id, item_ids, catalog_name, order_number, extradata) VALUES (?, ?, ?, ?, ?)"
|
||||
: "INSERT INTO catalog_items (page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, club_only, extradata, have_offer, offer_id, limited_stack, order_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
Statement.RETURN_GENERATED_KEYS)) {
|
||||
String cleanItemIds = (itemIds == null || itemIds.trim().isEmpty()) ? "0" : itemIds.trim();
|
||||
statement.setInt(1, pageId);
|
||||
statement.setString(2, String.valueOf(itemId));
|
||||
statement.setString(2, cleanItemIds);
|
||||
statement.setString(3, catalogName);
|
||||
|
||||
if (pageType == CatalogPageType.BUILDER) {
|
||||
|
||||
+35
-22
@@ -20,7 +20,7 @@ public class CatalogAdminSaveOfferEvent extends MessageHandler {
|
||||
|
||||
int offerId = this.packet.readInt();
|
||||
int pageId = this.packet.readInt();
|
||||
int itemId = this.packet.readInt();
|
||||
String itemIds = this.packet.readString();
|
||||
String catalogName = this.packet.readString();
|
||||
int costCredits = this.packet.readInt();
|
||||
int costPoints = this.packet.readInt();
|
||||
@@ -34,31 +34,44 @@ public class CatalogAdminSaveOfferEvent extends MessageHandler {
|
||||
int orderNumber = this.packet.readInt();
|
||||
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
|
||||
|
||||
boolean updateItemIds = itemIds != null && !itemIds.trim().isEmpty();
|
||||
|
||||
String sql;
|
||||
if (pageType == CatalogPageType.BUILDER) {
|
||||
sql = updateItemIds
|
||||
? "UPDATE catalog_items_bc SET page_id = ?, item_ids = ?, catalog_name = ?, order_number = ?, extradata = ? WHERE id = ?"
|
||||
: "UPDATE catalog_items_bc SET page_id = ?, catalog_name = ?, order_number = ?, extradata = ? WHERE id = ?";
|
||||
} else {
|
||||
sql = updateItemIds
|
||||
? "UPDATE catalog_items SET page_id = ?, item_ids = ?, catalog_name = ?, cost_credits = ?, cost_points = ?, points_type = ?, amount = ?, club_only = ?, extradata = ?, have_offer = ?, offer_id = ?, limited_stack = ?, order_number = ? WHERE id = ?"
|
||||
: "UPDATE catalog_items SET page_id = ?, catalog_name = ?, cost_credits = ?, cost_points = ?, points_type = ?, amount = ?, club_only = ?, extradata = ?, have_offer = ?, offer_id = ?, limited_stack = ?, order_number = ? WHERE id = ?";
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
(pageType == CatalogPageType.BUILDER)
|
||||
? "UPDATE catalog_items_bc SET page_id = ?, item_ids = ?, catalog_name = ?, order_number = ?, extradata = ? WHERE id = ?"
|
||||
: "UPDATE catalog_items SET page_id = ?, item_ids = ?, catalog_name = ?, cost_credits = ?, cost_points = ?, points_type = ?, amount = ?, club_only = ?, extradata = ?, have_offer = ?, offer_id = ?, limited_stack = ?, order_number = ? WHERE id = ?")) {
|
||||
statement.setInt(1, pageId);
|
||||
statement.setString(2, String.valueOf(itemId));
|
||||
statement.setString(3, catalogName);
|
||||
PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
int idx = 1;
|
||||
statement.setInt(idx++, pageId);
|
||||
if (updateItemIds) {
|
||||
statement.setString(idx++, itemIds.trim());
|
||||
}
|
||||
statement.setString(idx++, catalogName);
|
||||
|
||||
if (pageType == CatalogPageType.BUILDER) {
|
||||
statement.setInt(4, orderNumber);
|
||||
statement.setString(5, extradata);
|
||||
statement.setInt(6, offerId);
|
||||
statement.setInt(idx++, orderNumber);
|
||||
statement.setString(idx++, extradata);
|
||||
statement.setInt(idx, offerId);
|
||||
} else {
|
||||
statement.setInt(4, costCredits);
|
||||
statement.setInt(5, costPoints);
|
||||
statement.setInt(6, pointsType);
|
||||
statement.setInt(7, amount);
|
||||
statement.setString(8, clubOnly == 1 ? "1" : "0");
|
||||
statement.setString(9, extradata);
|
||||
statement.setString(10, haveOffer ? "1" : "0");
|
||||
statement.setInt(11, offerIdGroup);
|
||||
statement.setInt(12, limitedStack);
|
||||
statement.setInt(13, orderNumber);
|
||||
statement.setInt(14, offerId);
|
||||
statement.setInt(idx++, costCredits);
|
||||
statement.setInt(idx++, costPoints);
|
||||
statement.setInt(idx++, pointsType);
|
||||
statement.setInt(idx++, amount);
|
||||
statement.setString(idx++, clubOnly == 1 ? "1" : "0");
|
||||
statement.setString(idx++, extradata);
|
||||
statement.setString(idx++, haveOffer ? "1" : "0");
|
||||
statement.setInt(idx++, offerIdGroup);
|
||||
statement.setInt(idx++, limitedStack);
|
||||
statement.setInt(idx++, orderNumber);
|
||||
statement.setInt(idx, offerId);
|
||||
}
|
||||
statement.execute();
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.catalog.CatalogPage;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
public class CatalogAdminSavePageIconEvent extends MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
int pageId = this.packet.readInt();
|
||||
int iconId = this.packet.readInt();
|
||||
|
||||
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
|
||||
|
||||
if (page == null) {
|
||||
this.client.sendResponse(new CatalogAdminResultComposer(false, "Page not found: " + pageId));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE catalog_pages SET icon_image = ? WHERE id = ?")) {
|
||||
statement.setInt(1, iconId);
|
||||
statement.setInt(2, pageId);
|
||||
statement.execute();
|
||||
}
|
||||
|
||||
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page icon saved"));
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.catalog.CatalogPage;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
public class CatalogAdminSavePageImagesEvent extends MessageHandler {
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
int pageId = this.packet.readInt();
|
||||
String headerImage = this.packet.readString();
|
||||
String teaserImage = this.packet.readString();
|
||||
|
||||
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
|
||||
|
||||
if (page == null) {
|
||||
this.client.sendResponse(new CatalogAdminResultComposer(false, "Page not found: " + pageId));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"UPDATE catalog_pages SET page_headline = ?, page_teaser = ? WHERE id = ?")) {
|
||||
statement.setString(1, headerImage);
|
||||
statement.setString(2, teaserImage);
|
||||
statement.setInt(3, pageId);
|
||||
statement.execute();
|
||||
}
|
||||
|
||||
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page images saved"));
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -1,6 +1,7 @@
|
||||
package com.eu.habbo.networking.gameserver;
|
||||
|
||||
import com.eu.habbo.messages.PacketManager;
|
||||
import com.eu.habbo.networking.gameserver.auth.AuthHttpHandler;
|
||||
import com.eu.habbo.networking.gameserver.codec.WebSocketCodec;
|
||||
import com.eu.habbo.networking.gameserver.decoders.*;
|
||||
import com.eu.habbo.networking.gameserver.encoders.GameServerMessageEncoder;
|
||||
@@ -49,10 +50,9 @@ public class WebSocketChannelInitializer extends ChannelInitializer<SocketChanne
|
||||
ch.pipeline().addLast("httpCodec", new HttpServerCodec());
|
||||
ch.pipeline().addLast("httpAggregator", new HttpObjectAggregator(MAX_FRAME_SIZE));
|
||||
ch.pipeline().addLast("wsHttpHandler", new WebSocketHttpHandler());
|
||||
ch.pipeline().addLast("authHttpHandler", new AuthHttpHandler());
|
||||
ch.pipeline().addLast("wsProtocolHandler", new WebSocketServerProtocolHandler(this.wsConfig));
|
||||
ch.pipeline().addLast("wsCodec", new WebSocketCodec());
|
||||
|
||||
// Standard game decoders
|
||||
ch.pipeline().addLast(new GamePolicyDecoder());
|
||||
ch.pipeline().addLast(new GameByteFrameDecoder());
|
||||
ch.pipeline().addLast(new GameByteDecoder());
|
||||
@@ -64,8 +64,6 @@ public class WebSocketChannelInitializer extends ChannelInitializer<SocketChanne
|
||||
ch.pipeline().addLast("idleEventHandler", new IdleTimeoutHandler(30, 60));
|
||||
ch.pipeline().addLast(new GameMessageRateLimit());
|
||||
ch.pipeline().addLast(new GameMessageHandler());
|
||||
|
||||
// Encoders
|
||||
ch.pipeline().addLast("messageEncoder", new GameServerMessageEncoder());
|
||||
|
||||
if (PacketManager.DEBUG_SHOW_PACKETS) {
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
package com.eu.habbo.networking.gameserver.auth;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.networking.gameserver.GameServerAttributes;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AuthHttpHandler.class);
|
||||
|
||||
private static final String LOGIN_PATH = "/api/auth/login";
|
||||
private static final String REGISTER_PATH = "/api/auth/register";
|
||||
private static final String FORGOT_PATH = "/api/auth/forgot-password";
|
||||
private static final String LOGOUT_PATH = "/api/auth/logout";
|
||||
|
||||
private static final Pattern USERNAME_RE = Pattern.compile("^[A-Za-z0-9._-]{3,32}$");
|
||||
private static final Pattern EMAIL_RE = Pattern.compile("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$");
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (!(msg instanceof FullHttpRequest req)) {
|
||||
super.channelRead(ctx, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
String path = new QueryStringDecoder(req.uri()).path();
|
||||
|
||||
if (!path.equals(LOGIN_PATH) && !path.equals(REGISTER_PATH)
|
||||
&& !path.equals(FORGOT_PATH) && !path.equals(LOGOUT_PATH)) {
|
||||
super.channelRead(ctx, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
handle(ctx, req, path);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(req);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(ChannelHandlerContext ctx, FullHttpRequest req, String path) {
|
||||
if (req.method() == HttpMethod.OPTIONS) {
|
||||
sendCors(ctx, req);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method() != HttpMethod.POST) {
|
||||
sendJson(ctx, req, HttpResponseStatus.METHOD_NOT_ALLOWED, errorPayload("Use POST."));
|
||||
return;
|
||||
}
|
||||
|
||||
String ip = resolveClientIp(ctx, req);
|
||||
|
||||
if (AuthRateLimiter.isLocked(ip)) {
|
||||
long secs = AuthRateLimiter.secondsUntilUnlock(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.TOO_MANY_REQUESTS,
|
||||
errorPayload("Too many attempts. Try again in " + secs + "s."));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject body;
|
||||
try {
|
||||
String text = req.content().toString(StandardCharsets.UTF_8);
|
||||
body = text.isEmpty() ? new JsonObject() : JsonParser.parseString(text).getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST, errorPayload("Invalid JSON body."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.equals(LOGOUT_PATH)) {
|
||||
handleLogout(ctx, req, body);
|
||||
return;
|
||||
}
|
||||
|
||||
String turnstileToken = readString(body, "turnstileToken");
|
||||
if (!TurnstileVerifier.verify(turnstileToken, ip)) {
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, errorPayload("Security check failed."));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (path) {
|
||||
case LOGIN_PATH -> handleLogin(ctx, req, body, ip);
|
||||
case REGISTER_PATH -> handleRegister(ctx, req, body, ip);
|
||||
case FORGOT_PATH -> handleForgot(ctx, req, body, ip);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Logout ────────────────────────────────────────────────────────── */
|
||||
|
||||
private void handleLogout(ChannelHandlerContext ctx, FullHttpRequest req, com.google.gson.JsonObject body) {
|
||||
String ssoTicket = readString(body, "ssoTicket");
|
||||
JsonObject ok = new JsonObject();
|
||||
ok.addProperty("message", "Logged out.");
|
||||
|
||||
if (ssoTicket == null || ssoTicket.isEmpty()) {
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement lookup = conn.prepareStatement(
|
||||
"SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) {
|
||||
lookup.setString(1, ssoTicket);
|
||||
int userId = 0;
|
||||
try (ResultSet rs = lookup.executeQuery()) {
|
||||
if (rs.next()) userId = rs.getInt("id");
|
||||
}
|
||||
|
||||
if (userId > 0) {
|
||||
try (PreparedStatement clear = conn.prepareStatement(
|
||||
"UPDATE users SET auth_ticket = '', online = '0' WHERE id = ? LIMIT 1")) {
|
||||
clear.setInt(1, userId);
|
||||
clear.executeUpdate();
|
||||
}
|
||||
|
||||
if (Emulator.getGameServer() != null
|
||||
&& Emulator.getGameServer().getGameClientManager() != null) {
|
||||
com.eu.habbo.habbohotel.users.Habbo habbo =
|
||||
Emulator.getGameServer().getGameClientManager().getHabbo(userId);
|
||||
if (habbo != null && habbo.getClient() != null) {
|
||||
Emulator.getGameServer().getGameClientManager().disposeClient(habbo.getClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Logout cleanup failed for ticket", e);
|
||||
}
|
||||
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
}
|
||||
|
||||
/* ─── Login ─────────────────────────────────────────────────────────── */
|
||||
|
||||
private void handleLogin(ChannelHandlerContext ctx, FullHttpRequest req, JsonObject body, String ip) {
|
||||
String username = readString(body, "username").trim();
|
||||
String password = readString(body, "password");
|
||||
|
||||
if (username.isEmpty() || password.isEmpty()) {
|
||||
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST, errorPayload("Missing credentials."));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT id, username, password FROM users WHERE username = ? LIMIT 1")) {
|
||||
stmt.setString(1, username);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
LOGGER.info("[auth/login] user not found username='{}' ip={}", username, ip);
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
|
||||
errorPayload("Invalid Habbo name or password."));
|
||||
return;
|
||||
}
|
||||
|
||||
int userId = rs.getInt("id");
|
||||
String stored = rs.getString("password");
|
||||
String storedPreview = stored == null
|
||||
? "<null>"
|
||||
: (stored.isEmpty() ? "<empty>" : stored.substring(0, Math.min(7, stored.length())) + "…(" + stored.length() + " chars)");
|
||||
|
||||
if (stored == null || stored.isEmpty() || !checkPassword(password, stored)) {
|
||||
LOGGER.info("[auth/login] password mismatch for user id={} username='{}' stored='{}'",
|
||||
userId, username, storedPreview);
|
||||
AuthRateLimiter.recordFailure(ip);
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
|
||||
errorPayload("Invalid Habbo name or password."));
|
||||
return;
|
||||
}
|
||||
|
||||
String ssoTicket = mintSsoTicket();
|
||||
|
||||
try (PreparedStatement upd = conn.prepareStatement(
|
||||
"UPDATE users SET auth_ticket = ?, ip_current = ? WHERE id = ? LIMIT 1")) {
|
||||
upd.setString(1, ssoTicket);
|
||||
upd.setString(2, ip == null ? "" : ip);
|
||||
upd.setInt(3, userId);
|
||||
upd.executeUpdate();
|
||||
}
|
||||
|
||||
AuthRateLimiter.recordSuccess(ip);
|
||||
|
||||
JsonObject ok = new JsonObject();
|
||||
ok.addProperty("ssoTicket", ssoTicket);
|
||||
ok.addProperty("username", rs.getString("username"));
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Login query failed for username=" + username, e);
|
||||
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Register ──────────────────────────────────────────────────────── */
|
||||
|
||||
private void handleRegister(ChannelHandlerContext ctx, FullHttpRequest req, JsonObject body, String ip) {
|
||||
if (!Emulator.getConfig().getBoolean("login.register.enabled", true)) {
|
||||
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, errorPayload("Registration is closed."));
|
||||
return;
|
||||
}
|
||||
|
||||
String username = readString(body, "username").trim();
|
||||
String email = readString(body, "email").trim();
|
||||
String password = readString(body, "password");
|
||||
|
||||
if (!USERNAME_RE.matcher(username).matches()) {
|
||||
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
|
||||
errorPayload("Username must be 3-32 chars (letters, numbers, . _ -)."));
|
||||
return;
|
||||
}
|
||||
if (!EMAIL_RE.matcher(email).matches()) {
|
||||
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST, errorPayload("Invalid email address."));
|
||||
return;
|
||||
}
|
||||
if (password.length() < 8) {
|
||||
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
|
||||
errorPayload("Password must be at least 8 characters."));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
int maxPerIp = Emulator.getConfig().getInt("register.max_per_ip", 5);
|
||||
if (maxPerIp > 0 && ip != null && !ip.isEmpty()) {
|
||||
try (PreparedStatement quota = conn.prepareStatement(
|
||||
"SELECT COUNT(*) FROM users WHERE ip_register = ?")) {
|
||||
quota.setString(1, ip);
|
||||
try (ResultSet rs = quota.executeQuery()) {
|
||||
if (rs.next() && rs.getInt(1) >= maxPerIp) {
|
||||
sendJson(ctx, req, HttpResponseStatus.TOO_MANY_REQUESTS,
|
||||
errorPayload("This IP has reached the maximum of "
|
||||
+ maxPerIp + " registered accounts."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try (PreparedStatement check = conn.prepareStatement(
|
||||
"SELECT username, mail FROM users WHERE username = ? OR mail = ? LIMIT 1")) {
|
||||
check.setString(1, username);
|
||||
check.setString(2, email);
|
||||
try (ResultSet rs = check.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
String existingUser = rs.getString("username");
|
||||
String existingMail = rs.getString("mail");
|
||||
boolean userTaken = existingUser != null && existingUser.equalsIgnoreCase(username);
|
||||
boolean mailTaken = existingMail != null && existingMail.equalsIgnoreCase(email);
|
||||
String message;
|
||||
if (userTaken && mailTaken) message = "That Habbo name and email are already in use.";
|
||||
else if (userTaken) message = "That Habbo name is already in use.";
|
||||
else message = "That email address is already in use.";
|
||||
sendJson(ctx, req, HttpResponseStatus.CONFLICT, errorPayload(message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String hashed = BCrypt.hashpw(password, BCrypt.gensalt(12));
|
||||
String defaultLook = Emulator.getConfig().getValue("register.default.look",
|
||||
"hr-100-7.hd-180-1.ch-210-66.lg-270-82.sh-290-80");
|
||||
String defaultMotto = Emulator.getConfig().getValue("register.default.motto", "I love Habbo!");
|
||||
int now = Emulator.getIntUnixTimestamp();
|
||||
|
||||
try (PreparedStatement ins = conn.prepareStatement(
|
||||
"INSERT INTO users (username, password, mail, account_created, " +
|
||||
"ip_register, ip_current, last_online, last_login, motto, look, gender, " +
|
||||
"credits, `rank`, home_room, machine_id, auth_ticket, online) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'M', 0, 1, 0, '', '', '0')",
|
||||
Statement.RETURN_GENERATED_KEYS)) {
|
||||
ins.setString(1, username);
|
||||
ins.setString(2, hashed);
|
||||
ins.setString(3, email);
|
||||
ins.setInt(4, now);
|
||||
ins.setString(5, ip == null ? "" : ip);
|
||||
ins.setString(6, ip == null ? "" : ip);
|
||||
ins.setInt(7, now);
|
||||
ins.setInt(8, now);
|
||||
ins.setString(9, defaultMotto);
|
||||
ins.setString(10, defaultLook);
|
||||
ins.executeUpdate();
|
||||
}
|
||||
|
||||
JsonObject ok = new JsonObject();
|
||||
ok.addProperty("message", "Welcome aboard, " + username + "! Your account is ready — log in below with the password you just chose.");
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Register query failed for username=" + username, e);
|
||||
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Forgot password ───────────────────────────────────────────────── */
|
||||
|
||||
private void handleForgot(ChannelHandlerContext ctx, FullHttpRequest req, JsonObject body, String ip) {
|
||||
String email = readString(body, "email").trim();
|
||||
|
||||
if (!EMAIL_RE.matcher(email).matches()) {
|
||||
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST, errorPayload("Invalid email address."));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject ok = new JsonObject();
|
||||
ok.addProperty("message", "Email sent! If an account matches that address you'll find a reset link in your inbox shortly (check spam if it doesn't show up within a minute).");
|
||||
|
||||
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT id, username FROM users WHERE mail = ? LIMIT 1")) {
|
||||
stmt.setString(1, email);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
int userId = rs.getInt("id");
|
||||
String username = rs.getString("username");
|
||||
String token = mintResetToken();
|
||||
long expiresAt = Instant.now().getEpochSecond() + 60L * 60L; // 1h
|
||||
|
||||
try (PreparedStatement ins = conn.prepareStatement(
|
||||
"INSERT INTO password_resets (user_id, token, expires_at, created_ip) " +
|
||||
"VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE " +
|
||||
"token = VALUES(token), expires_at = VALUES(expires_at), created_ip = VALUES(created_ip)")) {
|
||||
ins.setInt(1, userId);
|
||||
ins.setString(2, token);
|
||||
ins.setTimestamp(3, Timestamp.from(Instant.ofEpochSecond(expiresAt)));
|
||||
ins.setString(4, ip == null ? "" : ip);
|
||||
ins.executeUpdate();
|
||||
}
|
||||
|
||||
String resetUrlBase = Emulator.getConfig().getValue("password.reset.url",
|
||||
"http://localhost/reset-password");
|
||||
String fullUrl = resetUrlBase + (resetUrlBase.contains("?") ? "&" : "?") + "token=" + token;
|
||||
String subject = "Reset your Habbo password";
|
||||
String message = "Hi " + username + ",\n\n" +
|
||||
"Someone (hopefully you) requested a password reset for your Habbo account.\n" +
|
||||
"Click the link below within the next hour to choose a new password:\n\n" +
|
||||
fullUrl + "\n\n" +
|
||||
"If you didn't request this you can safely ignore this email.";
|
||||
|
||||
Emulator.getThreading().getService().submit((Runnable) () -> SmtpMailService.send(email, subject, message));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Forgot-password query failed for email=" + email, e);
|
||||
}
|
||||
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
||||
}
|
||||
|
||||
/* ─── Helpers ───────────────────────────────────────────────────────── */
|
||||
|
||||
private static boolean checkPassword(String plain, String stored) {
|
||||
String compatible = stored.startsWith("$2y$") ? "$2a$" + stored.substring(4) : stored;
|
||||
try {
|
||||
return BCrypt.checkpw(plain, compatible);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String mintSsoTicket() {
|
||||
byte[] buf = new byte[32];
|
||||
RNG.nextBytes(buf);
|
||||
return "nitro-" + Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
|
||||
}
|
||||
|
||||
private static String mintResetToken() {
|
||||
byte[] buf = new byte[32];
|
||||
RNG.nextBytes(buf);
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
|
||||
}
|
||||
|
||||
private static String readString(JsonObject obj, String key) {
|
||||
if (obj == null || !obj.has(key) || obj.get(key).isJsonNull()) return "";
|
||||
try {
|
||||
return obj.get(key).getAsString();
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String resolveClientIp(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
String ipHeader = Emulator.getConfig() != null
|
||||
? Emulator.getConfig().getValue("ws.ip.header", "")
|
||||
: "";
|
||||
if (!ipHeader.isEmpty() && req.headers().contains(ipHeader)) {
|
||||
String hv = req.headers().get(ipHeader);
|
||||
if (hv != null && !hv.isEmpty()) {
|
||||
int comma = hv.indexOf(',');
|
||||
return (comma > 0 ? hv.substring(0, comma) : hv).trim();
|
||||
}
|
||||
}
|
||||
if (ctx.channel().attr(GameServerAttributes.WS_IP).get() != null) {
|
||||
return ctx.channel().attr(GameServerAttributes.WS_IP).get();
|
||||
}
|
||||
if (ctx.channel().remoteAddress() instanceof InetSocketAddress addr) {
|
||||
return addr.getAddress().getHostAddress();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static JsonObject errorPayload(String message) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("error", message);
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static void sendJson(ChannelHandlerContext ctx, FullHttpRequest req,
|
||||
HttpResponseStatus status, JsonObject body) {
|
||||
byte[] bytes = body.toString().getBytes(StandardCharsets.UTF_8);
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(bytes));
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
|
||||
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
|
||||
applyCors(req, response);
|
||||
boolean keepAlive = isKeepAlive(req);
|
||||
if (keepAlive) response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||
var future = ctx.writeAndFlush(response);
|
||||
if (!keepAlive) future.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private static void sendCors(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT);
|
||||
applyCors(req, response);
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private static void applyCors(FullHttpRequest req, FullHttpResponse response) {
|
||||
String origin = req.headers().get(HttpHeaderNames.ORIGIN);
|
||||
if (origin != null && !origin.isEmpty()) {
|
||||
response.headers().set("Access-Control-Allow-Origin", origin);
|
||||
response.headers().set("Vary", "Origin");
|
||||
response.headers().set("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
response.headers().set("Access-Control-Allow-Methods", "POST, OPTIONS");
|
||||
response.headers().set("Access-Control-Allow-Headers", "Content-Type, X-Requested-With");
|
||||
}
|
||||
|
||||
private static boolean isKeepAlive(FullHttpRequest req) {
|
||||
String connection = req.headers().get(HttpHeaderNames.CONNECTION);
|
||||
return connection == null || !"close".equalsIgnoreCase(connection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.eu.habbo.networking.gameserver.auth;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class AuthRateLimiter {
|
||||
|
||||
private static final Map<String, AtomicReference<State>> STATE = new ConcurrentHashMap<>();
|
||||
|
||||
private AuthRateLimiter() {}
|
||||
|
||||
public static boolean isLocked(String ip) {
|
||||
if (!isEnabled() || ip == null || ip.isEmpty()) return false;
|
||||
|
||||
AtomicReference<State> ref = STATE.get(ip);
|
||||
if (ref == null) return false;
|
||||
|
||||
State current = ref.get();
|
||||
return current != null && current.lockedUntilMillis > System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static long secondsUntilUnlock(String ip) {
|
||||
AtomicReference<State> ref = STATE.get(ip);
|
||||
if (ref == null) return 0;
|
||||
|
||||
State current = ref.get();
|
||||
if (current == null) return 0;
|
||||
|
||||
long remainingMs = current.lockedUntilMillis - System.currentTimeMillis();
|
||||
return remainingMs > 0 ? (remainingMs / 1000L) + 1L : 0L;
|
||||
}
|
||||
|
||||
public static void recordFailure(String ip) {
|
||||
if (!isEnabled() || ip == null || ip.isEmpty()) return;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long windowMs = configInt("login.ratelimit.window_sec", 60) * 1000L;
|
||||
int maxAttempts = configInt("login.ratelimit.max_attempts", 5);
|
||||
long lockoutMs = configInt("login.ratelimit.lockout_sec", 120) * 1000L;
|
||||
|
||||
STATE.computeIfAbsent(ip, k -> new AtomicReference<>(new State(0, 0L, 0L)))
|
||||
.updateAndGet(prev -> {
|
||||
if (prev == null || (now - prev.windowStartMillis) > windowMs) {
|
||||
return new State(1, now, 0L);
|
||||
}
|
||||
|
||||
int attempts = prev.attempts + 1;
|
||||
long lockedUntil = attempts >= maxAttempts ? now + lockoutMs : 0L;
|
||||
return new State(attempts, prev.windowStartMillis, lockedUntil);
|
||||
});
|
||||
}
|
||||
|
||||
public static void recordSuccess(String ip) {
|
||||
if (ip == null || ip.isEmpty()) return;
|
||||
STATE.remove(ip);
|
||||
}
|
||||
|
||||
private static boolean isEnabled() {
|
||||
return Emulator.getConfig() != null
|
||||
&& Emulator.getConfig().getBoolean("login.ratelimit.enabled", true);
|
||||
}
|
||||
|
||||
private static int configInt(String key, int fallback) {
|
||||
return Emulator.getConfig() != null ? Emulator.getConfig().getInt(key, fallback) : fallback;
|
||||
}
|
||||
|
||||
private record State(int attempts, long windowStartMillis, long lockedUntilMillis) {}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.eu.habbo.networking.gameserver.auth;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import jakarta.mail.Authenticator;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.PasswordAuthentication;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Transport;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public final class SmtpMailService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SmtpMailService.class);
|
||||
|
||||
private SmtpMailService() {}
|
||||
|
||||
public static boolean send(String toAddress, String subject, String body) {
|
||||
try {
|
||||
String provider = Emulator.getConfig().getValue("smtp.provider", "own").toLowerCase();
|
||||
String username = Emulator.getConfig().getValue("smtp.username", "");
|
||||
String password = Emulator.getConfig().getValue("smtp.password", "");
|
||||
String fromAddr = Emulator.getConfig().getValue("smtp.from_address", username);
|
||||
String fromName = Emulator.getConfig().getValue("smtp.from_name", "Habbo Hotel");
|
||||
|
||||
if (toAddress == null || toAddress.isEmpty() || fromAddr == null || fromAddr.isEmpty()) {
|
||||
LOGGER.warn("SMTP send aborted — missing to/from address (to={}, from={})", toAddress, fromAddr);
|
||||
return false;
|
||||
}
|
||||
|
||||
String host;
|
||||
int port;
|
||||
boolean useSsl;
|
||||
boolean useTls;
|
||||
|
||||
switch (provider) {
|
||||
case "gmail" -> {
|
||||
host = "smtp.gmail.com";
|
||||
port = 465;
|
||||
useSsl = true;
|
||||
useTls = false;
|
||||
}
|
||||
case "sendgrid" -> {
|
||||
host = "smtp.sendgrid.net";
|
||||
port = 587;
|
||||
useSsl = false;
|
||||
useTls = true;
|
||||
}
|
||||
case "mailgun" -> {
|
||||
host = "smtp.mailgun.org";
|
||||
port = 587;
|
||||
useSsl = false;
|
||||
useTls = true;
|
||||
}
|
||||
default -> {
|
||||
host = Emulator.getConfig().getValue("smtp.host", "localhost");
|
||||
port = Emulator.getConfig().getInt("smtp.port", 587);
|
||||
useSsl = Emulator.getConfig().getBoolean("smtp.use_ssl", false);
|
||||
useTls = Emulator.getConfig().getBoolean("smtp.use_tls", true);
|
||||
}
|
||||
}
|
||||
|
||||
Properties props = new Properties();
|
||||
props.put("mail.smtp.host", host);
|
||||
props.put("mail.smtp.port", String.valueOf(port));
|
||||
props.put("mail.smtp.auth", String.valueOf(!username.isEmpty()));
|
||||
if (useTls) props.put("mail.smtp.starttls.enable", "true");
|
||||
if (useSsl) {
|
||||
props.put("mail.smtp.ssl.enable", "true");
|
||||
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
props.put("mail.smtp.socketFactory.port", String.valueOf(port));
|
||||
}
|
||||
props.put("mail.smtp.connectiontimeout", "10000");
|
||||
props.put("mail.smtp.timeout", "10000");
|
||||
|
||||
Session session = username.isEmpty()
|
||||
? Session.getInstance(props)
|
||||
: Session.getInstance(props, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(fromAddr, fromName, "UTF-8"));
|
||||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddress));
|
||||
message.setSubject(subject, "UTF-8");
|
||||
message.setText(body, "UTF-8");
|
||||
|
||||
Transport.send(message);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to send SMTP mail to " + toAddress, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.eu.habbo.networking.gameserver.auth;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
|
||||
public final class TurnstileVerifier {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TurnstileVerifier.class);
|
||||
private static final String VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
||||
|
||||
private static final HttpClient CLIENT = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
private TurnstileVerifier() {}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return Emulator.getConfig() != null
|
||||
&& Emulator.getConfig().getBoolean("login.turnstile.enabled", false);
|
||||
}
|
||||
|
||||
public static boolean verify(String token, String remoteIp) {
|
||||
if (!isEnabled()) return true;
|
||||
|
||||
if (token == null || token.isEmpty()) return false;
|
||||
|
||||
String secret = Emulator.getConfig().getValue("login.turnstile.secretkey", "");
|
||||
if (secret.isEmpty()) {
|
||||
LOGGER.warn("login.turnstile.enabled=1 but login.turnstile.secretkey is empty — refusing the request");
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder form = new StringBuilder();
|
||||
form.append("secret=").append(URLEncoder.encode(secret, StandardCharsets.UTF_8));
|
||||
form.append("&response=").append(URLEncoder.encode(token, StandardCharsets.UTF_8));
|
||||
if (remoteIp != null && !remoteIp.isEmpty()) {
|
||||
form.append("&remoteip=").append(URLEncoder.encode(remoteIp, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(VERIFY_URL))
|
||||
.timeout(Duration.ofSeconds(8))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(form.toString(), StandardCharsets.UTF_8))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() != 200) {
|
||||
LOGGER.warn("Turnstile siteverify returned HTTP {} for ip={}", response.statusCode(), remoteIp);
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
|
||||
boolean success = json.has("success") && json.get("success").getAsBoolean();
|
||||
if (!success) {
|
||||
LOGGER.info("Turnstile token rejected for ip={} body={}", remoteIp, response.body());
|
||||
}
|
||||
return success;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Turnstile verification failed for ip=" + remoteIp, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user