/*
* 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;
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);