# HG changeset patch
# Node ID fc8e79ad29fb
# Parent  c8d3ab896cc6
diff --git a/ChangeLog.API b/ChangeLog.API
--- a/ChangeLog.API
+++ b/ChangeLog.API
@@ -332,6 +332,8 @@
 		* PurpleAccount->ui_data
 		* PurpleAccountPrefsUiOps
 		* PurpleAccountRequestType
+		* PurpleAccountUiOps:close_account_request
+		* PurpleAccountUiOps:request_add
 		* PurpleAccountUiOps:request_authorization
 		* purple_account_add_buddies_with_invite
 		* purple_account_add_buddy_with_invite
@@ -825,6 +827,7 @@
 		* pidgin_append_blist_node_privacy_menu
 		* pidgin_append_blist_node_proto_menu
 		* pidgin_append_menu_action
+		* pidgin_blist_add_alert
 		* pidgin_blist_draw_tooltip
 		* pidgin_blist_get_theme
 		* pidgin_blist_layout_get_type
@@ -969,9 +972,22 @@
 		* PidginIconThemeLoaderClass
 		* pidgin_idle_get_ui_ops
 		* pidgin_load_accels
-		* pidgin_make_mini_dialog_with_custom_icon; use
-		  pidgin_mini_dialog_new_with_custom_icon instead.
+		* PidginMiniDialog
+		* PidginMiniDialogCallback
+		* pidgin_make_mini_dialog_with_custom_icon
 		* pidgin_make_pretty_arrows
+		* pidgin_mini_dialog_add_button
+		* pidgin_mini_dialog_add_non_closing_button
+		* pidgin_mini_dialog_enable_description_markup
+		* pidgin_mini_dialog_get_num_children
+		* pidgin_mini_dialog_new
+		* pidgin_mini_dialog_new_with_buttons
+		* pidgin_mini_dialog_new_with_custom_icon
+		* pidgin_mini_dialog_set_custom_icon
+		* pidgin_mini_dialog_set_description
+		* pidgin_mini_dialog_set_icon_name
+		* pidgin_mini_dialog_set_link_callback
+		* pidgin_mini_dialog_set_title
 		* pidgin_menu_position_func_helper
 		* pidgin_mini_dialog_links_supported
 		* pidgin_new_check_item
@@ -991,6 +1007,9 @@
 		* pidgin_protocol_option_menu_get_selected
 		* pidgin_save_accels
 		* pidgin_save_accels_cb
+		* PidginScrollBook
+		* pidgin_scroll_book_new
+		* pidgin_scroll_book_get_notebook
 		* pidgin_separator
 		* pidgin_set_cursor
 		* pidgin_set_custom_buddy_icon
@@ -1097,6 +1116,7 @@
 		* gnt_ui_uninit renamed to finch_ui_uninit
 
 		Removed:
+		* finch_accounts_get_ui_ops
 		* finch_idle_get_ui_ops
 		* finch_pounce_editor_show
 		* finch_pounces_get_handle
diff --git a/finch/gntaccount.h b/finch/gntaccount.h
--- a/finch/gntaccount.h
+++ b/finch/gntaccount.h
@@ -34,15 +34,6 @@
  **********************************************************************/
 
 /**
- * finch_accounts_get_ui_ops:
- *
- * Get the ui-functions.
- *
- * Returns: The PurpleAccountUiOps structure populated with the appropriate functions.
- */
-PurpleAccountUiOps *finch_accounts_get_ui_ops(void);
-
-/**
  * finch_accounts_init:
  *
  * Perform necessary initializations.
diff --git a/finch/gntaccount.c b/finch/gntaccount.c
--- a/finch/gntaccount.c
+++ b/finch/gntaccount.c
@@ -896,109 +896,3 @@
 	if (accounts.window)
 		gnt_widget_destroy(accounts.window);
 }
-
-/* The following uiops stuff are copied from gtkaccount.c */
-typedef struct
-{
-	PurpleAccount *account;
-	char *username;
-	char *alias;
-} AddUserData;
-
-static char *
-make_info(PurpleAccount *account, PurpleConnection *gc, const char *remote_user,
-          const char *id, const char *alias, const char *msg)
-{
-	if (msg != NULL && *msg == '\0')
-		msg = NULL;
-
-	return g_strdup_printf(_("%s%s%s%s has made %s his or her buddy%s%s"),
-	                       remote_user,
-	                       (alias != NULL ? " ("  : ""),
-	                       (alias != NULL ? alias : ""),
-	                       (alias != NULL ? ")"   : ""),
-	                       (id != NULL
-	                        ? id
-	                        : (purple_connection_get_display_name(gc) != NULL
-	                           ? purple_connection_get_display_name(gc)
-	                           : purple_account_get_username(account))),
-	                       (msg != NULL ? ": " : "."),
-	                       (msg != NULL ? msg  : ""));
-}
-
-static void
-free_add_user_data(AddUserData *data)
-{
-	g_free(data->username);
-
-	g_free(data->alias);
-
-	g_free(data);
-}
-
-static void
-add_user_cb(AddUserData *data)
-{
-	PurpleConnection *gc = purple_account_get_connection(data->account);
-
-	if (g_list_find(purple_connections_get_all(), gc))
-	{
-		purple_blist_request_add_buddy(data->account, data->username,
-									 NULL, data->alias);
-	}
-
-	free_add_user_data(data);
-}
-
-static void
-request_add(PurpleAccount *account, const char *remote_user,
-		  const char *id, const char *alias,
-		  const char *msg)
-{
-	char *buffer;
-	PurpleConnection *gc;
-	AddUserData *data;
-
-	gc = purple_account_get_connection(account);
-
-	data = g_new0(AddUserData, 1);
-	data->account  = account;
-	data->username = g_strdup(remote_user);
-	data->alias    = (alias != NULL ? g_strdup(alias) : NULL);
-
-	buffer = make_info(account, gc, remote_user, id, alias, msg);
-	purple_request_action(NULL, NULL, _("Add buddy to your list?"), buffer,
-		PURPLE_DEFAULT_ACTION_NONE,
-		purple_request_cpar_from_account(account), data, 2,
-		_("Add"), G_CALLBACK(add_user_cb),
-		_("Cancel"), G_CALLBACK(free_add_user_data));
-	g_free(buffer);
-}
-
-/* Copied from gtkaccount.c */
-typedef struct {
-	PurpleAccountRequestAuthorizationCb auth_cb;
-	PurpleAccountRequestAuthorizationCb deny_cb;
-	void *data;
-	char *username;
-	char *alias;
-	PurpleAccount *account;
-} auth_and_add;
-
-static void
-finch_request_close(void *uihandle)
-{
-	purple_request_close(PURPLE_REQUEST_ACTION, uihandle);
-}
-
-static PurpleAccountUiOps ui_ops =
-{
-	.request_add = request_add,
-	.close_account_request = finch_request_close,
-};
-
-PurpleAccountUiOps *finch_accounts_get_ui_ops()
-{
-	return &ui_ops;
-}
-
diff --git a/finch/gntui.c b/finch/gntui.c
--- a/finch/gntui.c
+++ b/finch/gntui.c
@@ -98,7 +98,6 @@
 
 	/* Accounts */
 	finch_accounts_init();
-	purple_accounts_set_ui_ops(finch_accounts_get_ui_ops());
 
 	/* Connections */
 	finch_connections_init();
diff --git a/libpurple/account.c b/libpurple/account.c
--- a/libpurple/account.c
+++ b/libpurple/account.c
@@ -31,6 +31,7 @@
 #include "prefs.h"
 #include "purpleaccountmanager.h"
 #include "purpleaccountpresence.h"
+#include "purpleaddcontactrequest.h"
 #include "purpleconversationmanager.h"
 #include "purplecredentialmanager.h"
 #include "purplenotification.h"
@@ -1163,18 +1164,29 @@
 
 void
 purple_account_request_add(PurpleAccount *account, const char *remote_user,
-                         const char *id, const char *alias,
-                         const char *message)
+                           const char *id, const char *alias,
+                           const char *message)
 {
-	PurpleAccountUiOps *ui_ops;
+	PurpleAddContactRequest *request = NULL;
+	PurpleNotification *notification = NULL;
+	PurpleNotificationManager *manager = NULL;
 
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 	g_return_if_fail(remote_user != NULL);
 
-	ui_ops = purple_accounts_get_ui_ops();
-
-	if (ui_ops != NULL && ui_ops->request_add != NULL)
-		ui_ops->request_add(account, remote_user, id, alias, message);
+	request = purple_add_contact_request_new(account, remote_user);
+	if(alias != NULL && *alias != '\0') {
+		purple_add_contact_request_set_alias(request, alias);
+	}
+
+	if(message != NULL && *message != '\0') {
+		purple_add_contact_request_set_message(request, message);
+	}
+
+	notification = purple_notification_new_from_add_contact_request(request);
+
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_add(manager, notification);
 }
 
 void
diff --git a/libpurple/accounts.h b/libpurple/accounts.h
--- a/libpurple/accounts.h
+++ b/libpurple/accounts.h
@@ -36,11 +36,6 @@
 /**
  * PurpleAccountUiOps:
  * @status_changed:        This account's status changed.
- * @request_add:           Someone we don't have on our list added us; prompt
- *                         to add them.
- * @close_account_request: Close a pending request for authorization.
- *                         @ui_handle is a handle as returned by
- *                         @request_authorize.
  * @permit_added: Called during a call to purple_account_privacy_permit_add().
  * @permit_removed: Called during a call to
  *                  purple_account_privacy_permit_removed().
@@ -56,14 +51,6 @@
 	void (*status_changed)(PurpleAccount *account,
 	                       PurpleStatus *status);
 
-	void (*request_add)(PurpleAccount *account,
-	                    const char *remote_user,
-	                    const char *id,
-	                    const char *alias,
-	                    const char *message);
-
-	void (*close_account_request)(void *ui_handle);
-
 	void (*permit_added)(PurpleAccount *account, const char *name);
 	void (*permit_removed)(PurpleAccount *account, const char *name);
 	void (*deny_added)(PurpleAccount *account, const char *name);
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -40,6 +40,7 @@
 	'purpleaccountoption.c',
 	'purpleaccountpresence.c',
 	'purpleaccountusersplit.c',
+	'purpleaddcontactrequest.c',
 	'purpleattachment.c',
 	'purpleauthorizationrequest.c',
 	'purplebuddypresence.c',
@@ -142,6 +143,7 @@
 	'purpleaccountoption.h',
 	'purpleaccountpresence.h',
 	'purpleaccountusersplit.h',
+	'purpleaddcontactrequest.h',
 	'purpleauthorizationrequest.h',
 	'purplebuddypresence.h',
 	'purplechatconversation.h',
diff --git a/libpurple/protocols/demo/purpledemoprotocolactions.c b/libpurple/protocols/demo/purpledemoprotocolactions.c
--- a/libpurple/protocols/demo/purpledemoprotocolactions.c
+++ b/libpurple/protocols/demo/purpledemoprotocolactions.c
@@ -33,6 +33,30 @@
 #define TEMPORARY_TICK_PLURAL_STR N_("Pruning connection in %d seconds...")
 #define TEMPORARY_DISCONNECT_STR N_("%s pruned the connection")
 
+static const gchar *contacts[] = {"Alice", "Bob", "Carlos", "Erin", NULL };
+
+static void
+purple_demo_protocol_remote_add(G_GNUC_UNUSED GSimpleAction *action,
+                                GVariant *parameter,
+                                G_GNUC_UNUSED gpointer data)
+{
+	PurpleAccount *account = NULL;
+	PurpleAccountManager *manager = NULL;
+	const gchar *account_id = NULL;
+	static gint counter = 0;
+
+	account_id = g_variant_get_string(parameter, NULL);
+	manager = purple_account_manager_get_default();
+	account = purple_account_manager_find_by_id(manager, account_id);
+
+	purple_account_request_add(account, contacts[counter], NULL, NULL, NULL);
+
+	counter++;
+	if(counter >= G_N_ELEMENTS(contacts)) {
+		counter = 0;
+	}
+}
+
 static gboolean
 purple_demo_protocol_failure_tick(gpointer data,
                                   PurpleConnectionError error_code,
@@ -175,7 +199,11 @@
 			.name = "fatal-failure",
 			.activate = purple_demo_protocol_fatal_failure_action_activate,
 			.parameter_type = "s",
-		},
+		}, {
+			.name = "remote-add",
+			.activate = purple_demo_protocol_remote_add,
+			.parameter_type = "s",
+		}
 	};
 	gsize nentries = G_N_ELEMENTS(entries);
 
@@ -208,6 +236,13 @@
 	g_menu_append_item(menu, item);
 	g_object_unref(item);
 
+	item = g_menu_item_new(_("Trigger a contact adding you"),
+	                       "prpl-demo.remote-add");
+	g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
+	                          "account");
+	g_menu_append_item(menu, item);
+	g_object_unref(item);
+
 	return menu;
 }
 
diff --git a/libpurple/purpleaddcontactrequest.h b/libpurple/purpleaddcontactrequest.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleaddcontactrequest.h
@@ -0,0 +1,149 @@
+/*
+ * 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/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <purple.h> may be included directly"
+#endif
+
+#ifndef PURPLE_ADD_CONTACT_REQUEST_H
+#define PURPLE_ADD_CONTACT_REQUEST_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "account.h"
+
+G_BEGIN_DECLS
+
+/**
+ * PurpleAddContactRequest:
+ *
+ * #PurpleAddContactRequest is a data structure that contains all of the
+ * information when someone has add you to their contact list, so the user
+ * interface can ask the user to add the remote user to their contact list.
+ *
+ * Since: 3.0.0
+ */
+
+#define PURPLE_TYPE_ADD_CONTACT_REQUEST (purple_add_contact_request_get_type())
+G_DECLARE_FINAL_TYPE(PurpleAddContactRequest, purple_add_contact_request,
+                     PURPLE, ADD_CONTACT_REQUEST, GObject)
+
+/**
+ * purple_add_contact_request_new:
+ * @account: The account that this request is for.
+ * @username: The username of the user requesting authorization.
+ *
+ * Creates a new [class@Purple.AddContactRequest] for @username on @account.
+ *
+ * This is typically only used by libpurple itself.
+ *
+ * Returns: The new instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleAddContactRequest *purple_add_contact_request_new(PurpleAccount *account, const gchar *username);
+
+/**
+ * purple_add_contact_request_get_account:
+ * @request: The instance.
+ *
+ * Gets the [class@Account] for @request.
+ *
+ * Returns: (transfer none): The account.
+ *
+ * Since: 3.0.0
+ */
+PurpleAccount *purple_add_contact_request_get_account(PurpleAddContactRequest *request);
+
+/**
+ * purple_add_contact_request_get_remote_username:
+ * @request: The instance.
+ *
+ * Gets the username for the user requesting authorization.
+ *
+ * Returns: The username of the remote user.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_add_contact_request_get_username(PurpleAddContactRequest *request);
+
+/**
+ * purple_add_contact_request_set_alias:
+ * @request: The instance.
+ * @alias: (nullable): The alias of the remote user.
+ *
+ * Sets the alias of the remote user to @alias. User interfaces can use this
+ * when presenting the authorization request to the end user.
+ *
+ * Since: 3.0.0
+ */
+void purple_add_contact_request_set_alias(PurpleAddContactRequest *request, const gchar *alias);
+
+/**
+ * purple_add_contact_request_get_alias:
+ * @request: The instance.
+ *
+ * Gets the alias of the remote user if one was set.
+ *
+ * Returns: (nullable): The alias if one was set.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_add_contact_request_get_alias(PurpleAddContactRequest *request);
+
+/**
+ * purple_add_contact_request_set_message:
+ * @request: The instance.
+ * @message: (nullable): An optional message from the remote user.
+ *
+ * Sets an optional message from remote user, that the user interface can
+ * display to the end user.
+ *
+ * Since: 3.0.0
+ */
+void purple_add_contact_request_set_message(PurpleAddContactRequest *request, const gchar *message);
+
+/**
+ * purple_add_contact_request_get_message:
+ * @request: The instance.
+ *
+ * Gets the message that was optionally sent by the remote user.
+ *
+ * Returns: (nullable): The optional message.
+ *
+ * Since: 3.0.0
+ */
+const gchar *purple_add_contact_request_get_message(PurpleAddContactRequest *request);
+
+/**
+ * purple_add_contact_request_add:
+ * @request: The instance.
+ *
+ * Emits the [signal@AddContactRequest::add] signal. This is typically called
+ * by the user interface when the user has clicked the add button.
+ *
+ * If this is called multiple times, then this does nothing.
+ *
+ * Since: 3.0.0
+ */
+void purple_add_contact_request_add(PurpleAddContactRequest *request);
+
+G_END_DECLS
+
+#endif /* PURPLE_ADD_CONTACT_REQUEST_H */
diff --git a/libpurple/purpleaddcontactrequest.c b/libpurple/purpleaddcontactrequest.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleaddcontactrequest.c
@@ -0,0 +1,329 @@
+/*
+ * 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 "purpleaddcontactrequest.h"
+
+struct _PurpleAddContactRequest {
+	GObject parent;
+
+	PurpleAccount *account;
+
+	gchar *username;
+	gchar *alias;
+	gchar *message;
+
+	/* This tracks whether _accept or _deny have been called. */
+	gboolean handled;
+};
+
+enum {
+	SIG_ADD,
+	N_SIGNALS
+};
+static guint signals[N_SIGNALS] = {0, };
+
+enum {
+	PROP_0,
+	PROP_ACCOUNT,
+	PROP_USERNAME,
+	PROP_ALIAS,
+	PROP_MESSAGE,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+G_DEFINE_TYPE(PurpleAddContactRequest, purple_add_contact_request,
+              G_TYPE_OBJECT)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_add_contact_request_set_account(PurpleAddContactRequest *request,
+                                       PurpleAccount *account)
+{
+	g_return_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request));
+	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
+
+	if(g_set_object(&request->account, account)) {
+		g_object_notify_by_pspec(G_OBJECT(request), properties[PROP_ACCOUNT]);
+	}
+}
+
+static void
+purple_add_contact_request_set_username(PurpleAddContactRequest *request,
+                                        const gchar *username)
+{
+	g_return_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request));
+
+	g_free(request->username);
+	request->username = g_strdup(username);
+
+	g_object_notify_by_pspec(G_OBJECT(request), properties[PROP_USERNAME]);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_add_contact_request_get_property(GObject *obj, guint param_id,
+                                        GValue *value, GParamSpec *pspec)
+{
+	PurpleAddContactRequest *request = PURPLE_ADD_CONTACT_REQUEST(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			g_value_set_object(value,
+			                   purple_add_contact_request_get_account(request));
+			break;
+		case PROP_USERNAME:
+			g_value_set_string(value,
+			                   purple_add_contact_request_get_username(request));
+			break;
+		case PROP_ALIAS:
+			g_value_set_string(value,
+			                   purple_add_contact_request_get_alias(request));
+			break;
+		case PROP_MESSAGE:
+			g_value_set_string(value,
+			                   purple_add_contact_request_get_message(request));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_add_contact_request_set_property(GObject *obj, guint param_id,
+                                        const GValue *value,
+                                        GParamSpec *pspec)
+{
+	PurpleAddContactRequest *request = PURPLE_ADD_CONTACT_REQUEST(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			purple_add_contact_request_set_account(request,
+			                                       g_value_get_object(value));
+			break;
+		case PROP_USERNAME:
+			purple_add_contact_request_set_username(request,
+			                                        g_value_get_string(value));
+			break;
+		case PROP_ALIAS:
+			purple_add_contact_request_set_alias(request,
+			                                     g_value_get_string(value));
+			break;
+		case PROP_MESSAGE:
+			purple_add_contact_request_set_message(request,
+			                                       g_value_get_string(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_add_contact_request_dispose(GObject *obj) {
+	PurpleAddContactRequest *request = PURPLE_ADD_CONTACT_REQUEST(obj);
+
+	g_clear_object(&request->account);
+
+	G_OBJECT_CLASS(purple_add_contact_request_parent_class)->dispose(obj);
+}
+
+static void
+purple_add_contact_request_finalize(GObject *obj) {
+	PurpleAddContactRequest *request = PURPLE_ADD_CONTACT_REQUEST(obj);
+
+	g_clear_pointer(&request->username, g_free);
+	g_clear_pointer(&request->alias, g_free);
+	g_clear_pointer(&request->message, g_free);
+
+	G_OBJECT_CLASS(purple_add_contact_request_parent_class)->finalize(obj);
+}
+
+static void
+purple_add_contact_request_init(PurpleAddContactRequest *request) {
+}
+
+static void
+purple_add_contact_request_class_init(PurpleAddContactRequestClass *klass)
+{
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = purple_add_contact_request_get_property;
+	obj_class->set_property = purple_add_contact_request_set_property;
+	obj_class->dispose = purple_add_contact_request_dispose;
+	obj_class->finalize = purple_add_contact_request_finalize;
+
+	/**
+	 * PurpleAddContactRequest::add:
+	 * @request: The [class@AddContactRequest] instance.
+	 *
+	 * Emitted when the user has told the ui to add the contact. This is
+	 * typically emitted by the user interface calling
+	 * [method@AddContactRequest.add].
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_ADD] = g_signal_new_class_handler(
+		"add",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		0);
+
+	/**
+	 * PurpleAddContactRequest:account: (attributes org.gtk.Property.get=purple_add_contact_request_get_account)
+	 *
+	 * The account that this authorization request is for.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ACCOUNT] = g_param_spec_object(
+		"account", "account",
+		"The account for this authorization request",
+		PURPLE_TYPE_ACCOUNT,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAddContactRequest:username: (attributes org.gtk.Property.get=purple_add_contact_request_get_username):
+	 *
+	 * The username of the remote user requesting authorization.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_USERNAME] = g_param_spec_string(
+		"username", "username",
+		"The username of the remote user requesting authorization",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAddContactRequest:alias: (attributes org.gtk.Property.get=purple_add_contact_request_get_alias org.gtk.Property.set=purple_add_contact_request_set_alias)
+	 *
+	 * The alias of the remote user request authorization.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ALIAS] = g_param_spec_string(
+		"alias", "alias",
+		"The alias of the remote user request authorization",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleAddContactRequest:message: (attributes org.gtk.Property.get=purple_add_contact_request_get_message org.gtk.Property.set=purple_add_contact_request_set_message)
+	 *
+	 * The optional message sent from the remote user.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_MESSAGE] = g_param_spec_string(
+		"message", "message",
+		"The optional message sent by the remote user",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleAddContactRequest *
+purple_add_contact_request_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_ADD_CONTACT_REQUEST,
+		"account", account,
+		"username", username,
+		NULL);
+}
+
+PurpleAccount *
+purple_add_contact_request_get_account(PurpleAddContactRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request), NULL);
+
+	return request->account;
+}
+
+const gchar *
+purple_add_contact_request_get_username(PurpleAddContactRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request), NULL);
+
+	return request->username;
+}
+
+void
+purple_add_contact_request_set_alias(PurpleAddContactRequest *request,
+                                     const gchar *alias)
+{
+	g_return_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request));
+
+	g_free(request->alias);
+	request->alias = g_strdup(alias);
+
+	g_object_notify_by_pspec(G_OBJECT(request), properties[PROP_ALIAS]);
+}
+
+const gchar *
+purple_add_contact_request_get_alias(PurpleAddContactRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request), NULL);
+
+	return request->alias;
+}
+
+void
+purple_add_contact_request_set_message(PurpleAddContactRequest *request,
+                                       const gchar *message)
+{
+	g_return_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request));
+
+	g_free(request->message);
+	request->message = g_strdup(message);
+
+	g_object_notify_by_pspec(G_OBJECT(request), properties[PROP_MESSAGE]);
+}
+
+const gchar *
+purple_add_contact_request_get_message(PurpleAddContactRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request), NULL);
+
+	return request->message;
+}
+
+void
+purple_add_contact_request_add(PurpleAddContactRequest *request) {
+	g_return_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request));
+
+	/* Calling this multiple times is a programming error. */
+	g_return_if_fail(request->handled == FALSE);
+
+	request->handled = TRUE;
+
+	g_signal_emit(request, signals[SIG_ADD], 0);
+}
diff --git a/libpurple/purplenotification.h b/libpurple/purplenotification.h
--- a/libpurple/purplenotification.h
+++ b/libpurple/purplenotification.h
@@ -28,6 +28,7 @@
 
 #include "account.h"
 #include "purpleauthorizationrequest.h"
+#include "purpleaddcontactrequest.h"
 
 G_BEGIN_DECLS
 
@@ -41,6 +42,7 @@
     PURPLE_NOTIFICATION_TYPE_GENERIC,
     PURPLE_NOTIFICATION_TYPE_CONNECTION_ERROR,
     PURPLE_NOTIFICATION_TYPE_AUTHORIZATION_REQUEST,
+    PURPLE_NOTIFICATION_TYPE_ADD_CONTACT,
     PURPLE_NOTIFICATION_TYPE_FILE_TRANSFER,
     PURPLE_NOTIFICATION_TYPE_CHAT_INVITE,
     PURPLE_NOTIFICATION_TYPE_MENTION,
@@ -79,11 +81,25 @@
 PurpleNotification *purple_notification_new(PurpleNotificationType type, PurpleAccount *account, gpointer data, GDestroyNotify data_destroy_func);
 
 /**
+ * purple_notification_new_from_add_contact_request:
+ * @request: (transfer full): The [class@AddContactRequest] instance.
+ *
+ * Creates a new [class@Notification] for @request. This helper will
+ * automatically fill out the notification according to the information in
+ * @request.
+ *
+ * Returns: (transfer full): The new notification.
+ *
+ * Since: 3.0.0.
+ */
+PurpleNotification *purple_notification_new_from_add_contact_request(PurpleAddContactRequest *request);
+
+/**
  * purple_notification_new_from_authorization_request:
  * @authorization_request: (transfer full): The [class@AuthorizationRequest]
  *                         instance.
  *
- * Creates a new [class@PurpleNotification] for the @authorization_request. This
+ * Creates a new [class@Notification] for the @authorization_request. This
  * helper will automatically fill out the notification according to the
  * information in @authorization_request.
  *
diff --git a/libpurple/purplenotification.c b/libpurple/purplenotification.c
--- a/libpurple/purplenotification.c
+++ b/libpurple/purplenotification.c
@@ -441,6 +441,38 @@
 }
 
 PurpleNotification *
+purple_notification_new_from_add_contact_request(PurpleAddContactRequest *request)
+{
+	PurpleAccount *account = NULL;
+	PurpleNotification *notification = NULL;
+	gchar *title = NULL;
+	const gchar *alias = NULL, *username = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_ADD_CONTACT_REQUEST(request), NULL);
+
+	account = purple_add_contact_request_get_account(request);
+	notification = purple_notification_new(PURPLE_NOTIFICATION_TYPE_ADD_CONTACT,
+	                                       account, request, g_object_unref);
+
+	username = purple_add_contact_request_get_username(request);
+	alias = purple_add_contact_request_get_alias(request);
+
+	if(alias != NULL && *alias != '\0') {
+		title = g_strdup_printf(_("%s (%s) added %s to their contact list"),
+		                        alias, username,
+		                        purple_account_get_username(account));
+	} else {
+		title = g_strdup_printf(_("%s added %s to their contact list"),
+		                        username, purple_account_get_username(account));
+	}
+
+	purple_notification_set_title(notification, title);
+	g_free(title);
+
+	return notification;
+}
+
+PurpleNotification *
 purple_notification_new_from_authorization_request(PurpleAuthorizationRequest *authorization_request)
 {
 	PurpleAccount *account = NULL;
diff --git a/pidgin/gtkaccount.c b/pidgin/gtkaccount.c
--- a/pidgin/gtkaccount.c
+++ b/pidgin/gtkaccount.c
@@ -34,7 +34,6 @@
 #include "gtkutils.h"
 #include "pidgincore.h"
 #include "pidgindialog.h"
-#include "minidialog.h"
 #include "pidginprotocolchooser.h"
 #include "pidginproxyoptions.h"
 
@@ -1281,91 +1280,8 @@
  * Accounts Dialog
  **************************************************************************/
 
-static void
-free_add_user_data(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
-                   G_GNUC_UNUSED GtkButton *button,
-                   gpointer user_data)
-{
-	PidginAccountAddUserData *data = user_data;
-	g_free(data->username);
-	g_free(data->alias);
-	g_free(data);
-}
-
-static void
-add_user_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
-            G_GNUC_UNUSED GtkButton *button, gpointer user_data)
-{
-	PidginAccountAddUserData *data = user_data;
-	PurpleConnection *gc = purple_account_get_connection(data->account);
-
-	if (g_list_find(purple_connections_get_all(), gc))
-	{
-		purple_blist_request_add_buddy(data->account, data->username,
-									 NULL, data->alias);
-	}
-
-	free_add_user_data(NULL, NULL, user_data);
-}
-
-static char *
-make_info(PurpleAccount *account, PurpleConnection *gc, const char *remote_user,
-          const char *id, const char *alias, const char *msg)
-{
-	if (msg != NULL && *msg == '\0')
-		msg = NULL;
-
-	return g_strdup_printf(_("%s%s%s%s has made %s his or her buddy%s%s"),
-	                       remote_user,
-	                       (alias != NULL ? " ("  : ""),
-	                       (alias != NULL ? alias : ""),
-	                       (alias != NULL ? ")"   : ""),
-	                       (id != NULL
-	                        ? id
-	                        : (purple_connection_get_display_name(gc) != NULL
-	                           ? purple_connection_get_display_name(gc)
-	                           : purple_account_get_username(account))),
-	                       (msg != NULL ? ": " : "."),
-	                       (msg != NULL ? msg  : ""));
-}
-
-static void
-pidgin_accounts_request_add(PurpleAccount *account, const char *remote_user,
-                              const char *id, const char *alias,
-                              const char *msg)
-{
-	char *buffer;
-	PurpleConnection *gc;
-	PidginAccountAddUserData *data;
-	GtkWidget *alert;
-
-	gc = purple_account_get_connection(account);
-
-	data = g_new0(PidginAccountAddUserData, 1);
-	data->account  = account;
-	data->username = g_strdup(remote_user);
-	data->alias    = g_strdup(alias);
-
-	buffer = make_info(account, gc, remote_user, id, alias, msg);
-	alert = pidgin_mini_dialog_new_with_buttons(
-		_("Add buddy to your list?"), buffer, "dialog-question", data,
-		_("Add"), add_user_cb, _("Cancel"), free_add_user_data, NULL);
-	pidgin_blist_add_alert(alert);
-
-	g_free(buffer);
-}
-
-static void
-pidgin_accounts_request_close(void *ui_handle)
-{
-	gtk_widget_destroy(GTK_WIDGET(ui_handle));
-}
-
-static PurpleAccountUiOps ui_ops =
-{
-	.request_add = pidgin_accounts_request_add,
-	.close_account_request = pidgin_accounts_request_close,
-};
+/* This still exists because gtkprivacy calls it to add the privacy ui ops */
+static PurpleAccountUiOps ui_ops = {};
 
 PurpleAccountUiOps *
 pidgin_accounts_get_ui_ops(void)
diff --git a/pidgin/gtkblist.h b/pidgin/gtkblist.h
--- a/pidgin/gtkblist.h
+++ b/pidgin/gtkblist.h
@@ -72,8 +72,6 @@
 	GtkWidget *menu;
 
 	PurpleBlistNode *selected_node;
-
-	GtkWidget *scrollbook;
 };
 
 G_BEGIN_DECLS
diff --git a/pidgin/gtkblist.c b/pidgin/gtkblist.c
--- a/pidgin/gtkblist.c
+++ b/pidgin/gtkblist.c
@@ -35,7 +35,6 @@
 #include "gtkprivacy.h"
 #include "gtkroomlist.h"
 #include "gtkutils.h"
-#include "pidgin/minidialog.h"
 #include "pidgin/pidginaccountchooser.h"
 #include "pidgin/pidginaccountfilterconnected.h"
 #include "pidgin/pidginaccountstore.h"
@@ -47,7 +46,6 @@
 #include "pidgin/pidgindebug.h"
 #include "pidgin/pidginmooddialog.h"
 #include "pidgin/pidginplugininfo.h"
-#include "pidginscrollbook.h"
 #include "pidgin/pidginstylecontext.h"
 
 #include <gdk/gdkkeysyms.h>
@@ -3152,9 +3150,6 @@
 	sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
 	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), sep, FALSE, FALSE, 0);
 
-	gtkblist->scrollbook = pidgin_scroll_book_new();
-	gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
-
 	/* Update some dynamic things */
 	pidgin_blist_update_sort_methods();
 
@@ -3765,11 +3760,6 @@
 					   NULL, NULL);
 }
 
-void pidgin_blist_add_alert(GtkWidget *widget)
-{
-	gtk_container_add(GTK_CONTAINER(gtkblist->scrollbook), widget);
-}
-
 PidginBuddyList *
 pidgin_blist_get_default_gtk_blist(void)
 {
diff --git a/pidgin/gtkutils.c b/pidgin/gtkutils.c
--- a/pidgin/gtkutils.c
+++ b/pidgin/gtkutils.c
@@ -45,7 +45,6 @@
 #include "gtkdialogs.h"
 #include "gtkrequest.h"
 #include "gtkutils.h"
-#include "minidialog.h"
 #include "pidgincore.h"
 
 /******************************************************************************
diff --git a/pidgin/libpidgin.c b/pidgin/libpidgin.c
--- a/pidgin/libpidgin.c
+++ b/pidgin/libpidgin.c
@@ -251,7 +251,6 @@
 	}
 
 	/* Set the UI operation structures. */
-	purple_accounts_set_ui_ops(pidgin_accounts_get_ui_ops());
 	purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
 	purple_blist_set_ui(PIDGIN_TYPE_BUDDY_LIST);
 	purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
diff --git a/pidgin/meson.build b/pidgin/meson.build
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -16,7 +16,6 @@
 	'gtkwhiteboard.c',
 	'gtkxfer.c',
 	'libpidgin.c',
-	'minidialog.c',
 	'pidginabout.c',
 	'pidginaccountchooser.c',
 	'pidginaccounteditor.c',
@@ -44,6 +43,7 @@
 	'pidginkeypad.c',
 	'pidginmessage.c',
 	'pidginmooddialog.c',
+	'pidginnotificationaddcontact.c',
 	'pidginnotificationauthorizationrequest.c',
 	'pidginnotificationconnectionerror.c',
 	'pidginnotificationlist.c',
@@ -54,7 +54,6 @@
 	'pidginprotocolchooser.c',
 	'pidginprotocolstore.c',
 	'pidginproxyoptions.c',
-	'pidginscrollbook.c',
 	'pidginstatusbox.c',
 	'pidginstatuseditor.c',
 	'pidginstatusmanager.c',
@@ -89,7 +88,6 @@
 	'gtkutils.h',
 	'gtkwhiteboard.h',
 	'gtkxfer.h',
-	'minidialog.h',
 	'pidginabout.h',
 	'pidginaccountchooser.h',
 	'pidginaccounteditor.h',
@@ -117,6 +115,7 @@
 	'pidginkeypad.h',
 	'pidginmessage.h',
 	'pidginmooddialog.h',
+	'pidginnotificationaddcontact.h',
 	'pidginnotificationauthorizationrequest.h',
 	'pidginnotificationconnectionerror.h',
 	'pidginnotificationlist.h',
@@ -127,7 +126,6 @@
 	'pidginprotocolchooser.h',
 	'pidginprotocolstore.h',
 	'pidginproxyoptions.h',
-	'pidginscrollbook.h',
 	'pidginstatusbox.h',
 	'pidginstatuseditor.h',
 	'pidginstatusmanager.h',
diff --git a/pidgin/minidialog.h b/pidgin/minidialog.h
deleted file mode 100644
--- a/pidgin/minidialog.h
+++ /dev/null
@@ -1,242 +0,0 @@
-/* pidgin
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
-# error "only <pidgin.h> may be included directly"
-#endif
-
-#ifndef PIDGIN_MINI_DIALOG_H
-#define PIDGIN_MINI_DIALOG_H
-
-#include <glib-object.h>
-#include <gtk/gtk.h>
-
-G_BEGIN_DECLS
-
-#define PIDGIN_TYPE_MINI_DIALOG (pidgin_mini_dialog_get_type())
-G_DECLARE_FINAL_TYPE(PidginMiniDialog, pidgin_mini_dialog, PIDGIN, MINI_DIALOG, GtkBox)
-
-/**
- * PidginMiniDialog:
- * @contents: A GtkBox into which extra widgets for the dialog should be packed.
- *
- * A widget resembling a diminutive dialog box, designed to be embedded in the
- * #PidginBuddyList.  Mini-dialogs have titles, optional descriptions, and a row
- * of buttons at the bottom; above the buttons is a #GtkBox into which
- * you can pack any random widgets you want to add to the dialog.  When any of
- * the dialog's buttons is clicked, the dialog will be destroyed.
- *
- * Dialogs have the following GObject properties:
- * <informaltable frame='none'>
- *   <tgroup cols='2'><tbody>
- *   <row><entry><literal>"title"</literal></entry>
- *     <entry>(<type>char *</type>) A string to be displayed as the dialog's
- *       title.</entry>
- *   </row>
- *   <row><entry><literal>"description"</literal></entry>
- *     <entry>(<type>char *</type>) A string to be displayed as the dialog's
- *       description.  If this is %NULL, the description widget will be
- *       hidden.</entry>
- *   </row>
- *   <row><entry><literal>"icon-name"</literal></entry>
- *     <entry>(<type>char *</type>)The #GtkIconTheme name of an icon for the
- *       dialog, or %NULL for no icon.</entry>
- *   </row>
- *   <row><entry><literal>"custom-icon"</literal></entry>
- *     <entry>(<type>GdkPixbuf *</type>) The custom icon to use instead of a
- *       #GtkIconTheme one (overrides the <literal>"icon-name"</literal>
- *       property).</entry>
- *   </row>
- *   </tbody></tgroup>
- * </informaltable>
- */
-struct _PidginMiniDialog {
-	GtkBox parent;
-
-	/*< public >*/
-	GtkBox *contents;
-};
-
-/**
- * PidginMiniDialogCallback:
- * @mini_dialog: a dialog, one of whose buttons has been pressed.
- * @button:      the button which was pressed.
- * @user_data:   arbitrary data, supplied to
- *                    pidgin_mini_dialog_add_button() when the button was
- *                    created.
- *
- * The type of a callback triggered by a button in a mini-dialog being pressed.
- */
-typedef void (*PidginMiniDialogCallback)(PidginMiniDialog *mini_dialog,
-	GtkButton *button, gpointer user_data);
-
-/**
- * pidgin_mini_dialog_new:
- *
- * Creates a new #PidginMiniDialog with a #GtkIconTheme icon. This is a
- * shortcut for creating the dialog with g_object_new() then setting each
- * property yourself.
- *
- * Returns: a new #PidginMiniDialog.
- */
-PidginMiniDialog *pidgin_mini_dialog_new(const gchar *title,
-	const gchar *description, const gchar *icon_name);
-
-/**
- * pidgin_mini_dialog_new_with_custom_icon:
- *
- * Creates a new #PidginMiniDialog with a custom icon. This is a shortcut for creating the dialog
- * with g_object_new() then setting each property yourself.
- *
- * Returns: a new #PidginMiniDialog.
- */
-PidginMiniDialog *pidgin_mini_dialog_new_with_custom_icon(const gchar *title,
-	const gchar *description, GdkPixbuf *custom_icon);
-
-/**
- * pidgin_mini_dialog_new_with_buttons:
- * @title: The primary text.
- * @description: The secondary text, or %NULL for no description.
- * @icon_name: The name of an icon to use in the mini dialog.
- * @user_data: Data to pass to the callbacks.
- * @...: A %NULL-terminated list of button labels (<type>char *</type>) and
- *       callbacks (#PidginMiniDialogCallback).  (Callbacks may be %NULL to
- *       take no action when the corresponding button is pressed.) When a
- *       button is pressed, the callback (if any) will be called; when the
- *       callback returns the dialog will be destroyed.
- *
- * Creates a #PidginMiniDialog, suitable for embedding in the buddy list
- * scrollbook with pidgin_blist_add_alert().
- *
- * Returns: (transfer full): A #PidginMiniDialog, suitable for passing to
- *          pidgin_blist_add_alert().
- *
- * Since: 3.0.0
- */
-GtkWidget *pidgin_mini_dialog_new_with_buttons(const gchar *title,
-                                               const gchar *description,
-                                               const gchar *icon_name,
-                                               gpointer user_data, ...)
-                                               G_GNUC_NULL_TERMINATED;
-
-/**
- * pidgin_mini_dialog_set_title:
- * @mini_dialog: a mini-dialog
- * @title:       the new title for @mini_dialog
- *
- * Shortcut for setting a mini-dialog's title via GObject properties.
- */
-void pidgin_mini_dialog_set_title(PidginMiniDialog *mini_dialog,
-	const gchar *title);
-
-/**
- * pidgin_mini_dialog_set_description:
- * @mini_dialog: a mini-dialog
- * @description: the new description for @mini_dialog, or %NULL to
- *                    hide the description widget.
- *
- * Shortcut for setting a mini-dialog's description via GObject properties.
- */
-void pidgin_mini_dialog_set_description(PidginMiniDialog *mini_dialog,
-	const gchar *description);
-
-/**
- * pidgin_mini_dialog_enable_description_markup:
- * @mini_dialog: a mini-dialog
- *
- * Enable GMarkup elements in the mini-dialog's description.
- */
-void pidgin_mini_dialog_enable_description_markup(PidginMiniDialog *mini_dialog);
-
-/**
- * pidgin_mini_dialog_set_link_callback:
- * @mini_dialog: a mini-dialog
- * @cb: (scope call): the callback to invoke
- * @user_data: the user data to pass to the callback
- *
- * Sets a callback which gets invoked when a hyperlink in the dialog's description is clicked on.
- */
-void pidgin_mini_dialog_set_link_callback(PidginMiniDialog *mini_dialog, GCallback cb, gpointer user_data);
-
-/**
- * pidgin_mini_dialog_set_icon_name:
- * @mini_dialog: a mini-dialog
- * @icon_name:   the #GtkIconTheme name of an icon, or %NULL for no icon.
- *
- * Shortcut for setting a mini-dialog's icon via GObject properties.
- */
-void pidgin_mini_dialog_set_icon_name(PidginMiniDialog *mini_dialog,
-	const gchar *icon_name);
-
-/**
- * pidgin_mini_dialog_set_custom_icon:
- * @mini_dialog: a mini-dialog
- * @custom_icon: the pixbuf to use as a custom icon
- *
- * Shortcut for setting a mini-dialog's custom icon via GObject properties.
- */
-void pidgin_mini_dialog_set_custom_icon(PidginMiniDialog *mini_dialog,
-	GdkPixbuf *custom_icon);
-
-/**
- * pidgin_mini_dialog_add_button:
- * @mini_dialog: a mini-dialog
- * @text:        the text to display on the new button
- * @clicked_cb: (scope call): the function to call when the button is clicked
- * @user_data:   arbitrary data to pass to @clicked_cb when it is
- *                    called.
- *
- * Adds a new button to a mini-dialog, and attaches the supplied callback to
- * its <literal>clicked</literal> signal.  After a button is clicked, the dialog
- * is destroyed.
- */
-void pidgin_mini_dialog_add_button(PidginMiniDialog *mini_dialog,
-	const gchar *text, PidginMiniDialogCallback clicked_cb,
-	gpointer user_data);
-
-/**
- * pidgin_mini_dialog_add_non_closing_button:
- * @mini_dialog: a mini-dialog
- * @text:        the text to display on the new button
- * @clicked_cb: (scope call): the function to call when the button is clicked
- * @user_data:   arbitrary data to pass to @clicked_cb when it is
- *                    called.
- *
- * Equivalent to pidgin_mini_dialog_add_button(), the only difference
- * is that the mini-dialog won't be closed after the button is clicked.
- */
-void pidgin_mini_dialog_add_non_closing_button(PidginMiniDialog *mini_dialog,
-	const gchar *text, PidginMiniDialogCallback clicked_cb,
-	gpointer user_data);
-
-/**
- * pidgin_mini_dialog_get_num_children:
- * @mini_dialog: a mini-dialog
- *
- * Gets the number of widgets packed into PidginMiniDialog.contents.
- *
- * Returns: the number of widgets in @mini_dialog->contents.
- */
-guint pidgin_mini_dialog_get_num_children(PidginMiniDialog *mini_dialog);
-
-G_END_DECLS
-
-#endif /* PIDGIN_MINI_DIALOG_H */
diff --git a/pidgin/minidialog.c b/pidgin/minidialog.c
deleted file mode 100644
--- a/pidgin/minidialog.c
+++ /dev/null
@@ -1,497 +0,0 @@
-/* pidgin
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include <gtk/gtk.h>
-
-#include <purple.h>
-
-#include "minidialog.h"
-
-enum {
-	PROP_0,
-	PROP_TITLE,
-	PROP_DESCRIPTION,
-	PROP_ICON_NAME,
-	PROP_CUSTOM_ICON,
-	PROP_ENABLE_DESCRIPTION_MARKUP,
-	PROP_LAST
-};
-
-static GParamSpec *properties[PROP_LAST] = {NULL};
-
-typedef struct {
-	GtkImage *icon;
-	GtkBox *title_box;
-	GtkLabel *title;
-	GtkLabel *desc;
-	GtkBox *buttons;
-	gboolean enable_description_markup;
-
-	guint idle_destroy_cb_id;
-} PidginMiniDialogPrivate;
-
-G_DEFINE_TYPE_WITH_PRIVATE(PidginMiniDialog, pidgin_mini_dialog, GTK_TYPE_BOX)
-
-struct _mini_dialog_button_clicked_cb_data {
-	PidginMiniDialog *mini_dialog;
-	PidginMiniDialogCallback callback;
-	gpointer user_data;
-	gboolean close_dialog_after_click;
-};
-
-/******************************************************************************
- * Callbacks
- *****************************************************************************/
-
-static gboolean
-idle_destroy_cb(GtkWidget *mini_dialog)
-{
-	gtk_widget_destroy(mini_dialog);
-	return FALSE;
-}
-
-static void
-mini_dialog_button_clicked_cb(GtkButton *button,
-                              gpointer user_data)
-{
-	struct _mini_dialog_button_clicked_cb_data *data = user_data;
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(data->mini_dialog);
-
-	if (data->close_dialog_after_click) {
-		/* Set up the destruction callback before calling the clicked callback,
-		 * so that if the mini-dialog gets destroyed during the clicked callback
-		 * the idle_destroy_cb is correctly removed by _finalize.
-		 */
-		priv->idle_destroy_cb_id =
-			g_idle_add((GSourceFunc)idle_destroy_cb, data->mini_dialog);
-	}
-
-	if (data->callback != NULL) {
-		data->callback(data->mini_dialog, button, data->user_data);
-	}
-}
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-mini_dialog_add_button(PidginMiniDialog *self, const gchar *text,
-                       PidginMiniDialogCallback clicked_cb, gpointer user_data,
-                       gboolean close_dialog_after_click)
-{
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-	struct _mini_dialog_button_clicked_cb_data *callback_data = NULL;
-	GtkWidget *button = NULL;
-	GtkWidget *label = NULL;
-	char *button_text = NULL;
-
-	label = gtk_label_new(NULL);
-	button_text = g_strdup_printf("<span size=\"smaller\">%s</span>", text);
-	gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), button_text);
-	g_free(button_text);
-
-	callback_data = g_new0(struct _mini_dialog_button_clicked_cb_data, 1);
-	callback_data->mini_dialog = self;
-	callback_data->callback = clicked_cb;
-	callback_data->user_data = user_data;
-	callback_data->close_dialog_after_click = close_dialog_after_click;
-
-	button = gtk_button_new();
-	gtk_container_add(GTK_CONTAINER(button), label);
-	g_signal_connect_data(G_OBJECT(button), "clicked",
-	                      G_CALLBACK(mini_dialog_button_clicked_cb),
-	                      callback_data, (GClosureNotify)g_free, 0);
-
-	gtk_box_pack_end(GTK_BOX(priv->buttons), button, FALSE, FALSE, 0);
-	gtk_widget_show_all(button);
-}
-
-static void
-mini_dialog_set_title(PidginMiniDialog *self, const gchar *title)
-{
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-
-	gchar *title_esc = g_markup_escape_text(title, -1);
-	gchar *title_markup = g_strdup_printf(
-	    "<span weight=\"bold\" size=\"smaller\">%s</span>",
-	    title_esc ? title_esc : "");
-
-	gtk_label_set_markup(priv->title, title_markup);
-
-	g_free(title_esc);
-	g_free(title_markup);
-
-	g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_TITLE]);
-}
-
-static void
-mini_dialog_set_description(PidginMiniDialog *self, const gchar *description)
-{
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-
-	if(description) {
-		gchar *desc_esc, *desc_markup;
-
-		desc_esc = priv->enable_description_markup
-		    ? g_strdup(description)
-		    : g_markup_escape_text(description, -1);
-		desc_markup = g_strdup_printf("<span size=\"smaller\">%s</span>",
-		                              desc_esc);
-
-		gtk_label_set_markup(priv->desc, desc_markup);
-
-		g_free(desc_esc);
-		g_free(desc_markup);
-
-		gtk_widget_show(GTK_WIDGET(priv->desc));
-		g_object_set(G_OBJECT(priv->desc), "no-show-all", FALSE, NULL);
-	} else {
-		gtk_label_set_text(priv->desc, NULL);
-		gtk_widget_hide(GTK_WIDGET(priv->desc));
-		/* make calling show_all() on the minidialog not affect desc
-		 * even though it's packed inside it.
-	 	 */
-		g_object_set(G_OBJECT(priv->desc), "no-show-all", TRUE, NULL);
-	}
-
-	g_object_notify_by_pspec(G_OBJECT(self), properties[PROP_DESCRIPTION]);
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-static void
-pidgin_mini_dialog_get_property(GObject *obj, guint param_id, GValue *value,
-                                GParamSpec *pspec)
-{
-	PidginMiniDialog *self = PIDGIN_MINI_DIALOG(obj);
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-
-	switch (param_id) {
-		case PROP_TITLE:
-			g_value_set_string(value, gtk_label_get_text(priv->title));
-			break;
-		case PROP_DESCRIPTION:
-			g_value_set_string(value, gtk_label_get_text(priv->desc));
-			break;
-		case PROP_ICON_NAME:
-		{
-			const gchar *icon_name = NULL;
-			gtk_image_get_icon_name(priv->icon, &icon_name, NULL);
-			g_value_set_string(value, icon_name);
-			break;
-		}
-		case PROP_CUSTOM_ICON:
-			g_value_set_object(value, gtk_image_get_pixbuf(priv->icon));
-			break;
-		case PROP_ENABLE_DESCRIPTION_MARKUP:
-			g_value_set_boolean(value, priv->enable_description_markup);
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-pidgin_mini_dialog_set_property(GObject *obj, guint param_id,
-                                const GValue *value,
-                                GParamSpec *pspec)
-{
-	PidginMiniDialog *self = PIDGIN_MINI_DIALOG(obj);
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-
-	switch (param_id) {
-		case PROP_TITLE:
-			mini_dialog_set_title(self, g_value_get_string(value));
-			break;
-		case PROP_DESCRIPTION:
-			mini_dialog_set_description(self, g_value_get_string(value));
-			break;
-		case PROP_ICON_NAME:
-			gtk_image_set_from_icon_name(priv->icon,
-					g_value_get_string(value),
-					GTK_ICON_SIZE_SMALL_TOOLBAR);
-			break;
-		case PROP_CUSTOM_ICON:
-			gtk_image_set_from_pixbuf(priv->icon, g_value_get_object(value));
-			break;
-		case PROP_ENABLE_DESCRIPTION_MARKUP:
-			priv->enable_description_markup = g_value_get_boolean(value);
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-pidgin_mini_dialog_finalize(GObject *obj)
-{
-	PidginMiniDialog *self = PIDGIN_MINI_DIALOG(obj);
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-
-	if (priv->idle_destroy_cb_id) {
-		g_source_remove(priv->idle_destroy_cb_id);
-	}
-
-	purple_prefs_disconnect_by_handle(self);
-
-	G_OBJECT_CLASS(pidgin_mini_dialog_parent_class)->finalize(obj);
-}
-
-static void
-pidgin_mini_dialog_class_init(PidginMiniDialogClass *klass)
-{
-	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
-
-	obj_class->get_property = pidgin_mini_dialog_get_property;
-	obj_class->set_property = pidgin_mini_dialog_set_property;
-	obj_class->finalize = pidgin_mini_dialog_finalize;
-
-	properties[PROP_TITLE] = g_param_spec_string(
-		"title", "title",
-		"String specifying the mini-dialog's title", NULL,
-		G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
-
-	properties[PROP_DESCRIPTION] = g_param_spec_string(
-		"description", "description",
-		"Description text for the mini-dialog, if desired", NULL,
-		G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
-
-	properties[PROP_ICON_NAME] = g_param_spec_string(
-		"icon-name", "icon-name",
-		"String specifying the GtkIconTheme name of the dialog's icon", NULL,
-		G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
-
-	properties[PROP_CUSTOM_ICON] = g_param_spec_object(
-		"custom-icon", "custom-icon",
-		"Pixbuf to use as the dialog's icon", GDK_TYPE_PIXBUF,
-		G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
-
-	properties[PROP_ENABLE_DESCRIPTION_MARKUP] = g_param_spec_boolean(
-		"enable-description-markup", "enable-description-markup",
-		"Use GMarkup in the description text", FALSE,
-		G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
-
-	g_object_class_install_properties(obj_class, PROP_LAST, properties);
-}
-
-static void
-pidgin_mini_dialog_init(PidginMiniDialog *self)
-{
-	GtkBox *self_box = GTK_BOX(self);
-	PidginMiniDialogPrivate *priv =
-		pidgin_mini_dialog_get_instance_private(self);
-
-	gtk_orientable_set_orientation(GTK_ORIENTABLE(self), GTK_ORIENTATION_VERTICAL);
-
-	gtk_container_set_border_width(GTK_CONTAINER(self), 6);
-
-	priv->title_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6));
-
-	priv->icon = GTK_IMAGE(gtk_image_new());
-	gtk_widget_set_halign(GTK_WIDGET(priv->icon), GTK_ALIGN_START);
-	gtk_widget_set_valign(GTK_WIDGET(priv->icon), GTK_ALIGN_START);
-
-	priv->title = GTK_LABEL(gtk_label_new(NULL));
-	gtk_label_set_line_wrap(priv->title, TRUE);
-	gtk_label_set_selectable(priv->title, TRUE);
-	gtk_label_set_xalign(priv->title, 0);
-	gtk_label_set_yalign(priv->title, 0);
-
-	gtk_box_pack_start(priv->title_box, GTK_WIDGET(priv->icon), FALSE, FALSE, 0);
-	gtk_box_pack_start(priv->title_box, GTK_WIDGET(priv->title), TRUE, TRUE, 0);
-
-	priv->desc = GTK_LABEL(gtk_label_new(NULL));
-	gtk_label_set_line_wrap(priv->desc, TRUE);
-	gtk_label_set_xalign(priv->desc, 0);
-	gtk_label_set_yalign(priv->desc, 0);
-	gtk_label_set_selectable(priv->desc, TRUE);
-	/* make calling show_all() on the minidialog not affect desc even though
-	 * it's packed inside it.
-	 */
-	g_object_set(G_OBJECT(priv->desc), "no-show-all", TRUE, NULL);
-
-	self->contents = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
-
-	priv->buttons = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
-
-	gtk_box_pack_start(self_box, GTK_WIDGET(priv->title_box), FALSE, FALSE, 0);
-	gtk_box_pack_start(self_box, GTK_WIDGET(priv->desc), FALSE, FALSE, 0);
-	gtk_box_pack_start(self_box, GTK_WIDGET(self->contents), TRUE, TRUE, 0);
-	gtk_box_pack_start(self_box, GTK_WIDGET(priv->buttons), FALSE, FALSE, 0);
-
-	gtk_widget_show_all(GTK_WIDGET(self));
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-PidginMiniDialog *
-pidgin_mini_dialog_new(const gchar *title,
-                       const gchar *description,
-                       const gchar *icon_name)
-{
-	PidginMiniDialog *mini_dialog = g_object_new(PIDGIN_TYPE_MINI_DIALOG,
-		"title", title,
-		"description", description,
-		"icon-name", icon_name,
-		NULL);
-	return mini_dialog;
-}
-
-PidginMiniDialog *
-pidgin_mini_dialog_new_with_custom_icon(const gchar *title,
-                                        const gchar *description,
-                                        GdkPixbuf *custom_icon)
-{
-	PidginMiniDialog *mini_dialog = g_object_new(PIDGIN_TYPE_MINI_DIALOG,
-		"title", title,
-		"description", description,
-		"custom-icon", custom_icon,
-		NULL);
-	return mini_dialog;
-}
-
-GtkWidget *
-pidgin_mini_dialog_new_with_buttons(const gchar *title,
-                                    const gchar *description,
-                                    const gchar *icon_name, gpointer user_data,
-                                    ...)
-{
-	PidginMiniDialog *mini_dialog = NULL;
-	const gchar *button_text = NULL;
-	va_list args;
-
-	mini_dialog = pidgin_mini_dialog_new(title, description, icon_name);
-
-	va_start(args, user_data);
-	while ((button_text = va_arg(args, gchar *)) != NULL) {
-		PidginMiniDialogCallback callback =
-			va_arg(args, PidginMiniDialogCallback);
-
-		pidgin_mini_dialog_add_button(mini_dialog, button_text, callback,
-		                              user_data);
-	}
-	va_end(args);
-
-	return GTK_WIDGET(mini_dialog);
-}
-
-void
-pidgin_mini_dialog_set_title(PidginMiniDialog *mini_dialog, const gchar *title)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	g_object_set(G_OBJECT(mini_dialog), "title", title, NULL);
-}
-
-void
-pidgin_mini_dialog_set_description(PidginMiniDialog *mini_dialog,
-                                   const gchar *description)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	g_object_set(G_OBJECT(mini_dialog), "description", description, NULL);
-}
-
-void
-pidgin_mini_dialog_enable_description_markup(PidginMiniDialog *mini_dialog)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	g_object_set(G_OBJECT(mini_dialog), "enable-description-markup", TRUE, NULL);
-}
-
-void
-pidgin_mini_dialog_set_link_callback(PidginMiniDialog *mini_dialog,
-                                     GCallback cb, gpointer user_data)
-{
-	PidginMiniDialogPrivate *priv = NULL;
-
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	priv = pidgin_mini_dialog_get_instance_private(mini_dialog);
-	g_signal_connect(priv->desc, "activate-link", cb, user_data);
-}
-
-void
-pidgin_mini_dialog_set_icon_name(PidginMiniDialog *mini_dialog,
-                                 const gchar *icon_name)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	g_object_set(G_OBJECT(mini_dialog), "icon-name", icon_name, NULL);
-}
-
-void
-pidgin_mini_dialog_set_custom_icon(PidginMiniDialog *mini_dialog,
-                                   GdkPixbuf *custom_icon)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	g_object_set(G_OBJECT(mini_dialog), "custom-icon", custom_icon, NULL);
-}
-
-guint
-pidgin_mini_dialog_get_num_children(PidginMiniDialog *mini_dialog)
-{
-	GList *tmp;
-	guint len;
-
-	g_return_val_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog), 0);
-
-	tmp = gtk_container_get_children(GTK_CONTAINER(mini_dialog->contents));
-	len = g_list_length(tmp);
-	g_list_free(tmp);
-
-	return len;
-}
-
-void
-pidgin_mini_dialog_add_button(PidginMiniDialog *mini_dialog,
-                              const gchar *text,
-                              PidginMiniDialogCallback clicked_cb,
-                              gpointer user_data)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	mini_dialog_add_button(mini_dialog, text, clicked_cb, user_data, TRUE);
-}
-
-void
-pidgin_mini_dialog_add_non_closing_button(PidginMiniDialog *mini_dialog,
-                                          const gchar *text,
-                                          PidginMiniDialogCallback clicked_cb,
-                                          gpointer user_data)
-{
-	g_return_if_fail(PIDGIN_IS_MINI_DIALOG(mini_dialog));
-
-	mini_dialog_add_button(mini_dialog, text, clicked_cb, user_data, FALSE);
-}
diff --git a/pidgin/pidginnotificationaddcontact.h b/pidgin/pidginnotificationaddcontact.h
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginnotificationaddcontact.h
@@ -0,0 +1,80 @@
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PIDGIN_NOTIFICATION_ADD_CONTACT_H
+#define PIDGIN_NOTIFICATION_ADD_CONTACT_H
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include <handy.h>
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginNotificationAddContact:
+ *
+ * #PidginNotificationAddContact is a widget that displays
+ * notifications from [class@Purple.NotificationManager] for
+ * [class@Purple.AddContactRequest].
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_NOTIFICATION_ADD_CONTACT (pidgin_notification_add_contact_get_type())
+G_DECLARE_FINAL_TYPE(PidginNotificationAddContact, pidgin_notification_add_contact,
+                     PIDGIN, NOTIFICATION_ADD_CONTACT, HdyActionRow)
+
+/**
+ * pidgin_notification_add_contact_new:
+ * @notification: A [class@Purple.Notification] to display.
+ *
+ * Creates a new #PidginNotificationAddContact instance that will display
+ * @notification.
+ *
+ * Returns: (transfer full): The new #PidginNotificationAddContact
+ *          instance.
+ */
+GtkWidget *pidgin_notification_add_contact_new(PurpleNotification *notification);
+
+/**
+ * pidgin_notification_add_contact_get_notification:
+ * @request: The instance.
+ *
+ * Gets the [class@Purple.Notification] that @request is displaying.
+ *
+ * Returns: (transfer none): The notification.
+ *
+ * Since: 3.0.0
+ */
+PurpleNotification *pidgin_notification_add_contact_get_notification(PidginNotificationAddContact *request);
+
+G_END_DECLS
+
+#endif /* PIDGIN_NOTIFICATION_ADD_CONTACT_H */
diff --git a/pidgin/pidginnotificationaddcontact.c b/pidgin/pidginnotificationaddcontact.c
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginnotificationaddcontact.c
@@ -0,0 +1,302 @@
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 <glib/gi18n-lib.h>
+
+#include <purple.h>
+
+#include "pidgin/pidginnotificationaddcontact.h"
+
+#include "pidgin/gtkdialogs.h"
+
+struct _PidginNotificationAddContact {
+	HdyActionRow parent;
+
+	PurpleNotification *notification;
+
+	GtkWidget *add;
+	GtkWidget *message;
+};
+
+enum {
+	PROP_0,
+	PROP_NOTIFICATION,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE(PidginNotificationAddContact, pidgin_notification_add_contact,
+              HDY_TYPE_ACTION_ROW)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+pidgin_notification_add_contact_update(PidginNotificationAddContact *add_contact)
+{
+	PurpleAccount *account = NULL;
+	PurpleAddContactRequest *request = NULL;
+	const gchar *title = NULL;
+	const gchar *icon_name = NULL, *message = NULL;
+
+	g_return_if_fail(PIDGIN_IS_NOTIFICATION_ADD_CONTACT(add_contact));
+
+	if(!PURPLE_IS_NOTIFICATION(add_contact->notification)) {
+		hdy_preferences_row_set_title(HDY_PREFERENCES_ROW(add_contact),
+		                              _("Notification missing"));
+
+		hdy_action_row_set_icon_name(HDY_ACTION_ROW(add_contact), NULL);
+		hdy_action_row_set_subtitle(HDY_ACTION_ROW(add_contact), NULL);
+
+		gtk_widget_hide(add_contact->add);
+		gtk_widget_hide(add_contact->message);
+
+		return;
+	}
+
+	account = purple_notification_get_account(add_contact->notification);
+	if(!PURPLE_IS_ACCOUNT(account)) {
+		hdy_preferences_row_set_title(HDY_PREFERENCES_ROW(add_contact),
+		                              _("Notification is missing an account"));
+
+		hdy_action_row_set_icon_name(HDY_ACTION_ROW(add_contact), NULL);
+		hdy_action_row_set_subtitle(HDY_ACTION_ROW(add_contact), NULL);
+
+		gtk_widget_hide(add_contact->add);
+		gtk_widget_hide(add_contact->message);
+
+		return;
+	}
+
+	request = purple_notification_get_data(add_contact->notification);
+
+	/* Set the icon name if one was specified. */
+	icon_name = purple_notification_get_icon_name(add_contact->notification);
+	if(icon_name == NULL) {
+		PurpleProtocol *protocol = NULL;
+
+		protocol = purple_account_get_protocol(account);
+		icon_name = purple_protocol_get_icon_name(protocol);
+
+		if(icon_name == NULL) {
+			icon_name = "dialog-question";
+		}
+	}
+	hdy_action_row_set_icon_name(HDY_ACTION_ROW(add_contact), icon_name);
+
+	title = purple_notification_get_title(add_contact->notification);
+	hdy_preferences_row_set_title(HDY_PREFERENCES_ROW(add_contact), title);
+
+	message = purple_add_contact_request_get_message(request);
+	hdy_action_row_set_subtitle(HDY_ACTION_ROW(add_contact), message);
+
+	gtk_widget_show(add_contact->add);
+	gtk_widget_show(add_contact->message);
+}
+
+static void
+pidgin_notification_add_contact_set_notification(PidginNotificationAddContact *add_contact,
+                                                 PurpleNotification *notification)
+{
+	if(g_set_object(&add_contact->notification, notification)) {
+		pidgin_notification_add_contact_update(add_contact);
+
+		g_object_notify_by_pspec(G_OBJECT(add_contact),
+		                         properties[PROP_NOTIFICATION]);
+	}
+}
+
+static void
+pidgin_notification_add_contact_close(PidginNotificationAddContact *add_contact)
+{
+	PurpleNotificationManager *manager = NULL;
+
+	purple_notification_delete(add_contact->notification);
+
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_remove(manager, add_contact->notification);
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_notification_add_contact_add_cb(G_GNUC_UNUSED GtkButton *button,
+                                       gpointer data)
+{
+	PidginNotificationAddContact *pidgin_request = data;
+	PurpleAddContactRequest *request = NULL;
+
+	request = purple_notification_get_data(pidgin_request->notification);
+
+	purple_add_contact_request_add(request);
+
+	pidgin_notification_add_contact_close(pidgin_request);
+}
+
+static void
+pidgin_notification_add_contact_message_cb(G_GNUC_UNUSED GtkButton *button,
+                                           gpointer data)
+{
+	PidginNotificationAddContact *pidgin_request = data;
+	PurpleAddContactRequest *request = NULL;
+	PurpleAccount *account = NULL;
+	const gchar *username = NULL;
+
+	request = purple_notification_get_data(pidgin_request->notification);
+
+	account = purple_add_contact_request_get_account(request);
+	username = purple_add_contact_request_get_username(request);
+
+	pidgin_dialogs_im_with_user(account, username);
+}
+
+static void
+pidgin_notification_add_contact_remove_cb(G_GNUC_UNUSED GtkButton *button,
+                                          gpointer data)
+{
+	PidginNotificationAddContact *request = data;
+
+	pidgin_notification_add_contact_close(request);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_notification_add_contact_get_property(GObject *obj, guint param_id,
+                                             GValue *value, GParamSpec *pspec)
+{
+	PidginNotificationAddContact *request = NULL;
+
+	request = PIDGIN_NOTIFICATION_ADD_CONTACT(obj);
+
+	switch(param_id) {
+		case PROP_NOTIFICATION:
+			g_value_set_object(value,
+			                   pidgin_notification_add_contact_get_notification(request));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_notification_add_contact_set_property(GObject *obj, guint param_id,
+                                             const GValue *value,
+                                             GParamSpec *pspec)
+{
+	PidginNotificationAddContact *request = NULL;
+
+	request = PIDGIN_NOTIFICATION_ADD_CONTACT(obj);
+
+	switch(param_id) {
+		case PROP_NOTIFICATION:
+			pidgin_notification_add_contact_set_notification(request,
+			                                                 g_value_get_object(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_notification_add_contact_dispose(GObject *obj) {
+	PidginNotificationAddContact *request = NULL;
+
+	request = PIDGIN_NOTIFICATION_ADD_CONTACT(obj);
+
+	g_clear_object(&request->notification);
+
+	G_OBJECT_CLASS(pidgin_notification_add_contact_parent_class)->dispose(obj);
+}
+
+static void
+pidgin_notification_add_contact_init(PidginNotificationAddContact *list) {
+	gtk_widget_init_template(GTK_WIDGET(list));
+}
+
+static void
+pidgin_notification_add_contact_class_init(PidginNotificationAddContactClass *klass)
+{
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	obj_class->get_property = pidgin_notification_add_contact_get_property;
+	obj_class->set_property = pidgin_notification_add_contact_set_property;
+	obj_class->dispose = pidgin_notification_add_contact_dispose;
+
+	/**
+	 * PidginNotificationAddContact:notification:
+	 *
+	 * The [type@Purple.Notification] that is being displayed.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NOTIFICATION] = g_param_spec_object(
+		"notification", "notification",
+		"The notification to display",
+		PURPLE_TYPE_NOTIFICATION,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+	gtk_widget_class_set_template_from_resource(
+	    widget_class,
+	    "/im/pidgin/Pidgin3/Notifications/addcontact.ui"
+	);
+
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginNotificationAddContact,
+	                                     add);
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginNotificationAddContact,
+	                                     message);
+
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_add_contact_add_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_add_contact_message_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_add_contact_remove_cb);
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+GtkWidget *
+pidgin_notification_add_contact_new(PurpleNotification *notification) {
+	return g_object_new(
+		PIDGIN_TYPE_NOTIFICATION_ADD_CONTACT,
+		"notification", notification,
+		NULL);
+}
+
+PurpleNotification *
+pidgin_notification_add_contact_get_notification(PidginNotificationAddContact *add_contact)
+{
+	g_return_val_if_fail(PIDGIN_IS_NOTIFICATION_ADD_CONTACT(add_contact), NULL);
+
+	return add_contact->notification;
+}
diff --git a/pidgin/pidginnotificationlist.c b/pidgin/pidginnotificationlist.c
--- a/pidgin/pidginnotificationlist.c
+++ b/pidgin/pidginnotificationlist.c
@@ -26,8 +26,9 @@
 
 #include "pidgin/pidginnotificationlist.h"
 
+#include "pidgin/pidginnotificationaddcontact.h"
+#include "pidgin/pidginnotificationauthorizationrequest.h"
 #include "pidgin/pidginnotificationconnectionerror.h"
-#include "pidgin/pidginnotificationauthorizationrequest.h"
 
 struct _PidginNotificationList {
 	GtkBox parent;
@@ -75,6 +76,9 @@
 		case PURPLE_NOTIFICATION_TYPE_AUTHORIZATION_REQUEST:
 			widget = pidgin_notification_authorization_request_new(notification);
 			break;
+		case PURPLE_NOTIFICATION_TYPE_ADD_CONTACT:
+			widget = pidgin_notification_add_contact_new(notification);
+			break;
 		default:
 			widget = pidgin_notification_list_unknown_notification(notification);
 			break;
diff --git a/pidgin/pidginscrollbook.h b/pidgin/pidginscrollbook.h
deleted file mode 100644
--- a/pidgin/pidginscrollbook.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Pidgin - Internet Messenger
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * 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(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
-# error "only <pidgin.h> may be included directly"
-#endif
-
-#ifndef PIDGIN_SCROLL_BOOK_H
-#define PIDGIN_SCROLL_BOOK_H
-/**
- * PidginScrollBook:
- *
- * This widget is used to show error messages in the buddy list window.
- */
-
-#include <gtk/gtk.h>
-
-G_BEGIN_DECLS
-
-#define PIDGIN_TYPE_SCROLL_BOOK (pidgin_scroll_book_get_type ())
-G_DECLARE_FINAL_TYPE(PidginScrollBook, pidgin_scroll_book, PIDGIN, SCROLL_BOOK,
-                     GtkBox)
-
-/**
- * pidgin_scroll_book_new:
- *
- * Creates a new #PidginScrollBook.
- *
- * Returns: (transfer full): The new #PidginScrollBook instance.
- */
-GtkWidget *pidgin_scroll_book_new(void);
-
-/**
- * pidgin_scroll_book_get_notebook:
- * @scroll_book: The #PidginScrollBook instance.
- *
- * Gets the notebook widget from @scroll_book.
- *
- * Returns: (transfer none): The notebook inside of @scroll_book.
- */
-GtkWidget *pidgin_scroll_book_get_notebook(PidginScrollBook *scroll_book);
-
-G_END_DECLS
-
-#endif  /* PIDGIN_SCROLL_BOOK_H */
diff --git a/pidgin/pidginscrollbook.c b/pidgin/pidginscrollbook.c
deleted file mode 100644
--- a/pidgin/pidginscrollbook.c
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Pidgin - Internet Messenger
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * 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 "pidginscrollbook.h"
-
-struct _PidginScrollBook {
-	GtkVBox parent;
-
-	GtkWidget *notebook;
-	GtkWidget *hbox;
-	GtkWidget *label;
-	GtkWidget *left_arrow;
-	GtkWidget *right_arrow;
-	GList *children;
-};
-
-G_DEFINE_TYPE(PidginScrollBook, pidgin_scroll_book, GTK_TYPE_BOX)
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static gboolean
-scroll_left_cb(PidginScrollBook *scroll_book, GdkEventButton *event)
-{
-	int index;
-
-	if (event->type != GDK_BUTTON_PRESS)
-		return FALSE;
-
-	index = gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook));
-
-	if (index > 0)
-		gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index - 1);
-	return TRUE;
-}
-
-static gboolean
-scroll_right_cb(PidginScrollBook *scroll_book, GdkEventButton *event)
-{
-	int index, count;
-
-	if (event->type != GDK_BUTTON_PRESS)
-		return FALSE;
-
-	index = gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook));
-	count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(scroll_book->notebook));
-
-	if (index + 1 < count)
-		gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index + 1);
-	return TRUE;
-}
-
-static void
-refresh_scroll_box(PidginScrollBook *scroll_book, int index, int count)
-{
-	char *label;
-
-	gtk_widget_show_all(GTK_WIDGET(scroll_book));
-	if (count < 1)
-		gtk_widget_hide(scroll_book->hbox);
-	else {
-		gtk_widget_show_all(scroll_book->hbox);
-		if (count == 1) {
-			gtk_widget_hide(scroll_book->label);
-			gtk_widget_hide(scroll_book->left_arrow);
-			gtk_widget_hide(scroll_book->right_arrow);
-		}
-	}
-
-	label = g_strdup_printf("<span size='smaller' weight='bold'>(%d/%d)</span>", index+1, count);
-	gtk_label_set_markup(GTK_LABEL(scroll_book->label), label);
-	g_free(label);
-
-	if (index == 0)
-		gtk_widget_set_sensitive(scroll_book->left_arrow, FALSE);
-	else
-		gtk_widget_set_sensitive(scroll_book->left_arrow, TRUE);
-
-
-	if (index + 1 == count)
-		gtk_widget_set_sensitive(scroll_book->right_arrow, FALSE);
-	else
-		gtk_widget_set_sensitive(scroll_book->right_arrow, TRUE);
-}
-
-
-static void
-page_count_change_cb(PidginScrollBook *scroll_book)
-{
-	int count;
-	int index = gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook));
-	count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(scroll_book->notebook));
-	refresh_scroll_box(scroll_book, index, count);
-}
-
-static gboolean
-scroll_close_cb(PidginScrollBook *scroll_book, GdkEventButton *event)
-{
-	if (event->type == GDK_BUTTON_PRESS)
-		gtk_widget_destroy(gtk_notebook_get_nth_page(GTK_NOTEBOOK(scroll_book->notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook))));
-	return FALSE;
-}
-
-static void
-switch_page_cb(GtkNotebook *notebook, GtkWidget *page, guint page_num, PidginScrollBook *scroll_book)
-{
-	int count;
-	count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(scroll_book->notebook));
-	refresh_scroll_box(scroll_book, page_num, count);
-}
-
-static gboolean
-close_button_left_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label)
-{
-	static GdkCursor *ptr = NULL;
-	if (ptr == NULL) {
-		GdkDisplay *display = gtk_widget_get_display(widget);
-		ptr = gdk_cursor_new_for_display(display, GDK_LEFT_PTR);
-	}
-
-	gtk_label_set_markup(label, "×");
-	gdk_window_set_cursor(event->window, ptr);
-	return FALSE;
-}
-
-static gboolean
-close_button_entered_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label)
-{
-	static GdkCursor *hand = NULL;
-	if (hand == NULL) {
-		GdkDisplay *display = gtk_widget_get_display(widget);
-		hand = gdk_cursor_new_for_display(display, GDK_HAND2);
-	}
-
-	gtk_label_set_markup(label, "<u>×</u>");
-	gdk_window_set_cursor(event->window, hand);
-	return FALSE;
-}
-
-/******************************************************************************
- * GtkContainer Implementation
- *****************************************************************************/
-static void
-pidgin_scroll_book_add(GtkContainer *container, GtkWidget *widget)
-{
-	PidginScrollBook *scroll_book;
-
-	g_return_if_fail(GTK_IS_WIDGET (widget));
-	g_return_if_fail(gtk_widget_get_parent(widget) == NULL);
-
-	scroll_book = PIDGIN_SCROLL_BOOK(container);
-	scroll_book->children = g_list_append(scroll_book->children, widget);
-	gtk_widget_show(widget);
-	gtk_notebook_append_page(GTK_NOTEBOOK(scroll_book->notebook), widget, NULL);
-	page_count_change_cb(PIDGIN_SCROLL_BOOK(container));
-}
-
-static void
-pidgin_scroll_book_remove(GtkContainer *container, GtkWidget *widget)
-{
-	int page;
-	PidginScrollBook *scroll_book;
-	g_return_if_fail(GTK_IS_WIDGET(widget));
-
-	scroll_book = PIDGIN_SCROLL_BOOK(container);
-	scroll_book->children = g_list_remove(scroll_book->children, widget);
-	/* gtk_widget_unparent(widget); */
-
-	page = gtk_notebook_page_num(GTK_NOTEBOOK(PIDGIN_SCROLL_BOOK(container)->notebook), widget);
-	if (page >= 0) {
-		gtk_notebook_remove_page(GTK_NOTEBOOK(PIDGIN_SCROLL_BOOK(container)->notebook), page);
-	}
-}
-
-static void
-pidgin_scroll_book_forall(GtkContainer *container,
-			   gboolean include_internals,
-			   GtkCallback callback,
-			   gpointer callback_data)
-{
-	PidginScrollBook *scroll_book;
-
-	g_return_if_fail(GTK_IS_CONTAINER(container));
-
-	scroll_book = PIDGIN_SCROLL_BOOK(container);
-
-	if (include_internals) {
-		(*callback)(scroll_book->hbox, callback_data);
-		(*callback)(scroll_book->notebook, callback_data);
-	}
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-static void
-pidgin_scroll_book_class_init(PidginScrollBookClass *klass) {
-	GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
-
-	container_class->add = pidgin_scroll_book_add;
-	container_class->remove = pidgin_scroll_book_remove;
-	container_class->forall = pidgin_scroll_book_forall;
-}
-
-static void
-pidgin_scroll_book_init(PidginScrollBook *scroll_book) {
-	GIcon *icon;
-	GtkWidget *eb;
-	GtkWidget *close_button;
-	const gchar *left_arrow_icon_names[] = {
-	        "pan-start-symbolic",
-	        "pan-left-symbolic",
-	};
-	const gchar *right_arrow_icon_names[] = {
-	        "pan-end-symbolic",
-	        "pan-right-symbolic",
-	};
-
-	gtk_orientable_set_orientation(GTK_ORIENTABLE(scroll_book), GTK_ORIENTATION_VERTICAL);
-
-	scroll_book->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-
-	/* Close */
-	eb = gtk_event_box_new();
-	gtk_box_pack_end(GTK_BOX(scroll_book->hbox), eb, FALSE, FALSE, 0);
-	gtk_event_box_set_visible_window(GTK_EVENT_BOX(eb), FALSE);
-	gtk_widget_set_events(eb, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
-	close_button = gtk_label_new("×");
-	g_signal_connect(G_OBJECT(eb), "enter-notify-event", G_CALLBACK(close_button_entered_cb), close_button);
-	g_signal_connect(G_OBJECT(eb), "leave-notify-event", G_CALLBACK(close_button_left_cb), close_button);
-	gtk_container_add(GTK_CONTAINER(eb), close_button);
-	g_signal_connect_swapped(G_OBJECT(eb), "button-press-event", G_CALLBACK(scroll_close_cb), scroll_book);
-
-	/* Right arrow */
-	eb = gtk_event_box_new();
-	gtk_box_pack_end(GTK_BOX(scroll_book->hbox), eb, FALSE, FALSE, 0);
-	icon = g_themed_icon_new_from_names((char **)right_arrow_icon_names,
-	                                    G_N_ELEMENTS(right_arrow_icon_names));
-	scroll_book->right_arrow =
-	        gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_BUTTON);
-	g_object_unref(icon);
-	gtk_container_add(GTK_CONTAINER(eb), scroll_book->right_arrow);
-	g_signal_connect_swapped(G_OBJECT(eb), "button-press-event", G_CALLBACK(scroll_right_cb), scroll_book);
-
-	/* Count */
-	scroll_book->label = gtk_label_new(NULL);
-	gtk_box_pack_end(GTK_BOX(scroll_book->hbox), scroll_book->label, FALSE, FALSE, 0);
-
-	/* Left arrow */
-	eb = gtk_event_box_new();
-	gtk_box_pack_end(GTK_BOX(scroll_book->hbox), eb, FALSE, FALSE, 0);
-	icon = g_themed_icon_new_from_names((char **)left_arrow_icon_names,
-	                                    G_N_ELEMENTS(left_arrow_icon_names));
-	scroll_book->left_arrow =
-	        gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_BUTTON);
-	g_object_unref(icon);
-	gtk_container_add(GTK_CONTAINER(eb), scroll_book->left_arrow);
-	g_signal_connect_swapped(G_OBJECT(eb), "button-press-event", G_CALLBACK(scroll_left_cb), scroll_book);
-
-	gtk_box_pack_start(GTK_BOX(scroll_book), scroll_book->hbox, FALSE, FALSE, 0);
-
-	scroll_book->notebook = gtk_notebook_new();
-	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(scroll_book->notebook), FALSE);
-	gtk_notebook_set_show_border(GTK_NOTEBOOK(scroll_book->notebook), FALSE);
-
-	gtk_box_pack_start(GTK_BOX(scroll_book), scroll_book->notebook, TRUE, TRUE, 0);
-
-	g_signal_connect_swapped(G_OBJECT(scroll_book->notebook), "remove", G_CALLBACK(page_count_change_cb), scroll_book);
-	g_signal_connect(G_OBJECT(scroll_book->notebook), "switch-page", G_CALLBACK(switch_page_cb), scroll_book);
-	gtk_widget_show_all(scroll_book->notebook);
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-GtkWidget *
-pidgin_scroll_book_new() {
-	return g_object_new(
-		PIDGIN_TYPE_SCROLL_BOOK,
-		"orientation", GTK_ORIENTATION_VERTICAL,
-		NULL);
-}
-
-GtkWidget *
-pidgin_scroll_book_get_notebook(PidginScrollBook *scroll_book) {
-	g_return_val_if_fail(PIDGIN_IS_SCROLL_BOOK(scroll_book), NULL);
-
-	return scroll_book->notebook;
-}
diff --git a/pidgin/resources/Notifications/addcontact.ui b/pidgin/resources/Notifications/addcontact.ui
new file mode 100644
--- /dev/null
+++ b/pidgin/resources/Notifications/addcontact.ui
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 
+
+Pidgin - Internet Messenger
+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/>.
+
+-->
+<interface>
+  <requires lib="gtk+" version="3.24"/>
+  <requires lib="libhandy" version="0.0"/>
+  <!-- interface-license-type gplv2 -->
+  <!-- interface-name Pidgin -->
+  <!-- interface-description Internet Messenger -->
+  <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+  <template class="PidginNotificationAddContact" parent="HdyActionRow">
+    <property name="visible">True</property>
+    <property name="can-focus">True</property>
+    <property name="activatable">False</property>
+    <property name="title-lines">3</property>
+    <property name="subtitle-lines">3</property>
+    <child>
+      <object class="GtkButton" id="add">
+        <property name="label" translatable="yes">Add</property>
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <property name="receives-default">True</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <signal name="clicked" handler="pidgin_notification_add_contact_add_cb" object="PidginNotificationAddContact" swapped="no"/>
+        <style>
+          <class name="suggested-action"/>
+        </style>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton" id="message">
+        <property name="label" translatable="yes">Message</property>
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <property name="receives-default">True</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <signal name="clicked" handler="pidgin_notification_add_contact_message_cb" object="PidginNotificationAddContact" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton" id="remove">
+        <property name="visible">True</property>
+        <property name="can-focus">True</property>
+        <property name="receives-default">True</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <property name="relief">none</property>
+        <signal name="clicked" handler="pidgin_notification_add_contact_remove_cb" object="PidginNotificationAddContact" swapped="no"/>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="icon-name">edit-delete-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/pidgin/resources/pidgin.gresource.xml b/pidgin/resources/pidgin.gresource.xml
--- a/pidgin/resources/pidgin.gresource.xml
+++ b/pidgin/resources/pidgin.gresource.xml
@@ -24,6 +24,7 @@
     <file compressed="true">Keypad/keypad.ui</file>
     <file compressed="true">Log/log-viewer.ui</file>
     <file compressed="true">Media/window.ui</file>
+    <file compressed="true">Notifications/addcontact.ui</file>
     <file compressed="true">Notifications/authorizationrequest.ui</file>
     <file compressed="true">Notifications/connectionerror.ui</file>
     <file compressed="true">Notifications/list.ui</file>
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -237,6 +237,7 @@
 libpurple/purpleaccountoption.c
 libpurple/purpleaccountpresence.c
 libpurple/purpleaccountusersplit.c
+libpurple/purpleaddcontactrequest.c
 libpurple/purpleattachment.c
 libpurple/purplebuddypresence.c
 libpurple/purplechatconversation.c
@@ -332,7 +333,6 @@
 pidgin/gtkwhiteboard.c
 pidgin/gtkxfer.c
 pidgin/libpidgin.c
-pidgin/minidialog.c
 pidgin/pidginabout.c
 pidgin/pidginaccountchooser.c
 pidgin/pidginaccounteditor.c
@@ -368,7 +368,6 @@
 pidgin/pidginprotocolchooser.c
 pidgin/pidginprotocolstore.c
 pidgin/pidginproxyoptions.c
-pidgin/pidginscrollbook.c
 pidgin/pidginstatusbox.c
 pidgin/pidginstatuseditor.c
 pidgin/pidginstatusmanager.c
