diff --git a/Assets/PlayroomKit/modules/Helpers/Helpers.cs b/Assets/PlayroomKit/modules/Helpers/Helpers.cs index 9c8bd9ef..fede4d94 100644 --- a/Assets/PlayroomKit/modules/Helpers/Helpers.cs +++ b/Assets/PlayroomKit/modules/Helpers/Helpers.cs @@ -24,7 +24,7 @@ public static string SerializeInitOptions(InitOptions options) node["roomCode"] = options.roomCode; node["skipLobby"] = options.skipLobby; node["reconnectGracePeriod"] = options.reconnectGracePeriod; - node["discord"] = options.discord; + node["discord"] = SerializeDiscord(options.discord); node["persistentMode"] = options.persistentMode; if (options.avatars != null) @@ -98,6 +98,27 @@ public static string SerializeInitOptions(InitOptions options) return node.ToString(); } + private static JSONNode SerializeDiscord(object discord) + { + if (discord is bool b) + return new JSONBool(b); + + if (discord is DiscordOptions opts) + { + var discordNode = new JSONObject(); + if (opts.Scope != null) + { + var scopeArray = new JSONArray(); + foreach (var s in opts.Scope) + scopeArray.Add(s); + discordNode["scope"] = scopeArray; + } + return discordNode; + } + + return JSONNull.CreateOrGet(); + } + private static JSONNode ConvertValueToJSON(object value) { if (value is string stringValue) diff --git a/Assets/PlayroomKit/modules/Options/DiscordOptions.cs b/Assets/PlayroomKit/modules/Options/DiscordOptions.cs new file mode 100644 index 00000000..8143fb3e --- /dev/null +++ b/Assets/PlayroomKit/modules/Options/DiscordOptions.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Playroom +{ + public class DiscordOptions + { + public string? Prompt; + public List? Scope; + public string? State; + } +} + \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/Options/DiscordOptions.cs.meta b/Assets/PlayroomKit/modules/Options/DiscordOptions.cs.meta new file mode 100644 index 00000000..dc6101bc --- /dev/null +++ b/Assets/PlayroomKit/modules/Options/DiscordOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4d9114789376d04bbfbc54b1f87456c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/PlayroomKit/modules/Options/InitOptions.cs b/Assets/PlayroomKit/modules/Options/InitOptions.cs index 4cde1a3e..c2a74c9d 100644 --- a/Assets/PlayroomKit/modules/Options/InitOptions.cs +++ b/Assets/PlayroomKit/modules/Options/InitOptions.cs @@ -15,10 +15,9 @@ public class InitOptions public bool skipLobby = false; public int reconnectGracePeriod = 0; public int? maxPlayersPerRoom; - + [CanBeNull] public string gameId; - public bool discord = false; public bool persistentMode = false; public Dictionary defaultStates = null; @@ -27,6 +26,9 @@ public class InitOptions private object matchmakingField; private object turnBasedField; + public object discord = false; + + public object matchmaking { get => matchmakingField; @@ -59,5 +61,20 @@ public object turnBased } } } + public object discordOptions + { + get => discord; + set + { + if (value is bool || value is DiscordOptions) + { + discord = value; + } + else + { + throw new ArgumentException("discordOptions must be a boolean or a DiscordOptions object."); + } + } + } } } \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/Store/ServerReward.cs b/Assets/PlayroomKit/modules/Store/ServerReward.cs new file mode 100644 index 00000000..573c118e --- /dev/null +++ b/Assets/PlayroomKit/modules/Store/ServerReward.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using SimpleJSON; +using UnityEngine; + +namespace Playroom +{ + [Serializable] + public class ServerReward + { + public string serverId; + public string id; + public string title; + public string description; + public string image; + public string message; + public bool active; + public string key; + public string skuId; + public string type; + public bool status; + + private static ServerReward FromJSONNode(JSONNode node) + { + ServerReward reward = new ServerReward + { + serverId = node["server_id"]?.Value ?? string.Empty, + id = node["id"]?.Value ?? string.Empty, + title = node["title"]?.Value ?? string.Empty, + description = node["description"]?.Value ?? string.Empty, + image = node["image"]?.Value ?? string.Empty, + message = node["message"]?.Value ?? string.Empty, + active = node["active"] != null && node["active"].AsBool, + key = node["key"]?.Value ?? string.Empty, + skuId = node["sku_id"]?.Value ?? string.Empty, + type = node["type"]?.Value ?? string.Empty, + status = node["status"] != null && node["status"].AsBool + }; + + return reward; + } + + public static List FromJSON(string jsonString) + { + List serverRewards = new(); + JSONNode root = JSON.Parse(jsonString); + + if (!root.IsArray) + Debug.LogWarning("Expected an array"); + + foreach (JSONNode item in root.AsArray) + { + var data = FromJSONNode(item); + serverRewards.Add(data); + } + + return serverRewards; + } + } +} \ No newline at end of file diff --git a/Assets/PlayroomKit/modules/Store/ServerReward.cs.meta b/Assets/PlayroomKit/modules/Store/ServerReward.cs.meta new file mode 100644 index 00000000..8af0862c --- /dev/null +++ b/Assets/PlayroomKit/modules/Store/ServerReward.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be07406aab4f4f04383c5e2b836c0607 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/TestScene.unity b/Assets/Scenes/TestScene.unity index 32c6ca94..bed4987e 100644 --- a/Assets/Scenes/TestScene.unity +++ b/Assets/Scenes/TestScene.unity @@ -286,11 +286,16 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: dc3a2b9cdc24aab40906ce4bcdee9943, type: 3} m_Name: m_EditorClassIdentifier: + gameId: FmOBeUfQO2AOLNIrJNSJ baseUrl: https://ws.joinplayroom.com/api/store - gameApiKey: + gameApiKey: 510a71af-3a69-4f5d-9b9b-296a1871e624 + token: eyJhbGciOiJIUzI1NiJ9.eyJkaXNjb3JkSWQiOiI0NzY3MDk1MjQwMTE2MTQyMTkiLCJyb29tSWQiOiJEQ1JEX2ktMTM4OTk1NjgzMTE0NDgzNzE0MC1wYy0xMzcxOTI3NzM0MTY2NDg3MDkwIiwiZ2FtZUlkIjoiRm1PQmVVZlFPMkFPTE5JckpOU0oiLCJndWlsZElkIjpudWxsLCJjaGFubmVsSWQiOiIxMzcxOTI3NzM0MTY2NDg3MDkwIiwiYWNjZXNzVG9rZW4iOiJNVE0zTURReE5qSTRORFk0T0RNeU1qWTRNZy5CNjdvckR6UjV1V0Q4dWE2TlZlUzNtczRpdEM2cGoiLCJhdXRoIjoiZGlzY29yZCIsInQiOjE3NTE0NjE5NzF9.Te4Fgg6KrSs9bqsxKPlyDYZotcumECoGCi_Q5cWCCAM text: {fileID: 1547749} skus: [] entitlements: [] + discordSkus: [] + discordEntitlements: [] + serverRewards: [] --- !u!4 &1054460207 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/GameManager.cs b/Assets/Scripts/GameManager.cs index 293f4aec..fa3fc150 100644 --- a/Assets/Scripts/GameManager.cs +++ b/Assets/Scripts/GameManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Text; using Discord; using Playroom; using SimpleJSON; @@ -11,7 +12,8 @@ public class GameManager : MonoBehaviour { #region Fields - [SerializeField] private string baseUrl = "https://ws.joinplayroom.com/api/store"; + [SerializeField] private string gameId = "FmOBeUfQO2AOLNIrJNSJ"; + [SerializeField] private string baseUrl = "https://ws.joinplayroom.com/api/"; [SerializeField] private string gameApiKey; [SerializeField] @@ -44,6 +46,9 @@ public class GameManager : MonoBehaviour [SerializeField] private List discordEntitlements = new List(); + [SerializeField] + private List serverRewards = new(); + #endregion @@ -76,11 +81,11 @@ void Awake() { if (Application.absoluteURL.Contains("discord")) { - baseUrl = ".proxy/_ws/api/store"; + baseUrl = ".proxy/_ws/api"; } else { - baseUrl = "https://ws.joinplayroom.com/api/store"; + baseUrl = "https://ws.joinplayroom.com/api"; } // Initialize fake Discord SKUs @@ -121,9 +126,109 @@ private void Start() { gameId = "FmOBeUfQO2AOLNIrJNSJ", maxPlayersPerRoom = 2, - discord = true, + discord = new DiscordOptions() + { + Scope = new() { "applications.commands", "guilds" } + }, + + // discord = true }, OnLaunchCallBack); } + public IEnumerator GetActiveServerRewards( + string gameId, + string jwtToken, + string gameApiKey, + Action onSuccess, + Action onError + ) + { + // Build full URL with query parameter + string url = $"{baseUrl}/discord/server-rewards?gameId={UnityWebRequest.EscapeURL(gameId)}"; + + using (UnityWebRequest request = UnityWebRequest.Get(url)) + { + request.SetRequestHeader("Authorization", $"Bearer {jwtToken}"); + request.SetRequestHeader("x-game-api", gameApiKey); + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.ConnectionError || + request.result == UnityWebRequest.Result.ProtocolError) + { + onError?.Invoke($"Error fetching server rewards: {request.error}"); + } + else + { + onSuccess?.Invoke(request.downloadHandler.text); + } + } + } + + public IEnumerator GrantServerReward( + string gameId, + string jwtToken, + string gameApiKey, + string rewardId, + Action onSuccess, + Action onError +) + { + string url = $"{baseUrl}/discord/server-rewards?gameId={UnityWebRequest.EscapeURL(gameId)}"; + + var bodyJson = $"{{\"rewardId\":\"{rewardId}\"}}"; + + Debug.LogWarning($"body: {bodyJson}"); + + byte[] bodyRaw = Encoding.UTF8.GetBytes(bodyJson); + using (var request = new UnityWebRequest(url, "POST")) + { + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + request.downloadHandler = new DownloadHandlerBuffer(); + + request.SetRequestHeader("Content-Type", "application/json"); + request.SetRequestHeader("Authorization", $"Bearer {jwtToken}"); + request.SetRequestHeader("x-game-api", gameApiKey); + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.ConnectionError || + request.result == UnityWebRequest.Result.DataProcessingError) + { + onError?.Invoke($"Error granting reward: {request.error}", request.downloadHandler.text); + yield break; + } + + long code = request.responseCode; + string text = request.downloadHandler.text; + + switch (code) + { + case 200: + onSuccess?.Invoke(text); + break; + + case 409: + onError?.Invoke("Reward already granted.", request.downloadHandler.text); + break; + + case 404: + onError?.Invoke("Server not yet joined. Please direct the player to join the Discord server first.", request.downloadHandler.text); + break; + + case 400: + onError?.Invoke("Invalid request. Check that gameId and rewardId are correct.", request.downloadHandler.text); + break; + + case 500: + onError?.Invoke("Server-side error. Please retry or contact support: " + request.error, request.downloadHandler.text); + break; + + default: + onError?.Invoke($"Unexpected response ({code}): {text}", request.downloadHandler.text); + break; + } + } + } private void Update() { @@ -221,6 +326,71 @@ private void Update() }); } + if (Input.GetKeyDown(KeyCode.M)) + { +#if !UNITY_EDITOR && UNITY_WEBGL + StartCoroutine(GetActiveServerRewards(gameId, playroomKit.GetPlayroomToken(), gameApiKey, (result) => + { + serverRewards = ServerReward.FromJSON(result); + text.text = "id :" + serverRewards[0].id + " - server id: " + serverRewards[0].serverId; + }, (error) => text.text = error)); +#elif UNITY_EDITOR + StartCoroutine(GetActiveServerRewards(gameId, token, gameApiKey, (result) => + { + serverRewards = ServerReward.FromJSON(result); + text.text = "id :" + serverRewards[0].id + " - server id: " + serverRewards[0].serverId; + }, (error) => text.text = error)); +#endif + } + + + + if (Input.GetKeyDown(KeyCode.N)) + { + +#if !UNITY_EDITOR && UNITY_WEBGL + StartCoroutine(GrantServerReward(gameId, playroomKit.GetPlayroomToken(), gameApiKey, serverRewards[0].serverId, (result) => + { + text.text = result; + }, (error, response) => + { + text.text = response; + Debug.LogError(error); + })); +#else + StartCoroutine(GrantServerReward(gameId, token, gameApiKey, serverRewards[0].serverId, (result) => + { + text.text = result; + }, (error, response) => + { + text.text = response; + Debug.LogError(error); + })); +#endif + } + + if (Input.GetKeyDown(KeyCode.L)) + { +#if !UNITY_EDITOR && UNITY_WEBGL + StartCoroutine(GrantServerReward(gameId, playroomKit.GetPlayroomToken(), gameApiKey, serverRewards[0].id, (result) => + { + text.text = result; + }, (error, response) => + { + text.text = response; + Debug.LogError(error); + })); +#elif UNITY_EDITOR + StartCoroutine(GrantServerReward(gameId, token, gameApiKey, serverRewards[0].id, (result) => + { + text.text = result; + }, (error, response) => + { + text.text = response; + Debug.LogError(error); + })); +#endif + } } #endregion @@ -232,7 +402,7 @@ public void FetchSKUS(Action onRequestComplete = null) private IEnumerator GetSKUS(Action onRequestComplete = null) { - var url = $"{baseUrl}/sku?gameId={UnityWebRequest.EscapeURL("FmOBeUfQO2AOLNIrJNSJ")}&platform={UnityWebRequest.EscapeURL("discord")}"; + var url = $"{baseUrl}/store/sku?gameId={UnityWebRequest.EscapeURL("FmOBeUfQO2AOLNIrJNSJ")}&platform={UnityWebRequest.EscapeURL("discord")}"; using (var req = UnityWebRequest.Get(url)) { @@ -257,7 +427,7 @@ public void FetchEntitlements(Action onRequestComplete) private IEnumerator GetEntitlements(Action onRequestComplete) { - var url = $"{baseUrl}/entitlement?gameId={UnityWebRequest.EscapeURL("FmOBeUfQO2AOLNIrJNSJ")}"; + var url = $"{baseUrl}/store/entitlement?gameId={UnityWebRequest.EscapeURL("FmOBeUfQO2AOLNIrJNSJ")}"; using (var req = UnityWebRequest.Get(url)) {