Create PurpleAuthorizationRequest and use it for notifications.

Mon, 22 Aug 2022 22:05:55 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 22 Aug 2022 22:05:55 -0500
changeset 41514
a96768bacb59
parent 41513
6cf1fed2d226
child 41515
e91ebfe7d08c

Create PurpleAuthorizationRequest and use it for notifications.

This replaces the old internal representation of authorization requests as well
as the UI's implementation of their own objects. Everything is now controlled
via PurpleAuthorizationRequest and the UI's display the notification for
interaction.

Testing Done:
Verified the notification and actions work in both finch and pidgin.

Reviewed at https://reviews.imfreedom.org/r/1613/

ChangeLog.API file | annotate | diff | comparison | revisions
finch/finchnotifications.c file | annotate | diff | comparison | revisions
finch/gntaccount.c file | annotate | diff | comparison | revisions
libpurple/account.c file | annotate | diff | comparison | revisions
libpurple/account.h file | annotate | diff | comparison | revisions
libpurple/accounts.h file | annotate | diff | comparison | revisions
libpurple/meson.build file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/presence.c file | annotate | diff | comparison | revisions
libpurple/purpleauthorizationrequest.c file | annotate | diff | comparison | revisions
libpurple/purpleauthorizationrequest.h file | annotate | diff | comparison | revisions
libpurple/purplenotification.c file | annotate | diff | comparison | revisions
libpurple/purplenotification.h file | annotate | diff | comparison | revisions
libpurple/tests/meson.build file | annotate | diff | comparison | revisions
libpurple/tests/test_authorization_request.c file | annotate | diff | comparison | revisions
pidgin/gtkaccount.c file | annotate | diff | comparison | revisions
pidgin/meson.build file | annotate | diff | comparison | revisions
pidgin/pidginnotificationauthorizationrequest.c file | annotate | diff | comparison | revisions
pidgin/pidginnotificationauthorizationrequest.h file | annotate | diff | comparison | revisions
pidgin/pidginnotificationconnectionerror.c file | annotate | diff | comparison | revisions
pidgin/pidginnotificationlist.c file | annotate | diff | comparison | revisions
pidgin/resources/Notifications/authorizationrequest.ui file | annotate | diff | comparison | revisions
pidgin/resources/pidgin.gresource.xml file | annotate | diff | comparison | revisions
--- a/ChangeLog.API	Mon Aug 22 21:49:41 2022 -0500
+++ b/ChangeLog.API	Mon Aug 22 22:05:55 2022 -0500
@@ -331,6 +331,8 @@
 		* network-configuration-changed signal
 		* PurpleAccount->ui_data
 		* PurpleAccountPrefsUiOps
+		* PurpleAccountRequestType
+		* PurpleAccountUiOps:request_authorization
 		* purple_account_add_buddies_with_invite
 		* purple_account_add_buddy_with_invite
 		* purple_account_destroy_log
@@ -343,6 +345,8 @@
 		* purple_account_get_ui_string
 		* purple_account_get_ui_bool
 		* purple_account_notify_added
+		* purple_account_request_authorization
+		* purple_account_request_close
 		* purple_account_set_check_mail
 		* purple_account_set_current_error
 		* purple_account_set_password.  Use
--- a/finch/finchnotifications.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/finch/finchnotifications.c	Mon Aug 22 22:05:55 2022 -0500
@@ -63,8 +63,9 @@
 	if(PURPLE_IS_NOTIFICATION(notification)) {
 		PurpleNotificationManager *manager = NULL;
 
+		purple_notification_delete(notification);
+
 		manager = purple_notification_manager_get_default();
-
 		purple_notification_manager_remove(manager, notification);
 	}
 }
@@ -123,6 +124,59 @@
 	gnt_widget_destroy(GNT_WIDGET(data));
 }
 
+static void
+finch_notification_contact_authorize(G_GNUC_UNUSED GntWidget *widget,
+                                     gpointer data)
+{
+	PurpleAccount *account = NULL;
+	PurpleNotification *notification = NULL;
+	PurpleNotificationManager *manager = NULL;
+	PurpleAuthorizationRequest *auth_request = NULL;
+	const gchar *alias = NULL, *username = NULL;
+
+	/* Get the notification and authorization request from the data. */
+	notification = g_object_get_data(data, "notification");
+	auth_request = purple_notification_get_data(notification);
+
+	/* Accept the authorization request. */
+	purple_authorization_request_accept(auth_request);
+
+	/* Remove the notification from the manager. */
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_remove(manager, notification);
+
+	/* Request the user to add the person they just authorized. */
+	account = purple_authorization_request_get_account(auth_request);
+	alias = purple_authorization_request_get_alias(auth_request);
+	username = purple_authorization_request_get_username(auth_request);
+	purple_blist_request_add_buddy(account, username, NULL, alias);
+
+	/* Destroy the dialog. */
+	gnt_widget_destroy(GNT_WIDGET(data));
+}
+
+static void
+finch_notification_contact_deny(G_GNUC_UNUSED GntWidget *widget, gpointer data)
+{
+	PurpleNotification *notification = NULL;
+	PurpleNotificationManager *manager = NULL;
+	PurpleAuthorizationRequest *auth_request = NULL;
+
+	/* Get the notification and authorization request from the data. */
+	notification = g_object_get_data(data, "notification");
+	auth_request = purple_notification_get_data(notification);
+
+	/* Deny the request. */
+	purple_authorization_request_deny(auth_request, NULL);
+
+	/* Remove the notification from the manager. */
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_remove(manager, notification);
+
+	/* Destroy the dialog. */
+	gnt_widget_destroy(GNT_WIDGET(data));
+}
+
 /*******************************************************************************
  * Finch Notification API
  ******************************************************************************/
@@ -155,7 +209,7 @@
 	} else if(type == PURPLE_NOTIFICATION_TYPE_CONNECTION_ERROR) {
 		PurpleConnectionErrorInfo *info = data;
 
-		/* Set the title and name */
+		/* Set the title. */
 		gnt_box_set_title(GNT_BOX(dialog), _("Connection Error"));
 
 		/* Add the connection error reason. */
@@ -181,6 +235,33 @@
 		                 G_CALLBACK(finch_notification_modify_account),
 		                 dialog);
 		gnt_box_add_widget(GNT_BOX(hbox), button);
+	} else if(type == PURPLE_NOTIFICATION_TYPE_AUTHORIZATION_REQUEST) {
+		PurpleAuthorizationRequest *auth_request = NULL;
+		const gchar *message = NULL;
+
+		/* Set the title. */
+		gnt_box_set_title(GNT_BOX(dialog), _("Authorization Request"));
+
+		auth_request = purple_notification_get_data(notification);
+		message = purple_authorization_request_get_message(auth_request);
+
+		/* Add the message if we have one. */
+		if(message != NULL && *message != '\0') {
+			label = gnt_label_new(message);
+			gnt_box_add_widget(GNT_BOX(dialog), label);
+		}
+
+		/* Add the buttons. */
+		button = gnt_button_new(_("Authorize"));
+		g_signal_connect(button, "activate",
+		                 G_CALLBACK(finch_notification_contact_authorize),
+		                 dialog);
+		gnt_box_add_widget(GNT_BOX(hbox), button);
+
+		button = gnt_button_new(_("Deny"));
+		g_signal_connect(button, "activate",
+		                 G_CALLBACK(finch_notification_contact_deny), dialog);
+		gnt_box_add_widget(GNT_BOX(hbox), button);
 	}
 
 	gnt_box_add_widget(GNT_BOX(dialog), hbox);
--- a/finch/gntaccount.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/finch/gntaccount.c	Mon Aug 22 22:05:55 2022 -0500
@@ -651,6 +651,7 @@
 static void
 really_delete_account(PurpleAccount *account)
 {
+	PurpleNotificationManager *manager = NULL;
 	GList *iter;
 	for (iter = accountdialogs; iter; iter = iter->next)
 	{
@@ -661,7 +662,10 @@
 			break;
 		}
 	}
-	purple_request_close_with_handle(account); /* Close any other opened delete window */
+
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_remove_with_account(manager, account);
+
 	purple_accounts_delete(account);
 }
 
@@ -982,124 +986,6 @@
 } auth_and_add;
 
 static void
-free_auth_and_add(auth_and_add *aa)
-{
-	g_free(aa->username);
-	g_free(aa->alias);
-	g_free(aa);
-}
-
-static void
-authorize_and_add_cb(auth_and_add *aa)
-{
-	aa->auth_cb(NULL, aa->data);
-	purple_blist_request_add_buddy(aa->account, aa->username,
-	 	                    NULL, aa->alias);
-}
-
-static void
-deny_no_add_cb(auth_and_add *aa)
-{
-	aa->deny_cb(NULL, aa->data);
-}
-
-static void *
-finch_request_authorize(PurpleAccount *account,
-                        const char *remote_user,
-                        const char *id,
-                        const char *alias,
-                        const char *message,
-                        gboolean on_list,
-                        PurpleAccountRequestAuthorizationCb auth_cb,
-                        PurpleAccountRequestAuthorizationCb deny_cb,
-                        void *user_data)
-{
-	char *buffer;
-	PurpleConnection *gc;
-	void *uihandle;
-
-	gc = purple_account_get_connection(account);
-	if (message != NULL && *message == '\0')
-		message = NULL;
-
-	buffer = g_strdup_printf(_("%s%s%s%s wants to add %s to his or her buddy list%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))),
-		                (message != NULL ? ": " : "."),
-		                (message != NULL ? message  : ""));
-	if (!on_list) {
-		GntWidget *widget;
-		GList *iter;
-		auth_and_add *aa = g_new(auth_and_add, 1);
-
-		aa->auth_cb = auth_cb;
-		aa->deny_cb = deny_cb;
-		aa->data = user_data;
-		aa->username = g_strdup(remote_user);
-		aa->alias = g_strdup(alias);
-		aa->account = account;
-
-		uihandle = gnt_vwindow_new(FALSE);
-		gnt_box_set_title(GNT_BOX(uihandle), _("Authorize buddy?"));
-		gnt_box_set_pad(GNT_BOX(uihandle), 0);
-
-		widget = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL,
-			PURPLE_DEFAULT_ACTION_NONE,
-			purple_request_cpar_from_account(account),
-			aa, 2,
-			_("Authorize"), authorize_and_add_cb,
-			_("Deny"), deny_no_add_cb);
-		/* Since GntWindow is a GntBox, hide it so it's unmapped, then
-		 * add it to the outer window, and make it visible again. */
-		gnt_widget_hide(widget);
-		gnt_box_set_toplevel(GNT_BOX(widget), FALSE);
-		gnt_box_add_widget(GNT_BOX(uihandle), widget);
-		gnt_widget_set_visible(widget, TRUE);
-
-		gnt_box_add_widget(GNT_BOX(uihandle), gnt_hline_new());
-
-		widget = finch_retrieve_user_info(purple_account_get_connection(account), remote_user);
-		for (iter = gnt_box_get_children(GNT_BOX(widget)); iter;
-		     iter = g_list_delete_link(iter, iter)) {
-			if (GNT_IS_BUTTON(iter->data)) {
-				gnt_widget_destroy(iter->data);
-				gnt_box_remove(GNT_BOX(widget), iter->data);
-				g_list_free(iter);
-				break;
-			}
-		}
-		/* Since GntWindow is a GntBox, hide it so it's unmapped, then
-		 * add it to the outer window, and make it visible again. */
-		gnt_widget_hide(widget);
-		gnt_box_set_toplevel(GNT_BOX(widget), FALSE);
-		gnt_box_add_widget(GNT_BOX(uihandle), widget);
-		gnt_widget_set_visible(widget, TRUE);
-
-		gnt_widget_show(uihandle);
-
-		g_signal_connect_swapped(G_OBJECT(uihandle), "destroy", G_CALLBACK(free_auth_and_add), aa);
-	} else {
-		uihandle = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL,
-			PURPLE_DEFAULT_ACTION_NONE,
-			purple_request_cpar_from_account(account),
-			user_data, 2,
-			_("Authorize"), auth_cb,
-			_("Deny"), deny_cb);
-	}
-	g_signal_connect(G_OBJECT(uihandle), "destroy",
-		G_CALLBACK(purple_account_request_close), NULL);
-	g_free(buffer);
-	return uihandle;
-}
-
-static void
 finch_request_close(void *uihandle)
 {
 	purple_request_close(PURPLE_REQUEST_ACTION, uihandle);
@@ -1108,7 +994,6 @@
 static PurpleAccountUiOps ui_ops =
 {
 	.request_add = request_add,
-	.request_authorize = finch_request_authorize,
 	.close_account_request = finch_request_close,
 };
 
--- a/libpurple/account.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/account.c	Mon Aug 22 22:05:55 2022 -0500
@@ -109,18 +109,6 @@
 
 typedef struct
 {
-	PurpleAccountRequestType type;
-	PurpleAccount *account;
-	void *ui_handle;
-	char *user;
-	gpointer userdata;
-	PurpleAccountRequestAuthorizationCb auth_cb;
-	PurpleAccountRequestAuthorizationCb deny_cb;
-	guint ref;
-} PurpleAccountRequestInfo;
-
-typedef struct
-{
 	PurpleAccount *account;
 	GCallback cb;
 	gpointer data;
@@ -144,7 +132,6 @@
 };
 
 static GParamSpec    *properties[PROP_LAST];
-static GList         *handles = NULL;
 
 G_DEFINE_TYPE_WITH_PRIVATE(PurpleAccount, purple_account, G_TYPE_OBJECT);
 
@@ -355,63 +342,6 @@
 	g_free(password);
 }
 
-static PurpleAccountRequestInfo *
-purple_account_request_info_unref(PurpleAccountRequestInfo *info)
-{
-	if (--info->ref)
-		return info;
-
-	/* TODO: This will leak info->user_data, but there is no callback to just clean that up */
-	g_free(info->user);
-	g_free(info);
-	return NULL;
-}
-
-static void
-purple_account_request_close_info(PurpleAccountRequestInfo *info)
-{
-	PurpleAccountUiOps *ops;
-
-	ops = purple_accounts_get_ui_ops();
-
-	if (ops != NULL && ops->close_account_request != NULL)
-		ops->close_account_request(info->ui_handle);
-
-	purple_account_request_info_unref(info);
-}
-
-static void
-request_auth_cb(const char *message, void *data)
-{
-	PurpleAccountRequestInfo *info = data;
-
-	handles = g_list_remove(handles, info);
-
-	if (info->auth_cb != NULL)
-		info->auth_cb(message, info->userdata);
-
-	purple_signal_emit(purple_accounts_get_handle(),
-			"account-authorization-granted", info->account, info->user, message);
-
-	purple_account_request_info_unref(info);
-}
-
-static void
-request_deny_cb(const char *message, void *data)
-{
-	PurpleAccountRequestInfo *info = data;
-
-	handles = g_list_remove(handles, info);
-
-	if (info->deny_cb != NULL)
-		info->deny_cb(message, info->userdata);
-
-	purple_signal_emit(purple_accounts_get_handle(),
-			"account-authorization-denied", info->account, info->user, message);
-
-	purple_account_request_info_unref(info);
-}
-
 static void
 change_password_cb(PurpleAccount *account, PurpleRequestFields *fields)
 {
@@ -1247,108 +1177,14 @@
 		ui_ops->request_add(account, remote_user, id, alias, message);
 }
 
-void *
-purple_account_request_authorization(PurpleAccount *account, const char *remote_user,
-				     const char *id, const char *alias, const char *message, gboolean on_list,
-				     PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data)
-{
-	PurpleAccountUiOps *ui_ops;
-	PurpleAccountRequestInfo *info;
-	int plugin_return;
-	char *response = NULL;
-
-	g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
-	g_return_val_if_fail(remote_user != NULL, NULL);
-
-	ui_ops = purple_accounts_get_ui_ops();
-
-	plugin_return = GPOINTER_TO_INT(
-			purple_signal_emit_return_1(
-				purple_accounts_get_handle(),
-				"account-authorization-requested",
-				account, remote_user, message, &response
-			));
-
-	switch (plugin_return)
-	{
-		case PURPLE_ACCOUNT_RESPONSE_IGNORE:
-			g_free(response);
-			return NULL;
-		case PURPLE_ACCOUNT_RESPONSE_ACCEPT:
-			if (auth_cb != NULL)
-				auth_cb(response, user_data);
-			g_free(response);
-			return NULL;
-		case PURPLE_ACCOUNT_RESPONSE_DENY:
-			if (deny_cb != NULL)
-				deny_cb(response, user_data);
-			g_free(response);
-			return NULL;
-	}
-
-	g_free(response);
-
-	if (ui_ops != NULL && ui_ops->request_authorize != NULL) {
-		info            = g_new0(PurpleAccountRequestInfo, 1);
-		info->type      = PURPLE_ACCOUNT_REQUEST_AUTHORIZATION;
-		info->account   = account;
-		info->auth_cb   = auth_cb;
-		info->deny_cb   = deny_cb;
-		info->userdata  = user_data;
-		info->user      = g_strdup(remote_user);
-		info->ref       = 2;  /* We hold an extra ref to make sure info remains valid
-		                         if any of the callbacks are called synchronously. We
-		                         unref it after the function call */
-
-		info->ui_handle = ui_ops->request_authorize(account, remote_user, id, alias, message,
-							    on_list, request_auth_cb, request_deny_cb, info);
-
-		info = purple_account_request_info_unref(info);
-		if (info) {
-			handles = g_list_append(handles, info);
-			return info->ui_handle;
-		}
-	}
-
-	return NULL;
-}
-
 void
-purple_account_request_close_with_account(PurpleAccount *account)
-{
-	GList *l, *l_next;
+purple_account_request_close_with_account(PurpleAccount *account) {
+	PurpleNotificationManager *manager = NULL;
 
 	g_return_if_fail(PURPLE_IS_ACCOUNT(account));
 
-	for (l = handles; l != NULL; l = l_next) {
-		PurpleAccountRequestInfo *info = l->data;
-
-		l_next = l->next;
-
-		if (info->account == account) {
-			handles = g_list_delete_link(handles, l);
-			purple_account_request_close_info(info);
-		}
-	}
-}
-
-void
-purple_account_request_close(void *ui_handle)
-{
-	GList *l, *l_next;
-
-	g_return_if_fail(ui_handle != NULL);
-
-	for (l = handles; l != NULL; l = l_next) {
-		PurpleAccountRequestInfo *info = l->data;
-
-		l_next = l->next;
-
-		if (info->ui_handle == ui_handle) {
-			handles = g_list_delete_link(handles, l);
-			purple_account_request_close_info(info);
-		}
-	}
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_remove_with_account(manager, account);
 }
 
 void
--- a/libpurple/account.h	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/account.h	Mon Aug 22 22:05:55 2022 -0500
@@ -52,17 +52,6 @@
 #include "xmlnode.h"
 
 /**
- * PurpleAccountRequestType:
- * @PURPLE_ACCOUNT_REQUEST_AUTHORIZATION: Account authorization request
- *
- * Account request types.
- */
-typedef enum
-{
-	PURPLE_ACCOUNT_REQUEST_AUTHORIZATION = 0
-} PurpleAccountRequestType;
-
-/**
  * PurpleAccountRequestResponse:
  * @PURPLE_ACCOUNT_RESPONSE_IGNORE: Silently ignore the request.
  * @PURPLE_ACCOUNT_RESPONSE_DENY: Block the request potentially informing the
@@ -233,30 +222,6 @@
                               const char *message);
 
 /**
- * purple_account_request_authorization:
- * @account:      The account that was added
- * @remote_user:  The name of the user that added this account.
- * @id:           The optional ID of the local account. Rarely used.
- * @alias:        The optional alias of the remote user.
- * @message:      The optional message sent by the user wanting to add you.
- * @on_list:      Is the remote user already on the buddy list?
- * @auth_cb:      (scope call): The callback called when the local user accepts
- * @deny_cb:      (scope call): The callback called when the local user rejects
- * @user_data:    Data to be passed back to the above callbacks
- *
- * Notifies the user that a remote user has wants to add the local user
- * to his or her buddy list and requires authorization to do so.
- *
- * This will present a dialog informing the user of this and ask if the
- * user authorizes or denies the remote user from adding him.
- *
- * Returns: A UI-specific handle.
- */
-void *purple_account_request_authorization(PurpleAccount *account, const char *remote_user,
-					const char *id, const char *alias, const char *message, gboolean on_list,
-					PurpleAccountRequestAuthorizationCb auth_cb, PurpleAccountRequestAuthorizationCb deny_cb, void *user_data);
-
-/**
  * purple_account_request_close_with_account:
  * @account:	   The account for which requests should be closed
  *
--- a/libpurple/accounts.h	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/accounts.h	Mon Aug 22 22:05:55 2022 -0500
@@ -38,13 +38,6 @@
  * @status_changed:        This account's status changed.
  * @request_add:           Someone we don't have on our list added us; prompt
  *                         to add them.
- * @request_authorize:     Prompt for authorization when someone adds this
- *                         account to their buddy list.  To authorize them to
- *                         see this account's presence, call
- *                         @authorize_cb (@message, @user_data) otherwise call
- *                         @deny_cb (@message, @user_data).
- *                         <sbr/>Returns: A UI-specific handle, as passed to
- *                         @close_account_request.
  * @close_account_request: Close a pending request for authorization.
  *                         @ui_handle is a handle as returned by
  *                         @request_authorize.
@@ -69,16 +62,6 @@
 	                    const char *alias,
 	                    const char *message);
 
-	void *(*request_authorize)(PurpleAccount *account,
-	                           const char *remote_user,
-	                           const char *id,
-	                           const char *alias,
-	                           const char *message,
-	                           gboolean on_list,
-	                           PurpleAccountRequestAuthorizationCb authorize_cb,
-	                           PurpleAccountRequestAuthorizationCb deny_cb,
-	                           void *user_data);
-
 	void (*close_account_request)(void *ui_handle);
 
 	void (*permit_added)(PurpleAccount *account, const char *name);
--- a/libpurple/meson.build	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/meson.build	Mon Aug 22 22:05:55 2022 -0500
@@ -41,6 +41,7 @@
 	'purpleaccountpresence.c',
 	'purpleaccountusersplit.c',
 	'purpleattachment.c',
+	'purpleauthorizationrequest.c',
 	'purplebuddypresence.c',
 	'purplechatconversation.c',
 	'purplechatuser.c',
@@ -141,6 +142,7 @@
 	'purpleaccountoption.h',
 	'purpleaccountpresence.h',
 	'purpleaccountusersplit.h',
+	'purpleauthorizationrequest.h',
 	'purplebuddypresence.h',
 	'purplechatconversation.h',
 	'purplechatuser.h',
--- a/libpurple/protocols/jabber/presence.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/protocols/jabber/presence.c	Mon Aug 22 22:05:55 2022 -0500
@@ -346,7 +346,9 @@
 	char *who;
 };
 
-static void authorize_add_cb(const char *message, gpointer data)
+static void
+authorize_add_cb(G_GNUC_UNUSED PurpleAuthorizationRequest *request,
+                 gpointer data)
 {
 	struct _jabber_add_permit *jap = data;
 
@@ -359,7 +361,9 @@
 	g_free(jap);
 }
 
-static void deny_add_cb(const char *message, gpointer data)
+static void
+deny_add_cb(G_GNUC_UNUSED PurpleAuthorizationRequest *request,
+            const char *message, gpointer data)
 {
 	struct _jabber_add_permit *jap = data;
 
@@ -927,6 +931,9 @@
 		struct _jabber_add_permit *jap = g_new0(struct _jabber_add_permit, 1);
 		gboolean onlist = FALSE;
 		PurpleAccount *account;
+		PurpleAuthorizationRequest *request = NULL;
+		PurpleNotification *notification = NULL;
+		PurpleNotificationManager *manager = NULL;
 		PurpleBuddy *buddy;
 		PurpleXmlNode *nick;
 
@@ -945,8 +952,18 @@
 		jap->who = g_strdup(presence.from);
 		jap->js = js;
 
-		purple_account_request_authorization(account, presence.from, NULL, presence.nickname,
-				NULL, onlist, authorize_add_cb, deny_add_cb, jap);
+		request = purple_authorization_request_new(account, presence.from);
+		purple_authorization_request_set_alias(request, presence.nickname);
+		purple_authorization_request_set_add(request, !onlist);
+		g_signal_connect(request, "accepted", G_CALLBACK(authorize_add_cb),
+		                 jap);
+		g_signal_connect(request, "denied", G_CALLBACK(deny_add_cb), jap);
+
+		notification = purple_notification_new_from_authorization_request(request);
+		manager = purple_notification_manager_get_default();
+		purple_notification_manager_add(manager, notification);
+
+		g_object_unref(notification);
 
 		goto out;
 	} else if (presence.type == JABBER_PRESENCE_SUBSCRIBED) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpleauthorizationrequest.c	Mon Aug 22 22:05:55 2022 -0500
@@ -0,0 +1,409 @@
+/*
+ * 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 "purpleauthorizationrequest.h"
+
+struct _PurpleAuthorizationRequest {
+	GObject parent;
+
+	PurpleAccount *account;
+
+	gchar *username;
+	gchar *alias;
+	gchar *message;
+	gboolean add;
+
+	/* This tracks whether _accept or _deny have been called. */
+	gboolean handled;
+};
+
+enum {
+	SIG_ACCEPTED,
+	SIG_DENIED,
+	N_SIGNALS
+};
+static guint signals[N_SIGNALS] = {0, };
+
+enum {
+	PROP_0,
+	PROP_ACCOUNT,
+	PROP_USERNAME,
+	PROP_ALIAS,
+	PROP_MESSAGE,
+	PROP_ADD,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+G_DEFINE_TYPE(PurpleAuthorizationRequest, purple_authorization_request,
+              G_TYPE_OBJECT)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_authorization_request_set_account(PurpleAuthorizationRequest *request,
+                                         PurpleAccount *account)
+{
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_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_authorization_request_set_username(PurpleAuthorizationRequest *request,
+                                          const gchar *username)
+{
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_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_authorization_request_get_property(GObject *obj, guint param_id,
+                                          GValue *value, GParamSpec *pspec)
+{
+	PurpleAuthorizationRequest *request = PURPLE_AUTHORIZATION_REQUEST(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			g_value_set_object(value,
+			                   purple_authorization_request_get_account(request));
+			break;
+		case PROP_USERNAME:
+			g_value_set_string(value,
+			                   purple_authorization_request_get_username(request));
+			break;
+		case PROP_ALIAS:
+			g_value_set_string(value,
+			                   purple_authorization_request_get_alias(request));
+			break;
+		case PROP_MESSAGE:
+			g_value_set_string(value,
+			                   purple_authorization_request_get_message(request));
+			break;
+		case PROP_ADD:
+			g_value_set_boolean(value,
+			                    purple_authorization_request_get_add(request));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_authorization_request_set_property(GObject *obj, guint param_id,
+                                          const GValue *value,
+                                          GParamSpec *pspec)
+{
+	PurpleAuthorizationRequest *request = PURPLE_AUTHORIZATION_REQUEST(obj);
+
+	switch(param_id) {
+		case PROP_ACCOUNT:
+			purple_authorization_request_set_account(request,
+			                                         g_value_get_object(value));
+			break;
+		case PROP_USERNAME:
+			purple_authorization_request_set_username(request,
+			                                          g_value_get_string(value));
+			break;
+		case PROP_ALIAS:
+			purple_authorization_request_set_alias(request,
+			                                       g_value_get_string(value));
+			break;
+		case PROP_MESSAGE:
+			purple_authorization_request_set_message(request,
+			                                         g_value_get_string(value));
+			break;
+		case PROP_ADD:
+			purple_authorization_request_set_add(request,
+			                                     g_value_get_boolean(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_authorization_request_dispose(GObject *obj) {
+	PurpleAuthorizationRequest *request = PURPLE_AUTHORIZATION_REQUEST(obj);
+
+	g_clear_object(&request->account);
+
+	G_OBJECT_CLASS(purple_authorization_request_parent_class)->dispose(obj);
+}
+
+static void
+purple_authorization_request_finalize(GObject *obj) {
+	PurpleAuthorizationRequest *request = PURPLE_AUTHORIZATION_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_authorization_request_parent_class)->finalize(obj);
+}
+
+static void
+purple_authorization_request_init(PurpleAuthorizationRequest *request) {
+}
+
+static void
+purple_authorization_request_class_init(PurpleAuthorizationRequestClass *klass)
+{
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = purple_authorization_request_get_property;
+	obj_class->set_property = purple_authorization_request_set_property;
+	obj_class->dispose = purple_authorization_request_dispose;
+	obj_class->finalize = purple_authorization_request_finalize;
+
+	/**
+	 * PurpleAuthorizationRequest::accepted:
+	 * @request: The [class@AuthorizationRequest] instance.
+	 *
+	 * Emitted when the user has accepted @request. This is typically emitted
+	 * by the user interface calling [method@AuthorizationRequest.accept].
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_ACCEPTED] = g_signal_new_class_handler(
+		"accepted",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		0);
+
+	/**
+	 * PurpleAuthorizationRequest::denied:
+	 * @request: The [class@AuthorizationRequest] instance.
+	 * @message: (nullable): An optional denial message.
+	 *
+	 * Emitted when the user has denied @request. This is typically emitted
+	 * by the user interface calling [method@AuthorizationRequest.deny].
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_DENIED] = g_signal_new_class_handler(
+		"denied",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		1,
+		G_TYPE_STRING);
+
+	/**
+	 * PurpleAuthorizationRequest:account: (attributes org.gtk.Property.get=purple_authorization_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);
+
+	/**
+	 * PurpleAuthorizationRequest:username: (attributes org.gtk.Property.get=purple_authorization_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);
+
+	/**
+	 * PurpleAuthorizationRequest:alias: (attributes org.gtk.Property.get=purple_authorization_request_get_alias org.gtk.Property.set=purple_authorization_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);
+
+	/**
+	 * PurpleAuthorizationRequest:message: (attributes org.gtk.Property.get=purple_authorization_request_get_message org.gtk.Property.set=purple_authorization_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);
+
+	/**
+	 * PurpleAuthorizationRequest:add:
+	 *
+	 * Whether or not the user interface should ask the end user to add the
+	 * remote user after accepting the end user's friend request.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ADD] = g_param_spec_boolean(
+		"add", "add",
+		"Whether or not to add the remote user back",
+		FALSE,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleAuthorizationRequest *
+purple_authorization_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_AUTHORIZATION_REQUEST,
+		"account", account,
+		"username", username,
+		NULL);
+}
+
+PurpleAccount *
+purple_authorization_request_get_account(PurpleAuthorizationRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(request), NULL);
+
+	return request->account;
+}
+
+const gchar *
+purple_authorization_request_get_username(PurpleAuthorizationRequest *request)
+{
+	g_return_val_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(request), NULL);
+
+	return request->username;
+}
+
+void
+purple_authorization_request_set_alias(PurpleAuthorizationRequest *request,
+                                       const gchar *alias)
+{
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_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_authorization_request_get_alias(PurpleAuthorizationRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(request), NULL);
+
+	return request->alias;
+}
+
+void
+purple_authorization_request_set_message(PurpleAuthorizationRequest *request,
+                                         const gchar *message)
+{
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_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_authorization_request_get_message(PurpleAuthorizationRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(request), NULL);
+
+	return request->message;
+}
+
+void
+purple_authorization_request_set_add(PurpleAuthorizationRequest *request,
+                                     gboolean add)
+{
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+	request->add = add;
+
+	g_object_notify_by_pspec(G_OBJECT(request), properties[PROP_ADD]);
+}
+
+gboolean
+purple_authorization_request_get_add(PurpleAuthorizationRequest *request) {
+	g_return_val_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(request), FALSE);
+
+	return request->add;
+}
+
+void
+purple_authorization_request_accept(PurpleAuthorizationRequest *request) {
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_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_ACCEPTED], 0);
+}
+
+void
+purple_authorization_request_deny(PurpleAuthorizationRequest *request,
+                                  const gchar *message)
+{
+	g_return_if_fail(PURPLE_IS_AUTHORIZATION_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_DENIED], 0, message);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpleauthorizationrequest.h	Mon Aug 22 22:05:55 2022 -0500
@@ -0,0 +1,192 @@
+/*
+ * 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_AUTHORIZATION_REQUEST_H
+#define PURPLE_AUTHORIZATION_REQUEST_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "account.h"
+
+G_BEGIN_DECLS
+
+/**
+ * PurpleAuthorizationRequest:
+ *
+ * #PurpleAuthorizationRequest is a data structure that contains all of the
+ * information when someone has requested authorization to add you to their
+ * contact list.
+ *
+ * Since: 3.0.0
+ */
+
+#define PURPLE_TYPE_AUTHORIZATION_REQUEST (purple_authorization_request_get_type())
+
+G_DECLARE_FINAL_TYPE(PurpleAuthorizationRequest, purple_authorization_request,
+                     PURPLE, AUTHORIZATION_REQUEST, GObject)
+
+/**
+ * purple_authorization_request_new:
+ * @account: The account that this request is for.
+ * @username: The username of the user requesting authorization.
+ *
+ * Creates a new [class@Purple.AuthorizationRequest] for @username on @account.
+ *
+ * This is typically only used by libpurple itself.
+ *
+ * Returns: The new instance.
+ *
+ * Since: 3.0.0
+ */
+PurpleAuthorizationRequest *purple_authorization_request_new(PurpleAccount *account, const gchar *username);
+
+/**
+ * purple_authorization_request_get_account:
+ * @request: The instance.
+ *
+ * Gets the [class@Account] for @request.
+ *
+ * Returns: (transfer none): The account.
+ *
+ * Since: 3.0.0
+ */
+PurpleAccount *purple_authorization_request_get_account(PurpleAuthorizationRequest *request);
+
+/**
+ * purple_authorization_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_authorization_request_get_username(PurpleAuthorizationRequest *request);
+
+/**
+ * purple_authorization_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_authorization_request_set_alias(PurpleAuthorizationRequest *request, const gchar *alias);
+
+/**
+ * purple_authorization_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_authorization_request_get_alias(PurpleAuthorizationRequest *request);
+
+/**
+ * purple_authorization_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_authorization_request_set_message(PurpleAuthorizationRequest *request, const gchar *message);
+
+/**
+ * purple_authorization_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_authorization_request_get_message(PurpleAuthorizationRequest *request);
+
+/**
+ * purple_authorization_request_set_add:
+ * @request: The instance.
+ * @add: Whether or not to ask the user to add the remote user back.
+ *
+ * Sets whether or not the user interface should ask the end user to add the
+ * remote user if the remote user was accepted.
+ *
+ * Since: 3.0.0
+ */
+void purple_authorization_request_set_add(PurpleAuthorizationRequest *request, gboolean add);
+
+/**
+ * purple_authorization_request_get_add:
+ * @request: The instance.
+ *
+ * Gets whether or not the user interface should ask the end user to add the
+ * remote user if the end user accepted the remote user's friend request.
+ *
+ * Returns: %TRUE if the user interface should request the end user to add the
+ *          remote user back.
+ *
+ * Since: 3.0.0
+ */
+gboolean purple_authorization_request_get_add(PurpleAuthorizationRequest *request);
+
+/**
+ * purple_authorization_request_accept:
+ * @request: The instance.
+ *
+ * Emits the [signal@AuthorizationRequest::accepted] signal. This is typically
+ * called by the user interface when the user has clicked the accept button.
+ *
+ * If this is called multiple times, or called after
+ * [method@AuthorizationRequest.deny] then this does nothing.
+ *
+ * Since: 3.0.0
+ */
+void purple_authorization_request_accept(PurpleAuthorizationRequest *request);
+
+/**
+ * purple_authorization_request_deny:
+ * @request: The instance.
+ * @message: (nullable): An optional denial message.
+ *
+ * Emits the [signal@AuthorizationRequest::denied] signal. This is typically
+ * called by the user interface when the user has clicked the deny button.
+ *
+ * If this is called multiple times, or called after
+ * [method@AuthorizationRequest.accept] then this does nothing.
+ *
+ * Since: 3.0.0
+ */
+void purple_authorization_request_deny(PurpleAuthorizationRequest *request, const gchar *message);
+
+G_END_DECLS
+
+#endif /* PURPLE_AUTHORIZATION_REQUEST */
--- a/libpurple/purplenotification.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/purplenotification.c	Mon Aug 22 22:05:55 2022 -0500
@@ -16,6 +16,8 @@
  * License along with this library; if not, see <https://www.gnu.org/licenses/>.
  */
 
+#include <glib/gi18n-lib.h>
+
 #include "purplenotification.h"
 
 #include "purpleenums.h"
@@ -35,9 +37,17 @@
 
 	gpointer data;
 	GDestroyNotify data_destroy_func;
+
+	gboolean deleted;
 };
 
 enum {
+	SIG_DELETED,
+	N_SIGNALS
+};
+static guint signals[N_SIGNALS] = {0, };
+
+enum {
 	PROP_0,
 	PROP_ID,
 	PROP_TYPE,
@@ -256,7 +266,27 @@
 	obj_class->finalize = purple_notification_finalize;
 
 	/**
-	 * PurpleNotification::id:
+	 * PurpleNotification::deleted:
+	 * @notification: The instance.
+	 *
+	 * Emitted when the notification is deleted. This is typically done by a
+	 * user interface calling [method@PurpleNotification.delete].
+	 *
+	 * Since: 3.0.0
+	 */
+	signals[SIG_DELETED] = g_signal_new_class_handler(
+		"deleted",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		0);
+
+	/**
+	 * PurpleNotification:id:
 	 *
 	 * The ID of the notification. Used for things that need to address it.
 	 * This is auto populated at creation time.
@@ -271,7 +301,7 @@
 	);
 
 	/**
-	 * PurpleNotification::type:
+	 * PurpleNotification:type:
 	 *
 	 * The [enum@NotificationType] of this notification.
 	 *
@@ -285,7 +315,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::account:
+	 * PurpleNotification:account:
 	 *
 	 * An optional [class@Account] that this notification is for.
 	 *
@@ -298,7 +328,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::created-timestamp:
+	 * PurpleNotification:created-timestamp:
 	 *
 	 * The creation time of this notification. This always represented as UTC
 	 * internally, and will be set to UTC now by default.
@@ -312,7 +342,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::title:
+	 * PurpleNotification:title:
 	 *
 	 * An optional title for this notification. A user interface may or may not
 	 * choose to use this when displaying the notification. Regardless, this
@@ -327,7 +357,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::icon-name:
+	 * PurpleNotification:icon-name:
 	 *
 	 * The icon-name in the icon theme to use for the notification. A user
 	 * interface may or may not choose to use this when display the
@@ -342,7 +372,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::read:
+	 * PurpleNotification:read:
 	 *
 	 * Whether or not the notification has been read.
 	 *
@@ -355,7 +385,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::interactive:
+	 * PurpleNotification:interactive:
 	 *
 	 * Whether or not the notification can be interacted with.
 	 *
@@ -368,7 +398,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::data:
+	 * PurpleNotification:data:
 	 *
 	 * Data specific to the [enum@NotificationType] for the notification.
 	 *
@@ -380,10 +410,9 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	/**
-	 * PurpleNotification::data-destroy-func:
+	 * PurpleNotification:data-destroy-func:
 	 *
-	 * A [func@GLib.DestroyFunc] to call to free
-	 * [property@PurpleNotification:data].
+	 * A function to call to free [property@PurpleNotification:data].
 	 *
 	 * Since: 3.0.0
 	 */
@@ -393,6 +422,7 @@
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
 }
 
 /******************************************************************************
@@ -410,6 +440,42 @@
 	                    NULL);
 }
 
+PurpleNotification *
+purple_notification_new_from_authorization_request(PurpleAuthorizationRequest *authorization_request)
+{
+	PurpleAccount *account = NULL;
+	PurpleNotification *notification = NULL;
+	gchar *title = NULL;
+	const gchar *alias = NULL, *username = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_AUTHORIZATION_REQUEST(authorization_request),
+	                     NULL);
+
+	account = purple_authorization_request_get_account(authorization_request);
+	notification = purple_notification_new(PURPLE_NOTIFICATION_TYPE_AUTHORIZATION_REQUEST,
+	                                       account, authorization_request,
+	                                       g_object_unref);
+
+	username = purple_authorization_request_get_username(authorization_request);
+	alias = purple_authorization_request_get_alias(authorization_request);
+
+	if(alias != NULL && *alias != '\0') {
+		title = g_strdup_printf(_("%s (%s) would like to add %s to their"
+		                          " contact list"),
+		                        alias, username,
+		                        purple_account_get_username(account));
+	} else {
+		title = g_strdup_printf(_("%s would like to add %s to their contact"
+		                          " list"),
+		                        username, purple_account_get_username(account));
+	}
+
+	purple_notification_set_title(notification, title);
+	g_free(title);
+
+	return notification;
+}
+
 const gchar *
 purple_notification_get_id(PurpleNotification *notification) {
 	g_return_val_if_fail(PURPLE_IS_NOTIFICATION(notification), NULL);
@@ -565,3 +631,15 @@
 	return g_date_time_compare(notification_a->created_timestamp,
 	                           notification_b->created_timestamp);
 }
+
+void
+purple_notification_delete(PurpleNotification *notification) {
+	g_return_if_fail(PURPLE_IS_NOTIFICATION(notification));
+
+	/* Calling this multiple times is a programming error. */
+	g_return_if_fail(notification->deleted == FALSE);
+
+	notification->deleted = TRUE;
+
+	g_signal_emit(notification, signals[SIG_DELETED], 0);
+}
--- a/libpurple/purplenotification.h	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/purplenotification.h	Mon Aug 22 22:05:55 2022 -0500
@@ -27,6 +27,7 @@
 #include <glib-object.h>
 
 #include "account.h"
+#include "purpleauthorizationrequest.h"
 
 G_BEGIN_DECLS
 
@@ -39,7 +40,7 @@
     PURPLE_NOTIFICATION_TYPE_UNKNOWN,
     PURPLE_NOTIFICATION_TYPE_GENERIC,
     PURPLE_NOTIFICATION_TYPE_CONNECTION_ERROR,
-    PURPLE_NOTIFICATION_TYPE_CONTACT_AUTHORIZATION,
+    PURPLE_NOTIFICATION_TYPE_AUTHORIZATION_REQUEST,
     PURPLE_NOTIFICATION_TYPE_FILE_TRANSFER,
     PURPLE_NOTIFICATION_TYPE_CHAT_INVITE,
     PURPLE_NOTIFICATION_TYPE_MENTION,
@@ -78,6 +79,21 @@
 PurpleNotification *purple_notification_new(PurpleNotificationType type, PurpleAccount *account, gpointer data, GDestroyNotify data_destroy_func);
 
 /**
+ * purple_notification_new_from_authorization_request:
+ * @authorization_request: (transfer full): The [class@AuthorizationRequest]
+ *                         instance.
+ *
+ * Creates a new [class@PurpleNotification] for the @authorization_request. This
+ * helper will automatically fill out the notification according to the
+ * information in @authorization_request.
+ *
+ * Returns: (transfer full): The new notification.
+ *
+ * Since: 3.0.0
+ */
+PurpleNotification *purple_notification_new_from_authorization_request(PurpleAuthorizationRequest *authorization_request);
+
+/**
  * purple_notification_get_id:
  * @notification: The instance.
  *
@@ -260,6 +276,20 @@
  */
 gint purple_notification_compare(gconstpointer a, gconstpointer b);
 
+/**
+ * purple_notification_delete:
+ * @notification: The instance.
+ *
+ * Emits the [signal@PurpleNotification::deleted] signal. This is typically
+ * called by a user interface when the user has deleted a notification.
+ *
+ * If this is called more than once for @notification, the signal will not be
+ * emitted.
+ *
+ * Since: 3.0.0
+ */
+void purple_notification_delete(PurpleNotification *notification);
+
 G_END_DECLS
 
 #endif /* PURPLE_NOTIFICATION */
--- a/libpurple/tests/meson.build	Mon Aug 22 21:49:41 2022 -0500
+++ b/libpurple/tests/meson.build	Mon Aug 22 22:05:55 2022 -0500
@@ -1,6 +1,7 @@
 PROGS = [
     'account_option',
     'account_manager',
+    'authorization_request',
     'circular_buffer',
     'credential_manager',
     'credential_provider',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_authorization_request.c	Mon Aug 22 22:05:55 2022 -0500
@@ -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 <glib.h>
+
+#include <purple.h>
+
+#include "test_ui.h"
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+test_purple_authorization_request_accepted_counter_cb(G_GNUC_UNUSED PurpleAuthorizationRequest *request,
+                                                      gpointer data)
+{
+	gint *counter = data;
+
+	*counter = *counter + 1;
+}
+
+static void
+test_purple_authorization_request_denied_counter_cb(G_GNUC_UNUSED PurpleAuthorizationRequest *request,
+                                                    G_GNUC_UNUSED const gchar *message,
+                                                    gpointer data)
+{
+	gint *counter = data;
+
+	*counter = *counter + 1;
+}
+
+static void
+test_purple_authorization_request_denied_message_cb(G_GNUC_UNUSED PurpleAuthorizationRequest *request,
+                                                    const gchar *message,
+                                                    gpointer data)
+{
+	gchar *expected = data;
+
+	g_assert_cmpstr(message, ==, expected);
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_authorization_request_new(void) {
+	PurpleAccount *account1 = NULL, *account2 = NULL;
+	PurpleAuthorizationRequest *request = NULL;
+	const gchar *username = NULL;
+
+	account1 = purple_account_new("test", "test");
+
+	request = purple_authorization_request_new(account1, "remote-username");
+
+	/* Make sure we got a valid authorization request. */
+	g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+	/* Verify the account is set properly. */
+	account2 = purple_authorization_request_get_account(request);
+	g_assert_nonnull(account2);
+	g_assert_true(account1 == account2);
+
+	/* Verify the username set properly. */
+	username = purple_authorization_request_get_username(request);
+	g_assert_cmpstr(username, ==, "remote-username");
+
+	/* Unref it to destroy it. */
+	g_clear_object(&request);
+
+	/* Clean up the account. */
+	g_clear_object(&account1);
+}
+
+static void
+test_purple_authorization_request_properties(void) {
+	PurpleAccount *account = NULL;
+	PurpleAuthorizationRequest *request = NULL;
+
+	account = purple_account_new("test", "test");
+	request = purple_authorization_request_new(account, "username");
+
+	g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+	/* Verify the alias property works and is nullable. */
+	purple_authorization_request_set_alias(request, "alias");
+	g_assert_cmpstr(purple_authorization_request_get_alias(request), ==,
+	                "alias");
+	purple_authorization_request_set_alias(request, NULL);
+	g_assert_null(purple_authorization_request_get_alias(request));
+
+	/* Verify the message property works and is nullable. */
+	purple_authorization_request_set_message(request, "message");
+	g_assert_cmpstr(purple_authorization_request_get_message(request), ==,
+	                "message");
+	purple_authorization_request_set_message(request, NULL);
+	g_assert_null(purple_authorization_request_get_message(request));
+
+	/* Cleanup. */
+	g_clear_object(&request);
+	g_clear_object(&account);
+}
+
+static void
+test_purple_authorization_request_accept(void) {
+	if(g_test_subprocess()) {
+		PurpleAccount *account = NULL;
+		PurpleAuthorizationRequest *request = NULL;
+		gint counter = 0;
+
+		account = purple_account_new("test", "test");
+		request = purple_authorization_request_new(account, "username");
+
+		g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+		g_signal_connect(request, "accepted",
+		                 G_CALLBACK(test_purple_authorization_request_accepted_counter_cb),
+		                 &counter);
+
+		/* Accept the request and verify that the callback was called. */
+		purple_authorization_request_accept(request);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Accept the request again to trigger the critical. */
+		purple_authorization_request_accept(request);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Cleanup. */
+		g_clear_object(&account);
+		g_clear_object(&request);
+	}
+
+	g_test_trap_subprocess(NULL, 0, 0);
+	g_test_trap_assert_stderr("*Purple-CRITICAL*request->handled*failed*");
+}
+
+static void
+test_purple_authorization_request_accept_deny(void) {
+	if(g_test_subprocess()) {
+		PurpleAccount *account = NULL;
+		PurpleAuthorizationRequest *request = NULL;
+		gint counter = 0;
+
+		account = purple_account_new("test", "test");
+		request = purple_authorization_request_new(account, "username");
+
+		g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+		g_signal_connect(request, "accepted",
+		                 G_CALLBACK(test_purple_authorization_request_accepted_counter_cb),
+		                 &counter);
+		g_signal_connect(request, "denied",
+		                 G_CALLBACK(test_purple_authorization_request_denied_counter_cb),
+		                 &counter);
+
+		/* Accept the request and verify that the callback was called. */
+		purple_authorization_request_accept(request);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Deny the request to trigger the critical. */
+		purple_authorization_request_deny(request, NULL);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Cleanup. */
+		g_clear_object(&account);
+		g_clear_object(&request);
+	}
+
+	g_test_trap_subprocess(NULL, 0, 0);
+	g_test_trap_assert_stderr("*Purple-CRITICAL*request->handled*failed*");
+}
+
+static void
+test_purple_authorization_request_deny(void) {
+	if(g_test_subprocess()) {
+		PurpleAccount *account = NULL;
+		PurpleAuthorizationRequest *request = NULL;
+		gint counter = 0;
+
+		account = purple_account_new("test", "test");
+		request = purple_authorization_request_new(account, "username");
+
+		g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+		g_signal_connect(request, "denied",
+		                 G_CALLBACK(test_purple_authorization_request_denied_counter_cb),
+		                 &counter);
+
+		/* Deny the request and verify that the callback was called. */
+		purple_authorization_request_deny(request, NULL);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Deny the request again to trigger the critical. */
+		purple_authorization_request_deny(request, NULL);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Cleanup. */
+		g_clear_object(&account);
+		g_clear_object(&request);
+	}
+
+	g_test_trap_subprocess(NULL, 0, 0);
+	g_test_trap_assert_stderr("*Purple-CRITICAL*request->handled*failed*");
+}
+
+static void
+test_purple_authorization_request_deny_accept(void) {
+	if(g_test_subprocess()) {
+		PurpleAccount *account = NULL;
+		PurpleAuthorizationRequest *request = NULL;
+		gint counter = 0;
+
+		account = purple_account_new("test", "test");
+		request = purple_authorization_request_new(account, "username");
+
+		g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+		g_signal_connect(request, "denied",
+		                 G_CALLBACK(test_purple_authorization_request_denied_counter_cb),
+		                 &counter);
+		g_signal_connect(request, "accepted",
+		                 G_CALLBACK(test_purple_authorization_request_accepted_counter_cb),
+		                 &counter);
+
+		/* Deny the request and verify that the callback was called. */
+		purple_authorization_request_deny(request, NULL);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Deny the request again to trigger the critical. */
+		purple_authorization_request_accept(request);
+		g_assert_cmpint(counter, ==, 1);
+
+		/* Cleanup. */
+		g_clear_object(&account);
+		g_clear_object(&request);
+	}
+
+	g_test_trap_subprocess(NULL, 0, 0);
+	g_test_trap_assert_stderr("*Purple-CRITICAL*request->handled*failed*");
+}
+
+static void
+test_purple_authorization_request_deny_message_null(void) {
+	PurpleAccount *account = NULL;
+	PurpleAuthorizationRequest *request = NULL;
+
+	account = purple_account_new("test", "test");
+	request = purple_authorization_request_new(account, "username");
+
+	g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+	g_signal_connect(request, "denied",
+	                 G_CALLBACK(test_purple_authorization_request_denied_message_cb),
+	                 NULL);
+
+	/* Deny the request the signal handler is expecting a message of NULL. */
+	purple_authorization_request_deny(request, NULL);
+
+	/* Cleanup. */
+	g_clear_object(&account);
+	g_clear_object(&request);
+}
+
+static void
+test_purple_authorization_request_deny_message_non_null(void) {
+	PurpleAccount *account = NULL;
+	PurpleAuthorizationRequest *request = NULL;
+
+	account = purple_account_new("test", "test");
+	request = purple_authorization_request_new(account, "username");
+
+	g_assert_true(PURPLE_IS_AUTHORIZATION_REQUEST(request));
+
+	g_signal_connect(request, "denied",
+	                 G_CALLBACK(test_purple_authorization_request_denied_message_cb),
+	                 "this is a message");
+
+	/* Deny the request the signal handler is expecting the above message. */
+	purple_authorization_request_deny(request, "this is a message");
+
+	/* Cleanup. */
+	g_clear_object(&account);
+	g_clear_object(&request);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	test_ui_purple_init();
+
+	g_test_add_func("/request-authorization/new",
+	                test_purple_authorization_request_new);
+	g_test_add_func("/request-authorization/properties",
+	                test_purple_authorization_request_properties);
+
+	g_test_add_func("/request-authorization/accept",
+	                test_purple_authorization_request_accept);
+	g_test_add_func("/request-authorization/accept-deny",
+	                test_purple_authorization_request_accept_deny);
+	g_test_add_func("/request-authorization/deny",
+	                test_purple_authorization_request_deny);
+	g_test_add_func("/request-authorization/deny-accept",
+	                test_purple_authorization_request_deny_accept);
+
+	g_test_add_func("/request-authorization/deny-message/null",
+	                test_purple_authorization_request_deny_message_null);
+	g_test_add_func("/request-authorization/deny-message/non-null",
+	                test_purple_authorization_request_deny_message_non_null);
+
+	return g_test_run();
+}
--- a/pidgin/gtkaccount.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/pidgin/gtkaccount.c	Mon Aug 22 22:05:55 2022 -0500
@@ -1355,214 +1355,6 @@
 	g_free(buffer);
 }
 
-struct auth_request
-{
-	PurpleAccountRequestAuthorizationCb auth_cb;
-	PurpleAccountRequestAuthorizationCb deny_cb;
-	void *data;
-	char *username;
-	char *alias;
-	PurpleAccount *account;
-	gboolean add_buddy_after_auth;
-};
-
-static void
-free_auth_request(struct auth_request *ar)
-{
-	g_free(ar->username);
-	g_free(ar->alias);
-	g_free(ar);
-}
-
-static void
-authorize_and_add_cb(struct auth_request *ar, const char *message)
-{
-	ar->auth_cb(message, ar->data);
-	if (ar->add_buddy_after_auth) {
-		purple_blist_request_add_buddy(ar->account, ar->username, NULL, ar->alias);
-	}
-}
-
-static void
-authorize_noreason_cb(struct auth_request *ar)
-{
-	authorize_and_add_cb(ar, NULL);
-}
-
-static void
-authorize_reason_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
-                    G_GNUC_UNUSED GtkButton *button, gpointer user_data)
-{
-	struct auth_request *ar = user_data;
-	PurpleProtocol *protocol = purple_account_get_protocol(ar->account);
-
-	if (protocol && (purple_protocol_get_options(protocol) & OPT_PROTO_AUTHORIZATION_GRANTED_MESSAGE)) {
-		/* Duplicate information because ar is freed by closing minidialog */
-		struct auth_request *aa = g_new0(struct auth_request, 1);
-		aa->auth_cb = ar->auth_cb;
-		aa->deny_cb = ar->deny_cb;
-		aa->data = ar->data;
-		aa->account = ar->account;
-		aa->username = g_strdup(ar->username);
-		aa->alias = g_strdup(ar->alias);
-		aa->add_buddy_after_auth = ar->add_buddy_after_auth;
-		purple_request_input(ar->account, NULL, _("Authorization acceptance message:"),
-		                     NULL, _("No reason given."), TRUE, FALSE, NULL,
-		                     _("OK"), G_CALLBACK(authorize_and_add_cb),
-		                     _("Cancel"), G_CALLBACK(authorize_noreason_cb),
-		                     purple_request_cpar_from_account(ar->account),
-		                     aa);
-		/* FIXME: aa is going to leak now. */
-	} else {
-		authorize_noreason_cb(ar);
-	}
-}
-
-static void
-deny_no_add_cb(struct auth_request *ar, const char *message)
-{
-	ar->deny_cb(message, ar->data);
-}
-
-static void
-deny_noreason_cb(struct auth_request *ar)
-{
-	ar->deny_cb(NULL, ar->data);
-}
-
-static void
-deny_reason_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
-               G_GNUC_UNUSED GtkButton *button, gpointer user_data)
-{
-	struct auth_request *ar = user_data;
-	PurpleProtocol *protocol = purple_account_get_protocol(ar->account);
-
-	if (protocol && (purple_protocol_get_options(protocol) & OPT_PROTO_AUTHORIZATION_DENIED_MESSAGE)) {
-		/* Duplicate information because ar is freed by closing minidialog */
-		struct auth_request *aa = g_new0(struct auth_request, 1);
-		aa->auth_cb = ar->auth_cb;
-		aa->deny_cb = ar->deny_cb;
-		aa->data = ar->data;
-		aa->add_buddy_after_auth = ar->add_buddy_after_auth;
-		purple_request_input(ar->account, NULL, _("Authorization denied message:"),
-		                     NULL, _("No reason given."), TRUE, FALSE, NULL,
-		                     _("OK"), G_CALLBACK(deny_no_add_cb),
-		                     _("Cancel"), G_CALLBACK(deny_noreason_cb),
-		                     purple_request_cpar_from_account(ar->account),
-		                     aa);
-		/* FIXME: aa is going to leak now. */
-	} else {
-		deny_noreason_cb(ar);
-	}
-}
-
-static gboolean
-get_user_info_cb(GtkWidget   *label,
-                 const gchar *uri,
-                 gpointer     data)
-{
-	struct auth_request *ar = data;
-	if (purple_strequal(uri, "viewinfo")) {
-		pidgin_retrieve_user_info(purple_account_get_connection(ar->account), ar->username);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-static void
-send_im_cb(G_GNUC_UNUSED PidginMiniDialog *mini_dialog,
-           G_GNUC_UNUSED GtkButton *button,
-           gpointer data)
-{
-	struct auth_request *ar = data;
-	pidgin_dialogs_im_with_user(ar->account, ar->username);
-}
-
-static void *
-pidgin_accounts_request_authorization(PurpleAccount *account,
-                                      const char *remote_user,
-                                      const char *id,
-                                      const char *alias,
-                                      const char *message,
-                                      gboolean on_list,
-                                      PurpleAccountRequestAuthorizationCb auth_cb,
-                                      PurpleAccountRequestAuthorizationCb deny_cb,
-                                      void *user_data)
-{
-	char *buffer;
-	PurpleConnection *gc;
-	GtkWidget *alert;
-	PidginMiniDialog *dialog;
-	GdkPixbuf *protocol_icon;
-	struct auth_request *aa;
-	const char *our_name;
-	gboolean have_valid_alias;
-	char *escaped_remote_user;
-	char *escaped_alias;
-	char *escaped_our_name;
-	char *escaped_message;
-
-	gc = purple_account_get_connection(account);
-	if (message != NULL && *message != '\0')
-		escaped_message = g_markup_escape_text(message, -1);
-	else
-		escaped_message = g_strdup("");
-
-	our_name = (id != NULL) ? id :
-			(purple_connection_get_display_name(gc) != NULL) ? purple_connection_get_display_name(gc) :
-			purple_account_get_username(account);
-	escaped_our_name = g_markup_escape_text(our_name, -1);
-
-	escaped_remote_user = g_markup_escape_text(remote_user, -1);
-
-	have_valid_alias = alias && *alias;
-	escaped_alias = have_valid_alias ? g_markup_escape_text(alias, -1) : g_strdup("");
-
-	buffer = g_strdup_printf(_("<a href=\"viewinfo\">%s</a>%s%s%s wants to add you (%s) to his or her buddy list%s%s"),
-				escaped_remote_user,
-				(have_valid_alias ? " ("  : ""),
-				escaped_alias,
-				(have_valid_alias ? ")"   : ""),
-				escaped_our_name,
-				(*escaped_message ? ": " : "."),
-				escaped_message);
-
-	g_free(escaped_remote_user);
-	g_free(escaped_alias);
-	g_free(escaped_our_name);
-	g_free(escaped_message);
-
-	protocol_icon = pidgin_create_protocol_icon(account, PIDGIN_PROTOCOL_ICON_SMALL);
-
-	aa = g_new0(struct auth_request, 1);
-	aa->auth_cb = auth_cb;
-	aa->deny_cb = deny_cb;
-	aa->data = user_data;
-	aa->username = g_strdup(remote_user);
-	aa->alias = g_strdup(alias);
-	aa->account = account;
-	aa->add_buddy_after_auth = !on_list;
-
-	dialog = pidgin_mini_dialog_new_with_custom_icon(
-		_("Authorize buddy?"), NULL, protocol_icon);
-	alert = GTK_WIDGET(dialog);
-
-	pidgin_mini_dialog_enable_description_markup(dialog);
-	pidgin_mini_dialog_set_link_callback(dialog, G_CALLBACK(get_user_info_cb), aa);
-	pidgin_mini_dialog_set_description(dialog, buffer);
-	pidgin_mini_dialog_add_button(dialog, _("Authorize"), authorize_reason_cb, aa);
-	pidgin_mini_dialog_add_button(dialog, _("Deny"), deny_reason_cb, aa);
-	pidgin_mini_dialog_add_non_closing_button(dialog, _("Send Instant Message"), send_im_cb, aa);
-
-	g_signal_connect_swapped(G_OBJECT(alert), "destroy", G_CALLBACK(free_auth_request), aa);
-	g_signal_connect(G_OBJECT(alert), "destroy", G_CALLBACK(purple_account_request_close), NULL);
-	pidgin_blist_add_alert(alert);
-
-	g_free(buffer);
-
-	return alert;
-}
-
 static void
 pidgin_accounts_request_close(void *ui_handle)
 {
@@ -1572,7 +1364,6 @@
 static PurpleAccountUiOps ui_ops =
 {
 	.request_add = pidgin_accounts_request_add,
-	.request_authorize = pidgin_accounts_request_authorization,
 	.close_account_request = pidgin_accounts_request_close,
 };
 
--- a/pidgin/meson.build	Mon Aug 22 21:49:41 2022 -0500
+++ b/pidgin/meson.build	Mon Aug 22 22:05:55 2022 -0500
@@ -44,6 +44,7 @@
 	'pidginkeypad.c',
 	'pidginmessage.c',
 	'pidginmooddialog.c',
+	'pidginnotificationauthorizationrequest.c',
 	'pidginnotificationconnectionerror.c',
 	'pidginnotificationlist.c',
 	'pidginplugininfo.c',
@@ -116,6 +117,7 @@
 	'pidginkeypad.h',
 	'pidginmessage.h',
 	'pidginmooddialog.h',
+	'pidginnotificationauthorizationrequest.h',
 	'pidginnotificationconnectionerror.h',
 	'pidginnotificationlist.h',
 	'pidginplugininfo.h',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginnotificationauthorizationrequest.c	Mon Aug 22 22:05:55 2022 -0500
@@ -0,0 +1,339 @@
+/*
+ * 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/pidginnotificationauthorizationrequest.h"
+
+#include "pidgin/gtkdialogs.h"
+
+struct _PidginNotificationAuthorizationRequest {
+	HdyActionRow parent;
+
+	PurpleNotification *notification;
+
+	GtkWidget *accept;
+	GtkWidget *deny;
+	GtkWidget *message;
+};
+
+enum {
+	PROP_0,
+	PROP_NOTIFICATION,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE(PidginNotificationAuthorizationRequest,
+              pidgin_notification_authorization_request, HDY_TYPE_ACTION_ROW)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+pidgin_notification_authorization_request_update(PidginNotificationAuthorizationRequest *request) {
+	PurpleAccount *account = NULL;
+	PurpleAuthorizationRequest *purple_request = NULL;
+	const gchar *title = NULL;
+	const gchar *icon_name = NULL, *message = NULL;
+
+	g_return_if_fail(PIDGIN_IS_NOTIFICATION_AUTHORIZATION_REQUEST(request));
+
+	if(!PURPLE_IS_NOTIFICATION(request->notification)) {
+		hdy_preferences_row_set_title(HDY_PREFERENCES_ROW(request),
+		                              _("Notification missing"));
+
+		hdy_action_row_set_icon_name(HDY_ACTION_ROW(request), NULL);
+		hdy_action_row_set_subtitle(HDY_ACTION_ROW(request), NULL);
+
+		gtk_widget_hide(request->accept);
+		gtk_widget_hide(request->deny);
+		gtk_widget_hide(request->message);
+
+		return;
+	}
+
+	account = purple_notification_get_account(request->notification);
+	if(!PURPLE_IS_ACCOUNT(account)) {
+		hdy_preferences_row_set_title(HDY_PREFERENCES_ROW(request),
+		                              _("Notification is missing an account"));
+
+		hdy_action_row_set_icon_name(HDY_ACTION_ROW(request), NULL);
+		hdy_action_row_set_subtitle(HDY_ACTION_ROW(request), NULL);
+
+		gtk_widget_hide(request->accept);
+		gtk_widget_hide(request->deny);
+		gtk_widget_hide(request->message);
+
+		return;
+	}
+
+	purple_request = purple_notification_get_data(request->notification);
+
+	/* Set the icon name if one was specified. */
+	icon_name = purple_notification_get_icon_name(request->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(request), icon_name);
+
+	title = purple_notification_get_title(request->notification);
+	hdy_preferences_row_set_title(HDY_PREFERENCES_ROW(request), title);
+
+	message = purple_authorization_request_get_message(purple_request);
+	hdy_action_row_set_subtitle(HDY_ACTION_ROW(request), message);
+
+	gtk_widget_show(request->accept);
+	gtk_widget_show(request->deny);
+	gtk_widget_show(request->message);
+}
+
+static void
+pidgin_notification_authorization_request_set_notification(PidginNotificationAuthorizationRequest *request,
+                                                           PurpleNotification *notification)
+{
+	if(g_set_object(&request->notification, notification)) {
+		pidgin_notification_authorization_request_update(request);
+
+		g_object_notify_by_pspec(G_OBJECT(request), properties[PROP_NOTIFICATION]);
+	}
+}
+
+static void
+pidgin_notification_authorization_request_close(PidginNotificationAuthorizationRequest *request)
+{
+	PurpleNotificationManager *manager = NULL;
+
+	purple_notification_delete(request->notification);
+
+	manager = purple_notification_manager_get_default();
+	purple_notification_manager_remove(manager, request->notification);
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_notification_authorization_request_accept_cb(G_GNUC_UNUSED GtkButton *button,
+                                                    gpointer data)
+{
+	PidginNotificationAuthorizationRequest *pidgin_request = data;
+	PurpleAuthorizationRequest *request = NULL;
+
+	request = purple_notification_get_data(pidgin_request->notification);
+
+	purple_authorization_request_accept(request);
+
+	if(purple_authorization_request_get_add(request)) {
+		PurpleAccount *account = NULL;
+		const gchar *username = NULL, *alias = NULL;
+
+		account = purple_authorization_request_get_account(request);
+		username = purple_authorization_request_get_username(request);
+		alias = purple_authorization_request_get_alias(request);
+
+		purple_blist_request_add_buddy(account, username, NULL, alias);
+	}
+
+	pidgin_notification_authorization_request_close(pidgin_request);
+}
+
+static void
+pidgin_notification_authorization_request_deny_cb(G_GNUC_UNUSED GtkButton *button,
+                                                  gpointer data)
+{
+	PidginNotificationAuthorizationRequest *pidgin_request = data;
+	PurpleAuthorizationRequest *request = NULL;
+
+	request = purple_notification_get_data(pidgin_request->notification);
+
+	purple_authorization_request_deny(request, NULL);
+
+	pidgin_notification_authorization_request_close(pidgin_request);
+}
+
+static void
+pidgin_notification_authorization_request_message_cb(G_GNUC_UNUSED GtkButton *button,
+                                                     gpointer data)
+{
+	PidginNotificationAuthorizationRequest *pidgin_request = data;
+	PurpleAuthorizationRequest *request = NULL;
+	PurpleAccount *account = NULL;
+	const gchar *username = NULL;
+
+	request = purple_notification_get_data(pidgin_request->notification);
+
+	account = purple_authorization_request_get_account(request);
+	username = purple_authorization_request_get_username(request);
+
+	pidgin_dialogs_im_with_user(account, username);
+}
+
+static void
+pidgin_notification_authorization_request_remove_cb(G_GNUC_UNUSED GtkButton *button,
+                                                    gpointer data)
+{
+	PidginNotificationAuthorizationRequest *request = data;
+
+	pidgin_notification_authorization_request_close(request);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_notification_authorization_request_get_property(GObject *obj,
+                                                       guint param_id,
+                                                       GValue *value,
+                                                       GParamSpec *pspec)
+{
+	PidginNotificationAuthorizationRequest *request = NULL;
+
+	request = PIDGIN_NOTIFICATION_AUTHORIZATION_REQUEST(obj);
+
+	switch(param_id) {
+		case PROP_NOTIFICATION:
+			g_value_set_object(value,
+			                   pidgin_notification_authorization_request_get_notification(request));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_notification_authorization_request_set_property(GObject *obj,
+                                                       guint param_id,
+                                                       const GValue *value,
+                                                       GParamSpec *pspec)
+{
+	PidginNotificationAuthorizationRequest *request = NULL;
+
+	request = PIDGIN_NOTIFICATION_AUTHORIZATION_REQUEST(obj);
+
+	switch(param_id) {
+		case PROP_NOTIFICATION:
+			pidgin_notification_authorization_request_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_authorization_request_dispose(GObject *obj) {
+	PidginNotificationAuthorizationRequest *request = NULL;
+
+	request = PIDGIN_NOTIFICATION_AUTHORIZATION_REQUEST(obj);
+
+	g_clear_object(&request->notification);
+
+	G_OBJECT_CLASS(pidgin_notification_authorization_request_parent_class)->dispose(obj);
+}
+
+static void
+pidgin_notification_authorization_request_init(PidginNotificationAuthorizationRequest *list)
+{
+	gtk_widget_init_template(GTK_WIDGET(list));
+}
+
+static void
+pidgin_notification_authorization_request_class_init(PidginNotificationAuthorizationRequestClass *klass)
+{
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	obj_class->get_property = pidgin_notification_authorization_request_get_property;
+	obj_class->set_property = pidgin_notification_authorization_request_set_property;
+	obj_class->dispose = pidgin_notification_authorization_request_dispose;
+
+	/**
+	 * PidginNotificationAuthorizationRequest:info:
+	 *
+	 * The [type@Purple.AuthorizationRequestInfo] that this notification is for.
+	 *
+	 * 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/authorizationrequest.ui"
+	);
+
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginNotificationAuthorizationRequest,
+	                                     accept);
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginNotificationAuthorizationRequest,
+	                                     deny);
+	gtk_widget_class_bind_template_child(widget_class,
+	                                     PidginNotificationAuthorizationRequest,
+	                                     message);
+
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_authorization_request_accept_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_authorization_request_deny_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_authorization_request_message_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_notification_authorization_request_remove_cb);
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+GtkWidget *
+pidgin_notification_authorization_request_new(PurpleNotification *notification) {
+	return g_object_new(
+		PIDGIN_TYPE_NOTIFICATION_AUTHORIZATION_REQUEST,
+		"notification", notification,
+		NULL);
+}
+
+PurpleNotification *
+pidgin_notification_authorization_request_get_notification(PidginNotificationAuthorizationRequest *request)
+{
+	g_return_val_if_fail(PIDGIN_IS_NOTIFICATION_AUTHORIZATION_REQUEST(request),
+	                     NULL);
+
+	return request->notification;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidginnotificationauthorizationrequest.h	Mon Aug 22 22:05:55 2022 -0500
@@ -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_AUTHORIZATION_REQUEST_H
+#define PIDGIN_NOTIFICATION_AUTHORIZATION_REQUEST_H
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include <handy.h>
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginNotificationAuthorizationRequest:
+ *
+ * #PidginNotificationAuthorizationRequest is a widget that displays
+ * notifications from [class@Purple.NotificationManager] for authorization
+ * requests.
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_NOTIFICATION_AUTHORIZATION_REQUEST (pidgin_notification_authorization_request_get_type())
+G_DECLARE_FINAL_TYPE(PidginNotificationAuthorizationRequest, pidgin_notification_authorization_request,
+                     PIDGIN, NOTIFICATION_AUTHORIZATION_REQUEST, HdyActionRow)
+
+/**
+ * pidgin_notification_authorization_request_new:
+ * @notification: A [class@Purple.Notification] to display.
+ *
+ * Creates a new #PidginNotificationAuthorizationRequest instance that will
+ * display @notification.
+ *
+ * Returns: (transfer full): The new #PidginNotificationAuthorizationRequest
+ *          instance.
+ */
+GtkWidget *pidgin_notification_authorization_request_new(PurpleNotification *notification);
+
+/**
+ * pidgin_notification_authorization_request_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_authorization_request_get_notification(PidginNotificationAuthorizationRequest *request);
+
+G_END_DECLS
+
+#endif /* PIDGIN_NOTIFICATION_AUTHORIZATION_REQUEST_H */
--- a/pidgin/pidginnotificationconnectionerror.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/pidgin/pidginnotificationconnectionerror.c	Mon Aug 22 22:05:55 2022 -0500
@@ -142,6 +142,8 @@
 	PidginNotificationConnectionError *error = data;
 	PurpleNotificationManager *manager = NULL;
 
+	purple_notification_delete(error->notification);
+
 	manager = purple_notification_manager_get_default();
 	purple_notification_manager_remove(manager, error->notification);
 }
--- a/pidgin/pidginnotificationlist.c	Mon Aug 22 21:49:41 2022 -0500
+++ b/pidgin/pidginnotificationlist.c	Mon Aug 22 22:05:55 2022 -0500
@@ -27,6 +27,7 @@
 #include "pidgin/pidginnotificationlist.h"
 
 #include "pidgin/pidginnotificationconnectionerror.h"
+#include "pidgin/pidginnotificationauthorizationrequest.h"
 
 struct _PidginNotificationList {
 	GtkBox parent;
@@ -71,6 +72,9 @@
 		case PURPLE_NOTIFICATION_TYPE_CONNECTION_ERROR:
 			widget = pidgin_notification_connection_error_new(notification);
 			break;
+		case PURPLE_NOTIFICATION_TYPE_AUTHORIZATION_REQUEST:
+			widget = pidgin_notification_authorization_request_new(notification);
+			break;
 		default:
 			widget = pidgin_notification_list_unknown_notification(notification);
 			break;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/resources/Notifications/authorizationrequest.ui	Mon Aug 22 22:05:55 2022 -0500
@@ -0,0 +1,92 @@
+<?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="PidginNotificationAuthorizationRequest" 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="accept">
+        <property name="label" translatable="yes">Accept</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_authorization_request_accept_cb" object="PidginNotificationAuthorizationRequest" swapped="no"/>
+        <style>
+          <class name="suggested-action"/>
+        </style>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton" id="deny">
+        <property name="label" translatable="yes">Deny</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_authorization_request_deny_cb" object="PidginNotificationAuthorizationRequest" swapped="no"/>
+        <style>
+          <class name="destructive-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_authorization_request_message_cb" object="PidginNotificationAuthorizationRequest" 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_authorization_request_remove_cb" object="PidginNotificationAuthorizationRequest" 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>
--- a/pidgin/resources/pidgin.gresource.xml	Mon Aug 22 21:49:41 2022 -0500
+++ b/pidgin/resources/pidgin.gresource.xml	Mon Aug 22 22:05:55 2022 -0500
@@ -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/authorizationrequest.ui</file>
     <file compressed="true">Notifications/connectionerror.ui</file>
     <file compressed="true">Notifications/list.ui</file>
     <file compressed="true">Plugins/dialog.ui</file>

mercurial