Skip to content

Commit

Permalink
Merge branch '1.19.2-fabric' into 1.18.2-fabric
Browse files Browse the repository at this point in the history
# Conflicts:
#	gradle.properties
  • Loading branch information
melontini committed Jul 23, 2023
2 parents c833475 + 4556c46 commit 7f393c9
Show file tree
Hide file tree
Showing 25 changed files with 293 additions and 131 deletions.
31 changes: 26 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
## What's new:

Breaking changes for the Mixpanel module, notable ones for others.

### Analytics

* New analytics module! `analytics-crashes`
* * Adds event-like crash handling. Also, features a class to quickly upload logs to mclo.gs
* Analytics config moved to JSON + new option for Crashlytics.
* Fixed new users getting null IDs.
* ID will only be assigned if analytics are enabled.
* Now, handlers will handle messages if either analytics or crashlytics are on.
* Added null checks for various method arguments.
* Mixpanel module has undergone a rewrite:
* `org.json` and `mixpanel` dependencies were dropped.
* `mixpanel` was condensed into a single MixpanelAPI class, which immediately sends your request.
* MessageProvider no longer requires you to return anything.

### Content

* Non-prefixed methods in ItemGroupExtensions are now deprecated.

### Danger

* Danger will no longer auto-start with the game.
* You no longer need `"dark-matter-danger:instrumentation"` in your `fabric.mod.json`.

### Enums

* EnumWrapper now uses `values()[0]` just in case anything gets renamed.

### Minecraft
### Recipe Book

* Fixed matrices not getting popped after rendering tooltips. (1.20+)
* Non-prefixed methods in PaginatedRecipeGroupButtonWidget and PaginatedRecipeBookWidget are now deprecated.
* Removed ApiStatus annotation from classes.
2 changes: 0 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,6 @@ allprojects {
dependencies {
localRuntime(annotationProcessor("com.github.llamalad7.mixinextras:mixinextras-fabric:${project.mixin_extras_version}"))
localRuntime("net.bytebuddy:byte-buddy-agent:${project.byte_buddy_agent_version}")
localRuntime("com.mixpanel:mixpanel-java:${project.mixpanel_version}")
localRuntime("org.json:json:${project.org_json_version}")

for (Project pj : subprojects) {
api(include(project(path: ":${pj.name}", configuration: 'namedElements')))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.melontini.dark_matter.analytics.crashes;

import me.melontini.dark_matter.util.MakeSure;
import me.melontini.dark_matter.util.classes.Tuple;
import net.fabricmc.api.EnvType;
import net.minecraft.util.crash.CrashReport;
Expand All @@ -13,10 +14,13 @@
public class Crashlytics {
private static final Map<String, Tuple<Decider, Handler>> HANDLERS = new HashMap<>();
public static void addHandler(String id, Decider decider, Handler handler) {
MakeSure.notEmpty(id, "Empty or null id provided!");
MakeSure.notNulls("Null arguments provided!", decider, handler);
HANDLERS.putIfAbsent(id, new Tuple<>(decider, handler));
}

public static void removeHandler(String id) {
MakeSure.notEmpty(id, "Empty or null id provided!");
HANDLERS.remove(id);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.melontini.dark_matter.DarkMatterLog;
import me.melontini.dark_matter.util.MakeSure;

import java.io.IOException;
import java.net.URI;
Expand All @@ -20,6 +21,7 @@ public class Uploader {
private static final String MCLO_GS_API = "https://api.mclo.gs/1/log";

public static String uploadToMclo_gs(String log) {
MakeSure.notEmpty(log, "Empty or null log provided!");
try {
HttpResponse<String> response = CLIENT.send(HttpRequest.newBuilder()
.uri(URI.create(MCLO_GS_API))
Expand Down
2 changes: 0 additions & 2 deletions dark-matter-analytics-mixpanel/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
dependencies {
implementation(include("com.mixpanel:mixpanel-java:${project.mixpanel_version}"))
implementation(include("org.json:json:${project.org_json_version}"))
api(project(path: ':dark-matter-analytics', configuration: 'namedElements'))
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package me.melontini.dark_matter.analytics.mixpanel;

import com.mixpanel.mixpanelapi.MessageBuilder;
import com.mixpanel.mixpanelapi.MixpanelAPI;
import com.google.gson.JsonObject;
import me.melontini.dark_matter.DarkMatterLog;
import me.melontini.dark_matter.analytics.Analytics;
import me.melontini.dark_matter.analytics.MessageHandler;
import me.melontini.dark_matter.analytics.Prop;
import me.melontini.dark_matter.analytics.mixpanel.api.MixpanelAPI;
import me.melontini.dark_matter.util.MakeSure;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -19,8 +19,6 @@
*/
public class MixpanelAnalytics {
private static final Map<String, Handler> MESSAGE_HANDLERS = new ConcurrentHashMap<>();
private static final MixpanelAPI MIXPANEL = new MixpanelAPI();
private static final MixpanelAPI MIXPANEL_EU = new MixpanelAPI("https://api-eu.mixpanel.com/track", "https://api-eu.mixpanel.com/engage", "https://api-eu.mixpanel.com/groups");

/**
* Initializes a Handler instance for the provided token and stores it in the MESSAGE_HANDLERS map.
Expand All @@ -30,7 +28,8 @@ public class MixpanelAnalytics {
* @return A MixpanelHandler instance for the provided token.
*/
public static Handler init(String token, boolean eu) {
return MESSAGE_HANDLERS.computeIfAbsent(token, k -> new Handler(eu, new MessageBuilder(token)));
MakeSure.notEmpty(token, "Invalid token provided! (null/empty)");
return MESSAGE_HANDLERS.computeIfAbsent(token, k -> new Handler(new MixpanelAPI(eu, token)));
}

/**
Expand All @@ -41,30 +40,25 @@ public static Handler init(String token, boolean eu) {
* @return The modified JSONObject.
*/
@Contract("_, _ -> param1")
public static JSONObject attachProps(JSONObject object, Prop @NotNull ... props) {
public static JsonObject attachProps(JsonObject object, Prop @NotNull ... props) {
for (Prop prop : props) {
object.put(getPropName(prop), prop.get());
object.addProperty(getPropName(prop), prop.get());
}
return object;
}

public static class Handler extends MessageHandler<MessageProvider> {
private final MessageBuilder messageBuilder;
private final MixpanelAPI mixpanel;

public Handler(boolean eu, MessageBuilder messageBuilder) {
this.messageBuilder = messageBuilder;
this.mixpanel = eu ? MIXPANEL_EU : MIXPANEL;
public Handler(MixpanelAPI api) {
this.mixpanel = api;
}

protected void sendInternal(MessageProvider consumer, boolean wait, boolean errors) {
if (!Analytics.isEnabled()) return;
if (!Analytics.isEnabled() && !Analytics.handleCrashes()) return;
Future<?> future = EXECUTOR.submit(() -> {
try {
JSONObject message = consumer.consume(messageBuilder);
if (message != null) {
mixpanel.sendMessage(message);
}
consumer.consume(this.mixpanel);
} catch (Exception e) {
if (errors) DarkMatterLog.error("Could not send analytics message", e);
}
Expand All @@ -85,7 +79,7 @@ public String getPropName(Prop prop) {
}

public interface MessageProvider {
JSONObject consume(MessageBuilder messageBuilder);
void consume(MixpanelAPI mixpanel);
}

public static String getPropName(Prop prop) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package me.melontini.dark_matter.analytics.mixpanel.api;

import com.google.gson.JsonArray;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;

import java.io.IOException;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* Basic API to interact with Mixpanel.
* <p>
* Only features basic functionality.
*/
//TODO (Union, Append, Remove) List properties
//TODO Group profiles (everything)
public class MixpanelAPI {
private static final String BASE_URL = "https://api.mixpanel.com";
private static final String EU_URL = "https://api-eu.mixpanel.com";

private final String endpoint;
private final HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofMillis(2000)).proxy(ProxySelector.getDefault()).build();
private final String token;

public MixpanelAPI(boolean eu, String projectToken) {
this.endpoint = eu ? EU_URL : BASE_URL;
this.token = projectToken;
}

public void trackEvent(String userID, String eventName, JsonObject props) {
JsonObject object = new JsonObject();
object.addProperty("event", eventName);

if (props == null) {
props = new JsonObject();
}

if (!props.has("token")) props.addProperty("token", this.token);
if (!props.has("time")) props.addProperty("time", System.currentTimeMillis());
if (!props.has("$insert_id")) props.addProperty("$insert_id", UUID.randomUUID().toString());
if (userID != null && !props.has("distinct_id")) props.addProperty("distinct_id", userID);

object.add("properties", props);

call("/track?ip=0", object);
}

public void set(String userID, JsonObject props) {
JsonObject object = profileDefaults(userID);
object.add("$set", props);
call("/engage?ip=0#profile-set", object);
}

public void setOnce(String userID, JsonObject props) {
JsonObject object = profileDefaults(userID);
object.add("$set_once", props);
call("/engage?ip=0#profile-set-once", object);
}

public void add(String userID, Map<String, Long> increment) {
JsonObject object = profileDefaults(userID);

JsonObject props = new JsonObject();
for (Map.Entry<String, Long> entry : increment.entrySet()) {
props.addProperty(entry.getKey(), entry.getValue());
}

object.add("$add", props);
call("/engage?ip=0#profile-numerical-add", object);
}

public void unset(String userID, String... props) {
JsonObject object = profileDefaults(userID);
JsonArray array = new JsonArray();
for (String prop : props) {
array.add(prop);
}
object.add("$unset", array);
call("/engage?ip=0#profile-unset", object);
}

public void unset(String userID, List<String> props) {
JsonObject object = profileDefaults(userID);
JsonArray array = new JsonArray();
for (String prop : props) {
array.add(prop);
}
object.add("$unset", array);
call("/engage?ip=0#profile-unset", object);
}

public void delete(String userID) {
JsonObject object = profileDefaults(userID);
object.add("$delete", JsonNull.INSTANCE);
call("/engage?ip=0#profile-delete", object);
}

public JsonObject profileDefaults(String userID) {
JsonObject object = new JsonObject();
if (!object.has("$token")) object.addProperty("$token", this.token);
if (!object.has("$time")) object.addProperty("$time", System.currentTimeMillis());
if (userID != null && !object.has("$distinct_id")) object.addProperty("$distinct_id", userID);
return object;
}

private void call(String endpoint, JsonObject... objects) {
JsonArray array = new JsonArray();
for (JsonObject object : objects) {
array.add(object);
}
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(this.endpoint + endpoint))
.header("accept", "text/plain")
.header("content-type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(array.toString()))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new RuntimeException("Status Code: " + response.statusCode() + " Body: " + response.body());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,15 @@


public class Analytics {
private static final UUID nullID = new UUID(0,0);
private static final Config CONFIG;
private static final UUID nullID = new UUID(0, 0);
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();

private static final Path CU_CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("cracker-util/analytics.properties");
private static final Path OLD_CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("dark-matter/analytics.properties");
private static final Config CONFIG;

static {
moveConfigIfExists();
CONFIG = loadConfig();
}

private static void moveConfigIfExists() {
if (Files.exists(CU_CONFIG_PATH)) {
try {
if (!Files.exists(OLD_CONFIG_PATH.getParent())) Files.createDirectories(OLD_CONFIG_PATH.getParent());
DarkMatterLog.info("Found old config at config/cracker_analytics.properties, moving to config/dark-matter/analytics.properties");
Files.move(CU_CONFIG_PATH, OLD_CONFIG_PATH);
} catch (IOException e) {
DarkMatterLog.error("Couldn't move old config!", e);
}
}
}

private static void upgradeToJson(Config config) {
if (Files.exists(OLD_CONFIG_PATH)) {
Properties properties = new Properties();
Expand All @@ -59,20 +44,21 @@ private static Config loadConfig() {
if (Files.exists(configPath)) {
try {
config = GSON.fromJson(Files.newBufferedReader(configPath), Config.class);
if (config.enabled && nullID.equals(config.userUUID)) config.userUUID = UUID.randomUUID();
if (!config.enabled) config.userUUID = nullID;
Files.write(configPath, GSON.toJson(config).getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
try {
Files.createDirectories(configPath.getParent());
Files.createFile(configPath);
if (config.enabled) config.userUUID = UUID.randomUUID();
Files.write(configPath, GSON.toJson(config).getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (!config.enabled) config.userUUID = nullID;
return config;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.melontini.dark_matter.analytics;

import me.melontini.dark_matter.util.MakeSure;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

Expand All @@ -12,15 +14,18 @@ public abstract class MessageHandler<T> {
public static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(r -> new Thread(r, "Dark Matter analytics thread"));

public final void send(T consumer, boolean wait, boolean errors) {
if (Analytics.isEnabled()) sendInternal(consumer, wait, errors);
MakeSure.notNull(consumer, "null consumer provided");
if (Analytics.isEnabled() || Analytics.handleCrashes()) sendInternal(consumer, wait, errors);
}

public final void send(T consumer, boolean wait) {
if (Analytics.isEnabled()) sendInternal(consumer, wait, false);
MakeSure.notNull(consumer, "null consumer provided");
if (Analytics.isEnabled() || Analytics.handleCrashes()) sendInternal(consumer, wait, false);
}

public final void send(T consumer) {
if (Analytics.isEnabled()) sendInternal(consumer, false, false);
MakeSure.notNull(consumer, "null consumer provided");
if (Analytics.isEnabled() || Analytics.handleCrashes()) sendInternal(consumer, false, false);
}

protected abstract void sendInternal(T consumer, boolean wait, boolean errors);
Expand Down
Loading

0 comments on commit 7f393c9

Please sign in to comment.