From 7089980dbaf1cea9141172284eac0727b275d5e1 Mon Sep 17 00:00:00 2001 From: Mykhailo Alipa <6442572+strobil@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:52:28 +0200 Subject: [PATCH 1/3] feat(api): add user filters to typed lookups --- docs/api/version/v13.md | 1 + .../java/net/coreprotect/api/BlockAPI.java | 1 + .../net/coreprotect/api/LookupFilter.java | 37 +++++++++++++++- .../net/coreprotect/api/LookupOptions.java | 24 +++++++++++ .../java/net/coreprotect/api/UsernameAPI.java | 42 +++++++++++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/api/version/v13.md b/docs/api/version/v13.md index 4320cf28d..ea2cfc208 100644 --- a/docs/api/version/v13.md +++ b/docs/api/version/v13.md @@ -11,6 +11,7 @@ API version 13 adds entity-spawn lookup support while retaining all API version ## Upgrading from API v12 - `LookupOptions` supports material inclusion/exclusion filters for typed container, item, and inventory lookups. +- `LookupOptions` supports `users(List)` and `excludeUsers(List)` for typed lookups. Inclusion matches any listed user, exclusions take precedence, and an existing `user(...)` remains an additional constraint. Empty lists add no restrictions. As with `user(...)`, an empty name or `#global` matches all users. Username history resolves these filters by UUID, including historical names. - Added `CoreProtectAction.ENTITY_SPAWN` with action ID `13`. - Added `CoreProtectPreLogEvent.Action.ENTITY_SPAWN`. - `BlockResult#getEntityType()` recognizes entity-spawn results. diff --git a/src/main/java/net/coreprotect/api/BlockAPI.java b/src/main/java/net/coreprotect/api/BlockAPI.java index 6f5a92a0c..008ae2d9c 100644 --- a/src/main/java/net/coreprotect/api/BlockAPI.java +++ b/src/main/java/net/coreprotect/api/BlockAPI.java @@ -158,6 +158,7 @@ public static List performLookup(Block block, LookupOptions options if (userId != null) { query.append(" AND ").append(ConfigHandler.databaseType.getUserColumn()).append(" = ?"); } + LookupFilter.appendUserWhere(query, "", LookupFilter.userIds(connection, options.getUsers()), LookupFilter.userIds(connection, options.getExcludeUsers())); query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder()); if (options.hasLimit()) { query.append(" LIMIT ").append(options.getLimitCount()).append(" OFFSET ").append(options.getLimitOffset()); diff --git a/src/main/java/net/coreprotect/api/LookupFilter.java b/src/main/java/net/coreprotect/api/LookupFilter.java index 73753e485..f43f50c32 100644 --- a/src/main/java/net/coreprotect/api/LookupFilter.java +++ b/src/main/java/net/coreprotect/api/LookupFilter.java @@ -30,8 +30,10 @@ final class LookupFilter { private final List includeMaterials; private final List excludeMaterials; private final Map materialTypes; + private final String includeUserIds; + private final String excludeUserIds; - private LookupFilter(Integer userId, int checkTime, Location location, int radius, int limitOffset, int limitCount, List includeMaterials, List excludeMaterials, Map materialTypes) { + private LookupFilter(Integer userId, int checkTime, Location location, int radius, int limitOffset, int limitCount, List includeMaterials, List excludeMaterials, Map materialTypes, String includeUserIds, String excludeUserIds) { this.userId = userId; this.checkTime = checkTime; this.location = location; @@ -41,6 +43,8 @@ private LookupFilter(Integer userId, int checkTime, Location location, int radiu this.includeMaterials = includeMaterials; this.excludeMaterials = excludeMaterials; this.materialTypes = materialTypes; + this.includeUserIds = includeUserIds; + this.excludeUserIds = excludeUserIds; } static LookupFilter fromOptions(Connection connection, LookupOptions options) throws Exception { @@ -65,7 +69,8 @@ static LookupFilter fromOptions(Connection connection, LookupOptions options) th } return new LookupFilter(userId, checkTime, options.getLocation(), options.getRadius(), options.getLimitOffset(), options.getLimitCount(), - options.getIncludeMaterials(), options.getExcludeMaterials(), materialTypes); + options.getIncludeMaterials(), options.getExcludeMaterials(), materialTypes, + userIds(connection, options.getUsers()), userIds(connection, options.getExcludeUsers())); } boolean hasInvalidUser() { @@ -111,6 +116,7 @@ void appendWhere(StringBuilder query, String alias) { if (userId != null) { query.append(" AND ").append(qualifier).append(ConfigHandler.databaseType.getUserColumn()).append(" = ?"); } + appendUserWhere(query, alias, includeUserIds, excludeUserIds); if (location != null) { query.append(" AND ").append(qualifier).append("wid = ?"); @@ -130,6 +136,7 @@ void appendEntityContainerWhere(StringBuilder query, String transactionAlias, St if (userId != null) { query.append(" AND ").append(transaction).append(ConfigHandler.databaseType.getUserColumn()).append(" = ?"); } + appendUserWhere(query, transactionAlias, includeUserIds, excludeUserIds); if (location == null) { return; } @@ -338,6 +345,32 @@ int bindEntityContainer(PreparedStatement statement, int parameterIndex) throws return parameterIndex; } + static String userIds(Connection connection, List users) throws Exception { + StringJoiner result = new StringJoiner(","); + for (String user : users) { + Integer id = MessageAPI.getUserId(connection, user); + if (id == null) { + // An empty name or #global matches every user. + return null; + } + result.add(String.valueOf(id)); + } + return result.toString(); + } + + static void appendUserWhere(StringBuilder query, String alias, String includeUserIds, String excludeUserIds) { + String column = (alias.isEmpty() ? "" : alias + ".") + ConfigHandler.databaseType.getUserColumn(); + if (includeUserIds != null && !includeUserIds.isEmpty()) { + query.append(" AND ").append(column).append(" IN (").append(includeUserIds).append(")"); + } + if (excludeUserIds == null) { + query.append(" AND 1 = 0"); + } + else if (!excludeUserIds.isEmpty()) { + query.append(" AND ").append(column).append(" NOT IN (").append(excludeUserIds).append(")"); + } + } + private String materialIds(List materials, boolean inventoryBlock) { StringJoiner result = new StringJoiner(","); for (Map.Entry entry : materialTypes.entrySet()) { diff --git a/src/main/java/net/coreprotect/api/LookupOptions.java b/src/main/java/net/coreprotect/api/LookupOptions.java index 491b04f65..31256f3ce 100644 --- a/src/main/java/net/coreprotect/api/LookupOptions.java +++ b/src/main/java/net/coreprotect/api/LookupOptions.java @@ -17,6 +17,8 @@ public final class LookupOptions { private final int limitCount; private final List includeMaterials; private final List excludeMaterials; + private final List users; + private final List excludeUsers; private LookupOptions(Builder builder) { this.user = builder.user; @@ -27,6 +29,8 @@ private LookupOptions(Builder builder) { this.limitCount = builder.limitCount; this.includeMaterials = builder.includeMaterials; this.excludeMaterials = builder.excludeMaterials; + this.users = builder.users; + this.excludeUsers = builder.excludeUsers; } public static Builder builder() { @@ -69,6 +73,14 @@ public List getExcludeMaterials() { return excludeMaterials; } + public List getUsers() { + return users; + } + + public List getExcludeUsers() { + return excludeUsers; + } + public static final class Builder { private String user; private int time; @@ -78,6 +90,8 @@ public static final class Builder { private int limitCount = -1; private List includeMaterials = List.of(); private List excludeMaterials = List.of(); + private List users = List.of(); + private List excludeUsers = List.of(); private Builder() { } @@ -120,6 +134,16 @@ public Builder excludeMaterials(List materials) { return this; } + public Builder users(List users) { + this.users = List.copyOf(users); + return this; + } + + public Builder excludeUsers(List users) { + this.excludeUsers = List.copyOf(users); + return this; + } + public LookupOptions build() { return new LookupOptions(this); } diff --git a/src/main/java/net/coreprotect/api/UsernameAPI.java b/src/main/java/net/coreprotect/api/UsernameAPI.java index 5e7342afc..9d23400d9 100644 --- a/src/main/java/net/coreprotect/api/UsernameAPI.java +++ b/src/main/java/net/coreprotect/api/UsernameAPI.java @@ -50,6 +50,25 @@ public static List performLookup(LookupOptions options) { return result; } + Set includedUuids = getUuids(connection, options.getUsers()); + if (!options.getUsers().isEmpty() && includedUuids != null) { + if (uuids.isEmpty()) { + uuids.addAll(includedUuids); + } + else { + uuids.retainAll(includedUuids); + } + if (uuids.isEmpty()) { + return result; + } + } + Set excludedUuids = getUuids(connection, options.getExcludeUsers()); + if (excludedUuids == null) { + return result; + } + // A NULL in NOT IN would also exclude unrelated users. + excludedUuids.remove(null); + int checkTime = 0; if (options.getTime() > 0) { checkTime = (int) (System.currentTimeMillis() / 1000L) - options.getTime(); @@ -62,6 +81,11 @@ public static List performLookup(LookupOptions options) { appendPlaceholders(query, uuids.size()); query.append(")"); } + if (!excludedUuids.isEmpty()) { + query.append(" AND uuid NOT IN ("); + appendPlaceholders(query, excludedUuids.size()); + query.append(")"); + } query.append(" ORDER BY ").append(ConfigHandler.getDescendingEventOrder()); if (options.hasLimit()) { query.append(" LIMIT ").append(options.getLimitCount()).append(" OFFSET ").append(options.getLimitOffset()); @@ -73,6 +97,9 @@ public static List performLookup(LookupOptions options) { for (String uuid : uuids) { statement.setString(parameterIndex++, uuid); } + for (String uuid : excludedUuids) { + statement.setString(parameterIndex++, uuid); + } try (ResultSet results = statement.executeQuery()) { while (results.next()) { @@ -130,6 +157,21 @@ private static Set getUuids(Connection connection, String user) throws E return result.isEmpty() ? null : result; } + private static Set getUuids(Connection connection, List users) throws Exception { + Set result = new LinkedHashSet<>(); + for (String user : users) { + Set matches = getUuids(connection, user); + if (matches != null) { + if (matches.isEmpty()) { + // An empty name or #global matches every user. + return null; + } + result.addAll(matches); + } + } + return result; + } + private static boolean looksLikeUuid(String value) { return value.length() == 36 && value.charAt(8) == '-' && value.charAt(13) == '-' && value.charAt(18) == '-' && value.charAt(23) == '-'; } From d9d6035117676538f9b3cb1df5f67e9870a4d6f4 Mon Sep 17 00:00:00 2001 From: Mykhailo Alipa <6442572+strobil@users.noreply.github.com> Date: Tue, 8 Sep 2026 22:56:05 +0200 Subject: [PATCH 2/3] docs(api): consolidate lookup filter options --- docs/api/version/v13.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/api/version/v13.md b/docs/api/version/v13.md index ea2cfc208..adc8438b0 100644 --- a/docs/api/version/v13.md +++ b/docs/api/version/v13.md @@ -10,8 +10,7 @@ API version 13 adds entity-spawn lookup support while retaining all API version ## Upgrading from API v12 -- `LookupOptions` supports material inclusion/exclusion filters for typed container, item, and inventory lookups. -- `LookupOptions` supports `users(List)` and `excludeUsers(List)` for typed lookups. Inclusion matches any listed user, exclusions take precedence, and an existing `user(...)` remains an additional constraint. Empty lists add no restrictions. As with `user(...)`, an empty name or `#global` matches all users. Username history resolves these filters by UUID, including historical names. +- `LookupOptions` supports material inclusion/exclusion filters for typed container, item, and inventory lookups, and `users(List)` / `excludeUsers(List)` filters for all typed lookups. - Added `CoreProtectAction.ENTITY_SPAWN` with action ID `13`. - Added `CoreProtectPreLogEvent.Action.ENTITY_SPAWN`. - `BlockResult#getEntityType()` recognizes entity-spawn results. From 02f87742ad6eeaabaa0d53d960540316db143ad1 Mon Sep 17 00:00:00 2001 From: Mykhailo Alipa <6442572+strobil@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:22:03 +0200 Subject: [PATCH 3/3] refactor(api): extract username inclusion filter resolution --- .../java/net/coreprotect/api/UsernameAPI.java | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/coreprotect/api/UsernameAPI.java b/src/main/java/net/coreprotect/api/UsernameAPI.java index 9d23400d9..568558e46 100644 --- a/src/main/java/net/coreprotect/api/UsernameAPI.java +++ b/src/main/java/net/coreprotect/api/UsernameAPI.java @@ -45,23 +45,11 @@ public static List performLookup(LookupOptions options) { return result; } - Set uuids = getUuids(connection, options.getUser()); + Set uuids = getUuids(connection, options); if (uuids == null) { return result; } - Set includedUuids = getUuids(connection, options.getUsers()); - if (!options.getUsers().isEmpty() && includedUuids != null) { - if (uuids.isEmpty()) { - uuids.addAll(includedUuids); - } - else { - uuids.retainAll(includedUuids); - } - if (uuids.isEmpty()) { - return result; - } - } Set excludedUuids = getUuids(connection, options.getExcludeUsers()); if (excludedUuids == null) { return result; @@ -124,6 +112,25 @@ private static void appendPlaceholders(StringBuilder query, int count) { } } + private static Set getUuids(Connection connection, LookupOptions options) throws Exception { + Set result = getUuids(connection, options.getUser()); + if (result == null || options.getUsers().isEmpty()) { + return result; + } + + Set includedUuids = getUuids(connection, options.getUsers()); + if (includedUuids == null) { + return result; + } + if (result.isEmpty()) { + result.addAll(includedUuids); + } + else { + result.retainAll(includedUuids); + } + return result.isEmpty() ? null : result; + } + private static Set getUuids(Connection connection, String user) throws Exception { Set result = new LinkedHashSet<>();