Path: blob/master/dep/rcheevos/src/rapi/rc_api_user.c
4255 views
#include "rc_api_user.h"1#include "rc_api_common.h"2#include "rc_api_runtime.h"3#include "rc_consoles.h"45#include "../rc_version.h"67#include <stdlib.h>8#include <string.h>910/* --- Login --- */1112int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {13return rc_api_init_login_request_hosted(request, api_params, &g_host);14}1516int rc_api_init_login_request_hosted(rc_api_request_t* request,17const rc_api_login_request_t* api_params,18const rc_api_host_t* host) {19rc_api_url_builder_t builder;2021rc_api_url_build_dorequest_url(request, host);2223if (!api_params->username || !*api_params->username)24return RC_INVALID_STATE;2526rc_url_builder_init(&builder, &request->buffer, 48);27rc_url_builder_append_str_param(&builder, "r", "login2");28rc_url_builder_append_str_param(&builder, "u", api_params->username);2930if (api_params->password && api_params->password[0])31rc_url_builder_append_str_param(&builder, "p", api_params->password);32else if (api_params->api_token && api_params->api_token[0])33rc_url_builder_append_str_param(&builder, "t", api_params->api_token);34else35return RC_INVALID_STATE;3637request->post_data = rc_url_builder_finalize(&builder);38request->content_type = RC_CONTENT_TYPE_URLENCODED;3940return builder.result;41}4243int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {44rc_api_server_response_t response_obj;4546memset(&response_obj, 0, sizeof(response_obj));47response_obj.body = server_response;48response_obj.body_length = rc_json_get_object_string_length(server_response);4950return rc_api_process_login_server_response(response, &response_obj);51}5253int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {54int result;55rc_json_field_t fields[] = {56RC_JSON_NEW_FIELD("Success"),57RC_JSON_NEW_FIELD("Error"),58RC_JSON_NEW_FIELD("Code"),59RC_JSON_NEW_FIELD("User"),60RC_JSON_NEW_FIELD("Token"),61RC_JSON_NEW_FIELD("Score"),62RC_JSON_NEW_FIELD("SoftcoreScore"),63RC_JSON_NEW_FIELD("Messages"),64RC_JSON_NEW_FIELD("AvatarUrl")65};6667memset(response, 0, sizeof(*response));68rc_buffer_init(&response->response.buffer);6970result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));71if (result != RC_OK || !response->response.succeeded)72return result;7374if (!rc_json_get_required_string(&response->username, &response->response, &fields[3], "User"))75return RC_MISSING_VALUE;76if (!rc_json_get_required_string(&response->api_token, &response->response, &fields[4], "Token"))77return RC_MISSING_VALUE;7879rc_json_get_optional_unum(&response->score, &fields[5], "Score", 0);80rc_json_get_optional_unum(&response->score_softcore, &fields[6], "SoftcoreScore", 0);81rc_json_get_optional_unum(&response->num_unread_messages, &fields[7], "Messages", 0);8283/* For the highest level of backwards compatibility, we have decided to just send the84* display_name back to the client as the "case-corrected username", and allow it to85* be used as the Username field for the other APIs. */86response->display_name = response->username;8788rc_json_get_optional_string(&response->avatar_url, &response->response, &fields[8], "AvatarUrl", NULL);89if (!response->avatar_url)90response->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, response->username);9192return RC_OK;93}9495void rc_api_destroy_login_response(rc_api_login_response_t* response) {96rc_buffer_destroy(&response->response.buffer);97}9899/* --- Start Session --- */100101int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {102return rc_api_init_start_session_request_hosted(request, api_params, &g_host);103}104105int rc_api_init_start_session_request_hosted(rc_api_request_t* request,106const rc_api_start_session_request_t* api_params,107const rc_api_host_t* host) {108rc_api_url_builder_t builder;109110rc_api_url_build_dorequest_url(request, host);111112if (api_params->game_id == 0)113return RC_INVALID_STATE;114115rc_url_builder_init(&builder, &request->buffer, 48);116if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) {117rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);118119if (api_params->game_hash && *api_params->game_hash) {120rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);121rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);122}123124rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);125126request->post_data = rc_url_builder_finalize(&builder);127request->content_type = RC_CONTENT_TYPE_URLENCODED;128}129130return builder.result;131}132133int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {134rc_api_server_response_t response_obj;135136memset(&response_obj, 0, sizeof(response_obj));137response_obj.body = server_response;138response_obj.body_length = rc_json_get_object_string_length(server_response);139140return rc_api_process_start_session_server_response(response, &response_obj);141}142143int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {144rc_api_unlock_entry_t* unlock;145rc_json_field_t array_field;146rc_json_iterator_t iterator;147uint32_t timet;148int result;149150rc_json_field_t fields[] = {151RC_JSON_NEW_FIELD("Success"),152RC_JSON_NEW_FIELD("Error"),153RC_JSON_NEW_FIELD("Unlocks"),154RC_JSON_NEW_FIELD("HardcoreUnlocks"),155RC_JSON_NEW_FIELD("ServerNow")156};157158rc_json_field_t unlock_entry_fields[] = {159RC_JSON_NEW_FIELD("ID"),160RC_JSON_NEW_FIELD("When")161};162163memset(response, 0, sizeof(*response));164rc_buffer_init(&response->response.buffer);165166result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));167if (result != RC_OK || !response->response.succeeded)168return result;169170if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &fields[2], "Unlocks") && response->num_unlocks) {171response->unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t));172if (!response->unlocks)173return RC_OUT_OF_MEMORY;174175memset(&iterator, 0, sizeof(iterator));176iterator.json = array_field.value_start;177iterator.end = array_field.value_end;178179unlock = response->unlocks;180while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {181if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))182return RC_MISSING_VALUE;183if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))184return RC_MISSING_VALUE;185unlock->when = (time_t)timet;186187++unlock;188}189}190191if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) {192response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t));193if (!response->hardcore_unlocks)194return RC_OUT_OF_MEMORY;195196memset(&iterator, 0, sizeof(iterator));197iterator.json = array_field.value_start;198iterator.end = array_field.value_end;199200unlock = response->hardcore_unlocks;201while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {202if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))203return RC_MISSING_VALUE;204if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))205return RC_MISSING_VALUE;206unlock->when = (time_t)timet;207208++unlock;209}210}211212rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0);213response->server_now = (time_t)timet;214215return RC_OK;216}217218void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {219rc_buffer_destroy(&response->response.buffer);220}221222/* --- Fetch User Unlocks --- */223224int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {225return rc_api_init_fetch_user_unlocks_request_hosted(request, api_params, &g_host);226}227228int rc_api_init_fetch_user_unlocks_request_hosted(rc_api_request_t* request,229const rc_api_fetch_user_unlocks_request_t* api_params,230const rc_api_host_t* host) {231rc_api_url_builder_t builder;232233rc_api_url_build_dorequest_url(request, host);234235rc_url_builder_init(&builder, &request->buffer, 48);236if (rc_api_url_build_dorequest(&builder, "unlocks", api_params->username, api_params->api_token)) {237rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);238rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);239request->post_data = rc_url_builder_finalize(&builder);240request->content_type = RC_CONTENT_TYPE_URLENCODED;241}242243return builder.result;244}245246int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {247rc_api_server_response_t response_obj;248249memset(&response_obj, 0, sizeof(response_obj));250response_obj.body = server_response;251response_obj.body_length = rc_json_get_object_string_length(server_response);252253return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);254}255256int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {257int result;258rc_json_field_t fields[] = {259RC_JSON_NEW_FIELD("Success"),260RC_JSON_NEW_FIELD("Error"),261RC_JSON_NEW_FIELD("UserUnlocks")262/* unused fields263RC_JSON_NEW_FIELD("GameID"),264RC_JSON_NEW_FIELD("HardcoreMode")265* unused fields */266};267268memset(response, 0, sizeof(*response));269rc_buffer_init(&response->response.buffer);270271result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));272if (result != RC_OK || !response->response.succeeded)273return result;274275result = rc_json_get_required_unum_array(&response->achievement_ids, &response->num_achievement_ids, &response->response, &fields[2], "UserUnlocks");276return result;277}278279void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {280rc_buffer_destroy(&response->response.buffer);281}282283/* --- Fetch Followed Users --- */284285int rc_api_init_fetch_followed_users_request(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params) {286return rc_api_init_fetch_followed_users_request_hosted(request, api_params, &g_host);287}288289int rc_api_init_fetch_followed_users_request_hosted(rc_api_request_t* request,290const rc_api_fetch_followed_users_request_t* api_params,291const rc_api_host_t* host) {292rc_api_url_builder_t builder;293294rc_api_url_build_dorequest_url(request, host);295296rc_url_builder_init(&builder, &request->buffer, 48);297if (rc_api_url_build_dorequest(&builder, "getfriendlist", api_params->username, api_params->api_token)) {298request->post_data = rc_url_builder_finalize(&builder);299request->content_type = RC_CONTENT_TYPE_URLENCODED;300}301302return builder.result;303}304305int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_users_response_t* response, const rc_api_server_response_t* server_response) {306rc_json_field_t array_field;307rc_json_iterator_t iterator;308rc_api_followed_user_t* user;309uint32_t timet;310int result;311rc_json_field_t fields[] = {312RC_JSON_NEW_FIELD("Success"),313RC_JSON_NEW_FIELD("Error"),314RC_JSON_NEW_FIELD("Friends")315};316317rc_json_field_t followed_user_entry_fields[] = {318RC_JSON_NEW_FIELD("Friend"),319RC_JSON_NEW_FIELD("AvatarUrl"),320RC_JSON_NEW_FIELD("RAPoints"),321RC_JSON_NEW_FIELD("LastSeen"),322RC_JSON_NEW_FIELD("LastSeenTime"),323RC_JSON_NEW_FIELD("LastGameId"),324RC_JSON_NEW_FIELD("LastGameTitle"),325RC_JSON_NEW_FIELD("LastGameIconUrl"),326};327328memset(response, 0, sizeof(*response));329rc_buffer_init(&response->response.buffer);330331result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));332if (result != RC_OK || !response->response.succeeded)333return result;334335if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends"))336return RC_MISSING_VALUE;337338if (response->num_users) {339response->users = (rc_api_followed_user_t*)rc_buffer_alloc(&response->response.buffer, response->num_users * sizeof(rc_api_followed_user_t));340if (!response->users)341return RC_OUT_OF_MEMORY;342343memset(&iterator, 0, sizeof(iterator));344iterator.json = array_field.value_start;345iterator.end = array_field.value_end;346347user = response->users;348while (rc_json_get_array_entry_object(followed_user_entry_fields, sizeof(followed_user_entry_fields) / sizeof(followed_user_entry_fields[0]), &iterator)) {349if (!rc_json_get_required_string(&user->display_name, &response->response, &followed_user_entry_fields[0], "Friend"))350return RC_MISSING_VALUE;351if (!rc_json_get_required_string(&user->avatar_url, &response->response, &followed_user_entry_fields[1], "AvatarUrl"))352return RC_MISSING_VALUE;353if (!rc_json_get_required_unum(&user->score, &response->response, &followed_user_entry_fields[2], "RAPoints"))354return RC_MISSING_VALUE;355356rc_json_get_optional_string(&user->recent_activity.description, &response->response, &followed_user_entry_fields[3], "LastSeen", NULL);357358rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0);359user->recent_activity.when = (time_t)timet;360361rc_json_get_optional_unum(&user->recent_activity.context_id, &followed_user_entry_fields[5], "LastGameId", 0);362rc_json_get_optional_string(&user->recent_activity.context, &response->response, &followed_user_entry_fields[6], "LastGameTitle", NULL);363rc_json_get_optional_string(&user->recent_activity.context_image_url, &response->response, &followed_user_entry_fields[7], "LastGameIconUrl", NULL);364365++user;366}367}368369return RC_OK;370}371372void rc_api_destroy_fetch_followed_users_response(rc_api_fetch_followed_users_response_t* response) {373rc_buffer_destroy(&response->response.buffer);374}375376/* --- Fetch All Progress --- */377378int rc_api_init_fetch_all_user_progress_request(rc_api_request_t* request,379const rc_api_fetch_all_user_progress_request_t* api_params)380{381return rc_api_init_fetch_all_user_progress_request_hosted(request, api_params, &g_host);382}383384int rc_api_init_fetch_all_user_progress_request_hosted(rc_api_request_t* request,385const rc_api_fetch_all_user_progress_request_t* api_params,386const rc_api_host_t* host)387{388rc_api_url_builder_t builder;389390rc_api_url_build_dorequest_url(request, host);391392if (api_params->console_id == RC_CONSOLE_UNKNOWN)393return RC_INVALID_STATE;394395rc_url_builder_init(&builder, &request->buffer, 48);396if (rc_api_url_build_dorequest(&builder, "allprogress", api_params->username, api_params->api_token))397{398rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);399request->post_data = rc_url_builder_finalize(&builder);400request->content_type = RC_CONTENT_TYPE_URLENCODED;401}402403return builder.result;404}405406int rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user_progress_response_t* response,407const rc_api_server_response_t* server_response)408{409rc_api_all_user_progress_entry_t* entry;410int result;411412rc_json_field_t fields[] = {413RC_JSON_NEW_FIELD("Success"),414RC_JSON_NEW_FIELD("Error"),415RC_JSON_NEW_FIELD("Response"),416};417418rc_json_field_t entry_fields[] = {419RC_JSON_NEW_FIELD("Achievements"),420RC_JSON_NEW_FIELD("Unlocked"),421RC_JSON_NEW_FIELD("UnlockedHardcore"),422};423424memset(response, 0, sizeof(*response));425rc_buffer_init(&response->response.buffer);426427result =428rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));429if (result != RC_OK || !response->response.succeeded)430return result;431432if (!fields[2].value_start) {433/* call rc_json_get_required_object to generate the error message */434rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");435return RC_MISSING_VALUE;436}437438response->num_entries = fields[2].array_size;439440if (response->num_entries > 0) {441rc_json_iterator_t iterator;442rc_json_field_t field;443char* end;444445rc_buffer_reserve(&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));446447response->entries = (rc_api_all_user_progress_entry_t*)rc_buffer_alloc(448&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));449if (!response->entries)450return RC_OUT_OF_MEMORY;451452memset(&iterator, 0, sizeof(iterator));453iterator.json = fields[2].value_start;454iterator.end = fields[2].value_end;455456entry = response->entries;457while (rc_json_get_next_object_field(&iterator, &field))458{459entry->game_id = strtol(field.name, &end, 10);460461field.name = "";462if (!rc_json_get_required_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]),463&response->response, &field, ""))464{465return RC_MISSING_VALUE;466}467468rc_json_get_optional_unum(&entry->num_achievements, &entry_fields[0], "Achievements", 0);469rc_json_get_optional_unum(&entry->num_unlocked_achievements, &entry_fields[1], "Unlocked", 0);470rc_json_get_optional_unum(&entry->num_unlocked_achievements_hardcore, &entry_fields[2], "UnlockedHardcore", 0);471472++entry;473}474}475476return RC_OK;477}478479void rc_api_destroy_fetch_all_user_progress_response(rc_api_fetch_all_user_progress_response_t* response)480{481rc_buffer_destroy(&response->response.buffer);482}483484485