# HG changeset patch
# Node ID ff5720d11120
# Parent  d78c0951ea2c
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -45,6 +45,7 @@
 	'purplechatconversation.c',
 	'purplechatuser.c',
 	'purpleconnectionerrorinfo.c',
+	'purplecontact.c',
 	'purpleconversation.c',
 	'purpleconversationmanager.c',
 	'purpleconversationuiops.c',
@@ -147,6 +148,7 @@
 	'purplechatconversation.h',
 	'purplechatuser.h',
 	'purpleconnectionerrorinfo.h',
+	'purplecontact.h',
 	'purpleconversation.h',
 	'purpleconversationmanager.h',
 	'purpleconversationuiops.h',
diff --git a/libpurple/purplecontact.h b/libpurple/purplecontact.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purplecontact.h
@@ -0,0 +1,214 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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 2 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 <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PURPLE_CONTACT_H
+#define PURPLE_CONTACT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <libpurple/account.h>
+#include <libpurple/purpletags.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_CONTACT (purple_contact_get_type())
+G_DECLARE_FINAL_TYPE(PurpleContact, purple_contact, PURPLE, CONTACT, GObject)
+
+/**
+ * PurpleContact:
+ *
+ * A representation of a user. Contacts are used everywhere where you need to
+ * refer to a user. Be it a chat, an direct message, a file transfer, etc.
+ */
+
+/**
+ * purple_contact_new:
+ * @account: The [class@Purple.Account] this contact is from.
+ * @username: The username of the contact.
+ *
+ * Creates a new [class@Purple.Contact].
+ */
+PurpleContact *purple_contact_new(PurpleAccount *account, const gchar *username);
+
+/**
+ * purple_contact_get_account:
+ * @contact: The instance.
+ *
+ * Gets the [class@Purple.Account] that @contact belongs to.
+ *
+ * Returns: (transfer none): The [class@Purple.Account] that @contact belongs
+ *          to.
+ *
+ * Since: 3.0.0
+ */
+PurpleAccount *purple_contact_get_account(PurpleContact *contact);
+
+/**
+ * purple_contact_get_username:
+ * @contact: The instance.
+ *
+ * Gets the username of @contact.
+ *
+ * Returns: The username of @contact.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_contact_get_username(PurpleContact *contact);
+
+/**
+ * purple_contact_set_username:
+ * @contact: The instance.
+ * @username: The new username.
+ *
+ * Sets the username of @contact to @username.
+ *
+ * This is primarily used by protocol plugins like IRC when a user changes
+ * their "nick" which is their username.
+ *
+ * Since: 3.0.0
+ */
+void purple_contact_set_username(PurpleContact *contact, const gchar *username);
+
+/**
+ * purple_contact_get_display_name:
+ * @contact: The instance.
+ *
+ * Gets the display name for @contact. The display name is typically set by the
+ * contact and is handled by the protocol plugin.
+ *
+ * Returns: (nullable): The display name of @contact if one is set, otherwise
+ *          %NULL will be returned.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_contact_get_display_name(PurpleContact *contact);
+
+/**
+ * purple_contact_set_display_name:
+ * @contact: The instance.
+ * @display_name: (nullable): The new displayname.
+ *
+ * Sets the display name of @contact to @display_name.
+ *
+ * This should primarily only be used by protocol plugins and everyone else
+ * should be using [method@Purple.Contact.set_alias].
+ *
+ * Since: 3.0.0
+ */
+void purple_contact_set_display_name(PurpleContact *contact, const gchar *display_name);
+
+/**
+ * purple_contact_get_alias:
+ * @contact: The instance.
+ *
+ * Gets the alias for @contact.
+ *
+ * Returns: (nullable): The alias of @contact if one is set, otherwise %NULL.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_contact_get_alias(PurpleContact *contact);
+
+/**
+ * purple_contact_set_alias:
+ * @contact: The instance.
+ * @alias: (nullable): The new alias.
+ *
+ * Sets the alias of @contact to @alias.
+ *
+ * Protocol plugins may use this value to synchronize across instances.
+ *
+ * Since: 3.0.0
+ */
+void purple_contact_set_alias(PurpleContact *contact, const gchar *alias);
+
+/**
+ * purple_contact_get_avatar:
+ * @contact: The instance.
+ *
+ * Gets the avatar for @contact if one is set.
+ *
+ * Returns: (transfer none): The avatar if set, otherwise %NULL.
+ *
+ * Since: 3.0.0
+ */
+GdkPixbuf *purple_contact_get_avatar(PurpleContact *contact);
+
+/**
+ * purple_contact_set_avatar:
+ * @contact: The instance.
+ * @avatar: (nullable): The new avatar to set.
+ *
+ * Sets the avatar for @contact to @avatar. If @avatar is %NULL an existing
+ * avatar will be removed.
+ *
+ * Typically this should only called by the protocol plugin.
+ *
+ * Since: 3.0.0
+ */
+void purple_contact_set_avatar(PurpleContact *contact, GdkPixbuf *avatar);
+
+/**
+ * purple_contact_get_presence:
+ * @contact: The instance.
+ *
+ * Gets the [class@Purple.Presence] for @contact.
+ *
+ * Returns: (nullable): The presence for @contact if one is set, otherwise
+ *          %NULL.
+ *
+ * Since: 3.0.0
+ */
+PurplePresence *purple_contact_get_presence(PurpleContact *contact);
+
+/**
+ * purple_contact_set_presence:
+ * @contact: The instance.
+ * @presence: (nullable): The new [class@Purple.Presence] to set.
+ *
+ * Sets the presence for @contact to @presence. If @presence is %NULL, the
+ * existing presence will be cleared.
+ *
+ * Typically this should only be called by the protocol plugin.
+ *
+ * Since: 3.0.0
+ */
+void purple_contact_set_presence(PurpleContact *contact, PurplePresence *presence);
+
+/**
+ * purple_contact_get_tags:
+ * @contact: The instance.
+ *
+ * Gets the [class@Purple.Tags] instance for @contact.
+ *
+ * Returns: (transfer none): The tags for @contact.
+ *
+ * Since: 3.0.0
+ */
+PurpleTags *purple_contact_get_tags(PurpleContact *contact);
+
+G_END_DECLS
+
+#endif /* PURPLE_CONTACT_H */
diff --git a/libpurple/purplecontact.c b/libpurple/purplecontact.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purplecontact.c
@@ -0,0 +1,380 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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 2 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "purplecontact.h"
+
+struct _PurpleContact {
+	GObject parent;
+
+	PurpleAccount *account;
+	gchar *username;
+	gchar *display_name;
+	gchar *alias;
+
+	GdkPixbuf *avatar;
+
+	PurplePresence *presence;
+
+	PurpleTags *tags;
+};
+
+enum {
+	PROP_0,
+	PROP_ACCOUNT,
+	PROP_USERNAME,
+	PROP_DISPLAY_NAME,
+	PROP_ALIAS,
+	PROP_AVATAR,
+	PROP_PRESENCE,
+	PROP_TAGS,
+	N_PROPERTIES
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+G_DEFINE_TYPE(PurpleContact, purple_contact, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_contact_set_account(PurpleContact *contact, PurpleAccount *account) {
+	g_return_if_fail(PURPLE_IS_CONTACT(contact));
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+
+	if(g_set_object(&contact->account, account)) {
+		g_object_notify_by_pspec(G_OBJECT(contact), properties[PROP_ACCOUNT]);
+	}
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_contact_get_property(GObject *obj, guint param_id, GValue *value,
+                            GParamSpec *pspec)
+{
+	PurpleContact *contact = PURPLE_CONTACT(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			g_value_set_object(value, purple_contact_get_account(contact));
+			break;
+		case PROP_USERNAME:
+			g_value_set_string(value, purple_contact_get_username(contact));
+			break;
+		case PROP_DISPLAY_NAME:
+			g_value_set_string(value, purple_contact_get_display_name(contact));
+			break;
+		case PROP_ALIAS:
+			g_value_set_string(value, purple_contact_get_alias(contact));
+			break;
+		case PROP_AVATAR:
+			g_value_set_object(value, purple_contact_get_avatar(contact));
+			break;
+		case PROP_PRESENCE:
+			g_value_set_object(value, purple_contact_get_presence(contact));
+			break;
+		case PROP_TAGS:
+			g_value_set_object(value, purple_contact_get_tags(contact));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_contact_set_property(GObject *obj, guint param_id, const GValue *value,
+                            GParamSpec *pspec)
+{
+	PurpleContact *contact = PURPLE_CONTACT(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			purple_contact_set_account(contact, g_value_get_object(value));
+			break;
+		case PROP_USERNAME:
+			purple_contact_set_username(contact, g_value_get_string(value));
+			break;
+		case PROP_DISPLAY_NAME:
+			purple_contact_set_display_name(contact, g_value_get_string(value));
+			break;
+		case PROP_ALIAS:
+			purple_contact_set_alias(contact, g_value_get_string(value));
+			break;
+		case PROP_AVATAR:
+			purple_contact_set_avatar(contact, g_value_get_object(value));
+			break;
+		case PROP_PRESENCE:
+			purple_contact_set_presence(contact, g_value_get_object(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_contact_dispose(GObject *obj) {
+	PurpleContact *contact = PURPLE_CONTACT(obj);
+
+	g_clear_object(&contact->account);
+	g_clear_object(&contact->avatar);
+	g_clear_object(&contact->presence);
+	g_clear_object(&contact->tags);
+
+	G_OBJECT_CLASS(purple_contact_parent_class)->dispose(obj);
+}
+
+static void
+purple_contact_finalize(GObject *obj) {
+	PurpleContact *contact = PURPLE_CONTACT(obj);
+
+	g_clear_pointer(&contact->username, g_free);
+	g_clear_pointer(&contact->display_name, g_free);
+	g_clear_pointer(&contact->alias, g_free);
+
+	G_OBJECT_CLASS(purple_contact_parent_class)->finalize(obj);
+}
+
+static void
+purple_contact_init(PurpleContact *contact) {
+	contact->tags = purple_tags_new();
+}
+
+static void
+purple_contact_class_init(PurpleContactClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = purple_contact_get_property;
+	obj_class->set_property = purple_contact_set_property;
+	obj_class->dispose = purple_contact_dispose;
+	obj_class->finalize = purple_contact_finalize;
+
+	/**
+	 * PurpleContact:account:
+	 *
+	 * The account that this contact belongs to.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ACCOUNT] = g_param_spec_object(
+		"account", "account",
+		"The account this contact belongs to",
+		PURPLE_TYPE_ACCOUNT,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleContact:username:
+	 *
+	 * The username for this contact. In rare cases this change change, like
+	 * when a user changes their "nick" on IRC which is their user name.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_USERNAME] = g_param_spec_string(
+		"username", "username",
+		"The username of the contact",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleContact:display-name:
+	 *
+	 * The display name for this contact. This is generally controlled by the
+	 * contact themself and controlled via the protocol plugin.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_DISPLAY_NAME] = g_param_spec_string(
+		"display-name", "display-name",
+		"The display name of the contact",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleContact:alias:
+	 *
+	 * The alias for this contact. This is controled but the libpurple user and
+	 * maybe be used by the protocol if allows for aliasing.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ALIAS] = g_param_spec_string(
+		"alias", "alias",
+		"The alias of the contact.",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleContact:avatar:
+	 *
+	 * The avatar for this contact. This is typically controlled by the protocol
+	 * and should only be read by others.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_AVATAR] = g_param_spec_object(
+		"avatar", "avatar",
+		"The avatar of the contact",
+		GDK_TYPE_PIXBUF,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleContact:presence:
+	 *
+	 * The [class@Purple.Presence] for this contact. This is typically
+	 * controlled by the protocol and should only be read by others.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_PRESENCE] = g_param_spec_object(
+		"presence", "presence",
+		"The presence of the contact",
+		PURPLE_TYPE_PRESENCE,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleContact:tags:
+	 *
+	 * The [class@Purple.Tags] for this contact.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_TAGS] = g_param_spec_object(
+		"tags", "tags",
+		"The tags for the contact",
+		PURPLE_TYPE_TAGS,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleContact *
+purple_contact_new(PurpleAccount *account, const gchar *username) {
+	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
+	g_return_val_if_fail(username != NULL, NULL);
+
+	return g_object_new(PURPLE_TYPE_CONTACT,
+		"account", account,
+		"username", username,
+		NULL);
+}
+
+PurpleAccount *
+purple_contact_get_account(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->account;
+}
+
+const gchar *
+purple_contact_get_username(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->username;
+}
+
+void
+purple_contact_set_username(PurpleContact *contact, const gchar *username) {
+	g_return_if_fail(PURPLE_IS_CONTACT(contact));
+	g_return_if_fail(username != NULL);
+
+	g_free(contact->username);
+	contact->username = g_strdup(username);
+
+	g_object_notify_by_pspec(G_OBJECT(contact), properties[PROP_USERNAME]);
+}
+
+const gchar *
+purple_contact_get_display_name(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->display_name;
+}
+
+void
+purple_contact_set_display_name(PurpleContact *contact,
+                                const gchar *display_name)
+{
+	g_return_if_fail(PURPLE_IS_CONTACT(contact));
+
+	g_free(contact->display_name);
+	contact->display_name = g_strdup(display_name);
+
+	g_object_notify_by_pspec(G_OBJECT(contact), properties[PROP_DISPLAY_NAME]);
+}
+
+const gchar *
+purple_contact_get_alias(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->alias;
+}
+
+void
+purple_contact_set_alias(PurpleContact *contact, const gchar *alias) {
+	g_return_if_fail(PURPLE_IS_CONTACT(contact));
+
+	g_free(contact->alias);
+	contact->alias = g_strdup(alias);
+
+	g_object_notify_by_pspec(G_OBJECT(contact), properties[PROP_ALIAS]);
+}
+
+GdkPixbuf *
+purple_contact_get_avatar(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->avatar;
+}
+
+void
+purple_contact_set_avatar(PurpleContact *contact, GdkPixbuf *avatar) {
+	g_return_if_fail(PURPLE_IS_CONTACT(contact));
+
+	if(g_set_object(&contact->avatar, avatar)) {
+		g_object_notify_by_pspec(G_OBJECT(contact), properties[PROP_AVATAR]);
+	}
+}
+
+PurplePresence *
+purple_contact_get_presence(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->presence;
+}
+
+void
+purple_contact_set_presence(PurpleContact *contact, PurplePresence *presence) {
+	g_return_if_fail(PURPLE_IS_CONTACT(contact));
+
+	if(g_set_object(&contact->presence, presence)) {
+		g_object_notify_by_pspec(G_OBJECT(contact), properties[PROP_PRESENCE]);
+	}
+}
+
+PurpleTags *
+purple_contact_get_tags(PurpleContact *contact) {
+	g_return_val_if_fail(PURPLE_IS_CONTACT(contact), NULL);
+
+	return contact->tags;
+}
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -3,6 +3,7 @@
     'account_manager',
     'authorization_request',
     'circular_buffer',
+    'contact',
     'credential_provider',
     'history_adapter',
     'history_manager',
diff --git a/libpurple/tests/test_contact.c b/libpurple/tests/test_contact.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_contact.c
@@ -0,0 +1,125 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+#include "test_ui.h"
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_contact_new(void) {
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+
+	account = purple_account_new("test", "test");
+	contact = purple_contact_new(account, "username");
+
+	g_assert_true(purple_contact_get_account(contact) == account);
+	g_assert_cmpstr(purple_contact_get_username(contact), ==, "username");
+
+	g_clear_object(&contact);
+	g_clear_object(&account);
+}
+
+static void
+test_purple_contact_properties(void) {
+	PurpleAccount *account = NULL;
+	PurpleAccount *account1 = NULL;
+	PurpleContact *contact = NULL;
+	PurplePresence *presence = NULL;
+	PurplePresence *presence1 = NULL;
+	PurpleTags *tags = NULL;
+	GdkPixbuf *avatar = NULL;
+	GdkPixbuf *avatar1 = NULL;
+	gchar *username = NULL;
+	gchar *display_name = NULL;
+	gchar *alias = NULL;
+
+	account = purple_account_new("test", "test");
+	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
+	presence = g_object_new(PURPLE_TYPE_PRESENCE, NULL);
+
+	/* Use g_object_new so we can test setting properties by name. All of them
+	 * call the setter methods, so by doing it this way we exercise more of the
+	 * code.
+	 */
+	contact = g_object_new(
+		PURPLE_TYPE_CONTACT,
+		"account", account,
+		"username", "username",
+		"display-name", "display-name",
+		"alias", "alias",
+		"avatar", avatar,
+		"presence", presence,
+		NULL);
+
+	/* Now use g_object_get to read all of the properties. */
+	g_object_get(contact,
+		"account", &account1,
+		"username", &username,
+		"display-name", &display_name,
+		"alias", &alias,
+		"avatar", &avatar1,
+		"presence", &presence1,
+		"tags", &tags,
+		NULL);
+
+	/* Compare all the things. */
+	g_assert_true(account1 == account);
+	g_assert_cmpstr(username, ==, "username");
+	g_assert_cmpstr(display_name, ==, "display-name");
+	g_assert_cmpstr(alias, ==, "alias");
+	g_assert_true(avatar1 == avatar);
+	g_assert_true(presence1 == presence);
+	g_assert_nonnull(tags);
+
+	/* Free/unref all the things. */
+	g_clear_object(&account1);
+	g_clear_pointer(&username, g_free);
+	g_clear_pointer(&display_name, g_free);
+	g_clear_pointer(&alias, g_free);
+	g_clear_object(&avatar1);
+	g_clear_object(&presence1);
+	g_clear_object(&tags);
+
+	g_clear_object(&presence);
+	g_clear_object(&avatar);
+	g_clear_object(&contact);
+	g_clear_object(&account);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	test_ui_purple_init();
+
+	g_test_add_func("/contact/new",
+	                test_purple_contact_new);
+	g_test_add_func("/contact/properties",
+	                test_purple_contact_properties);
+
+	return g_test_run();
+}
\ No newline at end of file
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -239,6 +239,7 @@
 libpurple/purplebuddypresence.c
 libpurple/purplechatconversation.c
 libpurple/purplechatuser.c
+libpurple/purplecontact.c
 libpurple/purpleconversation.c
 libpurple/purpleconversationmanager.c
 libpurple/purpleconversationuiops.c
@@ -289,6 +290,7 @@
 libpurple/tests/test_account_manager.c
 libpurple/tests/test_account_option.c
 libpurple/tests/test_circular_buffer.c
+libpurple/tests/test_contact.c
 libpurple/tests/test_credential_manager.c
 libpurple/tests/test_credential_provider.c
 libpurple/tests/test_history_adapter.c
