Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions service.friend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ repositories {
dependencies {
implementation(project(":service.generic"))
implementation(project(":commons"))
implementation(project(":proxy.api"))
implementation("com.github.ben-manes.caffeine:caffeine:3.1.8")
implementation("org.tinylog:tinylog-api:2.7.0")
implementation("org.tinylog:tinylog-impl:2.7.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package net.swofty.service.friend;

import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import net.swofty.commons.friend.*;
import net.swofty.commons.friend.events.*;
import net.swofty.commons.friend.events.response.*;
import net.swofty.commons.service.FromServiceChannels;
import net.swofty.proxyapi.ProxyPlayer;
import net.swofty.service.generic.redis.ServiceToServerManager;
import org.json.JSONObject;
import org.bson.Document;
import org.tinylog.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -276,7 +286,7 @@ public static void handleToggleSettingRequest(FriendToggleSettingRequestEvent ev
sendEvent(new FriendSettingToggledResponseEvent(player, settingType, newValue));
}

public static void handleListRequest(FriendListRequestEvent event, Map<UUID, String> playerNames, Map<UUID, Boolean> onlineStatus) {
public static void handleListRequest(FriendListRequestEvent event) {
UUID player = event.getPlayer();
int page = event.getPage();
boolean bestOnly = event.isBestOnly();
Expand All @@ -291,9 +301,16 @@ public static void handleListRequest(FriendListRequestEvent event, Map<UUID, Str
int startIndex = (page - 1) * FRIENDS_PER_PAGE;
int endIndex = Math.min(startIndex + FRIENDS_PER_PAGE, totalFriends);

List<Friend> pageFriends = friends.subList(startIndex, endIndex);
Map<UUID, String> playerNames = resolvePlayerNames(pageFriends.stream()
.map(Friend::getUuid)
.toList());
Map<UUID, Boolean> onlineStatus = resolveOnlineStatus(pageFriends.stream()
.map(Friend::getUuid)
.toList());

List<FriendListResponseEvent.FriendListEntry> entries = new ArrayList<>();
for (int i = startIndex; i < endIndex; i++) {
Friend friend = friends.get(i);
for (Friend friend : pageFriends) {
String name = playerNames.getOrDefault(friend.getUuid(), "Unknown");
boolean isOnline = onlineStatus.getOrDefault(friend.getUuid(), false);
entries.add(new FriendListResponseEvent.FriendListEntry(
Expand All @@ -308,7 +325,7 @@ public static void handleListRequest(FriendListRequestEvent event, Map<UUID, Str
sendEvent(new FriendListResponseEvent(player, entries, page, totalPages, bestOnly));
}

public static void handleRequestsListRequest(FriendRequestsListEvent event, Map<UUID, String> playerNames) {
public static void handleRequestsListRequest(FriendRequestsListEvent event) {
UUID player = event.getPlayer();
int page = event.getPage();

Expand All @@ -322,10 +339,14 @@ public static void handleRequestsListRequest(FriendRequestsListEvent event, Map<
int startIndex = (page - 1) * FRIENDS_PER_PAGE;
int endIndex = Math.min(startIndex + FRIENDS_PER_PAGE, totalRequests);

List<PendingFriendRequest> pageRequests = requests.subList(startIndex, endIndex);
Map<UUID, String> playerNames = resolvePlayerNames(pageRequests.stream()
.map(PendingFriendRequest::getFrom)
.toList());

List<FriendRequestsListResponseEvent.FriendRequestEntry> entries = new ArrayList<>();
for (int i = startIndex; i < endIndex; i++) {
PendingFriendRequest request = requests.get(i);
String senderName = playerNames.getOrDefault(request.getFrom(), "Unknown");
for (PendingFriendRequest request : pageRequests) {
String senderName = playerNames.getOrDefault(request.getFrom(), request.getFromName());
entries.add(new FriendRequestsListResponseEvent.FriendRequestEntry(
request.getFrom(),
senderName,
Expand Down Expand Up @@ -361,6 +382,79 @@ public static void handlePlayerLeave(UUID playerUuid, String playerName) {
cachedFriendData.remove(playerUuid);
}

public static String getPlayerName(UUID uuid) {
return resolvePlayerNames(List.of(uuid)).getOrDefault(uuid, "Unknown");
}

private static Map<UUID, String> resolvePlayerNames(Collection<UUID> uuids) {
Map<UUID, String> names = new HashMap<>();
if (uuids == null || uuids.isEmpty() || FriendDatabase.database == null) return names;

List<String> idStrings = uuids.stream().map(UUID::toString).toList();
names.putAll(fetchNamesFromCollection("data", idStrings));

Set<UUID> unresolved = new HashSet<>(uuids);
unresolved.removeAll(names.keySet());
if (!unresolved.isEmpty()) {
List<String> unresolvedIds = unresolved.stream().map(UUID::toString).toList();
names.putAll(fetchNamesFromCollection("profiles", unresolvedIds));
}

return names;
}

private static Map<UUID, Boolean> resolveOnlineStatus(Collection<UUID> uuids) {
Map<UUID, Boolean> status = new ConcurrentHashMap<>();
if (uuids == null || uuids.isEmpty()) return status;

List<CompletableFuture<Void>> futures = new ArrayList<>();
for (UUID uuid : uuids) {
CompletableFuture<Void> future = new ProxyPlayer(uuid)
.isOnline()
.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(e -> false)
.thenAccept(isOnline -> status.put(uuid, isOnline));
futures.add(future);
}

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
return status;
}

private static Map<UUID, String> fetchNamesFromCollection(String collectionName, List<String> ids) {
Map<UUID, String> names = new HashMap<>();
try {
MongoCollection<Document> collection = FriendDatabase.database.getCollection(collectionName);
if (collection == null || ids.isEmpty()) return names;

for (Document doc : collection.find(Filters.in("_id", ids))) {
String id = doc.getString("_id");
if (id == null) continue;

String ign = parseStoredName(doc.getString("ign"));
if (ign == null && doc.containsKey("ignLowercase")) {
ign = parseStoredName(doc.getString("ignLowercase"));
}

if (ign != null) {
names.put(UUID.fromString(id), ign);
}
}
} catch (Exception e) {
Logger.error(e, "Failed to resolve player names from collection {}", collectionName);
}
return names;
}

private static String parseStoredName(String raw) {
if (raw == null) return null;
raw = raw.trim();
if (raw.startsWith("\"") && raw.endsWith("\"") && raw.length() >= 2) {
raw = raw.substring(1, raw.length() - 1);
}
return raw.isEmpty() ? null : raw;
}

private static void persistFriendData(UUID playerUuid) {
FriendData data = cachedFriendData.get(playerUuid);
if (data != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import net.swofty.service.generic.redis.ServiceEndpoint;
import org.tinylog.Logger;

import java.util.HashMap;

public class FriendEventToServiceEndpoint implements ServiceEndpoint<
SendFriendEventToServiceProtocolObject.SendFriendEventToServiceMessage,
SendFriendEventToServiceProtocolObject.SendFriendEventToServiceResponse> {
Expand All @@ -29,16 +27,37 @@ public SendFriendEventToServiceProtocolObject.SendFriendEventToServiceResponse o

System.out.println("Received friend event: " + event.getClass().getSimpleName());
switch (event) {
case FriendAddRequestEvent e -> FriendCache.handleAddRequest(e, "Player", "Player");
case FriendAcceptRequestEvent e -> FriendCache.handleAcceptRequest(e, "Player", "Player");
case FriendDenyRequestEvent e -> FriendCache.handleDenyRequest(e, "Player");
case FriendRemoveRequestEvent e -> FriendCache.handleRemoveRequest(e, "Player", "Player");
case FriendAddRequestEvent e -> FriendCache.handleAddRequest(
e,
FriendCache.getPlayerName(e.getSender()),
FriendCache.getPlayerName(e.getTarget())
);
case FriendAcceptRequestEvent e -> FriendCache.handleAcceptRequest(
e,
FriendCache.getPlayerName(e.getAccepter()),
FriendCache.getPlayerName(e.getRequester())
);
case FriendDenyRequestEvent e -> FriendCache.handleDenyRequest(
e,
FriendCache.getPlayerName(e.getDenier())
);
case FriendRemoveRequestEvent e -> FriendCache.handleRemoveRequest(
e,
FriendCache.getPlayerName(e.getRemover()),
FriendCache.getPlayerName(e.getTarget())
);
case FriendRemoveAllRequestEvent e -> FriendCache.handleRemoveAllRequest(e);
case FriendToggleBestRequestEvent e -> FriendCache.handleToggleBestRequest(e, "Player");
case FriendSetNicknameRequestEvent e -> FriendCache.handleSetNicknameRequest(e, "Player");
case FriendToggleBestRequestEvent e -> FriendCache.handleToggleBestRequest(
e,
FriendCache.getPlayerName(e.getTarget())
);
case FriendSetNicknameRequestEvent e -> FriendCache.handleSetNicknameRequest(
e,
FriendCache.getPlayerName(e.getTarget())
);
case FriendToggleSettingRequestEvent e -> FriendCache.handleToggleSettingRequest(e);
case FriendListRequestEvent e -> FriendCache.handleListRequest(e, new HashMap<>(), new HashMap<>());
case FriendRequestsListEvent e -> FriendCache.handleRequestsListRequest(e, new HashMap<>());
case FriendListRequestEvent e -> FriendCache.handleListRequest(e);
case FriendRequestsListEvent e -> FriendCache.handleRequestsListRequest(e);
default -> Logger.warn("Unknown friend event type: " + event.getClass().getSimpleName());
}

Expand Down
Loading