/* * Steam Mobile Plugin for Pidgin * Copyright (C) 2012-2016 Eion Robb * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "libsteam.h" #include "steam_connection.h" static gboolean core_is_haze = FALSE; // Hack to fix OSX compatibility :) #ifdef __APPLE__ #undef G_OS_UNIX #endif #ifdef G_OS_UNIX #include #ifdef USE_GNOME_KEYRING #include // Copy of GNOME_KEYRING_NETWORK_PASSWORD to use locally static const GnomeKeyringPasswordSchema network_password_schema = { GNOME_KEYRING_ITEM_NETWORK_PASSWORD, { { "user", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, { "domain", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, { "object", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, { "protocol", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, { "port", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, { "server", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, { "NULL", 0 }, } }; static const GnomeKeyringPasswordSchema *my_GKNP = &network_password_schema; static gpointer gnome_keyring_lib = NULL; typedef gpointer (*gnome_keyring_store_password_type)(const GnomeKeyringPasswordSchema *schema, const gchar *keyring, const gchar *display_name, const gchar *password, GnomeKeyringOperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, ...); static gnome_keyring_store_password_type my_gnome_keyring_store_password = NULL; typedef gpointer (*gnome_keyring_delete_password_type)(const GnomeKeyringPasswordSchema *schema, GnomeKeyringOperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, ...); static gnome_keyring_delete_password_type my_gnome_keyring_delete_password = NULL; typedef gpointer (*gnome_keyring_find_password_type)(const GnomeKeyringPasswordSchema *schema, GnomeKeyringOperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data, ...); static gnome_keyring_find_password_type my_gnome_keyring_find_password = NULL; #else // USE_GNOME_KEYRING #include // Copy of SECRET_SCHEMA_COMPAT_NETWORK to use locally static const SecretSchema network_schema = { "org.gnome.keyring.NetworkPassword", SECRET_SCHEMA_NONE, { { "user", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "domain", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "object", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "authtype", SECRET_SCHEMA_ATTRIBUTE_STRING }, { NULL, 0 }, } }; static const SecretSchema *my_SSCN = &network_schema; static gpointer secret_lib = NULL; typedef gpointer (*secret_password_store_type)(const SecretSchema *schema, const gchar *collection, const gchar *label, const gchar *password, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, ...); static secret_password_store_type my_secret_password_store = NULL; typedef gpointer (*secret_password_clear_type)(const SecretSchema *schema, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, ...); static secret_password_clear_type my_secret_password_clear = NULL; typedef gpointer (*secret_password_lookup_type)(const SecretSchema *schema, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, ...); static secret_password_lookup_type my_secret_password_lookup = NULL; typedef gpointer (*secret_password_lookup_finish_type)(GAsyncResult *result, GError **error); static secret_password_lookup_finish_type my_secret_password_lookup_finish = NULL; #endif // USE_GNOME_KEYRING #endif #if !PURPLE_VERSION_CHECK(3, 0, 0) #define purple_connection_error purple_connection_error_reason #define purple_notify_user_info_add_pair_html purple_notify_user_info_add_pair #endif static const gchar * steam_account_get_access_token(SteamAccount *sa) { if (core_is_haze) { if (sa->cached_access_token) return sa->cached_access_token; return ""; } else { return purple_account_get_string(sa->account, "access_token", ""); } } #ifdef G_OS_UNIX #ifdef USE_GNOME_KEYRING static void dummy_gnome_callback(GnomeKeyringResult result, gpointer user_data) { // Gnome keyring throws toys out of cots if there's no callback! if (result == GNOME_KEYRING_RESULT_OK) { purple_debug_info("steam", "Access token stored OK\n"); } else if (result == GNOME_KEYRING_RESULT_CANCELLED) { purple_debug_error("steam", "Access token not stored, user cancelled\n"); } else { purple_debug_error("steam", "Access token not stored (%d)\n", result); } } #endif //USE_GNOME_KEYRING #endif static void steam_account_set_access_token(SteamAccount *sa, const gchar *access_token) { #ifdef G_OS_UNIX if (core_is_haze) { if (access_token != NULL) { g_free(sa->cached_access_token); sa->cached_access_token = g_strdup(access_token); #ifdef USE_GNOME_KEYRING my_gnome_keyring_store_password(my_GKNP, //GNOME_KEYRING_NETWORK_PASSWORD, NULL, _("Steam Mobile OAuth Token"), access_token, dummy_gnome_callback, NULL, NULL, "user", sa->account->username, "server", "api.steamcommunity.com", "protocol", "steammobile", "domain", "libpurple", NULL); #else // !USE_GNOME_KEYRING my_secret_password_store(my_SSCN, //SECRET_SCHEMA_COMPAT_NETWORK NULL, _("Steam Mobile OAuth Token"), access_token, NULL, NULL, NULL, "user", sa->account->username, "server", "api.steamcommunity.com", "protocol", "steammobile", "domain", "libpurple", NULL); #endif //USE_GNOME_KEYRING } else { g_free(sa->cached_access_token); sa->cached_access_token = NULL; #ifdef USE_GNOME_KEYRING my_gnome_keyring_delete_password(my_GKNP, //GNOME_KEYRING_NETWORK_PASSWORD, dummy_gnome_callback, NULL, NULL, "user", sa->account->username, "server", "api.steamcommunity.com", "protocol", "steammobile", "domain", "libpurple", NULL); #else // !USE_GNOME_KEYRING my_secret_password_clear(my_SSCN, //SECRET_SCHEMA_COMPAT_NETWORK NULL, NULL, NULL, "user", sa->account->username, "server", "api.steamcommunity.com", "protocol", "steammobile", "domain", "libpurple", NULL); #endif // USE_GNOME_KEYRING } return; } #endif purple_account_set_string(sa->account, "access_token", access_token); } static const gchar * steam_personastate_to_statustype(gint64 state) { const char *status_id; PurpleStatusPrimitive prim; switch(state) { default: case 0: //Offline prim = PURPLE_STATUS_OFFLINE; break; case 1: //Online prim = PURPLE_STATUS_AVAILABLE; break; case 2: //Busy prim = PURPLE_STATUS_UNAVAILABLE; break; case 3: //Away prim = PURPLE_STATUS_AWAY; break; case 4: //Snoozing prim = PURPLE_STATUS_EXTENDED_AWAY; break; case 5: //Looking to trade return "trade"; case 6: //Looking to play return "play"; } status_id = purple_primitive_get_id_from_type(prim); return status_id; } static const gchar * steam_accountid_to_steamid(gint64 accountid) { static gchar steamid[21]; sprintf(steamid, "%" G_GINT64_FORMAT, accountid + G_GINT64_CONSTANT(76561197960265728)); return steamid; } static const gchar * steam_steamid_to_accountid(const gchar *steamid) { static gchar accountid[21]; gint64 steamid_int = g_ascii_strtoll(steamid, NULL, 10); g_return_val_if_fail(steamid_int, NULL); sprintf(accountid, "%" G_GINT64_FORMAT, steamid_int - G_GINT64_CONSTANT(76561197960265728)); return accountid; } static void steam_fetch_new_sessionid(SteamAccount *sa); static void steam_get_friend_summaries(SteamAccount *sa, const gchar *who); static void steam_get_rsa_key(SteamAccount *sa); static void steam_get_conversations(SteamAccount *sa); static void steam_friend_action(SteamAccount *sa, const gchar *who, const gchar *action) { //Possible actions: add, remove GString *postdata = g_string_new(NULL); const gchar *url; if (g_str_equal(action, "remove")) url = "/actions/RemoveFriendAjax"; else url = "/actions/AddFriendAjax"; g_string_append_printf(postdata, "steamid=%s&", purple_url_encode(who)); g_string_append_printf(postdata, "sessionID=%s", purple_url_encode(sa->sessionid)); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, "steamcommunity.com", url, postdata->str, NULL, NULL, FALSE); g_string_free(postdata, TRUE); if (g_str_equal(action, "add")) { steam_get_friend_summaries(sa, who); } } static void steam_friend_invite_action(SteamAccount *sa, const gchar *who, const gchar *action) { //Possible actions: accept, ignore, block GString *postdata = g_string_new(NULL); gchar *url = g_strdup_printf("/profiles/%s/home_process", purple_url_encode(sa->steamid)); g_string_append(postdata, "json=1&"); g_string_append(postdata, "xml=1&"); g_string_append(postdata, "action=approvePending&"); g_string_append(postdata, "itype=friend&"); g_string_append_printf(postdata, "perform=%s&", purple_url_encode(action)); g_string_append_printf(postdata, "sessionID=%s&", purple_url_encode(sa->sessionid)); g_string_append_printf(postdata, "id=%s", purple_url_encode(who)); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, "steamcommunity.com", url, postdata->str, NULL, NULL, FALSE); g_free(url); g_string_free(postdata, TRUE); } static void steam_register_game_key_text(SteamAccount *sa, const gchar *game_key) { //Possible actions: accept, ignore, block GString *postdata = g_string_new(NULL); g_string_append_printf(postdata, "product_key=%s&", purple_url_encode(game_key)); g_string_append_printf(postdata, "sessionid=%s&", purple_url_encode(sa->sessionid)); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, "store.steampowered.com", "/account/ajaxregisterkey/", postdata->str, NULL, NULL, FALSE); g_string_free(postdata, TRUE); } static void steam_register_game_key(PurplePluginAction *action) { PurpleConnection *pc = (PurpleConnection *) action->context; SteamAccount *sa = pc->proto_data; purple_request_input(pc, "Activate a Product on Steam", "Redeem a Steam Key", NULL, "XXXXX-XXXXX-XXXXX", FALSE, FALSE, NULL, _("_Search"), G_CALLBACK(steam_register_game_key_text), _("_Cancel"), NULL, purple_connection_get_account(pc), NULL, NULL, sa); } static void steam_fetch_new_sessionid_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { if (g_hash_table_lookup(sa->cookie_table, "sessionid")) { g_free(sa->sessionid); sa->sessionid = g_strdup(g_hash_table_lookup(sa->cookie_table, "sessionid")); } } static void steam_fetch_new_sessionid(SteamAccount *sa) { gchar *steamLogin; steamLogin = g_strconcat(sa->steamid, "||oauth:", steam_account_get_access_token(sa), NULL); g_hash_table_replace(sa->cookie_table, g_strdup("steamLogin"), steamLogin); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, "steamcommunity.com", "/mobilesettings/GetManifest/v0001", NULL, steam_fetch_new_sessionid_cb, NULL, FALSE); } static void steam_captcha_cancel_cb(PurpleConnection *pc, PurpleRequestFields *fields) { purple_connection_error_reason(pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Could not authenticate captcha."); } static void steam_captcha_ok_cb(PurpleConnection *pc, PurpleRequestFields *fields) { SteamAccount *sa = pc->proto_data; const gchar *captcha_response; captcha_response = purple_request_fields_get_string(fields, "captcha_response"); sa->captcha_text = g_strdup(captcha_response); //re-login steam_get_rsa_key(sa); } static void steam_captcha_image_cb(PurpleUtilFetchUrlData *url_data, gpointer userdata, const gchar *response, gsize len, const gchar *error_message) { SteamAccount *sa = userdata; PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; fields = purple_request_fields_new(); group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); #ifdef DISPLAY_CAPTCHA_AS_URL field = purple_request_field_string_new("captcha_image", _("Image url"), g_strdup_printf(STEAM_CAPTCHA_URL, sa->captcha_gid), FALSE); #else field = purple_request_field_image_new("captcha_image", _("Image"), response, len); #endif purple_request_field_group_add_field(group, field); field = purple_request_field_string_new("captcha_response", _("Response"), "", FALSE); purple_request_field_group_add_field(group, field); purple_request_fields(sa->pc, _("Steam Captcha"), _("Steam Captcha"), #ifdef DISPLAY_CAPTCHA_AS_URL _("Paste the url in your browser and input the displayed captcha"), #else _("Please verify you are human by typing the following"), #endif fields, _("OK"), G_CALLBACK(steam_captcha_ok_cb), _("Logout"), G_CALLBACK(steam_captcha_cancel_cb), sa->account, NULL, NULL, sa->pc ); } static guint active_icon_downloads = 0; static void steam_get_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { PurpleBuddy *buddy = user_data; SteamBuddy *sbuddy; if (!buddy || !buddy->proto_data) return; sbuddy = buddy->proto_data; purple_buddy_icons_set_for_user(buddy->account, buddy->name, g_memdup(url_text, len), len, sbuddy->avatar); active_icon_downloads--; } static void steam_get_icon_now(PurpleBuddy *buddy) { const gchar *old_avatar = purple_buddy_icons_get_checksum_for_user(buddy); SteamBuddy *sbuddy; purple_debug_info("steam", "getting new buddy icon for %s\n", buddy->name); if (!buddy || !buddy->proto_data) { purple_debug_info("steam", "no buddy proto_data :(\n"); return; } sbuddy = buddy->proto_data; if (!sbuddy->avatar || (old_avatar && g_str_equal(sbuddy->avatar, old_avatar))) return; #if PURPLE_VERSION_CHECK(3, 0, 0) purple_util_fetch_url_request(buddy->account, sbuddy->avatar, TRUE, NULL, FALSE, NULL, FALSE, -1, steam_get_icon_cb, buddy); #else purple_util_fetch_url_request(sbuddy->avatar, TRUE, NULL, FALSE, NULL, FALSE, steam_get_icon_cb, buddy); #endif active_icon_downloads++; } static gboolean steam_get_icon_queuepop(gpointer data) { PurpleBuddy *buddy = data; // Only allow 4 simultaneous downloads if (active_icon_downloads > 4) return TRUE; steam_get_icon_now(buddy); return FALSE; } static void steam_get_icon(PurpleBuddy *buddy) { if (!buddy) return; purple_timeout_add(100, steam_get_icon_queuepop, (gpointer)buddy); } static void steam_poll(SteamAccount *sa, gboolean secure, guint message); gboolean steam_timeout(gpointer userdata) { SteamAccount *sa = userdata; steam_poll(sa, FALSE, sa->message); // If no response within 3 minutes, assume connection lost and try again purple_timeout_remove(sa->watchdog_timeout); sa->watchdog_timeout = purple_timeout_add_seconds(3 * 60, steam_timeout, sa); return FALSE; } static void steam_auth_accept_cb(gpointer user_data) { PurpleBuddy *temp_buddy = user_data; PurpleAccount *account = purple_buddy_get_account(temp_buddy); PurpleConnection *pc = purple_account_get_connection(account); SteamAccount *sa = pc->proto_data; steam_friend_invite_action(sa, temp_buddy->name, "accept"); purple_buddy_destroy(temp_buddy); } static void steam_auth_reject_cb(gpointer user_data) { PurpleBuddy *temp_buddy = user_data; PurpleAccount *account = purple_buddy_get_account(temp_buddy); PurpleConnection *pc = purple_account_get_connection(account); SteamAccount *sa = pc->proto_data; steam_friend_invite_action(sa, temp_buddy->name, "ignore"); purple_buddy_destroy(temp_buddy); } void steam_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data) { PurpleAccount *account = purple_connection_get_account(pc); if (!purple_find_buddy(account, g_list_nth_data(row, 0))) purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Steam", g_list_nth_data(row, 1)); } void steam_search_display_results(SteamAccount *sa, JsonObject *obj, gpointer user_data) { PurpleNotifySearchResults *results; PurpleNotifySearchColumn *column; JsonArray *players = NULL; guint index; gchar *search_term = user_data; if (!json_object_has_member(obj, "players")) { g_free(search_term); return; } results = purple_notify_searchresults_new(); if (results == NULL) { g_free(search_term); return; } /* columns: Friend ID, Name, Network */ column = purple_notify_searchresults_column_new(_("ID")); purple_notify_searchresults_column_add(results, column); column = purple_notify_searchresults_column_new(_("Persona")); purple_notify_searchresults_column_add(results, column); column = purple_notify_searchresults_column_new(_("Real name")); purple_notify_searchresults_column_add(results, column); column = purple_notify_searchresults_column_new(_("Profile")); purple_notify_searchresults_column_add(results, column); purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, steam_search_results_add_buddy); players = json_object_get_array_member(obj, "players"); for(index = 0; index < json_array_get_length(players); index++) { JsonObject *player = json_array_get_object_element(players, index); /* the row in the search results table */ /* prepend to it backwards then reverse to speed up adds */ GList *row = NULL; row = g_list_prepend(row, g_strdup(json_object_get_string_member(player, "steamid"))); row = g_list_prepend(row, g_strdup(json_object_get_string_member(player, "personaname"))); row = g_list_prepend(row, g_strdup(json_object_get_string_member(player, "realname"))); row = g_list_prepend(row, g_strdup(json_object_get_string_member(player, "profileurl"))); row = g_list_reverse(row); purple_notify_searchresults_row_add(results, row); } purple_notify_searchresults(sa->pc, NULL, search_term, NULL, results, NULL, NULL); } void steam_search_users_text_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonArray *results = NULL; guint index; GString *userids; gchar *search_term = user_data; if (json_object_get_int_member(obj, "count") == 0 || !json_object_has_member(obj, "results")) { gchar *primary_text = g_strdup_printf("Your search for the user \"%s\" returned no results", search_term); purple_notify_warning(sa->pc, "No users found", primary_text, ""); g_free(primary_text); g_free(search_term); return; } userids = g_string_new(""); results = json_object_get_array_member(obj, "results"); for(index = 0; index < json_array_get_length(results); index++) { JsonObject *result = json_array_get_object_element(results, index); g_string_append_printf(userids, "%s,", json_object_get_string_member(result, "steamid")); } if (userids && userids->str && *userids->str) { GString *url = g_string_new("/ISteamUserOAuth/GetUserSummaries/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(url, "steamids=%s", purple_url_encode(userids->str)); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, NULL, url->str, NULL, steam_search_display_results, search_term, TRUE); g_string_free(url, TRUE); } else { g_free(search_term); } g_string_free(userids, TRUE); } void steam_search_users_text(gpointer user_data, const gchar *text) { SteamAccount *sa = user_data; GString *url = g_string_new("/ISteamUserOAuth/Search/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(url, "keywords=%s&", purple_url_encode(text)); g_string_append(url, "offset=0&"); g_string_append(url, "count=50&"); g_string_append(url, "targets=users&"); g_string_append(url, "fields=all&"); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, "api.steampowered.com", url->str, NULL, steam_search_users_text_cb, g_strdup(text), FALSE); g_string_free(url, TRUE); } void steam_search_users(PurplePluginAction *action) { PurpleConnection *pc = (PurpleConnection *) action->context; SteamAccount *sa = pc->proto_data; purple_request_input(pc, "Search for Steam Friends", "Search for Steam Friends", NULL, NULL, FALSE, FALSE, NULL, _("_Search"), G_CALLBACK(steam_search_users_text), _("_Cancel"), NULL, purple_connection_get_account(pc), NULL, NULL, sa); } static void steam_get_friend_summaries_internal(SteamAccount *sa, const gchar *who, SteamProxyCallbackFunc callback_func, gpointer user_data) { GString *url; g_return_if_fail(sa && who && *who); url = g_string_new("/ISteamUserOAuth/GetUserSummaries/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(url, "steamids=%s", purple_url_encode(who)); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, NULL, url->str, NULL, callback_func, user_data, TRUE); g_string_free(url, TRUE); } static void steam_got_friend_state(SteamAccount *sa, JsonObject *obj, gpointer user_data) { const gchar *steamid = json_object_get_string_member(obj, "m_ulSteamID"); gint64 personastate = json_object_get_int_member(obj, "m_ePersonaState"); gchar *game_name = NULL; if (json_object_has_member(obj, "m_strInGameName")) { game_name = purple_utf8_salvage(json_object_get_string_member(obj, "m_strInGameName")); } if (core_is_haze) { if (game_name && *game_name) { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), "message", g_markup_printf_escaped("In game %s", game_name), NULL); } else { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), "message", NULL, NULL); } } else { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), NULL); } if (game_name && *game_name) { purple_prpl_got_user_status(sa->account, steamid, "ingame", "game", game_name, NULL); } else { purple_prpl_got_user_status_deactive(sa->account, steamid, "ingame"); } PurpleBuddy *buddy = purple_find_buddy(sa->account, steamid); if (!buddy) return; SteamBuddy *sbuddy = buddy->proto_data; if (!sbuddy) return; g_free(sbuddy->gameextrainfo); sbuddy->gameextrainfo = game_name; g_free(sbuddy->gameid); sbuddy->gameid = json_object_has_member(obj, "m_nInGameAppID") ? g_strdup(json_object_get_string_member(obj, "m_nInGameAppID")) : NULL; } static void steam_get_friend_state(SteamAccount *sa, const gchar *who) { GString *url; const gchar *accountid = steam_steamid_to_accountid(who); g_return_if_fail(sa && who && *who); url = g_string_new("/chat/friendstate/"); g_string_append_printf(url, "%s", purple_url_encode(accountid)); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, "steamcommunity.com", url->str, NULL, steam_got_friend_state, NULL, TRUE); g_string_free(url, TRUE); } static void steam_request_add_user(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonArray *players = json_object_get_array_member(obj, "players"); PurpleBuddy *buddy = user_data; guint index; for(index = 0; index < json_array_get_length(players); index++) { JsonObject *player = json_array_get_object_element(players, index); const gchar *steamid = json_object_get_string_member(player, "steamid"); const gchar *personaname; if (!steamid || !g_str_equal(buddy->name, steamid)) continue; // This is not the droid we are looking for personaname = json_object_get_string_member(player, "personaname"); purple_account_request_authorization( sa->account, steamid, personaname, NULL, NULL, TRUE, steam_auth_accept_cb, steam_auth_reject_cb, buddy); return; } // What? The buddy we wanted info about wasn't in the response??? purple_buddy_destroy(buddy); } static void steam_poll_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonArray *messages = NULL; guint index; gint secure = GPOINTER_TO_INT(user_data); guint server_timestamp; time_t local_timestamp; GString *users_to_update = g_string_new(NULL); server_timestamp = (guint) json_object_get_int_member(obj, "timestamp"); local_timestamp = time(NULL); if (json_object_has_member(obj, "messages")) messages = json_object_get_array_member(obj, "messages"); if (messages != NULL) for(index = 0; index < json_array_get_length(messages); index++) { JsonObject *message = json_array_get_object_element(messages, index); const gchar *type = json_object_get_string_member(message, "type"); if (g_str_equal(type, "typing")) { serv_got_typing(sa->pc, json_object_get_string_member(message, "steamid_from"), 20, PURPLE_TYPING); } else if (g_str_equal(type, "saytext") || g_str_equal(type, "emote") || g_str_equal(type, "my_saytext") || g_str_equal(type, "my_emote")) { if (json_object_has_member(message, "secure_message_id")) { guint secure_message_id = (guint) json_object_get_int_member(message, "secure_message_id"); steam_poll(sa, TRUE, secure_message_id); sa->message = MAX(sa->message, secure_message_id); } else { time_t real_timestamp; if (json_object_has_member(message, "utc_timestamp")) { real_timestamp = json_object_get_int_member(message, "utc_timestamp"); } else { guint new_timestamp = (guint) json_object_get_int_member(message, "timestamp"); real_timestamp = local_timestamp - ((server_timestamp - new_timestamp) / 1000); } if (real_timestamp > sa->last_message_timestamp) { gchar *text, *html; const gchar *from; if (g_str_equal(type, "emote") || g_str_equal(type, "my_emote")) { text = g_strconcat("/me ", json_object_get_string_member(message, "text"), NULL); } else { text = g_strdup(json_object_get_string_member(message, "text")); } html = purple_markup_escape_text(text, -1); from = json_object_get_string_member(message, "steamid_from"); if (g_str_has_prefix(type, "my_")) { PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, sa->account); if (conv == NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, sa->account, from); } purple_conversation_write(conv, from, html, PURPLE_MESSAGE_SEND, real_timestamp); } else { serv_got_im(sa->pc, from, html, PURPLE_MESSAGE_RECV, real_timestamp); } g_free(html); g_free(text); sa->last_message_timestamp = real_timestamp; } } } else if (g_str_equal(type, "personastate")) { gint64 personastate = json_object_get_int_member(message, "persona_state"); const gchar *steamid = json_object_get_string_member(message, "steamid_from"); if (!STEAMID_IS_GROUP(steamid)) { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), NULL); serv_got_alias(sa->pc, steamid, json_object_get_string_member(message, "persona_name")); g_string_append_c(users_to_update, ','); g_string_append(users_to_update, steamid); steam_get_friend_state(sa, steamid); } } else if (g_str_equal(type, "personarelationship")) { const gchar *steamid = json_object_get_string_member(message, "steamid_from"); gint64 persona_state = json_object_get_int_member(message, "persona_state"); if (!STEAMID_IS_GROUP(steamid)) { if (persona_state == 0) { purple_blist_remove_buddy(purple_find_buddy(sa->account, steamid)); } else if (persona_state == 2) { // Find out the name of the buddy before we display the auth request steam_get_friend_summaries_internal(sa, steamid, steam_request_add_user, purple_buddy_new(sa->account, steamid, NULL)); } else if (persona_state == 3) { if (!purple_find_buddy(sa->account, steamid)) { purple_blist_add_buddy(purple_buddy_new(sa->account, steamid, NULL), NULL, purple_find_group("Steam"), NULL); g_string_append_c(users_to_update, ','); g_string_append(users_to_update, steamid); } } } } else if (g_str_equal(type, "leftconversation")) { const gchar *steamid = json_object_get_string_member(message, "steamid_from"); PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, steamid, sa->account); const gchar *alias = purple_buddy_get_alias(purple_find_buddy(sa->account, steamid)); gchar *has_left_msg = g_strdup_printf("%s has left the conversation", alias ? alias : "User"); purple_conversation_write(conv, "", has_left_msg, PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(has_left_msg); } else { purple_debug_error("steam", "unknown message type %s\n", type); } } if (sa->last_message_timestamp > 0) purple_account_set_int(sa->account, "last_message_timestamp", sa->last_message_timestamp); if (json_object_has_member(obj, "messagelast")) sa->message = MAX(sa->message, (guint) json_object_get_int_member(obj, "messagelast")); if (json_object_has_member(obj, "error") && g_str_equal(json_object_get_string_member(obj, "error"), "Not Logged On")) { g_string_free(users_to_update, TRUE); purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Reconnect needed")); return; } if (!secure) { sa->poll_timeout = purple_timeout_add_seconds(1, steam_timeout, sa); } if (users_to_update && users_to_update->len) { steam_get_friend_summaries(sa, users_to_update->str); } g_string_free(users_to_update, TRUE); } static void steam_poll(SteamAccount *sa, gboolean secure, guint message) { GString *post = g_string_new(NULL); SteamMethod method = STEAM_METHOD_POST; const gchar *url = "/ISteamWebUserPresenceOAuth/PollStatus/v0001"; if (secure == TRUE || purple_account_get_bool(sa->account, "always_use_https", FALSE)) { method |= STEAM_METHOD_SSL; url = "/ISteamWebUserPresenceOAuth/Poll/v0001"; g_string_append_printf(post, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); } else { g_string_append_printf(post, "steamid=%s&", purple_url_encode(sa->steamid)); } g_string_append_printf(post, "umqid=%s&", purple_url_encode(sa->umqid)); g_string_append_printf(post, "message=%u&", message?message:sa->message); g_string_append_printf(post, "secidletime=%d", sa->idletime); steam_post_or_get(sa, method, NULL, url, post->str, steam_poll_cb, GINT_TO_POINTER(secure?1:0), TRUE); g_string_free(post, TRUE); } static void steam_got_friend_summaries(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonArray *players = json_object_get_array_member(obj, "players"); PurpleBuddy *buddy; SteamBuddy *sbuddy; guint index; for(index = 0; index < json_array_get_length(players); index++) { JsonObject *player = json_array_get_object_element(players, index); const gchar *steamid = json_object_get_string_member(player, "steamid"); gint64 personastate = -1; if (steamid == NULL) continue; if (purple_strequal(steamid, sa->steamid) && purple_account_get_bool(sa->account, "change_status_to_game", FALSE)) { const gchar *gameid = json_object_get_string_member(player, "gameid"); const gchar *last_gameid = purple_account_get_string(sa->account, "current_gameid", NULL); if (!purple_strequal(last_gameid, gameid)) { PurpleSavedStatus *current_status = purple_savedstatus_get_current(); // We changed our in-game status purple_account_set_string(sa->account, "current_gameid", gameid); if (!last_gameid) { //Starting a game purple_account_set_string(sa->account, "last_status_message", purple_savedstatus_get_message(current_status)); } if (!gameid) { //Finishing game purple_savedstatus_set_message(current_status, purple_account_get_string(sa->account, "last_status_message", NULL)); purple_account_set_string(sa->account, "last_status_message", NULL); } else { //Starting or changing a game gchar *new_message = g_markup_printf_escaped("In game %s", json_object_get_string_member(player, "gameextrainfo")); purple_savedstatus_set_message(current_status, new_message); g_free(new_message); } purple_savedstatus_activate(current_status); } } buddy = purple_find_buddy(sa->account, steamid); if (!buddy) continue; sbuddy = buddy->proto_data; if (sbuddy == NULL) { sbuddy = g_new0(SteamBuddy, 1); buddy->proto_data = sbuddy; sbuddy->steamid = g_strdup(steamid); } g_free(sbuddy->personaname); sbuddy->personaname = g_strdup(json_object_get_string_member(player, "personaname")); serv_got_alias(sa->pc, steamid, sbuddy->personaname); g_free(sbuddy->realname); sbuddy->realname = g_strdup(json_object_get_string_member(player, "realname")); g_free(sbuddy->profileurl); sbuddy->profileurl = g_strdup(json_object_get_string_member(player, "profileurl")); g_free(sbuddy->avatar); sbuddy->avatar = g_strdup(json_object_get_string_member(player, "avatarfull")); sbuddy->personastateflags = (guint) json_object_get_int_member(player, "personastateflags"); // Optional : g_free(sbuddy->gameid); sbuddy->gameid = json_object_has_member(player, "gameid") ? g_strdup(json_object_get_string_member(player, "gameid")) : NULL; // No longer provided here //g_free(sbuddy->gameextrainfo); sbuddy->gameextrainfo = json_object_has_member(player, "gameextrainfo") ? purple_utf8_salvage(json_object_get_string_member(player, "gameextrainfo")) : NULL; g_free(sbuddy->gameserversteamid); sbuddy->gameserversteamid = json_object_has_member(player, "gameserversteamid") ? g_strdup(json_object_get_string_member(player, "gameserversteamid")) : NULL; g_free(sbuddy->lobbysteamid); sbuddy->lobbysteamid = json_object_has_member(player, "lobbysteamid") ? g_strdup(json_object_get_string_member(player, "lobbysteamid")) : NULL; g_free(sbuddy->gameserverip); sbuddy->gameserverip = json_object_has_member(player, "gameserverip") ? g_strdup(json_object_get_string_member(player, "gameserverip")) : NULL; sbuddy->lastlogoff = (guint) json_object_get_int_member(player, "lastlogoff"); personastate = json_object_get_int_member(player, "personastate"); if (core_is_haze) { if (sbuddy->gameextrainfo && *(sbuddy->gameextrainfo)) { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), "message", g_markup_printf_escaped("In game %s", sbuddy->gameextrainfo), NULL); } else { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), "message", NULL, NULL); } } else { purple_prpl_got_user_status(sa->account, steamid, steam_personastate_to_statustype(personastate), NULL); } if (sbuddy->gameextrainfo && *(sbuddy->gameextrainfo)) { purple_prpl_got_user_status(sa->account, steamid, "ingame", "game", sbuddy->gameextrainfo, NULL); } else { purple_prpl_got_user_status_deactive(sa->account, steamid, "ingame"); } steam_get_icon(buddy); } } static void steam_get_friend_summaries(SteamAccount *sa, const gchar *who) { steam_get_friend_summaries_internal(sa, who, steam_got_friend_summaries, NULL); } static void steam_get_nickname_list_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonObject *response = json_object_get_object_member(obj, "response"); JsonArray *nicknames = json_object_get_array_member(response, "nicknames"); guint index; for(index = 0; index < json_array_get_length(nicknames); index++) { JsonObject *friend = json_array_get_object_element(nicknames, index); gint64 accountid = json_object_get_int_member(friend, "accountid"); const gchar *nickname = json_object_get_string_member(friend, "nickname"); purple_serv_got_private_alias(sa->pc, steam_accountid_to_steamid(accountid), nickname); } } static void steam_get_friend_list_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonArray *friends = json_object_get_array_member(obj, "friends"); PurpleGroup *group = NULL; gchar *users_to_fetch = g_strdup(""), *temp; guint index; for(index = 0; index < json_array_get_length(friends); index++) { JsonObject *friend = json_array_get_object_element(friends, index); const gchar *steamid = json_object_get_string_member(friend, "steamid"); const gchar *relationship = json_object_get_string_member(friend, "relationship"); if (STEAMID_IS_GROUP(steamid)) continue; if (g_str_equal(relationship, "friend")) { if (!purple_find_buddy(sa->account, steamid)) { if (!group) { group = purple_find_group("Steam"); if (!group) { group = purple_group_new("Steam"); purple_blist_add_group(group, NULL); } } purple_blist_add_buddy(purple_buddy_new(sa->account, steamid, NULL), NULL, group, NULL); } temp = users_to_fetch; users_to_fetch = g_strconcat(users_to_fetch, ",", steamid, NULL); g_free(temp); } else if (g_str_equal(relationship, "requestrecipient")) { // Find out the name of the buddy before we display the auth request steam_get_friend_summaries_internal(sa, steamid, steam_request_add_user, purple_buddy_new(sa->account, steamid, NULL)); } } if (users_to_fetch && *users_to_fetch) { steam_get_friend_summaries(sa, users_to_fetch); } g_free(users_to_fetch); if (purple_account_get_bool(sa->account, "download_offline_history", TRUE)) { steam_get_conversations(sa); } } static void steam_get_friend_list(SteamAccount *sa) { GString *url = g_string_new("/ISteamUserOAuth/GetFriendList/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(url, "steamid=%s&", purple_url_encode(sa->steamid)); g_string_append(url, "relationship=friend,requestrecipient"); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, NULL, url->str, NULL, steam_get_friend_list_cb, NULL, TRUE); g_string_free(url, TRUE); // Grab user nicknames url = g_string_new("/IPlayerService/GetNicknameList/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, NULL, url->str, NULL, steam_get_nickname_list_cb, NULL, TRUE); g_string_free(url, TRUE); } static void steam_get_offline_history_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonObject *response = json_object_get_object_member(obj, "response"); JsonArray *messages = json_object_get_array_member(response, "messages"); guint index; gchar *who = user_data; gint last_message_stored_timestamp = purple_account_get_int(sa->account, "last_message_timestamp", 0); for(index = json_array_get_length(messages); index > 0; index--) { JsonObject *message = json_array_get_object_element(messages, index - 1); gint64 accountid = json_object_get_int_member(message, "accountid"); gint64 timestamp = json_object_get_int_member(message, "timestamp"); const gchar *text = json_object_get_string_member(message, "message"); if (timestamp < last_message_stored_timestamp) continue; if (g_str_equal(steam_accountid_to_steamid(accountid), sa->steamid)) { PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, sa->account); if (conv == NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, sa->account, who); } purple_conversation_write(conv, who, text, PURPLE_MESSAGE_SEND, timestamp); } else { serv_got_im(sa->pc, who, text, PURPLE_MESSAGE_RECV, timestamp); } if (timestamp > sa->last_message_timestamp) sa->last_message_timestamp = timestamp; } g_free(who); } static void steam_get_offline_history(SteamAccount *sa, const gchar *who, gint since) { GString *url = g_string_new("/IFriendMessagesService/GetRecentMessages/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(url, "steamid1=%s&", purple_url_encode(sa->steamid)); g_string_append_printf(url, "steamid2=%s&", purple_url_encode(who)); g_string_append_printf(url, "rtime32_start_time=%d&", since); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, NULL, url->str, NULL, steam_get_offline_history_cb, g_strdup(who), TRUE); g_string_free(url, TRUE); } static void steam_get_conversations_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { JsonObject *response = json_object_get_object_member(obj, "response"); JsonArray *message_sessions = json_object_get_array_member(response, "message_sessions"); guint index; gint last_message_stored_timestamp = purple_account_get_int(sa->account, "last_message_timestamp", 0); if (last_message_stored_timestamp <= 0) return; for(index = 0; index < json_array_get_length(message_sessions); index++) { JsonObject *session = json_array_get_object_element(message_sessions, index); gint64 accountid_friend = json_object_get_int_member(session, "accountid_friend"); gint64 last_message = json_object_get_int_member(session, "last_message"); //gint64 last_view = json_object_get_int_member(session, "last_view"); //gint64 unread_message_count = json_object_get_int_member(session, "unread_message_count"); if (last_message > last_message_stored_timestamp) { steam_get_offline_history(sa, steam_accountid_to_steamid(accountid_friend), last_message_stored_timestamp); } } } static void steam_get_conversations(SteamAccount *sa) { GString *url = g_string_new("/IFriendMessagesService/GetActiveMessageSessions/v0001?"); g_string_append_printf(url, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, NULL, url->str, NULL, steam_get_conversations_cb, NULL, TRUE); g_string_free(url, TRUE); } /******************************************************************************/ /* PRPL functions */ /******************************************************************************/ static const char *steam_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { return "steam"; } static gchar *steam_status_text(PurpleBuddy *buddy) { SteamBuddy *sbuddy = buddy->proto_data; if (sbuddy && sbuddy->gameextrainfo) { if (sbuddy->gameid && *(sbuddy->gameid)) { return g_markup_printf_escaped("In game %s", sbuddy->gameextrainfo); } else { return g_markup_printf_escaped("In non-Steam game %s", sbuddy->gameextrainfo); } } return NULL; } void steam_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full) { SteamBuddy *sbuddy = buddy->proto_data; if (sbuddy) { purple_notify_user_info_add_pair_html(user_info, "Name", sbuddy->personaname); purple_notify_user_info_add_pair_html(user_info, "Real Name", sbuddy->realname); if (sbuddy->gameextrainfo) { gchar *gamename = purple_strdup_withhtml(sbuddy->gameextrainfo); if (sbuddy->gameid && *(sbuddy->gameid)) { purple_notify_user_info_add_pair_html(user_info, "In game", gamename); } else { purple_notify_user_info_add_pair_html(user_info, "In non-Steam game", gamename); } g_free(gamename); } } } const gchar * steam_list_emblem(PurpleBuddy *buddy) { SteamBuddy *sbuddy = buddy->proto_data; if (sbuddy) { if (sbuddy->gameextrainfo || sbuddy->personastateflags & 2) { return "game"; } if (sbuddy->personastateflags & 256) { //Web return "external"; } if (sbuddy->personastateflags & 512) { //Steam mobile, also Pidgin return "mobile"; } if (sbuddy->personastateflags & 1024) { //Big Picture mode return "hiptop"; } } return NULL; } GList * steam_status_types(PurpleAccount *account) { GList *types = NULL; PurpleStatusType *status; purple_debug_info("steam", "status_types\n"); status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, "Online", TRUE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, "Offline", TRUE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, NULL, "Busy", TRUE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_AWAY, NULL, "Away", TRUE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_EXTENDED_AWAY, NULL, "Snoozing", TRUE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, "trade", "Looking to Trade", TRUE, FALSE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, "play", "Looking to Play", TRUE, FALSE, FALSE); types = g_list_append(types, status); if (core_is_haze) { // Telepathy-Haze only displays status_text if the status has a "message" attr GList *iter; for(iter = types; iter; iter = iter->next) { purple_status_type_add_attr(iter->data, "message", "Game Title", purple_value_new(PURPLE_TYPE_STRING)); } } // Independent, unsettable status for being in-game status = purple_status_type_new_with_attrs(PURPLE_STATUS_TUNE, "ingame", NULL, FALSE, FALSE, TRUE, "game", "Game Title", purple_value_new(PURPLE_TYPE_STRING), NULL); types = g_list_append(types, status); return types; } static void steam_login_access_token_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { if (!g_str_equal(json_object_get_string_member(obj, "error"), "OK")) { purple_debug_error("steam", "access_token login error: %s\n", json_object_get_string_member(obj, "error")); purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, json_object_get_string_member(obj, "error")); return; } if (json_object_has_member(obj, "umqid")) { g_free(sa->umqid); sa->umqid = g_strdup(json_object_get_string_member(obj, "umqid")); } if (json_object_has_member(obj, "steamid")) { g_free(sa->steamid); sa->steamid = g_strdup(json_object_get_string_member(obj, "steamid")); } sa->message = (guint) json_object_get_int_member(obj, "message"); purple_connection_set_state(sa->pc, PURPLE_CONNECTED); steam_get_friend_list(sa); steam_poll(sa, FALSE, 0); steam_fetch_new_sessionid(sa); } static void steam_login_with_access_token_error_cb(SteamAccount *sa, const gchar *data, gssize data_len, gpointer user_data) { purple_debug_error("steam", "Access token login error: %s\n", data); if (g_strstr_len(data, data_len, "401 Unauthorized") || g_strstr_len(data, data_len, "Unauthorized") || g_strstr_len(data, data_len, "Forbidden")) { // Our access_token looks like it expired? //Wipe it and try re-auth purple_debug_info("steam", "Clearing expired access_token\n"); steam_account_set_access_token(sa, NULL); steam_get_rsa_key(sa); } else { xmlnode *error_response = xmlnode_from_str(data, data_len); if (error_response != NULL) { xmlnode *title = xmlnode_get_child(error_response, "title"); gchar *title_str = xmlnode_get_data_unescaped(title); purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, title_str); g_free(title_str); xmlnode_free(error_response); } else { gchar *http_error = g_strndup(data, strchr(data, '\n') - data); purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, http_error); g_free(http_error); } } } static void steam_login_with_access_token(SteamAccount *sa) { GString *postdata = g_string_new(NULL); SteamConnection *sconn; g_string_append_printf(postdata, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); if (purple_account_get_string(sa->account, "ui_mode", NULL)) { g_string_append_printf(postdata, "ui_mode=%s", purple_url_encode(purple_account_get_string(sa->account, "ui_mode", "mobile"))); } //TODO, handle a 401 response from the server - trash the steamguard and access_token sconn = steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, NULL, "/ISteamWebUserPresenceOAuth/Logon/v0001", postdata->str, steam_login_access_token_cb, NULL, TRUE); g_string_free(postdata, TRUE); sconn->error_callback = steam_login_with_access_token_error_cb; } static void steam_set_steam_guard_token_cb(gpointer data, const gchar *steam_guard_token) { SteamAccount *sa = data; if (steam_guard_token && *steam_guard_token) { purple_account_set_string(sa->account, "steam_guard_code", steam_guard_token); steam_get_rsa_key(sa); } else { purple_account_set_string(sa->account, "steam_guard_code", ""); purple_connection_error_reason(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Could not authenticate steam-guard code."); } } static void steam_set_two_factor_auth_code_cb(gpointer data, const gchar *twofactorcode) { SteamAccount *sa = data; if (twofactorcode && *twofactorcode) { sa->twofactorcode = g_strdup(twofactorcode); //re-login steam_get_rsa_key(sa); } else { purple_connection_error_reason(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Could not authenticate two-factor code."); } } static void steam_login_cb(SteamAccount *sa, JsonObject *obj, gpointer user_data) { //{"success":true,"redirect_uri":"steammobile:\/\/mobileloginsucceeded","login_complete":true,"oauth":"{\"steamid\":\"id\",\"oauth_token\":\"oauthtoken\",\"webcookie\":\"webcookie\"}"} //{"success":false,"captcha_needed":false,"captcha_gid":-1,"message":"Incorrect login"} //{"success":false,"message":"SteamGuard","emailauth_needed":true,"emaildomain":"domain","emailsteamid":"id"} //{"success":false,"message":"Error verifying humanity","captcha_needed":true,"captcha_gid":"1587796635006345822"} if(json_object_get_boolean_member(obj, "success")) { JsonParser *parser = json_parser_new(); const gchar *oauthjson = json_object_get_string_member(obj, "oauth"); if (!json_parser_load_from_data(parser, oauthjson, -1, NULL)) { purple_debug_error("steam", "Error parsing response: %s\n", oauthjson); purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "JSON decoding error"); } else { JsonNode *root = json_parser_get_root(parser); JsonObject *oauthobj = json_node_get_object(root); steam_account_set_access_token(sa, json_object_get_string_member(oauthobj, "oauth_token")); steam_login_with_access_token(sa); } g_object_unref(parser); } else { const gchar *error_description = json_object_get_string_member(obj, "message"); if (json_object_has_member(obj, "clear_password_field") && json_object_get_boolean_member(obj, "clear_password_field")) { purple_account_set_password(sa->account, ""); purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_description); } else if (json_object_has_member(obj, "emailauth_needed") && json_object_get_boolean_member(obj, "emailauth_needed")) { const gchar *steam_guard_code = purple_account_get_string(sa->account, "steam_guard_code", NULL); if (steam_guard_code && *steam_guard_code) { // We have a guard token set, and we need to clear it out and re-request steam_set_steam_guard_token_cb(sa, NULL); } else { if (json_object_has_member(obj, "emailsteamid")) purple_account_set_string(sa->account, "emailsteamid", json_object_get_string_member(obj, "emailsteamid")); purple_request_input(sa->pc, NULL, _("Set your Steam Guard Code"), _("Copy the Steam Guard Code you will have received in your email"), NULL, FALSE, FALSE, "Steam Guard Code", _("OK"), G_CALLBACK(steam_set_steam_guard_token_cb), _("Cancel"), G_CALLBACK(steam_set_steam_guard_token_cb), sa->account, NULL, NULL, sa); } } else if (json_object_get_boolean_member(obj, "requires_twofactor")) { purple_request_input(sa->pc, NULL, _("Steam two-factor authentication"), _("Copy the two-factor auth code you have received"), NULL, FALSE, FALSE, "Two-Factor Auth Code", _("OK"), G_CALLBACK(steam_set_two_factor_auth_code_cb), _("Cancel"), G_CALLBACK(steam_set_two_factor_auth_code_cb), sa->account, NULL, NULL, sa); } else if (json_object_has_member(obj, "captcha_needed") && json_object_get_boolean_member(obj, "captcha_needed")) { const gchar *captcha_gid = json_object_get_string_member(obj, "captcha_gid"); gchar *captcha_url = g_strdup_printf(STEAM_CAPTCHA_URL, captcha_gid); sa->captcha_gid = g_strdup(captcha_gid); #if PURPLE_VERSION_CHECK(3, 0, 0) purple_util_fetch_url_request(sa->account, captcha_url, TRUE, NULL, FALSE, NULL, FALSE, -1, steam_captcha_image_cb, sa); #else purple_util_fetch_url_request(captcha_url, TRUE, NULL, FALSE, NULL, FALSE, steam_captcha_image_cb, sa); #endif g_free(captcha_url); } else { if (g_str_equal(error_description, "SteamGuard")) { steam_set_steam_guard_token_cb(sa, NULL); } else { purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, error_description); } } } } #include "steam_rsa.c" static void steam_login_got_rsakey(SteamAccount *sa, JsonObject *obj, gpointer user_data) { //{"success":true,"publickey_mod":"pubkeyhex","publickey_exp":"pubkeyhex","timestamp":"165685150000"} GString *post = NULL; gchar *encrypted_password; PurpleAccount *account; if(!json_object_get_boolean_member(obj, "success")) { purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_INVALID_USERNAME, _("Invalid username")); return; } account = sa->account; encrypted_password = steam_encrypt_password( json_object_get_string_member(obj, "publickey_mod"), json_object_get_string_member(obj, "publickey_exp"), account->password); //purple_debug_misc("steam", "Encrypted password is %s\n", encrypted_password); if (!encrypted_password) { purple_connection_error(sa->pc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("Unable to RSA encrypt the password")); return; } post = g_string_new(NULL); g_string_append_printf(post, "password=%s&", purple_url_encode(encrypted_password)); g_string_append_printf(post, "username=%s&", purple_url_encode(account->username)); g_string_append_printf(post, "emailauth=%s&", purple_url_encode(purple_account_get_string(account, "steam_guard_code", ""))); g_string_append_printf(post, "emailsteamid=%s&", purple_url_encode(purple_account_get_string(account, "emailsteamid", ""))); g_string_append(post, "loginfriendlyname=#login_emailauth_friendlyname_mobile&"); g_string_append(post, "oauth_client_id=3638BFB1&"); g_string_append(post, "oauth_scope=read_profile write_profile read_client write_client&"); if (sa->captcha_gid != NULL) { g_string_append_printf(post, "captchagid=%s&", purple_url_encode(sa->captcha_gid)); if (sa->captcha_text != NULL) { g_string_append_printf(post, "captcha_text=%s&", purple_url_encode(sa->captcha_text)); } g_free(sa->captcha_gid); sa->captcha_gid = NULL; g_free(sa->captcha_text); sa->captcha_text = NULL; } else { g_string_append(post, "captchagid=-1&"); g_string_append(post, "captchatext=enter%20above%20characters&"); } if (sa->twofactorcode != NULL) { g_string_append_printf(post, "twofactorcode=%s&", purple_url_encode(sa->twofactorcode)); g_free(sa->twofactorcode); sa->twofactorcode = NULL; } else { g_string_append(post, "twofactorcode=&"); } g_string_append_printf(post, "rsatimestamp=%s&", purple_url_encode(json_object_get_string_member(obj, "timestamp"))); g_string_append(post, "remember_login=false&"); //purple_debug_misc("steam", "Postdata: %s\n", post->str); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, "steamcommunity.com", "/mobilelogin/dologin/", post->str, steam_login_cb, NULL, TRUE); g_string_free(post, TRUE); g_free(encrypted_password); } static void steam_get_rsa_key(SteamAccount *sa) { gchar *url; url = g_strdup_printf("/mobilelogin/getrsakey?username=%s", purple_url_encode(sa->account->username)); steam_post_or_get(sa, STEAM_METHOD_GET | STEAM_METHOD_SSL, "steamcommunity.com", url, NULL, steam_login_got_rsakey, NULL, TRUE); g_free(url); } #ifdef G_OS_UNIX static void #ifdef USE_GNOME_KEYRING steam_keyring_got_password(GnomeKeyringResult res, const gchar* access_token, gpointer user_data) { #else // !USE_GNOME_KEYRING steam_keyring_got_password(GObject *source_object, GAsyncResult *res, gpointer user_data) { gchar *access_token = my_secret_password_lookup_finish(res, NULL); #endif SteamAccount *sa = user_data; if (access_token && *access_token) { sa->cached_access_token = g_strdup(access_token); steam_login_with_access_token(sa); } else { steam_get_rsa_key(sa); } #ifndef USE_GNOME_KEYRING g_free(access_token); #endif } #endif static void steam_login(PurpleAccount *account) { PurpleConnection *pc = purple_account_get_connection(account); SteamAccount *sa = g_new0(SteamAccount, 1); pc->proto_data = sa; if (!purple_ssl_is_supported()) { purple_connection_error (pc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL for login. No TLS/SSL support found.")); return; } sa->account = account; sa->pc = pc; sa->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_replace(sa->cookie_table, g_strdup("forceMobile"), g_strdup("1")); g_hash_table_replace(sa->cookie_table, g_strdup("mobileClient"), g_strdup("ios")); g_hash_table_replace(sa->cookie_table, g_strdup("mobileClientVersion"), g_strdup("1291812")); g_hash_table_replace(sa->cookie_table, g_strdup("Steam_Language"), g_strdup("english")); sa->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); sa->sent_messages_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); sa->waiting_conns = g_queue_new(); sa->last_message_timestamp = purple_account_get_int(sa->account, "last_message_timestamp", 0); #ifdef G_OS_UNIX if(core_is_haze) { #ifdef USE_GNOME_KEYRING my_gnome_keyring_find_password(my_GKNP, //GNOME_KEYRING_NETWORK_PASSWORD, steam_keyring_got_password, sa, NULL, "user", account->username, "server", "api.steamcommunity.com", "protocol", "steammobile", "domain", "libpurple", NULL); #else // !USE_GNOME_KEYRING my_secret_password_lookup(my_SSCN, //SECRET_SCHEMA_COMPAT_NETWORK NULL, steam_keyring_got_password, sa, "user", account->username, "server", "api.steamcommunity.com", "protocol", "steammobile", "domain", "libpurple", NULL); #endif } else #endif if (purple_account_get_string(account, "access_token", NULL)) { steam_login_with_access_token(sa); } else { steam_get_rsa_key(sa); } purple_connection_set_state(pc, PURPLE_CONNECTING); purple_connection_update_progress(pc, _("Connecting"), 1, 3); } static void steam_close(PurpleConnection *pc) { SteamAccount *sa; GString *post; g_return_if_fail(pc != NULL); g_return_if_fail(pc->proto_data != NULL); sa = pc->proto_data; // Go offline on the website if (sa->umqid != NULL) { post = g_string_new(NULL); g_string_append_printf(post, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(post, "umqid=%s&", purple_url_encode(sa->umqid)); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, NULL, "/ISteamWebUserPresenceOAuth/Logoff/v0001", post->str, NULL, NULL, TRUE); g_string_free(post, TRUE); } if (sa->poll_timeout) { purple_timeout_remove(sa->poll_timeout); } if (sa->watchdog_timeout) { purple_timeout_remove(sa->watchdog_timeout); } if (sa->last_message_timestamp > 0) purple_account_set_int(sa->account, "last_message_timestamp", sa->last_message_timestamp); purple_debug_info("steam", "destroying %d waiting connections\n", g_queue_get_length(sa->waiting_conns)); while (!g_queue_is_empty(sa->waiting_conns)) steam_connection_destroy(g_queue_pop_tail(sa->waiting_conns)); g_queue_free(sa->waiting_conns); purple_debug_info("steam", "destroying %d incomplete connections\n", g_slist_length(sa->conns)); while (sa->conns != NULL) steam_connection_destroy(sa->conns->data); while (sa->dns_queries != NULL) { PurpleDnsQueryData *dns_query = sa->dns_queries->data; purple_debug_info("steam", "canceling dns query for %s\n", purple_dnsquery_get_host(dns_query)); sa->dns_queries = g_slist_remove(sa->dns_queries, dns_query); purple_dnsquery_destroy(dns_query); } g_hash_table_destroy(sa->sent_messages_hash); g_hash_table_destroy(sa->cookie_table); g_hash_table_destroy(sa->hostname_ip_cache); g_free(sa->captcha_gid); g_free(sa->captcha_text); g_free(sa->twofactorcode); g_free(sa->cached_access_token); g_free(sa->umqid); g_free(sa); } static unsigned int steam_send_typing(PurpleConnection *pc, const gchar *name, PurpleTypingState state) { SteamAccount *sa = pc->proto_data; if (state == PURPLE_TYPING) { GString *post = g_string_new(NULL); g_string_append_printf(post, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(post, "umqid=%s&", purple_url_encode(sa->umqid)); g_string_append(post, "type=typing&"); g_string_append_printf(post, "steamid_dst=%s", name); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, NULL, "/ISteamWebUserPresenceOAuth/Message/v0001", post->str, NULL, NULL, TRUE); g_string_free(post, TRUE); } return 20; } static void steam_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *pc = purple_account_get_connection(account); SteamAccount *sa = pc->proto_data; PurpleStatusPrimitive prim = purple_status_type_get_primitive(purple_status_get_type(status)); guint state_id; GString *post = NULL; switch(prim) { default: case PURPLE_STATUS_OFFLINE: state_id = 0; break; case PURPLE_STATUS_AVAILABLE: state_id = 1; break; case PURPLE_STATUS_UNAVAILABLE: state_id = 2; break; case PURPLE_STATUS_AWAY: state_id = 3; break; case PURPLE_STATUS_EXTENDED_AWAY: state_id = 4; break; } post = g_string_new(NULL); g_string_append_printf(post, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(post, "umqid=%s&", purple_url_encode(sa->umqid)); g_string_append(post, "type=personastate&"); g_string_append_printf(post, "persona_state=%u", state_id); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, NULL, "/ISteamWebUserPresenceOAuth/Message/v0001", post->str, NULL, NULL, TRUE); g_string_free(post, TRUE); } static void steam_set_idle(PurpleConnection *pc, int time) { SteamAccount *sa = pc->proto_data; sa->idletime = time; } static gint steam_send_im(PurpleConnection *pc, const gchar *who, const gchar *msg, PurpleMessageFlags flags) { SteamAccount *sa = pc->proto_data; GString *post = g_string_new(NULL); gchar *stripped; g_string_append_printf(post, "access_token=%s&", purple_url_encode(steam_account_get_access_token(sa))); g_string_append_printf(post, "umqid=%s&", purple_url_encode(sa->umqid)); stripped = purple_markup_strip_html(msg); g_string_append(post, "type=saytext&"); g_string_append_printf(post, "text=%s&", purple_url_encode(stripped)); g_string_append_printf(post, "steamid_dst=%s", who); steam_post_or_get(sa, STEAM_METHOD_POST | STEAM_METHOD_SSL, NULL, "/ISteamWebUserPresenceOAuth/Message/v0001", post->str, NULL, NULL, TRUE); g_string_free(post, TRUE); g_free(stripped); return 1; } static void steam_buddy_free(PurpleBuddy *buddy) { SteamBuddy *sbuddy = buddy->proto_data; if (sbuddy != NULL) { buddy->proto_data = NULL; g_free(sbuddy->steamid); g_free(sbuddy->personaname); g_free(sbuddy->realname); g_free(sbuddy->profileurl); g_free(sbuddy->avatar); g_free(sbuddy->gameid); g_free(sbuddy->gameextrainfo); g_free(sbuddy->gameserversteamid); g_free(sbuddy->lobbysteamid); g_free(sbuddy->gameserverip); g_free(sbuddy); } } void steam_fake_group_buddy(PurpleConnection *pc, const char *who, const char *old_group, const char *new_group) { // Do nothing to stop the remove+add behaviour } void steam_fake_group_rename(PurpleConnection *pc, const char *old_name, PurpleGroup *group, GList *moved_buddies) { // Do nothing to stop the remove+add behaviour } void #if PURPLE_VERSION_CHECK(3, 0, 0) steam_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char* message) #else steam_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) #endif { SteamAccount *sa = pc->proto_data; if (g_ascii_strtoull(buddy->name, NULL, 10)) { steam_friend_action(sa, buddy->name, "add"); } else { purple_blist_remove_buddy(buddy); purple_notify_warning(pc, "Invalid friend id", "Invalid friend id", "Friend ID's must be numeric.\nTry searching from the account menu."); } } void steam_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) { SteamAccount *sa = pc->proto_data; steam_friend_action(sa, buddy->name, "remove"); } /******************************************************************************/ /* Plugin functions */ /******************************************************************************/ static gboolean plugin_load(PurplePlugin *plugin) { purple_debug_info("steam", "Purple core UI name: %s\n", purple_core_get_ui()); #ifdef G_OS_UNIX core_is_haze = g_str_equal(purple_core_get_ui(), "haze"); #ifdef USE_GNOME_KEYRING if (core_is_haze && gnome_keyring_lib == NULL) { purple_debug_info("steam", "UI Core is Telepathy-Haze, attempting to load Gnome-Keyring\n"); gnome_keyring_lib = dlopen("libgnome-keyring.so", RTLD_NOW | RTLD_GLOBAL); if (!gnome_keyring_lib) { purple_debug_error("steam", "Could not load Gnome-Keyring library. This plugin requires Gnome-Keyring when used with Telepathy-Haze\n"); return FALSE; } my_gnome_keyring_store_password = (gnome_keyring_store_password_type) dlsym(gnome_keyring_lib, "gnome_keyring_store_password"); my_gnome_keyring_delete_password = (gnome_keyring_delete_password_type) dlsym(gnome_keyring_lib, "gnome_keyring_delete_password"); my_gnome_keyring_find_password = (gnome_keyring_find_password_type) dlsym(gnome_keyring_lib, "gnome_keyring_find_password"); if (!my_gnome_keyring_store_password || !my_gnome_keyring_delete_password || !my_gnome_keyring_find_password) { dlclose(gnome_keyring_lib); gnome_keyring_lib = NULL; purple_debug_error("steam", "Could not load Gnome-Keyring functions. This plugin requires Gnome-Keyring when used with Telepathy-Haze\n"); return FALSE; } } #else // !USE_GNOME_KEYRING if (core_is_haze && secret_lib == NULL) { purple_debug_info("steam", "UI Core is Telepathy-Haze, attempting to load libsecret\n"); secret_lib = dlopen("libsecret-1.so", RTLD_NOW | RTLD_GLOBAL); if (!secret_lib) { purple_debug_error("steam", "Could not load libsecret library. This plugin requires libsecret when used with Telepathy-Haze\n"); return FALSE; } my_secret_password_store = (secret_password_store_type) dlsym(secret_lib, "secret_password_store"); my_secret_password_clear = (secret_password_clear_type) dlsym(secret_lib, "secret_password_clear"); my_secret_password_lookup = (secret_password_lookup_type) dlsym(secret_lib, "secret_password_lookup"); my_secret_password_lookup_finish = (secret_password_lookup_finish_type) dlsym(secret_lib, "secret_password_lookup_finish"); if (!my_secret_password_store || !my_secret_password_clear || !my_secret_password_lookup || !my_secret_password_lookup_finish) { dlclose(secret_lib); secret_lib = NULL; purple_debug_error("steam", "Could not load libsecret functions. This plugin requires libsecret when used with Telepathy-Haze\n"); return FALSE; } } #endif // USE_GNOME_KEYRING #endif return TRUE; } static gboolean plugin_unload(PurplePlugin *plugin) { #ifdef G_OS_UNIX #ifdef USE_GNOME_KEYRING if (gnome_keyring_lib) { dlclose(gnome_keyring_lib); gnome_keyring_lib = NULL; } #else // !USE_GNOME_KEYRING if (secret_lib) { dlclose(secret_lib); secret_lib = NULL; } #endif // USE_GNOME_KEYRING #endif return TRUE; } static GList *steam_actions(PurplePlugin *plugin, gpointer context) { GList *m = NULL; PurplePluginAction *act; act = purple_plugin_action_new(_("Search for friends..."), steam_search_users); m = g_list_append(m, act); act = purple_plugin_action_new(_("Redeem game key..."), steam_register_game_key); m = g_list_append(m, act); return m; } void steam_blist_launch_game(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; SteamBuddy *sbuddy; PurplePlugin *handle = purple_find_prpl(STEAM_PLUGIN_ID); if(!PURPLE_BLIST_NODE_IS_BUDDY(node)) return; buddy = (PurpleBuddy *) node; if (!buddy) return; sbuddy = buddy->proto_data; if (sbuddy && sbuddy->gameid) { gchar *runurl = g_strdup_printf("steam://rungameid/%s", sbuddy->gameid); purple_notify_uri(handle, runurl); g_free(runurl); } } void steam_blist_join_game(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; SteamBuddy *sbuddy; PurplePlugin *handle = purple_find_prpl(STEAM_PLUGIN_ID); if(!PURPLE_BLIST_NODE_IS_BUDDY(node)) return; buddy = (PurpleBuddy *) node; if (!buddy) return; sbuddy = buddy->proto_data; if (sbuddy) { if (sbuddy->gameserverip && (!sbuddy->gameserversteamid || !g_str_equal(sbuddy->gameserversteamid, "1"))) { gchar *joinurl = g_strdup_printf("steam://connect/%s", sbuddy->gameserverip); purple_notify_uri(handle, joinurl); g_free(joinurl); } else if (sbuddy->lobbysteamid) { gchar *joinurl = g_strdup_printf("steam://joinlobby/%s/%s/%s", sbuddy->gameid, sbuddy->lobbysteamid, sbuddy->steamid); purple_notify_uri(handle, joinurl); g_free(joinurl); } } } void steam_blist_view_profile(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; SteamBuddy *sbuddy; PurplePlugin *handle = purple_find_prpl(STEAM_PLUGIN_ID); if(!PURPLE_BLIST_NODE_IS_BUDDY(node)) return; buddy = (PurpleBuddy *) node; if (!buddy) return; sbuddy = buddy->proto_data; if (sbuddy && sbuddy->profileurl) { purple_notify_uri(handle, sbuddy->profileurl); } else { gchar *profileurl = g_strdup_printf("http://steamcommunity.com/profiles/%s", buddy->name); purple_notify_uri(handle, profileurl); g_free(profileurl); } } static GList * steam_node_menu(PurpleBlistNode *node) { GList *m = NULL; PurpleMenuAction *act; PurpleBuddy *buddy; SteamBuddy *sbuddy; if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { buddy = (PurpleBuddy *)node; act = purple_menu_action_new("View online Profile", PURPLE_CALLBACK(steam_blist_view_profile), NULL, NULL); m = g_list_append(m, act); sbuddy = buddy->proto_data; if (sbuddy && sbuddy->gameid) { act = purple_menu_action_new("Launch Game", PURPLE_CALLBACK(steam_blist_launch_game), NULL, NULL); m = g_list_append(m, act); if (sbuddy->lobbysteamid || (sbuddy->gameserverip && (!sbuddy->gameserversteamid || !g_str_equal(sbuddy->gameserversteamid, "1")))) { act = purple_menu_action_new("Join Game", PURPLE_CALLBACK(steam_blist_join_game), NULL, NULL); m = g_list_append(m, act); } } } return m; } static void plugin_init(PurplePlugin *plugin) { PurpleAccountOption *option; PurplePluginInfo *info = plugin->info; PurplePluginProtocolInfo *prpl_info = info->extra_info; GList *ui_mode_list = NULL; PurpleKeyValuePair *kvp; option = purple_account_option_string_new( _("Steam Guard Code"), "steam_guard_code", ""); prpl_info->protocol_options = g_list_append( prpl_info->protocol_options, option); option = purple_account_option_bool_new( _("Always use HTTPS"), "always_use_https", TRUE); prpl_info->protocol_options = g_list_append( prpl_info->protocol_options, option); option = purple_account_option_bool_new( _("Change status when in-game"), "change_status_to_game", FALSE); prpl_info->protocol_options = g_list_append( prpl_info->protocol_options, option); option = purple_account_option_bool_new( _("Download offline history"), "download_offline_history", TRUE); prpl_info->protocol_options = g_list_append( prpl_info->protocol_options, option); kvp = g_new0(PurpleKeyValuePair, 1); kvp->key = g_strdup(_("Mobile")); kvp->value = g_strdup("mobile"); ui_mode_list = g_list_append(ui_mode_list, kvp); kvp = g_new0(PurpleKeyValuePair, 1); kvp->key = g_strdup(_("Web")); kvp->value = g_strdup("web"); ui_mode_list = g_list_append(ui_mode_list, kvp); option = purple_account_option_list_new( _("Identify as"), "ui_mode", ui_mode_list); prpl_info->protocol_options = g_list_append( prpl_info->protocol_options, option); } static PurplePluginProtocolInfo prpl_info = { #if PURPLE_VERSION_CHECK(3, 0, 0) sizeof(PurplePluginProtocolInfo), /* struct_size */ #endif /* options */ OPT_PROTO_MAIL_CHECK, NULL, /* user_splits */ NULL, /* protocol_options */ /* NO_BUDDY_ICONS */ /* icon_spec */ {"png,jpeg", 0, 0, 64, 64, 0, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ steam_list_icon, /* list_icon */ steam_list_emblem, /* list_emblems */ steam_status_text, /* status_text */ steam_tooltip_text, /* tooltip_text */ steam_status_types, /* status_types */ steam_node_menu, /* blist_node_menu */ NULL,//steam_chat_info, /* chat_info */ NULL,//steam_chat_info_defaults, /* chat_info_defaults */ steam_login, /* login */ steam_close, /* close */ steam_send_im, /* send_im */ NULL, /* set_info */ steam_send_typing, /* send_typing */ NULL,//steam_get_info, /* get_info */ steam_set_status, /* set_status */ steam_set_idle, /* set_idle */ NULL, /* change_passwd */ steam_add_buddy, /* add_buddy */ NULL, /* add_buddies */ steam_buddy_remove, /* remove_buddy */ NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ NULL, /* set_permit_deny */ NULL,//steam_fake_join_chat, /* join_chat */ NULL, /* reject chat invite */ NULL,//steam_get_chat_name, /* get_chat_name */ NULL, /* chat_invite */ NULL,//steam_chat_fake_leave, /* chat_leave */ NULL, /* chat_whisper */ NULL,//steam_chat_send, /* chat_send */ NULL, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ #if !PURPLE_VERSION_CHECK(3, 0, 0) NULL, /* get_cb_away */ #endif NULL, /* alias_buddy */ steam_fake_group_buddy, /* group_buddy */ steam_fake_group_rename, /* rename_group */ steam_buddy_free, /* buddy_free */ NULL,//steam_conversation_closed, /* convo_closed */ purple_normalize_nocase,/* normalize */ NULL, /* set_buddy_icon */ NULL,//steam_group_remove, /* remove_group */ NULL, /* get_cb_real_name */ NULL, /* set_chat_topic */ NULL, /* find_blist_chat */ NULL, /* roomlist_get_list */ NULL, /* roomlist_cancel */ NULL, /* roomlist_expand_category */ NULL, /* can_receive_file */ NULL, /* send_file */ NULL, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* attention_types */ #if (PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 5) || PURPLE_MAJOR_VERSION > 2 #if PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 5 sizeof(PurplePluginProtocolInfo), /* struct_size */ #endif NULL, // steam_get_account_text_table, /* get_account_text_table */ NULL, NULL, NULL, NULL, NULL #else (gpointer) sizeof(PurplePluginProtocolInfo) #endif }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, /* major_version */ PURPLE_MINOR_VERSION, /* minor version */ PURPLE_PLUGIN_PROTOCOL, /* type */ NULL, /* ui_requirement */ 0, /* flags */ NULL, /* dependencies */ PURPLE_PRIORITY_DEFAULT, /* priority */ STEAM_PLUGIN_ID, /* id */ "Steam", /* name */ STEAM_PLUGIN_VERSION, /* version */ N_("Steam Protocol Plugin"), /* summary */ N_("Steam Protocol Plugin"), /* description */ "Eion Robb ", /* author */ "http://pidgin-opensteamworks.googlecode.com/", /* homepage */ plugin_load, /* load */ plugin_unload, /* unload */ NULL, /* destroy */ NULL, /* ui_info */ &prpl_info, /* extra_info */ NULL, /* prefs_info */ steam_actions, /* actions */ /* padding */ NULL, NULL, NULL, NULL }; PURPLE_INIT_PLUGIN(steam, plugin_init, info);