diff --git a/data/gacha_records.html b/data/gacha_records.html new file mode 100644 index 000000000..21270a046 --- /dev/null +++ b/data/gacha_records.html @@ -0,0 +1,176 @@ + + + + + + + +
+

Gacha Records

+

+
+
+ + + + + + + +
TimeItem
+
+ +
+ + + + + \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index b2dae0446..2a247b46e 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -3,11 +3,14 @@ package emu.grasscutter.database; import java.util.List; import com.mongodb.client.result.DeleteResult; +import dev.morphia.query.FindOptions; +import dev.morphia.query.Sort; import dev.morphia.query.experimental.filters.Filters; import emu.grasscutter.GameConstants; import emu.grasscutter.game.Account; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.friends.Friendship; +import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; @@ -78,6 +81,11 @@ public final class DatabaseHelper { return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("token", token)).first(); } + public static Account getAccountBySessionKey(String sessionKey) { + if(sessionKey == null) return null; + return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("sessionKey", sessionKey)).first(); + } + public static Account getAccountById(String uid) { return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("_id", uid)).first(); } @@ -181,5 +189,36 @@ public final class DatabaseHelper { )).first(); } + public static List getGachaRecords(int ownerId, int page, int gachaType){ + return getGachaRecords(ownerId, page, gachaType, 10); + } + + public static List getGachaRecords(int ownerId, int page, int gachaType, int pageSize){ + return DatabaseManager.getDatastore().find(GachaRecord.class).filter( + Filters.eq("ownerId", ownerId), + Filters.eq("gachaType", gachaType) + ).iterator(new FindOptions() + .sort(Sort.descending("transactionDate")) + .skip(pageSize * page) + .limit(pageSize) + ).toList(); + } + + public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType){ + return getGachaRecordsMaxPage(ownerId, page, gachaType, 10); + } + + public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize){ + long count = DatabaseManager.getDatastore().find(GachaRecord.class).filter( + Filters.eq("ownerId", ownerId), + Filters.eq("gachaType", gachaType) + ).count(); + return count / 10 + (count % 10 > 0 ? 1 : 0 ); + } + + public static void saveGachaRecord(GachaRecord gachaRecord){ + DatabaseManager.getDatastore().save(gachaRecord); + } + public static char AWJVN = 'e'; } diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index c6a5f329a..2376451db 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -16,6 +16,7 @@ import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.game.Account; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.friends.Friendship; +import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; @@ -28,7 +29,7 @@ public final class DatabaseManager { private static Datastore dispatchDatastore; private static final Class[] mappedClasses = new Class[] { - DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class + DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, GachaRecord.class }; public static Datastore getDatastore() { diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 9b54c924f..2317af38e 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -91,9 +91,21 @@ public class GachaBanner { return eventChance; } + @Deprecated public GachaInfo toProto() { - String record = "http://" + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getDispatchOptions().Ip : Grasscutter.getConfig().getDispatchOptions().PublicIp) + "/gacha"; - + return toProto(""); + } + public GachaInfo toProto(String sessionKey) { + String record = "https://" + + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? + Grasscutter.getConfig().getDispatchOptions().Ip : + Grasscutter.getConfig().getDispatchOptions().PublicIp) + + ":" + + Integer.toString(Grasscutter.getConfig().getDispatchOptions().PublicPort == 0 ? + Grasscutter.getConfig().getDispatchOptions().Port : + Grasscutter.getConfig().getDispatchOptions().PublicPort) + + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; + // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() .setGachaType(this.getGachaType()) .setScheduleId(this.getScheduleId()) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 5cd484e9a..cd1b5ea94 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -14,6 +14,7 @@ import com.sun.nio.file.SensitivityWatchEventModifier; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.ItemData; +import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.gacha.GachaBanner.BannerType; import emu.grasscutter.game.inventory.GameItem; @@ -196,6 +197,10 @@ public class GachaManager { if (itemData == null) { continue; } + + // Write gacha record + GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), gachaType); + DatabaseHelper.saveGachaRecord(gachaRecord); // Create gacha item GachaItem.Builder gachaItem = GachaItem.newBuilder(); @@ -321,6 +326,7 @@ public class GachaManager { } } + @Deprecated private synchronized GetGachaInfoRsp createProto() { GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345); @@ -330,12 +336,26 @@ public class GachaManager { return proto.build(); } + + private synchronized GetGachaInfoRsp createProto(String sessionKey) { + GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345); + + for (GachaBanner banner : getGachaBanners().values()) { + proto.addGachaInfoList(banner.toProto(sessionKey)); + } + + return proto.build(); + } + @Deprecated public GetGachaInfoRsp toProto() { if (this.cachedProto == null) { this.cachedProto = createProto(); } - return this.cachedProto; } + + public GetGachaInfoRsp toProto(String sessionKey) { + return createProto(sessionKey); + } } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaRecord.java b/src/main/java/emu/grasscutter/game/gacha/GachaRecord.java new file mode 100644 index 000000000..ffaf983b6 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/gacha/GachaRecord.java @@ -0,0 +1,75 @@ +package emu.grasscutter.game.gacha; + +import java.util.Date; + +import org.bson.types.ObjectId; + +import dev.morphia.annotations.*; + +@Entity(value = "gachas", useDiscriminator = false) +public class GachaRecord { + @Id private ObjectId id; + + @Indexed private int ownerId; + + private Date transactionDate; + private int itemID; + @Indexed private int gachaType; + + public GachaRecord() {} + + public GachaRecord(int itemId ,int ownerId, int gachaType){ + this.transactionDate = new Date(); + this.itemID = itemId; + this.ownerId = ownerId; + this.gachaType = gachaType; + } + + public int getOwnerId() { + return ownerId; + } + + public void setOwnerId(int ownerId) { + this.ownerId = ownerId; + } + + public int getGachaType() { + return gachaType; + } + + public void setGachaType(int type) { + this.gachaType = type; + } + + public Date getTransactionDate() { + return transactionDate; + } + + public void setTransactionDate(Date transactionDate) { + this.transactionDate = transactionDate; + } + + public int getItemID() { + return itemID; + } + + public void setItemID(int itemID) { + this.itemID = itemID; + } + + public ObjectId getId(){ + return id; + } + + public void setId(ObjectId id) { + this.id = id; + } + + public String toString() { + return toJsonString(); + } + public String toJsonString() { + return "{\"time\": " + this.transactionDate.getTime() + ",\"item\":" + this.itemID + "}"; + } + +} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index b7e1e34ee..7b5418e15 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -18,6 +18,7 @@ import emu.grasscutter.server.dispatch.json.*; import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; +import emu.grasscutter.server.http.gacha.GachaRecordHandler; import emu.grasscutter.utils.FileUtils; import express.Express; import org.eclipse.jetty.server.Connector; @@ -485,7 +486,7 @@ public final class DispatchServer { // webstatic-sea.hoyoverse.com httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); - httpServer.get("/gacha", (req, res) -> res.send("Gacha")); + httpServer.get("/gacha", new GachaRecordHandler()); httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); Grasscutter.getLogger().info("[Dispatch] Dispatch server started on port " + httpServer.raw().port()); diff --git a/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java new file mode 100644 index 000000000..0798a150f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/gacha/GachaRecordHandler.java @@ -0,0 +1,52 @@ +package emu.grasscutter.server.http.gacha; + +import java.io.File; +import java.io.IOException; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.utils.FileUtils; +import express.http.HttpContextHandler; +import express.http.Request; +import express.http.Response; + +public final class GachaRecordHandler implements HttpContextHandler { + String render_template; + public GachaRecordHandler() { + File template = new File(Grasscutter.getConfig().DATA_FOLDER + "gacha_records.html"); + if (template.exists()) { + // Load from cache + render_template = new String(FileUtils.read(template)); + } else { + render_template = "{{REPLACE_RECORD}}"; + } + } + + @Override + public void handle(Request req, Response res) throws IOException { + // Grasscutter.getLogger().info( req.query().toString() ); + String sessionKey = req.query("s"); + int page = 0; + int gachaType = 0; + if (req.query("p") != null) { + page = Integer.valueOf(req.query("p")); + } + + if (req.query("gachaType") != null) { + gachaType = Integer.valueOf(req.query("gachaType")); + } + + Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); + if (account != null) { + String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), page, gachaType).toString(); + // Grasscutter.getLogger().info(records); + String response = render_template.replace("{{REPLACE_RECORD}}", records) + .replace("{{REPLACE_MAXPAGE}}", String.valueOf(DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType))); + + res.send(response); + } else { + res.send("404"); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java index 6c4c703a8..76d267b99 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetGachaInfoReq.java @@ -11,7 +11,10 @@ public class HandlerGetGachaInfoReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager())); + session.send(new PacketGetGachaInfoRsp(session.getServer().getGachaManager(), + // TODO: use other Nonce/key insteadof session key to ensure the overall security for the player + session.getPlayer().getAccount().getSessionKey()) + ); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java index 89af334a5..84d857681 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetGachaInfoRsp.java @@ -6,9 +6,17 @@ import emu.grasscutter.net.packet.PacketOpcodes; public class PacketGetGachaInfoRsp extends BasePacket { + @Deprecated public PacketGetGachaInfoRsp(GachaManager manager) { super(PacketOpcodes.GetGachaInfoRsp); this.setData(manager.toProto()); } + + public PacketGetGachaInfoRsp(GachaManager manager, String sessionKey) { + super(PacketOpcodes.GetGachaInfoRsp); + + this.setData(manager.toProto(sessionKey)); + } + }