package net.minecraft.client.telemetry; import com.google.common.base.Suppliers; import com.mojang.authlib.minecraft.TelemetrySession; import com.mojang.authlib.minecraft.UserApiService; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.User; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class ClientTelemetryManager implements AutoCloseable { private static final AtomicInteger THREAD_COUNT = new AtomicInteger(1); private static final Executor EXECUTOR = Executors.newSingleThreadExecutor(runnable -> { Thread thread = new Thread(runnable); thread.setName("Telemetry-Sender-#" + THREAD_COUNT.getAndIncrement()); return thread; }); private final Minecraft minecraft; private final UserApiService userApiService; private final TelemetryPropertyMap deviceSessionProperties; private final Path logDirectory; private final CompletableFuture> logManager; private final Supplier outsideSessionSender = Suppliers.memoize(this::createEventSender); public ClientTelemetryManager(Minecraft minecraft, UserApiService userApiService, User user) { this.minecraft = minecraft; this.userApiService = userApiService; TelemetryPropertyMap.Builder builder = TelemetryPropertyMap.builder(); user.getXuid().ifPresent(string -> builder.put(TelemetryProperty.USER_ID, string)); user.getClientId().ifPresent(string -> builder.put(TelemetryProperty.CLIENT_ID, string)); builder.put(TelemetryProperty.MINECRAFT_SESSION_ID, UUID.randomUUID()); builder.put(TelemetryProperty.GAME_VERSION, SharedConstants.getCurrentVersion().getId()); builder.put(TelemetryProperty.OPERATING_SYSTEM, Util.getPlatform().telemetryName()); builder.put(TelemetryProperty.PLATFORM, System.getProperty("os.name")); builder.put(TelemetryProperty.CLIENT_MODDED, Minecraft.checkModStatus().shouldReportAsModified()); builder.putIfNotNull(TelemetryProperty.LAUNCHER_NAME, Minecraft.getLauncherBrand()); this.deviceSessionProperties = builder.build(); this.logDirectory = minecraft.gameDirectory.toPath().resolve("logs/telemetry"); this.logManager = TelemetryLogManager.open(this.logDirectory); } public WorldSessionTelemetryManager createWorldSessionManager(boolean newWorld, @Nullable Duration worldLoadDuration, @Nullable String minigameName) { return new WorldSessionTelemetryManager(this.createEventSender(), newWorld, worldLoadDuration, minigameName); } public TelemetryEventSender getOutsideSessionSender() { return (TelemetryEventSender)this.outsideSessionSender.get(); } private TelemetryEventSender createEventSender() { if (!this.minecraft.allowsTelemetry()) { return TelemetryEventSender.DISABLED; } else { TelemetrySession telemetrySession = this.userApiService.newTelemetrySession(EXECUTOR); if (!telemetrySession.isEnabled()) { return TelemetryEventSender.DISABLED; } else { CompletableFuture> completableFuture = this.logManager .thenCompose( optional -> (CompletionStage)optional.map(TelemetryLogManager::openLogger).orElseGet(() -> CompletableFuture.completedFuture(Optional.empty())) ); return (telemetryEventType, consumer) -> { if (!telemetryEventType.isOptIn() || Minecraft.getInstance().telemetryOptInExtra()) { TelemetryPropertyMap.Builder builder = TelemetryPropertyMap.builder(); builder.putAll(this.deviceSessionProperties); builder.put(TelemetryProperty.EVENT_TIMESTAMP_UTC, Instant.now()); builder.put(TelemetryProperty.OPT_IN, telemetryEventType.isOptIn()); consumer.accept(builder); TelemetryEventInstance telemetryEventInstance = new TelemetryEventInstance(telemetryEventType, builder.build()); completableFuture.thenAccept(optional -> { if (!optional.isEmpty()) { ((TelemetryEventLogger)optional.get()).log(telemetryEventInstance); telemetryEventInstance.export(telemetrySession).send(); } }); } }; } } } public Path getLogDirectory() { return this.logDirectory; } public void close() { this.logManager.thenAccept(optional -> optional.ifPresent(TelemetryLogManager::close)); } }