Title: Migrating From Purple 2
Slug: migrating-from-purple-2

Nearly everything has changed between purple 2 and purple 3. We're truly sorry
for this, but there just wasn't another way to go about it. This document will
do its best to guide you through the changes but if something isn't clear,
please let us know so that we can update this document.

# Background

The APIs in purple 2 were designed for a different era and there just wasn't a
way to do incremental changes to get where we needed to.

The usual example for this is messages which are pretty important in a chat
client. In purple 2 a message would come in from a protocol with a string for
the contents. That string would be optionally written to the logger, which
usually just wrote it out to the end of a file, and then it got written out to
the end of the conversation window. And just like that purple 2 forgot about
the message.

There was no way to tell the logger to edit the message, no way to tell the
conversation window that someone added a response to it, no way to delete the
message, no way to jump to a message in the history, etc.

To fix this, we needed to change the way messages worked in purple. Which of
course meant changes to purple, the protocols, and of course the user
interfaces.

The solution introduced a lot of things, but they are all focused around
[class@Message] which keeps track of everything about each message including
[property@Message:id]'s that are how we are now able to lookup and find
messages after initial receipt and update them for all of the modern chat
features that just were not possible in purple 2.

There are many more smaller examples, but this one is good at describing the
necessary scope of these changes which is why we use it.

# Terminology

There have been a number of terminology changes that need to be covered before
anything else.

* Instant Messages (IMs) are now referred to as Direct Messages (DMs). This is
a pretty standard change that has been adopted by most other clients as far as
we can tell.

* Chats are now referred to as Channels. This is because many users call Group
Direct Messages chats and unlike Group DMs, channels can be public, have
infrastructure around them like permissions, passwords, access levels, and can
be discovered via a channel search.

* `PurpleContact` has been replaced by [class@Person] which has also picked up
responsibility for who shows up on your contact list.

* `PurpleBuddy` has been replaced by [class@Contact] and no longer signifies
that the person this represents is on your contact list. This is important
because modern chat protocols typically just send an identifier for a user and
the client is expected to cache the information about the user and only
request it from the server when necessary. We'll get more into this later, but
that means that purple 3 is designed to be that cache and track all of the
contacts that you run into.

* `PurpleConvChatBuddy` has been replaced by [class@ConversationMember] which
has a [class@Contact] and additional properties for that contact in that
specific conversation.

* Buddy Icons are now called Avatars and are backed by [class@Image]. We're
currently reworking this a bit so details are a bit sparse right now.

# Supporting Libraries

Purple 3 has a number of supporting libraries that were designed to help with
purple 3 but are maintained externally with the hope that they are useful to
others and need not be an exclusive purple 3 API.

## Birb

[Birb](https://docs.imfreedom.org/birb/) is a utility library which holds some
things that we end up using all over the place including some of our protocol
libraries.

## GPlugin

[GPlugin](https://docs.imfreedom.org/gplugin/) is a plugin library. Purple has
always made heavy use of plugins but the previous purple exclusive API made
some things easier and others harder.

More importantly than all of that is that GPlugin uses [GObject
Introspection](https://gi.readthedocs.io/en/latest/) for loading plugins in
other languages.

GObject Introspection is a well defined API and data format that describes an
API which makes language bindings trivial. This is made possible because the
language bindings only need to know how to translate the GObject Introspection
format to their target language.

GPlugin, Purple, and Pidgin all create a GObject Introspection Repository
(GIR) file as part of their build that can be used by these translation layers
to expose the API automatically with no hand crafted bindings.

GPlugin currently contains loaders for any native plugins assuming they expose
the correct functions, [Lua](https://www.lua.org/), and [Python
3](https://python.org/). It even provides examples of plugins written in
[Vala](https://vala.dev/),
[Genie](https://wiki.gnome.org/Projects(2f)Genie.html), and there has been a
proof of concept for [Rust](https://www.rust-lang.org/) plugins as well.

## HASL

[HASL](https://docs.imfreedom.org/hasl/) is a new
[SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer)
library we wrote to deal with some of the issues in the existing offerings.

## Seagull

[Seagull](https://docs.imfreedom.org/seagull/) is an alternative API to
[SQLite](https://sqlite.org/). It's designed to force a specific workflow of
using prepared statements with named parameters and making that workflow
easier for the developer to get correct.

It also provides API for handling migrations and makes working with GObjects
and other GLib data structures in SQLite much easier.

## Traversity

[Traversity](https://docs.imfreedom.org/traversity/) is meant to be a Swiss
Army Knife when it comes to [NAT
Traversal](https://en.wikipedia.org/wiki/NAT_traversal).

It has a long way to go, but this will eventually replace all of the NAT
traversal API that existed in purple 2 but has already been removed from purple
3.

# Conceptual Changes

We've tried to keep things as familiar as possible, but of course some things
had to change.

## Meson

As you may or may not know, we moved to [Meson](https://mesonbuild.com) as our
build system some time ago. Meson is a very easy to use and capable build
system. It has a lot of stuff built in to make our lives easier, but the
absolute biggest thing, is that we can use it everywhere.

In the purple 2 days we had two build systems: one on Windows and one for
everything else. With Meson, this is no longer a thing as it runs everywhere.
It does come with a few small caveats though.

First, it is implemented in Python which can sometimes be problematic to
install and keep up to date on Windows and macOS. But that also means we can
write utility scripts in Python which will work everywhere as well.

Secondly it doesn't provide a way to uninstall the project. This is why we
recommend that you do not install Pidgin 3 and instead run it from a
development environment.

Speaking of development environments, or `devenv`'s. The purple 3 code base is
set up so that you do _not_ need to install it to develop against it. The
`devenv` will set everything up to make it discoverable.

You can run `meson devenv` in the purple 3 build directory to create a shell,
and then `cd` to your own project and generate your build system with Meson,
or whatever tool, because Meson has set up a number of environment variables
including `PATH`, `PKG_CONFIG_PATH`, `PKG_CONFIG`, and even `LD_LIBRARY_PATH`.
This is helpful if you're working on something that uses purple, like a
protocol plugin, a UI, etc.

These environment variables are the standard ones that build systems look at
to find libraries and executables as well as run them which is why they work
with whatever your build system is. This is currently the recommended way to
develop against purple 3.

## Unit Tests

First and foremost we are trying very hard to make as much of purple 3
testable and to implement tests for new APIs when they're added. This allows
us to ensure things are working as expected and help us find memory leaks as
soon as possible.

## GObjects

One of the big things that complicated the development of purple 3 was the
transition to [GObjects](https://docs.gtk.org/gobject/). There has always been
many reasons to make purple a GObject library and it was always going to be a
lot of work, but it was more work than anyone expected.

A lot of this work was done in a few [Google Summer of
Code](https://summerofcode.withgoogle.com/) projects, but it was never quite
completed let alone polished. Doing so took far longer than anyone imagined
and there were some caveats that still needed to be dealt with.

Regardless, purple 3 is a full GObject based library now and GObject must be
understood to do many things in purple 3. There is a tutorial for GObjects
[here](https://docs.gtk.org/gobject/tutorial.html) which should probably be
reviewed.

## In Tree Protocols

In the Gaim and purple 2 days we accepted many protocols into our main code
base. This made things easier for users as it was less things for them to
find, download, and install, but it came with a much heavier cost than we
realized.

Over time the people maintaining these protocols would retire and the remaining
developers didn't have access to the required servers or networks to test
these plugins. Which of course meant everything started to bit rot.

On top of that, the proprietary protocols were always a legally dubious point
of contention which has caused some people to shy away as well as have
employers ask their employees not to be affiliated with the project.

Nowadays, distribution and discovery is much easier, especially with things
like [Flatpak](https://flatpak.org/), all of the other package managers, and
software stores that are out there.

So, we are now only carrying Open Source and Open Specification based protocol
plugins. This means we will only carry plugins for protocols like
[IRCv3](https://ircv3.net/), [XMPP](https://xmpp.org/),
[Matrix](https://matrix.org/), and so on.

All protocols that connect to proprietary networks or are reverse engineered
will not be accepted into our main repository. That doesn't mean we won't have
any of them as separate repository, just that our main releases only contain
Open Source and Open Specification protocols.

This is better for everyone as it keeps us focused on Open Source and Open
Specifications, but it's also better for the proprietary protocol plugin
maintainers as they don't have to wait on us for a release when the owners of
the protocol make a breaking change. It might be a bit more work for users,
but the app stores out there should make discoverability easier and keep them
up to date as well.

## Groups

Currently groups are under implemented because many modern protocols lack the
concept entirely. We are currently working on the assumption that a tag on the
contact info named group (which can be specified multiple times) _should_ be
able to handle our needs, but we haven't proven this out yet.

## Icons

Instead of passing around raw image data or file names we have moved what we
can to using the [XDG Icon Theme
Specification](https://specifications.freedesktop.org/icon-theme-spec/latest/)
as much as possible.

Most widget toolkits already have helpers for all of this and if a user
interface doesn't want to support icons, these are just strings that can be
easily ignored.

## Asynchronous APIs

Most APIs in purple 2 were asynchronous, but not in a way that you could
reliably get the result of them. This is one of the reasons why Pidgin 2 and
earlier had so many popup windows. An action would be fired off asynchronously
and by the time we had the result we had lost all context of the request and
the only way to let the user know was to pop up a dialog.

Some examples of this include getting a user's profile, a message failing to
send, file transfers, and so on.

To fix this we've moved to the asynchronous API that's built into Gio named
[class@Gio.Task] that implements the [iface@Gio.AsyncResult] interface.

This API is pretty simple as a consumer. You call an `_async` function and
provide an optional callback to it which is called when the task has finished.
In your callback, you call a `_finish` method that gets you the result.

For example, a user interface would call
[method@ProtocolConversation.set_topic_async] to set the topic of a
conversation and then in their callback call
[method@ProtocolConversation.set_topic_finish] to determine if the set was
successful. If it wasn't successful the UI could show the error in the widget
where they gathered the new topic from the user.

Implementing these can get a bit trickier, especially if you need to chain
them to other `_async` methods. For example, say you're implementing
[vfunc@ProtocolConversation.set_topic_async] but you need to make an HTTP
request to set the topic using libsoup. You would do something like the
following:

```c
/* This is some helper api for the callback function, you probably want to
 * start in the _async function below.
 */
typedef struct {
	PurpleConversation *conversation;
	char *topic;
} MigrateProtocolSetTopicData;

static MigrateProtocolSetTopicData *
migrate_protocol_set_topic_data_new(PurpleConversation *conversation,
                                    const char *topic)
{
	MigrateProtocolSetTopicData *data = g_new(MigrateProtocolSetTopicData, 1);

	data->conversation = g_object_ref(conversation);
	data->topic = g_strdup(topic);

	return data;
}

static void
migrate_protocol_set_topic_data_free(MigrateProtocolSetTopicData *data) {
	g_clear_object(&data->conversation);
	g_clear_pointer(&data->topic, g_free);
	g_free(data);
}

/* This is the callback function for the _async function below, you probably
 * want to start there.
 */
static void
migrate_protocol_conversation_set_topic_cb(GObject *self,
                                           GAsyncResult *result,
                                           gpointer user_data)
{
	GBytes *bytes = NULL;
	GError *error = NULL;
	GTask *task = user_data;

	bytes = soup_message_send_and_read_finish(SOUP_SESSION(self), result,
	                                          &error);

	/* We aren't expecting a body so we just need to free whatever we got. */
	g_clear_pointer(&bytes, g_bytes_unref);

	if(error != NULL) {
		/* If we got an error, return it. */
		g_task_return_error(task, error);
	} else {
		/* If we didn't get an error, set the topic on the conversation and
		 * return true.
		 */
		MigrateProtocolSetTopicData *topic_data = NULL;

		topic_data = g_task_get_task_data(task);

		purple_conversation_topic(topic_data->conversation, topic_data->topic);

		g_task_return_boolean(task, TRUE);
	}

	/* Clearing the task will cause the user's callback to be called, where
	 * they will call purple_protocol_conversation_set_topic_finish which will
	 * call our implementation below and return either TRUE or the error we set
	 * above.
	 */
	g_clear_object(&task);
}

static void
migrate_protocol_conversation_set_topic_async(PurpleProtocolConversation *protocol,
                                              PurpleConversation *conversation,
                                              const char *topic,
                                              GCancellable *cancellable,
                                              GAsyncReadyCallback callback,
                                              gpointer data)
{
	MigrateProtocolSetTopicData *set_topic_data = NULL;
	PurpleAccount *account = NULL;
	PurpleConnection *connection = NULL;
	GTask *task = NULL;
	GUri *uri = NULL;
	SoupMessage *message = NULL;
	SoupSession *session = NULL;
	char *query = NULL;

	/* Get the soup session for our connection. */
	account = purple_conversation_get_account(conversation);
	connection = purple_account_get_connection(account);
	session = migrate_connection_get_soup_session(MIGRATE_CONNECTION(connection));

	/* We create our GTask which needs to report back to our caller. */
	task = g_task_new(protocol, cancellable, callback, data);

	/* We add the conversation to our task data adding a reference to make sure
	 * it stays valid.
	 */
	set_topic_data = migrate_protocol_set_topic_data_new(conversation, topic);
	g_task_set_task_data(task, set_topic_data,
	                     (GDestroyNotify)migrate_protocol_set_topic_data_free);

	/* Create our soup request. */
	query = g_strdup_printf("channel=%s&topic=%s",
	                        purple_conversation_get_id(conversation),
	                        topic);
	uri = g_uri_build(G_URI_FLAGS_NONE, "http://", NULL, "localhost", 80,
	                  "/channel/topic", query, NULL);
	g_clear_pointer(&query, g_free);
	message = soup_message_new_from_uri("POST", uri);
	g_clear_pointer(&uri, g_uri_unref);

	/* Now send the message asynchronously passing our callback and our task as
	 * data to that callback but reusing the cancellable the user provided.
	 */
	soup_session_send_read_async(session, message, G_PRIORITY_DEFAULT,
	                             cancellable,
	                             migrate_protocol_conversation_set_topic_cb,
	                             task);

	/* Clear the message as we're done with it. */
	g_clear_object(&message);
}

static void
migrate_protocol_conversation_set_topic_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
                                               GAsyncResult *result,
                                               GError **error)
{
	/* Propagate the result that we got from our http request. */
	g_task_propagate_boolean(G_TASK(result), error);
}
```

## ui_data

Many objects in purple 2 had a `ui_data` member that allowed the user interface
to attach an opaque pointer them. Now that everything is a
[class@GObject.Object], these have been removed as
[method@GObject.Object.get_data] and [method@GObject.Object.set_data] can be
used to add any amount of data to an object.

## proto_data

Similar to `ui_data` above there were a number of objects in purple 2 that
allowed a protocol to associate an opaque pointer with it. These cases have
been replaced by making those objects derivable and requiring the protocol to
implement their own implementation. [class@Connection] is the biggest one at
the time of this writing.

## Managers

In purple 2 we had "subsystems" for everything; accounts, buddies,
conversations, file transfers, plugins, whiteboards, etc. All of these allowed
management of their contained types and signals for events on those instances.

One of the big issues with the subsystems was that they depended on global
state. For example, when an account was created, it would automatically get
added to the account subsystem.

This was great from a developer point of view, but it meant you can't create
an account in a unit test without initializing the account subsystem, and the
account subsystem depends on the protocols being loaded, which depends on the
plugin subsystem being loaded, and so on. Obviously this wasn't ideal.

However, the biggest issue with the subsystems was that their signals were
"class based". Meaning that you could connect to the `account-disconnected`
signal in the account subsystem and your callback would get called when any
account disconnected. In GObject, signals are instance based, which means
there is no easy way to have a callback called whenever any account is
disconnected.

To address these issues we introduced what we call "managers". A manager keeps
track of a collection of objects and will propagate their signals essentially
imitating the "class based" signals from the subsystems.

So for the `account-disconnected` signal in purple 2 mentioned above you would
connect to the [signal@AccountManager::account-disconnected] signal of an
[class@AccountManager]. But now you need to know where to get an instance of
[class@AccountManager] from. That's where the next step comes in.

To avoid the issue with global state that we had in purple 2 we made it
possible to create multiple instances of the managers. This means that all of
their data is self contained and can easily be used in unit tests. But we
still needed specific managers for general use in purple. At startup, purple
creates instances of these managers and exposes them via `_get_default`
functions.

For the [class@AccountManager] example above, you would do something like the
following:

```c
PurpleAccountManager *manager = NULL;

manager = purple_account_manager_get_default();
g_signal_connect(manager, "account-disconnected", G_CALLBACK(my_callback), NULL);
```

[func@AccountManager.get_default] returns the default instance of
[class@AccountManager] that purple created during startup. Once you have that,
you can use the normal signal API in GObject to connect to the
[signal@AccountManager::account-disconnected] signal on the manager and your
callback will be called whenever any account the manager knows about is
disconnected.

Most of the managers have add/remove functions, and some still have
register/unregister functions that will probably be changed to add/remove in
the future. They all should also implement the [iface@Gio.ListModel] interface
which is something that is used very heavily in purple 3 and you will want to
get familiar with as well.

There's a few more caveats to managers, but we're going to skip them for now.

## Account Options and User Splits

Historically speaking user splits were originally created to help identify
which account was which when displaying them in a list. It also made the
account editor a bit easier or harder depending on the user. They split
things like an XMPP ID (JID) into multiple fields which was not expected
behavior for XMPP.

To solve the issue for being able to determine which account is which, we
added the [property@Account:name] property to [class@Account] to let users
name them whatever they want. This solves the original purpose of user splits
but we still need account specific settings for things like usernames, what
server to connect to as well as other advanced options.

In purple 2 accounts options were all displayed under the advanced tab in the
account editor with the user splits in the main tab. This was easy for users,
but kind of annoying for developers as there's two different interfaces to
very similar data.

We are currently in the process of replacing these with the new
[class@AccountSetting] and its subclasses and hoping to have have this work
done by the Experimental 4 release.

[class@AccountSetting] has an [property@AccountSetting:id] property for
identifying the settings and a [property@AccountSetting:label] property which
will be displayed in the user interface. [class@AccountSettings] also have
an [property@AccountSetting:advanced] property that can be set to tell the
user interface which settings are advanced.

Protocols will need to implement the
[vfunc@Protocol.get_default_account_settings] function creating an instance
of [class@AccountSettings] which the user interface will call when the user is
creating or editing an account. These settings will then all be stored with
the account allowing the protocol author to change defaults without affecting
users.

A protocol can then use the `]property@Account:settings]` to get the account
specific settings which replaces the old `purple_account_get_bool`,
`purple_account_get_int`, and `purple_account_get_string` methods.

The [class@AccountSettings] collection and [class@AccountSetting] instances
unify account settings which gives protocols the ability to use the
nomenclature that users of those protocols expect.

We have also been working on creating a concept of "Networks". For example, a
user knows that they want to connect to the `Libera.chat` or `OFTC` IRC
networks, but they probably don't know or care to know the specific hostname
they need to connect to to do so.

With the [class@AccountSettings] object, we're looking at adding a method to
"merge" two instances. This ability will help us implement the networks
feature as we could then merge the settings for a network with the defaults
from a protocol to fill in the network specific settings.

This is still all conceptual, but this does seem like an idea that will work.
We have [PIDGIN-18064](https://issues.imfreedom.org/issue/PIDGIN-18064) to
track this, but it's still a bit out.

## Contacts

As mentioned earlier in the terminology section, much has changed when it
comes to contacts as well.

At the base of all contacts we now have [class@ContactInfo] which keeps track
of all information about a contact but is not tied to a specific account. This
is necessary as [class@Account]s have a [class@ContactInfo] via their
[property@Account:contact-info] property.

Next we have [class@Contact] which ties a [class@ContactInfo] to an
[class@Account]. This is pretty straight forward and is the main way to
interact with a contact outside of a conversation.

To represent contacts in a [class@Conversation] there is now
[class@ConversationMember] which has additional properties for the contact in
the conversation. Things like a conversation specific nickname, their typing
state, and so on. Conversation members are stored in the
[property@Conversation:members] property and should be managed via the
[class@ConversationMembers] API.

Finally we have [class@Person] which is a collection of contacts for a
specific person. In purple 2 this is roughly what a `PurpleContact` was, but
in purple 3 this is also used to determine who is visible in your contact
list/roster.

When a protocol is adding new contacts, it should check if the contact is on
the protocol's list of contacts and create a person for that contact if one
does not exist.

## Contact Management

In purple 2 contact management only consisted of people on your contact
list/roster. This made sense at the time, but as chat evolved many protocols
moved to associating lots of data with users which required them to refer to
users with just an identifier to save bandwidth.

This means that messages from any user, on your contact list or not, just come
in with an identifier and that identifier would have to be resolved somehow to
get a name to display. Obviously the protocol providers don't want you looking
that up all the time so the clients have to cache that.

The modern protocols implemented in purple 2 tend to do this by adding all
contacts to the BuddyList or by maintaining an internal cache. Both of these
options are subpar as the former pollutes your contact list and the later
leads to code duplication across each protocol.

To combat this, we created [class@ContactManager]. The contact manager keeps a
list of all contacts by account allowing you to do basic
[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
operations against them as well as persisting them on disk.

Protocol implementations should use the contact manager to find contacts and
add ones that the contact manager doesn't know about yet. This will allow a
purple user to alias anyone, add notes to anyone, and so on. Doing so also
eliminates a lot of synchronization during the initial connection as the data
on disk should be relatively up to date.

## Preferences to GSettings

The preferences API in purple 2 was good for its time but it caused issues for
other user interfaces like Instant Bird which required some API changes to
make it work better.

However, since that API was written [class@Gio.Settings] has been created and
does all the purple preferences API did and then some. As such we decided to
move to [class@Gio.Settings] in Purple 3.

GSettings can be a bit intimidating to get started, but purple does provide
a [class@Gio.SettingsBackend] to manage its settings via
[func@Core.get_settings_backend]. This backend is actually created by the user
interface, but Purple exposes it for use throughout the program.

You can find examples throughout the code base including any plugin that has
settings.

## Status API

The status API in purple 2 was as complicated as was the problem it was trying
to solve. Fortunately, status has simplified over time due to complexity and
privacy concerns. As such our new status API is just about as simple as it can
get.

At the center of it is [class@Presence]. It has a number of properties for
tracking the presence of a contact with [property@Presence:primitive] and
[property@Presence:message] being the most important.

[class@PurplePresence] does not try to enforce any rules about what is and is
not valid for any specific protocol deferring these decisions to the protocol
itself. In most cases the protocol will be the only one setting these
properties which keeps things nice and simple.

When it comes to setting the presence for the purple user, that's where things
get a bit trickier. We have a lot of stuff in place for this, but it hasn't
been completely proved out yet.

The global state of the purple user's presence is managed by
[class@PresenceManager] which manages instances of [class@SavedPresence] and
has an [property@PresenceManager:active-presence] to represent which
[class@SavedPresence] is currently in use.

The per account presence is managed solely by the protocol plugin via the
[property@PurpleContactInfo:presence] of the [property@Account:contact-info] of
each [class@PurpleAccount].

When an account connects, the protocol can grab the active saved presence and
set it during the connection process. I don't recall the specifics, but in
purple 2 it was impossible to connect while invisible as there wasn't a way to
determine the presence or something before the account was connected. With the
new API, that limitation no longer exists.

When it comes to updating the purple user's presence, the idea right now is to
have protocols listen to changes to the active presence property and then
forward those updates according to the protocol.

Some interest has been expressed to have a protocol virtual function that
would get called when something changes in the purple user's presence but that
feels like extra steps when everything else will already be using the signals
to track changes. Especially since a protocol can connect to the signals with
[func@GObject.signal_connect_object] and tie the lifetime of the signal
handler to their [class@Connection] instance. At any rate, if you have
thoughts on this, please let us know!

There are currently no helper APIs for making a [class@Presence] match a
[class@SavedPresence]. We may add some at some point, but some protocols will
need to map [enum@PresencePrimitive] and do other things, so we've avoided
this for the time being.

## Message Formatting

In purple 2 the [Rich Text
Format](https://en.wikipedia.org/wiki/Rich_Text_Format) was
[HTML](https://en.wikipedia.org/wiki/HTML). This worked okay, but required
passing over the message content multiple times for clients like Pidgin. The
protocol would generate HTML and then Pidgin would convert the HTML into the
format used in [GtkTextView](https://docs.gtk.org/gtk4/class.TextView.html).

This also had the undesired affect that users couldn't temporarily turn off
formatting which has quite a few use cases. Looking back on it, we might have
been able to come up with something for this, but it would have required a
significant amount of work.

Many modern chat protocols support Markdown, which could have been a possible
contender but its lack of color support made it a non starter.

Other protocols like Twitch will use start and end offsets in the plain text
to define attributes about that text. This has the added benefit of splitting
the display (formatting) from the content (plain text) and allows you to
disable the formatting without throwing it away.

After a bit of digging we realized that [struct@Pango.Attribute] has start and
end indices, already implements all of the basic text formatting options,
allows for custom attributes, and for GTK based clients requires no additional
work on our side.

To implement this, we have [property@Message:contents] which is the plain text
content of the messages and [property@Message:attributes] which is a
[struct@Pango.AttrList] of the [struct@Pango.Attribute]s which contain the
formatting of the message contents.

We do not currently have any helpers for working with
[struct@Pango.Attribute]s but we may add some for Markdown in the future.

Likewise, we do not currently have custom attributes for things like mentions,
custom emojis, inline images, and so on. These are planned but we're debating
where to put them as we want to use them in
[Ibis](https://docs.imfreedom.org/ibis),
[Myna](https://docs.imfreedom.org/myna/), and
[Xeme](https://docs.imfreedom.org/xeme/) which means that they will most
likely end up in [Birb](https://docs.imfreedom.org/birb/).

Also user interfaces will be expected to provide formatting via
[property@Message:attributes] when sending messages.

## Connection State Tracking

In purple 2 the state of a connection was tied to the connection object itself
which was kind of weird since connections only existed when accounts were not
disconnected.

This led to some interesting API, so in purple 3 we moved the connection state
to the [property@Account:connection-state] property which will always reflect
the connection state of the account.

## Actions

All actions in purple have now been moved to [iface@Gio.Action] and
[class@Gio.MenuModel]. These are worked into a number of places that you would
expect and we created a helper [class@Birb.ActionMenu] to make working with
them a bit easier.

This also means that plugins in other languages can now add actions to things
as we no longer require a C function pointer for the callback.

One of the big benefits of [class@Gio.MenuModel] is that you have full control
over the menu including adding sections, sub menus, and so on.

# New Stuff

There is a lot of new stuff and this section is focused on just a small amount
of them.

## Attachments

Message attachments are something new that didn't really exist in purple 2 but
bears some mentioning here. Many modern chat protocols will let you attach
images and/or files to a message and will use the attachments to provide a
preview of links among other things.

Messages have an [property@Message:attachments] property that contains a
collection of [class@Attachments]. When a message is received these are used by
the user interface to present them to the user in whichever way it sees fit.
Likewise, when sending a message the protocol will need to parse these and send
them out as necessary.

It is worth noting that inline images and files will be implemented via
attachments, but will not typically be shown with the other attachments as they
are inline. They would be referenced via a message formatting attribute which
has not yet been defined.

## Badges

Many modern protocols have what we call badges. These are little icons that are
displayed before or after a contact's name. Sometimes they are conversation
specific like owner, moderator, etc, and other times they are contact specific
like administrator, staff, etc.

In purple 2 we only had support for the conversation specific ones and only in
chats (what we now call channels). These were previously implemented via the
`PurpleConvChatBuddyFlags` flag.

`PurpleConvChatBuddyFlags` were functional for the time, but didn't allow for
the kind of customization that we would see from Slack, which shows the
presence emoji after someone's name. Then there is Twitch that has custom
badges per channel and contact specific badges to signify partners, Twitch
staff, and participation in specific events.

[class@ContactInfo] has a [property@ContactInfo:badges] to add badges globally
for a contact, while conversation specific badges can be set via the
[property@ConversationMember:badges] property on [class@ConversationMember].

## Tags

There is a common need to store random arbitrary data with an object. For
example, purple 2 had the `purple_buddy_[gs]et_{bool,int,string}` API. To
simplify this, we created [class@Tags] which is a well defined format that has
been added to many of our core data structures.

Right now tags are designed to only work on strings but we are looking at
adding helpers to make it easier to work with integer and boolean values.

## for-display functions

In purple 2 we had a number of features like local aliases and avatars but no
helpers to make sure the correct one was selected. This lead to issues where
aliases wouldn't be displayed because not all uses were updated and so on. In
purple 3 we've added "for-display" properties to address this.

For example [class@ContactInfo] has [property@ContactInfo:name-for-display]
which will check a number of properties and always return the proper one. A
full break down can be seen in the documentation for the property.

There are a number of these throughout the code base, if you think there
should be one where there isn't let us know.

## Notifications

Purple 2 didn't really have a way to define notifications. Pidgin 2 did a bit
of this by showing connection errors, contact authorization requests, and some
other stuff in the bottom of the Buddy List window but since these weren't
handled in purple 2 it lead to more popup windows. These popup windows were
very often closed on accident which was less than ideal when it was an
announcement from a server admin or file transfer request.

To avoid these issues we created [class@Notification] and
[class@NotificationManager] to manage them. We've played around with the exact
API a few times and will probably do so again, but you can create a
notification and add it to the default manager and the UI will display it with
some actions that can be performed.

Notifications are not persisted and are instead intended to be synchronized
when reconnecting to services as they could have been dismissed while an
account was offline.

# Protocol Interfaces

Of course with all of the other changes, the protocol interface has changed
drastically as well.

[class@Protocol] is now an abstract class that you must subclass. The class
itself is very simple as all of the big features have been moved to separate
interfaces. This change was done to hopefully help keep things more organized
in everyone's code but does come with a small amount of additional boilerplate
code.

Also many of the old virtual functions have been removed as they are either no
longer necessary or have been implemented differently.

* `icon_spec` has been removed and will be address in
`Purple.AccountSettingAvatar` as defined in
[PIDGIN-18125](https://issues.imfreedom.org/issue/PIDGIN-18125).

* `list_emblem` has been removed as most of its past usages are now covered
by [class@Purple.Badges].

* `status_text` is no longer necessary as we have a presence for every contact
including a status message.

* `tooltip_text` has been removed and is unlikely to return.

* `status_types` is no longer necessary due to the changes in the status API.

* `set_info` does not yet have a replacement.

* `set_status` is unlikely to be replaced as the protocol can get the active
status from the status manager and apply it when there are changes.

* `set_idle` like `set_status` above all of this information is available to
the protocol whenever it needs it.

* `change_password` is unlikely to be replaced, at least directly as there are
many different way to authenticate now.

* `add_permit`, `add_deny`, `rem_permit`, `rem_deny`, and `set_permit_deny`
have all been replaced by [property@ContactInfo:permission].

* `keepalive` is unlikely to get replaced as this was really just a timeout
handled by purple to tell the protocol when to ping the server, but the
protocol has always had a better idea of when that is necessary than purple.

* `register_user` and `unregister_user` will eventually be replaced by some sort
of a registration interface, but we haven't looked into this yet.

* `get_cb_alias`, `get_cb_away`, `get_cb_info`, and `get_cb_real_name` have
been removed as they are now implemented via [class@ConversationMember] which
has a [class@ContactInfo] and between the two everything about them should be
available.

* `group_buddy` and `rename_group` need some redesigning yet. As mentioned
above groups are essentially not implemented right now.

* `buddy_free` will not be replaced. [class@ContactInfo] has a lot of
properties which hopefully means you won't need to store much other data with
them. However, since they are [class@GObject.Object]s now, you can use
[method@GObject.Object.set_data] and [method@GObject.Object.set_data_full] to
attach data to them and have it automatically freed when they are destroyed.

* `normalize` is unlikely to get replaced. This was necessary because we used
usernames everywhere that were directly displayed to users. Instead the
normalized identifier of a contact should be stored in
[property@ContactInfo:id] and the display name from the server should be
stored in [property@ContactInfo:display-name]. This avoids the issue entirely
as user interfaces can sort and filter on the display name and the protocol
knows its normalization rules for id based look-ups.

* `find_blist_chat` will not be replaced. [class@ConversationManager]
remembers all conversations across program restarts and thus conversations no
longer need to be added to a list to be remembered as that now automatically
happens.

* `roomlist_get_list`, `roomlist_cancel`, `roomlist_expand_category`, and
`roomlist_room_serialize` are going to be radically changed and replaced by
[iface@ProtocolDirectory] but we have not yet gotten to it.

* `offline_message` will not be replaced.
[method@Conversation.send_message_async] will return an error if the message
could not be delivered.

* `send_raw` will most likely not be replaced. It's an interesting idea and
could be easily implemented but we're unsure on the utility of it and thus
haven't looked at it yet.

* `send_attention` and `get_attention_types` have been removed for now and
we're currently undecided on bringing something similar back. If this is
something you're interested in, please let us know.

* `get_account_text_table` will not be replaced. This isn't used by any in
tree protocols and doesn't look useful.

* `initiate_media` and `get_media_caps` will be replaced by a media API that
we're still in the process of designing.

* `get_public_alias` will not be replaced as the protocol should update the
[property@ContactInfo:display-name] property when it needs to.

* `add_buddy_with_invite` and `add_buddies_with_invite` were meant to be
covered by [iface@ProtocolRoster] but that does not handle authorization
requests and will need to be revisited.

* `chat_can_receive_file` will eventually be replaced by a property on
[class@Conversation] which the protocol will be responsible for setting.

* `chat_send_file` will not be replaced. The protocol can create an
[class@Attachment] and add it to the [property@Message:attachments] of a
[class@Message].

## Protocol Class

The protocol class has a few virtual functions that must be implemented and a
few that are optional.

[class@Protocol] also has a number of properties that used to be virtual
functions.

* *icon-name*: Replaces `list_icon`
* *id*: Replaces the use of the plugin id as the protocol id as plugins can
support more than one protocol now.
* *name*: Replaces the use of display name of the plugin as protocol name as
plugins can support more than one protocol now.
* *options*: Replaces the options property from the structure but is likely to
be removed in the future.

[vfunc@Protocol.get_user_splits] and [vfunc@Protocol.get_account_options] are
deprecated and have been replaced by
[vfunc@Protocol.get_default_account_settings].

Also, [vfunc@Protocol.get_whiteboard_ops] will most likely be changed to
something like `create_whiteboard` but we don't have whiteboards implemented
anywhere at the moment so this move has been de-prioritized as we don't have a
way to test it.

### Can Connect

In purple 2 we used to use an operating system specific API to check if we
were connected to the internet and if so, we would then allow accounts to be
connected.

This sounds fine in theory, but what happens when you're in a corporate network
connecting to a local XMPP or IRC server and they only allow HTTPS traffic. In
theory this will work, but with something like Link Local Messaging that is
local network only you wouldn't be able to connect if you didn't have an
internet connection.

Regardless there is no way for purple to reliably guess what level of network
connectivity a protocol needs and that's where
[vfunc@Protocol.can_connect_async] and [vfunc@Protocol.can_connect_finish] come
in.

These virtual functions are required to be implemented.

[method@Account.connect] will call these functions before attempting
to bring an account online. If [vfunc@Protocol.can_connect_finish] returns
`true` then the connection attempt will continue. This gives the protocol the
sole power to decide if a connection attempt should be made.

Most protocols will use [iface@Gio.NetworkMonitor] to make this decision using
[method@Gio.NetworkMonitor.can_reach_async], but of course a protocol can use
whatever means it finds are necessary.

### Create Connection

As mentioned previously, protocols now need to subclass [class@Connection].
When connecting an account purple will call [method@Protocol.create_connection]
to create an instance of that subclass and then call the
[vfunc@Connection.connect] on it.

This gives the protocol full control over that object for the entire lifetime
of the connection. When the account has finished its initial connection
negotiations and is ready for user interaction, it should call
[method@Account.ready] to signal as much.

There is also [method@Account.disconnect] for disconnecting an account
normally, and [method@Account.disconnect_with_error] for disconnecting
accounts with an error. When the protocol wants to disconnect, it should call
one of these and handle cleanup in the [vfunc@Connection.disconnect]
implementation.

[vfunc@Connection.connect] replaces the `login` virtual function, and
[vfunc@Connection.disconnect] replaces the `close` virtual function.

### Generate Account Name

As mentioned [above](#account-options-and-user-splits) accounts now have a
user specified name. However, we realize that users won't necessarily pick
something reasonably by default. That's where the mandatory
[vfunc@Protocol.generate_account_name] comes in. It is meant to help fix that
problem by returning something like `IRC 1` or `XMPP 2`.

### Get Default Account Settings

[vfunc@Protocol.get_default_account_settings] replaces the `get_user_splits`
and `get_account_options` virtual functions.

### Get Action Menu

[vfunc@Protocol.get_action_menu] is an optional virtual function that returns
a list of actions for the account. These are the actions that you saw in the
`Accounts` menu in Pidgin 2.

In purple 2 these actions were defined at the plugin level which meant that
they couldn't be customized on a per account basis. But, with this being a
virtual function on the protocol, customization is possible and the plugin can
have separate actions as well.

### Set Display Name

The [vfunc@Protocol.set_display_name_async] and
[vfunc@Protocol.set_display_name_finish] virtual functions replace the
`set_public_alias` virtual function.

## Protocol Contacts

[iface@ProtocolContacts] handles everything for managing remote contacts.
However it leaves roster management to a separate interface.

### Get Profile

The `get_info` virtual function has been replaced by
[vfunc@ProtocolContacts.get_profile_async] and
[vfunc@ProtocolContacts.get_profile_finish] async pair.

These may be replaced in the future to handle profiles with rich content.

### Get Actions / Get Menu

The `blist_node_menu` virtual functions have been replaced by
[vfunc@ProtocolContacts.get_action_menu]. This gets the actions and the menu
in one swoop.

### Aliasing

The `alias_buddy` virtual function has been replaced by
[vfunc@ProtocolContacts.set_alias_async] and
[vfunc@ProtocolContacts.set_alias_finish].

These will work on any [class@Contact] and since they are an async pair the
result is now accessible which should help avoid desynchronization.

## Protocol Conversation

[iface@ProtocolConversation] handles everything for managing conversations
which includes DMs, Group DMs, Channels and Threads.

Protocols are able to create a conversation at any time and add it to the
default [class@ConversationManager] even if they don't implement this
interface. This isn't exactly supported but it _should_ work.

Many protocols handle channels very differently than direct messages and group
direct messages at least when it comes to creation and joining. To handle
this, there are virtual functions for creating direct messages and group
direct messages specifically and additional virtual functions for joining a
channel. Both of these are covered separately below.

Also, we do not currently have virtual functions for creating a channel. This
will be added eventually, but we haven't had a need for it yet.

### Creating Conversations

Creating a conversation at a user's request is quite complicated. For starters
the way to create a direct message or group direct message is different than
how a channel is created.

To create a DM or Group DM, there are three virtual functions that need to be
implemented. They are:
[vfunc@ProtocolConversation.get_create_conversation_details],
[vfunc@ProtocolConversation.create_conversation_async], and
[vfunc@ProtocolConversation.create_conversation_finish].

[vfunc@ProtocolConversation.get_create_conversation_details] asks the protocol
to create a [class@CreateConversationDetails]. This method will typically be
called by the user interface, but could also be called by anything that wants
to create a conversation.

This is roughly an equivalent of the old `chat_info_defaults` virtual function
but is a lot more rigid. However, as this is a [class@GObject.Object] the
protocol can store additional data on the returned object with
[method@GObject.Object.set_data].

Once the caller has a [class@CreateConversationDetails], it should set
[property@CreateConversationDetails:participants] with the contacts to add to
the conversation. Then [vfunc@ProtocolConversation.create_conversation_async]
can be called with the details to create the conversation and of course call
[vfunc@ProtocolConversation.create_conversation_finish] in the callback
function to get the result.

### Joining Channels

Similar to creating conversations, joining existing channels is a bit
complicated. The old `join_chat`, `chat_info`, and `chat_info_defaults`
virtual functions have been replaced by
[vfunc@ProtocolConversation.get_channel_join_details],
[vfunc@ProtocolConversation.join_channel_async], and
[vfunc@ProtocolConversation.join_channel_finish].

[vfunc@ProtocolConversation.get_channel_join_details] needs to return a
[class@ChannelJoinDetails] with specifics about what options the protocol
supports when it comes to joining channels. A caller will fill out any
necessary fields and call [vfunc@ProtocolConversation.join_channel_async] to
join the channel.

### Leaving Conversations

In Purple 2 you could really only leave a channel as most protocol didn't
have any sort of persistence for DMs. But that has obviously changed. To
accommodate that, we now have the
[vfunc@ProtocolConversation.leave_conversation_async] and
[vfunc@ProtocolConversation.leave_conversation_finish] async pair which
replaces the old `chat_leave` virtual function.

### Refreshing Conversations

There are times when a conversation may need to be "refreshed". The typical
case here is when an account comes back online. Purple doesn't know the state
of an existing conversation when the account comes back online so it will call
[method@ProtocolConversation.refresh] to have the protocol do whatever it
needs to bring a conversation back online.

### Sending Messages

Since conversations are all represented by the same class the old `send_id`
and `send_chat` virtual functions are no more. They are replaced by the async
pair of [vfunc@ProtocolConversation.send_message_async] and
[vfunc@ProtocolConversation.send_message_finish].

### Typing Notifications

Purple 2 only ever implemented typing notifications for DMs, but typing
notifications now exist in all conversation types. As such we now have
[vfunc@ProtocolConversation.send_typing] which replaces the old `send_typing`
virtual function. Purple manages all of the state here and will call them when
it changes.

### Setting Avatars

Some protocols allow setting avatar for group DMs and channels. This was not
supported in Purple 2 but is now available in Purple 3 via the async pair of
[vfunc@ProtocolConversation.set_avatar_async] and
[vfunc@ProtocolConversation.set_avatar_finish].

### Setting Descriptions

Some protocols allow setting a description for conversations. This is similar
to a topic but usually allows for much more text. This was not supported in
Purple 2 as it wasn't really a thing back then.

To allow setting descriptions we have the async pair of
[vfunc@ProtocolConversation.set_description_async] and
[vfunc@ProtocolConversation.set_description_finish].

### Setting Titles

The title of a conversations is usually assigned by the server for DMs or the
name of a channel that was set at creation time. However, group DMs are unique
in that you can generally change their title whenever.

To allow the purple user to set the title we have an async pair of
[vfunc@ProtocolConversation.set_title_async] and
[vfunc@ProtocolConversation.set_title_finish].

### Setting Topics

Topics are commonly used to describe a discussion topic for a channel.
Previously, this was implemented by the `set_chat_topic` virtual function
but is now replaced by the [vfunc@ProtocolConversation.set_topic_async] and
[vfunc@ProtocolConversation.set_topic_finish] async pair.

## Protocol Directory

The protocol directory API is the interface to querying directories of
contacts and channels on a server.

### Searching Contacts

Many protocols now have a way to search contacts whether that's publicly or
from your contacts which is often used when creating Group DMs.

To support this [vfunc@ProtocolDirectory.search_contacts_async] and
[vfunc@ProtocolDirectory.search_contacts_finish] have been created.

### Searching Channels

The old roomlist API has already been removed, but will be replaced by virtual
functions in [iface@ProtocolDirectory] once the design has been completed and
implemented.

## Protocol File Transfers

Like most protocol interfaces, the file transfers interface has been
completely overhauled as well.

The `new_xfer` virtual function has been removed as [class@FileTransfer]
should be able to fulfill any custom needs.

The `can_receive_file` virtual function has been replaced by the async pair of
[vfunc@ProtocolFileTransfer.receive_async] and
[vfunc@ProtocolFileTransfer.receive_finish]. The protocol will read the data
from the client and store it into [property@FileTransfer:local_file].

The `send_file` virtual function has been replaced by the async pair of
[vfunc@ProtocolFileTransfer.send_async] and
[vfunc@ProtocolFileTransfer.send_finish]. The protocol will read the data from
[property@FileTransfer:local-file] and send it out to the remote side.

We may need to add more API here in the future for negotiations

## Protocol Roster

Roster, or contact list, management has been broken out to its own interface
as well. Purple 3 will cache everything between program invocations now, so
protocols that don't have a "server side" roster can skip this entirely unless
they want to add a contact to the contact list by creating a [class@Person]
for them.

For protocols that do have a "server side" roster, this interface is used to
manage it.

Currently groups are only being implemented via [class@Tags] with the name of
`group`. This allows contacts to easily be in multiple groups and lets the user
interfaces display contacts however they wish.

We will probably need to add an authorization request API here, but we haven't
had a need yet.

### Add

The `add_buddy` virtual function has been replaced by an async pair,
[vfunc@ProtocolRoster.add_async] and [vfunc@ProtocolRoster.add_finish]. This
pair will ask the protocol to add a contact to your roster.

The `add_buddies` virtual function has been removed and there is currently no
plan to add it back unless a good use case is discovered for it.

### Update

The update async pair is used to update additional data about a contact like
when a custom avatar or tags are changed for example. There could be more to
this in the future.

### Remove

The `remove_buddy` virtual function has been replace by an async pair,
[vfunc@ProtocolRoster.remove_async] and [vfunc@ProtocolRoster.remove_finish].
These will ask the protocol to remove a contact from your roster.

The `remove_buddies` virtual function has been removed and there is no plan to
add it back unless a good use case is discovered for it.

## Protocol Whiteboard

The whiteboard API in general is best effort right now as we don't have a way
to test any of it. This is strictly due to time.

### Create

The create virtual function is meant to create a protocol specific whiteboard,
but hasn't been looked at in a very long time as it even predates using
[class@ContactInfo] for all users.
