<commit_before>#include <cassert>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

#include "steam++.h"

// unistd.h is broken in MinGW
#ifdef _WIN32
#include <win32/win32dep.h>
#else
#include <unistd.h>
#endif

#include <purple.h>

using namespace Steam;

struct SteamPurple {
	SteamClient client;
	
	int fd;
	std::vector<unsigned char> read_buffer;
	std::vector<unsigned char> write_buffer;
	std::vector<unsigned char>::size_type read_offset;
	guint watcher;
	PurpleProxyConnectData *connect_data;
	
	guint timer;
	std::function<void()> callback;
};

static gboolean plugin_load(PurplePlugin *plugin) {
	return TRUE;
}

static const char *steam_list_icon(PurpleAccount *account, PurpleBuddy *buddy) {
	return "steam";
}

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);
	
	return types;
}

static void steam_connect(PurpleAccount *account, SteamPurple* steam) {
	auto &endpoint = servers[rand() % (sizeof(servers) / sizeof(servers[0]))];
	steam->connect_data = purple_proxy_connect(NULL, account, endpoint.host, endpoint.port, [](gpointer data, gint source, const gchar *error_message) {
		auto pc = (PurpleConnection *)data;
		auto steam = reinterpret_cast<SteamPurple*>(purple_connection_get_protocol_data(pc));
		if (source == -1) {
			steam->connect_data = NULL;
			purple_connection_error_reason(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message);
			return;
		}
		steam->fd = source;
		auto next_length = steam->client.connected();
		steam->read_buffer.resize(next_length);
		steam->watcher = purple_input_add(source, PURPLE_INPUT_READ, [](gpointer data, gint source, PurpleInputCondition cond) {
			auto steam = reinterpret_cast<SteamPurple*>(data);
			auto len = read(source, &steam->read_buffer[steam->read_offset], steam->read_buffer.size() - steam->read_offset);
			purple_debug_info("steam", "read: %i\n", len);
			// len == 0: preceded by a ClientLoggedOff or ClientLogOnResponse, socket should be already closed by us
			// len == -1: TODO
			assert(len > 0);
			steam->read_offset += len;
			if (steam->read_offset == steam->read_buffer.size()) {
				auto next_len = steam->client.readable(steam->read_buffer.data());
				steam->read_offset = 0;
				steam->read_buffer.resize(next_len);
			}
		}, steam);
	}, purple_account_get_connection(account));
	assert(steam->connect_data);
}

static void steam_set_steam_guard_token_cb(gpointer data, const gchar *steam_guard_token) {
	auto pc = (PurpleConnection *)data;
	auto account = purple_connection_get_account(pc);
	auto steam = reinterpret_cast<SteamPurple*>(purple_connection_get_protocol_data(pc));
	
	purple_debug_info("steam", "Got token: %s\n", steam_guard_token);
	steam_connect(account, steam);
	
	std::string token(steam_guard_token);
	steam->client.onHandshake = [steam, account, token] {
		// we just copied the token twice (compiler optimizations notwithstanding) but this is the prettiest way to access it in the lambda
		// yay for capture by move in C++14
		steam->client.LogOn(purple_account_get_username(account), purple_account_get_password(account), nullptr, token.c_str());
	};
}

static void steam_login(PurpleAccount *account) {
	auto pc = purple_account_get_connection(account);
	auto steam = new SteamPurple {
		// SteamClient constructor
		{
			// why use pc instead of steam directly?<commit_msg>Handle socket errors in steampurple<commit_after>#include <cassert>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

#include "steam++.h"

// unistd.h is broken in MinGW
#ifdef _WIN32
#include <win32/win32dep.h>
#else
#include <unistd.h>
#endif

#include <purple.h>

using namespace Steam;

struct SteamPurple {
	SteamClient client;
	
	int fd;
	std::vector<unsigned char> read_buffer;
	std::vector<unsigned char> write_buffer;
	std::vector<unsigned char>::size_type read_offset;
	guint watcher;
	PurpleProxyConnectData *connect_data;
	
	guint timer;
	std::function<void()> callback;
};

static gboolean plugin_load(PurplePlugin *plugin) {
	return TRUE;
}

static const char *steam_list_icon(PurpleAccount *account, PurpleBuddy *buddy) {
	return "steam";
}

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);
	
	return types;
}

static void steam_connect(PurpleAccount *account, SteamPurple* steam) {
	auto &endpoint = servers[rand() % (sizeof(servers) / sizeof(servers[0]))];
	steam->connect_data = purple_proxy_connect(NULL, account, endpoint.host, endpoint.port, [](gpointer data, gint source, const gchar *error_message) {
		auto pc = (PurpleConnection *)data;
		auto steam = reinterpret_cast<SteamPurple*>(purple_connection_get_protocol_data(pc));
		if (source == -1) {
			steam->connect_data = NULL;
			purple_connection_error_reason(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message);
			return;
		}
		steam->fd = source;
		auto next_length = steam->client.connected();
		steam->read_buffer.resize(next_length);
		steam->watcher = purple_input_add(source, PURPLE_INPUT_READ, [](gpointer data, gint source, PurpleInputCondition cond) {
			auto pc = (PurpleConnection *)data;
			auto steam = reinterpret_cast<SteamPurple*>(purple_connection_get_protocol_data(pc));
			auto len = read(source, &steam->read_buffer[steam->read_offset], steam->read_buffer.size() - steam->read_offset);
			purple_debug_info("steam", "read: %i\n", len);
			// len == 0: preceded by a ClientLoggedOff or ClientLogOnResponse, socket should be already closed by us
			if (len == -1) {
				purple_connection_error_reason(pc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, strerror(errno));
				purple_input_remove(steam->watcher); // on Linux, steam_close is called too late and Pidgin catches the EOF
				steam->watcher = 0;
				return;
			}
			assert(len > 0);
			steam->read_offset += len;
			if (steam->read_offset == steam->read_buffer.size()) {
				auto next_len = steam->client.readable(steam->read_buffer.data());
				steam->read_offset = 0;
				steam->read_buffer.resize(next_len);
			}
		}, pc);
	}, purple_account_get_connection(account));
	assert(steam->connect_data);
}

static void steam_set_steam_guard_token_cb(gpointer data, const gchar *steam_guard_token) {
	auto pc = (PurpleConnection *)data;
	auto account = purple_connection_get_account(pc);
	auto steam = reinterpret_cast<SteamPurple*>(purple_connection_get_protocol_data(pc));
	
	purple_debug_info("steam", "Got token: %s\n", steam_guard_token);
	steam_connect(account, steam);
	
	std::string token(steam_guard_token);
	steam->client.onHandshake = [steam, account, token] {
		// we just copied the token twice (compiler optimizations notwithstanding) but this is the prettiest way to access it in the lambda
		// yay for capture by move in C++14
		steam->client.LogOn(purple_account_get_username(account), purple_account_get_password(account), nullptr, token.c_str());
	};
}

static void steam_login(PurpleAccount *account) {
	auto pc = purple_account_get_connection(account);
	auto steam = new SteamPurple {
		// SteamClient constructor
		{
			// why use pc instead of steam directly?
{'fraction_non_alphanumeric': 0.12104689203925845, 'fraction_numerical': 0.0031624863685932388, 'mean_word_length': 6.098297213622291, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '58947', 'n_tokens_mistral': 3352, 'n_tokens_neox': 3119, 'n_words': 700}