Josh Posted November 22, 2016 Share Posted November 22, 2016 Does anyone here know to use the REST API to do things? I am not familiar with this: http://restapidocs.gameanalytics.com/ I have CURL set up and it seems like this should be super simple but the docs are...no Leadwerks docs, let's say. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Rick Posted November 22, 2016 Share Posted November 22, 2016 http://restapidocs.gameanalytics.com/#routes Do the postman thing they are talking about. They have a button (in the link above) that look like it'll set it up for you to see how it works. From command line via curl it looks like posts are done via: curl -i -X POST -H "Content-Type:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/ -d '{"title":"- The Naked Scientists Podcast - Stripping Down Science","linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science","feed":"feed_placeholder","description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."}' The bit in '{}' is json where you are filling out fields that the api is expecting and should be in the docs. Quote Link to comment Share on other sites More sharing options...
martyj Posted November 22, 2016 Share Posted November 22, 2016 So it looks like you have to init the API first. POST /v2/<game_key>/init Your body data needs the following fields: platform, os_version, sdk_version The Docs ask for JSON to be submitted, but I'd imagine URLEncoding should work. Once you do that, you should be able to create different events here: POST /v2/<game_key>/events Depending on the category in your data, your fields change. For example, a Category of business appears to support the following fields: category, event_id, amount, currency, transaction_num, cart_type, receipt_info A Category even of type Resource requires the following fields: category, event_id, amount. Quote Link to comment Share on other sites More sharing options...
Josh Posted November 22, 2016 Author Share Posted November 22, 2016 Ah cool, that's enough to get me started. I'll work it out later. Thanks for pointing that out! Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted November 25, 2016 Author Share Posted November 25, 2016 I fail to see how the stuff in PostMan translates into CURL commands. I'm trying this but I just get "not found": curl.setWriteString() curl.setOptInt(CURLOPT_VERBOSE, 0) curl.setOptInt(CURLOPT_FOLLOWLOCATION, 1) curl.setOptString(CURLOPT_URL,"http://sandbox-api.gameanalytics.com/v2/5c6bcb5402204249437fb5a7a80a4959/init") curl.setOptString(CURLOPT_BODY,"{~qplatform~q: ~qios~q, ~qsdk_version~q: ~qrest api v2~q, ~qos_version~q: ~qios 8.2~q}") curl.perform() Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
martyj Posted November 25, 2016 Share Posted November 25, 2016 What you're setting the body to isn't valid json. What I would do is see if you can use CURLOPT_POSTFIELDS and pass it like this: curl.setOptString(CURLOPT_CURLOPT_POSTFIELDS, "platform=Windows&os_version=10&sdk_version=rest api v2"); What library of CURL are you using? I can try to get a quick example tomorrow morning of each of the different requests. Quote Link to comment Share on other sites More sharing options...
martyj Posted November 26, 2016 Share Posted November 26, 2016 Two Questions 1. Are you using the sandbox API keys for the sandbox API? 2. Are you creating the Authorization header correctly? From what I can tell, it's a hmac hash of your POST data using your secret key, then base64 encoding it I looked into CURL a little bit further. Here is my text example thanks to libcurl's example. /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at https://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ***************************************************************************/ /* <DESC> * simple HTTP POST using the easy interface * </DESC> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <curl/curl.h> struct MemoryStruct { char *memory; size_t size; }; static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct MemoryStruct *mem = (struct MemoryStruct *)userp; mem->memory = realloc(mem->memory, mem->size + realsize + 1); if(mem->memory == NULL) { /* out of memory! */ printf("not enough memory (realloc returned NULL)\n"); return 0; } memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; return realsize; } int main(void) { CURL *curl; CURLcode res; struct MemoryStruct chunk; chunk.memory = malloc(1); chunk.size = 0; char* postData = "{\"platform\":\"Windows\", \"os_version\":\"10\", \"sdk_version\":\"api v2\"}"; /* In windows, this will init the winsock stuff */ curl_global_init(CURL_GLOBAL_ALL); /* get a curl handle */ curl = curl_easy_init(); if(curl) { struct curl_slist *headers=NULL; headers = curl_slist_append(headers, "Content-Type: text/xml"); headers = curl_slist_append(headers, "Authorization: <token_from_hmac_sha256_hash_with_secret>"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); /* First set the URL that is about to receive our POST. This URL can just as well be a https:// URL if that is what should receive the data. */ curl_easy_setopt(curl, CURLOPT_URL, "http://api.gameanalytics.com/v2/<game_key_here>/init"); /* Now specify the POST data */ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postData)); /* Perform the request, res will get the return code */ res = curl_easy_perform(curl); /* Check for errors */ if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { printf("%s\n",chunk.memory); } /* always cleanup */ curl_easy_cleanup(curl); } curl_global_cleanup(); return 0; } To generate an Hmac hash I'd check out this code: https://github.com/unixpickle/LibOrange/blob/master/LibOrange/hmac-sha256.c Then all you have to do is to base64 encode that for your Authorization token. 1 Quote Link to comment Share on other sites More sharing options...
Rick Posted November 30, 2016 Share Posted November 30, 2016 While you are in LE adding curl can you make it so we can call out to any API? When it's a get call pass the string data received back to a callback function we specify and put this into the game launcher exe as this would be very handy. Quote Link to comment Share on other sites More sharing options...
Josh Posted November 30, 2016 Author Share Posted November 30, 2016 While you are in LE adding curl can you make it so we can call out to any API? When it's a get call pass the string data received back to a callback function we specify and put this into the game launcher exe as this would be very handy. You know I can't do that in the Game Launcher. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Rick Posted November 30, 2016 Share Posted November 30, 2016 You might want to check with Valve. Seeing as we can't save to file or execute anything from the game launcher I'm not sure what harm could come from allowing web API calls. Quote Link to comment Share on other sites More sharing options...
Josh Posted November 30, 2016 Author Share Posted November 30, 2016 That's true. What other APIs do you want to call? Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Rick Posted November 30, 2016 Share Posted November 30, 2016 My own. I just would want the ability to call any web api. This could allow some basic turn based multiplayer games on the launcher which would be nice. Quote Link to comment Share on other sites More sharing options...
martyj Posted December 1, 2016 Share Posted December 1, 2016 While you're in there adding http requests with libcurl, you should make them available for other C++ developers without using libcurl . It would be nice to do a request to a URL with a map as GET or POST params. Quote Link to comment Share on other sites More sharing options...
gamecreator Posted December 1, 2016 Share Posted December 1, 2016 While you're in there adding http requests with libcurl, you should make them available for other C++ developers without using libcurl What do you mean by "without using libcurl?" Not like how we can call Steam and Newton functions directly? Quote Link to comment Share on other sites More sharing options...
Josh Posted December 1, 2016 Author Share Posted December 1, 2016 Got this compiling. I really do not understand the authorization stuff. struct MemoryStruct { char *memory; size_t size; }; curl_global_init(CURL_GLOBAL_DEFAULT); CURL* curl = curl_easy_init(); if (curl) { char* postData = "{\"platform\":\"Windows\", \"os_version\":\"10\", \"sdk_version\":\"api v2\"}"; struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "Content-Type: text/xml"); headers = curl_slist_append(headers, "Authorization: <token_from_hmac_sha256_hash_with_secret>");// wtf? curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, "http://sandbox-api.gameanalytics.com/v2/5c6bcb5402204249437fb5a7a80a4959/init"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postData)); CURLcode res = curl_easy_perform(curl); struct MemoryStruct chunk; chunk.memory = (char*)malloc(1); chunk.size = 0; /* Check for errors */ if (res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); } else { printf("%s\n", chunk.memory); } curl_easy_cleanup(curl); } curl_global_cleanup(); Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Rick Posted December 1, 2016 Share Posted December 1, 2016 They have stored on their side your key so you had to register with them to get one I assume. This is unique per person who registered and tells the API provider that you're ok to use their API. I'm kind of shocked the URL isn't https though. Also I would assume each game dev would need their own key or else we'd all be using yours which you don't want. That would mean we have to sign up, get a key, and tell LE to use our key. Quote Link to comment Share on other sites More sharing options...
martyj Posted December 1, 2016 Share Posted December 1, 2016 I think their python example gives the best use case of how their auth works. http://restapidocs.gameanalytics.com/#python-example # sandbox game keys game_key = "5c6bcb5402204249437fb5a7a80a4959" secret_key = "16813a12f718bc5c620f56944e1abc3ea13ccbac" # sandbox API urls url_init = 'http://sandbox-api.gameanalytics.com/v2/' + game_key + '/init' init_payload = { 'platform': platform, 'os_version': os_version, 'sdk_version': sdk_version } init_payload_json = json.dumps(init_payload) headers = { 'Authorization': hmac_hash_with_secret(init_payload_json, secret_key), 'Content-Type': 'application/json' } try: init_response = requests.post(url_init, data=init_payload_json, headers=headers) except: print "Init request failed!" sys.exit() Notice the line 'Authorization': hmac_hash_with_secret(event_list_json, secret_key), Makes use of this function def hmac_hash_with_secret(message, key): return base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()) Create hmac hash based upon key and message. Base64 encode hmac hash. Quote Link to comment Share on other sites More sharing options...
Josh Posted December 1, 2016 Author Share Posted December 1, 2016 I think the token is a combination of the secret key (which I have) and the post body. How are these combined? //Public and private keys std::string gamekey = "bacdf164822d96d83b4d185357e633f5"; std::string secretkey = "xxxxxxxxxx"; //Post data std::string postData = "{\"platform\":\"Windows\", \"os_version\":\"10\", \"sdk_version\":\"api v2\"}"; //Construct authentication token std::string token = "";//hash_some_stuff_or_something(secretkey,postData); Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
martyj Posted December 1, 2016 Share Posted December 1, 2016 They are combined with this hash https://github.com/unixpickle/LibOrange/blob/master/LibOrange/hmac-sha256.c Then base 64 encoding the result of that. I can give you a better example Saturday morning. Quote Link to comment Share on other sites More sharing options...
Josh Posted December 1, 2016 Author Share Posted December 1, 2016 That would be great. I've got the class written and integrated in the editor and Lua: #include "../Leadwerks.h" namespace Leadwerks { bool Analytics::disabled = false; bool Analytics::initialized = false; long Analytics::starttime = 0; std::string Analytics::publickey; std::string Analytics::privatekey; std::string Analytics::GetUserID() { if (Steamworks::initialized) return String(Steamworks::GetUserID()); return ""; #ifdef _WIN32 /*GUID guid; CoCreateGuid(&guid); std::string userid = String(long(guid.Data1)) + String(guid.Data2) + String(guid.Data3); for (int i = 0; i < 8; ++i) { userid += String(guid.Data4); } return userid;*/ #elif __linux__ //void uuid_generate(); #endif } int Analytics::GetUserTeam() { std::string userid = GetUserID(); if ((int(userid[userid.length() - 1]) & 2) == 1) return 1; return 0; } bool Analytics::SetKeys(const std::string& gamekey, const std::string& secretkey) { if (initialized) return false; publickey = gamekey; privatekey = secretkey; return Initialize(); } void Analytics::Disable() { disabled = true; } void Analytics::Shutdown() { if (initialized) { SendEvent("session_end", "", "\"length\" = \""+String((Time::Millisecs() - starttime) / 1000)+"\""); curl_global_cleanup(); initialized = false; } } bool Analytics::Initialize() { if (disabled) return false; struct MemoryStruct { char *memory; size_t size; }; CURL* curl = NULL; CURLcode res; std::string postData; std::string token; struct curl_slist *headers = NULL; std::string userID = GetUserID(); if (initialized) return true; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); //Post data postData = std::string("{\"platform\":\"") + System::GetPlatformName() + "\", \"os_version\":\"0\", \"sdk_version\":\"api v2\"}"; //Construct authentication token token = "";//hash_some_stuff_or_something(privatekey,postData)???; //Construct header headers = NULL; headers = curl_slist_append(headers, "Content-Type: text/xml"); headers = curl_slist_append(headers, (std::string("Authorization: ") + token).c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); //Set URL curl_easy_setopt(curl, CURLOPT_URL, (std::string("http://sandbox-api.gameanalytics.com/v2/") + publickey + std::string("/init")).c_str()); //Set body curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)postData.length()); //Send res = curl_easy_perform(curl); //Get the result if (res != CURLE_OK) { fprintf(stderr, "Error: curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); curl_easy_cleanup(curl); return false; } else { struct MemoryStruct chunk; chunk.memory = (char*)malloc(1); chunk.size = 0; printf("%s\n", chunk.memory); initialized = true; } curl_easy_cleanup(curl); initialized = true; starttime = Time::Millisecs(); SendEvent("user",""); return true; } bool Analytics::SendEvent(const std::string& category, const std::string& eventid, const std::string& params) { if (disabled) return false; if (!initialized) return false; if (publickey == "") return false; if (privatekey == "") return false; struct MemoryStruct { char *memory; size_t size; }; CURL* curl = NULL; CURLcode res; std::string postData; std::string token; struct curl_slist *headers = NULL; std::string userID = GetUserID(); curl = curl_easy_init(); //Post data postData = "{"; postData += "\"session_id\": \"0\""; postData += ", "; postData += std::string("\"user_id\": \"" + userID + "\""); if (eventid != "") { postData += ", "; postData += std::string("\"event_id\": \"" + eventid + "\""); } if (params != "") { postData += ", "; postData += params; } postData += "}"; //Construct authentication token token = "";//hash_some_stuff_or_something(privatekey,postData)???; //Construct header headers = NULL; headers = curl_slist_append(headers, "Content-Type: text/xml"); headers = curl_slist_append(headers, (std::string("Authorization: ") + token).c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); //Set URL curl_easy_setopt(curl, CURLOPT_URL, (std::string("http://api.gameanalytics.com/v2/") + publickey + "/" + category).c_str()); //Set body curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)postData.length()); //Send res = curl_easy_perform(curl); //Get the result if (res != CURLE_OK) { fprintf(stderr, "Error: curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); curl_easy_cleanup(curl); return false; } else { struct MemoryStruct chunk; chunk.memory = (char*)malloc(1); chunk.size = 0; printf("%s\n", chunk.memory); } curl_easy_cleanup(curl); return true; } } Usage is very easy: static void SetKeys(const std::string& gamekey, const std::string& secretkey) static bool SendEvent(category, const std::string& eventid, params="") Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Josh Posted December 1, 2016 Author Share Posted December 1, 2016 Here's why you guys should have this: Setting a new funnel up in a single level would be super useful. You could see how many people can find the blue key, can find the blue door, move on to the next step, etc. And instead of just listening to one person who you aren't sure is really representative of everyone, you will actually be able to see how people play your game. 1 Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
gamecreator Posted December 1, 2016 Share Posted December 1, 2016 I was honestly very curious how many people got to the last level of my game and how many beat it. Quote Link to comment Share on other sites More sharing options...
Josh Posted December 1, 2016 Author Share Posted December 1, 2016 I was honestly very curious how many people got to the last level of my game and how many beat it. I think you would have seen a huge dropoff at that first hard part...I think it was level 3. But this will let you know whether the game is too hard if I just suck at jumping. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
gamecreator Posted December 1, 2016 Share Posted December 1, 2016 You're likely right. I sometimes make things a little too difficult just to challenge myself, then forget to turn it back down. Quote Link to comment Share on other sites More sharing options...
Josh Posted December 2, 2016 Author Share Posted December 2, 2016 This is really cool. I've added scripts for the types of events that are allowed: Script.status=0--choice "Status" "Start, Complete, Fail" Script.levelname=""--string "Level name" function Script:Send(score, attempt_num)--in Analytics:SendProgressionEvent(self.status,levelname,score,attempt_num) end You can have an end-of-level trigger activate an object to send an event for the level being completed, and if the player dies you can have them send an event for them to fail the level. Quote My job is to make tools you love, with the features you want, and performance you can't live without. Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.