Move the Demo and IRCv3 protocols to the new protocols directory
Testing Done:
Ran `meson dist` and `ninja turtles`
Reviewed at https://reviews.imfreedom.org/r/3047/
--- a/libpurple/protocols/demo/meson.build Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
- 'purpledemoconnection.c',
- 'purpledemoconnection.h',
- 'purpledemocontacts.c',
- 'purpledemocontacts.h',
- 'purpledemoprotocol.c',
- 'purpledemoprotocol.h',
- 'purpledemoprotocolactions.c',
- 'purpledemoprotocolactions.h',
- 'purpledemoprotocolclient.c',
- 'purpledemoprotocolclient.h',
- 'purpledemoprotocolcontacts.c',
- 'purpledemoprotocolcontacts.h',
- 'purpledemoprotocolconversation.c',
- 'purpledemoprotocolconversation.h',
- 'purpledemoprotocolmedia.c',
- 'purpledemoprotocolmedia.h',
- demo_resources = gnome.compile_resources('purpledemoresource',
- 'resources/purpledemo.gresource.xml',
- source_dir : 'resources',
- c_name : 'purple_demo')
- DEMO_SOURCES += demo_resources
- shared_library('demo', DEMO_SOURCES,
- c_args : ['-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Purple-Demo"'],
- gnu_symbol_visibility : 'hidden',
- dependencies : [glib, json, libpurple_dep],
- install_dir : PURPLE_PLUGINDIR)
- devenv.append('PURPLE_PLUGIN_PATH', meson.current_build_dir())
--- a/libpurple/protocols/demo/purpledemoconnection.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpledemoconnection.h"
-#include "purpledemocontacts.h"
-struct _PurpleDemoConnection {
- PurpleConnection parent;
-G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleDemoConnection, purple_demo_connection,
- PURPLE_TYPE_CONNECTION, G_TYPE_FLAG_FINAL, {})
-/******************************************************************************
- * PurpleConnection Implementation
- *****************************************************************************/
-purple_demo_connection_connect(PurpleConnection *connection,
- G_GNUC_UNUSED GError **error)
- PurpleAccount *account = purple_connection_get_account(connection);
- purple_connection_set_state(connection, PURPLE_CONNECTION_STATE_CONNECTED);
- purple_demo_contacts_load(account);
-purple_demo_connection_disconnect(G_GNUC_UNUSED PurpleConnection *connection,
- G_GNUC_UNUSED GError **error)
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-purple_demo_connection_init(G_GNUC_UNUSED PurpleDemoConnection *connection) {
-purple_demo_connection_class_finalize(G_GNUC_UNUSED PurpleDemoConnectionClass *klass) {
-purple_demo_connection_class_init(PurpleDemoConnectionClass *klass) {
- PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass);
- connection_class->connect = purple_demo_connection_connect;
- connection_class->disconnect = purple_demo_connection_disconnect;
-/******************************************************************************
- *****************************************************************************/
-purple_demo_connection_register(GPluginNativePlugin *plugin) {
- purple_demo_connection_register_type(G_TYPE_MODULE(plugin));
--- a/libpurple/protocols/demo/purpledemoconnection.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_CONNECTION_H
-#define PURPLE_DEMO_CONNECTION_H
-#define PURPLE_DEMO_TYPE_CONNECTION (purple_demo_connection_get_type())
-G_DECLARE_FINAL_TYPE(PurpleDemoConnection, purple_demo_connection, PURPLE_DEMO,
- CONNECTION, PurpleConnection)
-G_GNUC_INTERNAL void purple_demo_connection_register(GPluginNativePlugin *plugin);
-#endif /* PURPLE_DEMO_CONNECTION_H */
--- a/libpurple/protocols/demo/purpledemocontacts.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,357 +0,0 @@
- * 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 <json-glib/json-glib.h>
-#include "purpledemocontacts.h"
-#include "purpledemoresource.h"
-/******************************************************************************
- *****************************************************************************/
-purple_demo_contacts_load_contact_icon(PurpleContactInfo *info,
- PurpleAvatar *avatar = NULL;
- path = g_strdup_printf("/im/pidgin/purple/demo/buddy_icons/%s.png", name);
- avatar = purple_avatar_new_from_resource(path, &error);
- g_message("Failed to load find an icon for %s: %s", path,
- if(PURPLE_IS_AVATAR(avatar)) {
- purple_contact_info_set_avatar(info, avatar);
- g_clear_object(&avatar);
-purple_demo_contacts_load_contact_person(JsonObject *person_object,
- PurpleContactInfo *info)
- PurplePerson *person = NULL;
- gboolean new_person = FALSE;
- const char *value = NULL;
- /* If the person has an id, grab it so we can use it when constructing the
- if(json_object_has_member(person_object, "id")) {
- value = json_object_get_string_member(person_object, "id");
- /* See if the contact has an existing person. */
- person = purple_contact_info_get_person(info);
- if(PURPLE_IS_PERSON(person)) {
- const char *existing_id = NULL;
- /* If the existing person's id doesn't match the new one, NULL out
- * person so it'll be recreated with the new id.
- existing_id = purple_person_get_id(person);
- if(!purple_strequal(existing_id, value)) {
- /* If the person didn't exist or it had a different id, create a new person
- if(!PURPLE_IS_PERSON(person)) {
- person = g_object_new(PURPLE_TYPE_PERSON, "id", value, NULL);
- if(json_object_has_member(person_object, "alias")) {
- value = json_object_get_string_member(person_object, "alias");
- if(!purple_strempty(value)) {
- purple_person_set_alias(person, value);
- /* Create the link between the person and the contact info. */
- purple_person_add_contact_info(person, info);
- purple_contact_info_set_person(info, person);
- g_clear_object(&person);
-purple_demo_contacts_load_contact_presence(JsonObject *presence_object,
- PurpleContactInfo *info)
- PurplePresence *presence = NULL;
- const gchar *value = NULL;
- presence = purple_contact_info_get_presence(info);
- if(json_object_has_member(presence_object, "emoji")) {
- value = json_object_get_string_member(presence_object, "emoji");
- if(!purple_strempty(value)) {
- purple_presence_set_emoji(presence, value);
- if(json_object_has_member(presence_object, "idle")) {
- GDateTime *idle_since = NULL;
- ivalue = json_object_get_int_member(presence_object, "idle");
- now = g_date_time_new_now_local();
- idle_since = g_date_time_add_minutes(now, -1 * ivalue);
- purple_presence_set_idle(presence, TRUE, idle_since);
- g_date_time_unref(idle_since);
- g_date_time_unref(now);
- if(json_object_has_member(presence_object, "message")) {
- value = json_object_get_string_member(presence_object, "message");
- if(!purple_strempty(value)) {
- purple_presence_set_message(presence, value);
- if(json_object_has_member(presence_object, "mobile")) {
- gboolean bvalue = FALSE;
- bvalue = json_object_get_boolean_member(presence_object, "mobile");
- purple_presence_set_mobile(presence, bvalue);
- if(json_object_has_member(presence_object, "primitive")) {
- PurplePresencePrimitive primitive = PURPLE_PRESENCE_PRIMITIVE_OFFLINE;
- value = json_object_get_string_member(presence_object, "primitive");
- if(!purple_strempty(value)) {
- GEnumClass *enum_class = NULL;
- GEnumValue *enum_value = NULL;
- enum_class = g_type_class_ref(PURPLE_TYPE_PRESENCE_PRIMITIVE);
- enum_value = g_enum_get_value_by_nick(enum_class, value);
- if(enum_value != NULL) {
- primitive = enum_value->value;
- g_type_class_unref(enum_class);
- purple_presence_set_primitive(presence, primitive);
-purple_demo_contacts_load_contact(PurpleContactManager *manager,
- PurpleAccount *account,
- JsonObject *contact_object)
- PurpleContact *contact = NULL;
- PurpleContactInfo *info = NULL;
- gboolean new_contact = FALSE;
- const char *value = NULL;
- /* If we have an id, grab so we can create the contact with it. */
- if(json_object_has_member(contact_object, "id")) {
- id = json_object_get_string_member(contact_object, "id");
- /* Look for an existing contact before creating a new one. This stops us
- * from getting multiples when we trigger connection errors.
- if(!purple_strempty(id)) {
- contact = purple_contact_manager_find_with_id(manager, account, id);
- /* If we didn't find an existing contact, create it now with the provided
- if(!PURPLE_IS_CONTACT(contact)) {
- contact = purple_contact_new(account, id);
- info = PURPLE_CONTACT_INFO(contact);
- if(json_object_has_member(contact_object, "alias")) {
- value = json_object_get_string_member(contact_object, "alias");
- if(!purple_strempty(value)) {
- purple_contact_info_set_alias(info, value);
- if(json_object_has_member(contact_object, "color")) {
- value = json_object_get_string_member(contact_object, "color");
- if(!purple_strempty(value)) {
- purple_contact_info_set_color(info, value);
- if(json_object_has_member(contact_object, "display_name")) {
- value = json_object_get_string_member(contact_object, "display_name");
- if(!purple_strempty(value)) {
- purple_contact_info_set_display_name(info, value);
- if(json_object_has_member(contact_object, "username")) {
- value = json_object_get_string_member(contact_object, "username");
- if(!purple_strempty(value)) {
- purple_contact_info_set_username(info, value);
- purple_demo_contacts_load_contact_icon(info, value);
- /* Load the profile if it exists. */
- if(json_object_has_member(contact_object, "profile")) {
- value = json_object_get_string_member(contact_object, "profile");
- if(!purple_strempty(value)) {
- g_object_set_data_full(G_OBJECT(info), "demo-profile",
- g_strdup(value), g_free);
- if(json_object_has_member(contact_object, "tags")) {
- PurpleTags *tags = purple_contact_info_get_tags(info);
- JsonArray *array = NULL;
- GList *elements = NULL;
- array = json_object_get_array_member(contact_object, "tags");
- elements = json_array_get_elements(array);
- while(elements != NULL) {
- JsonNode *tag_node = elements->data;
- const char *tag = json_node_get_string(tag_node);
- purple_tags_add(tags, tag);
- elements = g_list_delete_link(elements, elements);
- if(json_object_has_member(contact_object, "person")) {
- JsonObject *person_object = NULL;
- person_object = json_object_get_object_member(contact_object,
- purple_demo_contacts_load_contact_person(person_object, info);
- /* Load the presence. */
- if(json_object_has_member(contact_object, "presence")) {
- JsonObject *presence_object = NULL;
- presence_object = json_object_get_object_member(contact_object,
- purple_demo_contacts_load_contact_presence(presence_object, info);
- /* Finally add the contact to the contact manager if it's new. */
- purple_contact_manager_add(manager, contact);
- g_clear_object(&contact);
-/******************************************************************************
- *****************************************************************************/
-purple_demo_contacts_load(PurpleAccount *account) {
- PurpleContactManager *manager = NULL;
- GInputStream *istream = NULL;
- GList *contacts = NULL;
- JsonArray *contacts_array = NULL;
- JsonNode *root_node = NULL;
- JsonParser *parser = NULL;
- /* get a stream to the contacts.json resource */
- istream = g_resource_open_stream(purple_demo_get_resource(),
- "/im/pidgin/purple/demo/contacts.json",
- G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
- /* create our parser */
- parser = json_parser_new();
- if(!json_parser_load_from_stream(parser, istream, NULL, &error)) {
- g_critical("%s", error->message);
- root_node = json_parser_get_root(parser);
- manager = purple_contact_manager_get_default();
- /* Load the contacts! */
- contacts_array = json_node_get_array(root_node);
- contacts = json_array_get_elements(contacts_array);
- while(contacts != NULL) {
- JsonNode *contact_node = NULL;
- JsonObject *contact_object = NULL;
- contact_node = contacts->data;
- contact_object = json_node_get_object(contact_node);
- purple_demo_contacts_load_contact(manager, account, contact_object);
- contacts = g_list_delete_link(contacts, contacts);
- /* Clean up everything else... */
- g_clear_object(&parser);
- g_input_stream_close(istream, NULL, NULL);
- g_object_unref(istream);
--- a/libpurple/protocols/demo/purpledemocontacts.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_CONTACTS_H
-#define PURPLE_DEMO_CONTACTS_H
-G_GNUC_INTERNAL void purple_demo_contacts_load(PurpleAccount *account);
-#endif /* PURPLE_DEMO_CONTACTS_H */
--- a/libpurple/protocols/demo/purpledemoplugin.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,113 +0,0 @@
- * 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/gi18n-lib.h>
-#include <gplugin-native.h>
-#include "purpledemoplugin.h"
-#include "purpledemoconnection.h"
-#include "purpledemoprotocol.h"
-/******************************************************************************
- *****************************************************************************/
-static PurpleProtocol *demo_protocol = NULL;
-/******************************************************************************
- * GPlugin Implementation
- *****************************************************************************/
-static GPluginPluginInfo *
-purple_demo_plugin_query(G_GNUC_UNUSED GError **error) {
- const gchar *authors[] = {
- "Pidgin Developers <devel@pidgin.im>",
- return purple_plugin_info_new(
- "name", "Demo Protocol Plugin",
- "version", PURPLE_VERSION,
- "category", N_("Protocol"),
- "summary", N_("A protocol plugin used for demos."),
- "description", N_("A protocol plugin that helps to demonstrate "
- "features of libpurple and clients."),
- "website", PURPLE_WEBSITE,
- "abi-version", PURPLE_ABI_VERSION,
- "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
- PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD,
-purple_demo_plugin_load(GPluginPlugin *plugin, GError **error) {
- PurpleProtocolManager *manager = NULL;
- if(PURPLE_IS_PROTOCOL(demo_protocol)) {
- g_set_error_literal(error, PURPLE_DEMO_DOMAIN, 0,
- "plugin was not cleaned up properly");
- purple_demo_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin));
- purple_demo_protocol_register(GPLUGIN_NATIVE_PLUGIN(plugin));
- manager = purple_protocol_manager_get_default();
- demo_protocol = purple_demo_protocol_new();
- if(!purple_protocol_manager_register(manager, demo_protocol, error)) {
- g_clear_object(&demo_protocol);
-purple_demo_plugin_unload(G_GNUC_UNUSED GPluginPlugin *plugin,
- G_GNUC_UNUSED gboolean shutdown,
- PurpleProtocolManager *manager = purple_protocol_manager_get_default();
- if(!PURPLE_IS_PROTOCOL(demo_protocol)) {
- g_set_error_literal(error, PURPLE_DEMO_DOMAIN, 0,
- "plugin was not setup properly");
- if(!purple_protocol_manager_unregister(manager, demo_protocol, error)) {
- g_clear_object(&demo_protocol);
-GPLUGIN_NATIVE_PLUGIN_DECLARE(purple_demo_plugin)
--- a/libpurple/protocols/demo/purpledemoplugin.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_PLUGIN_H
-#define PURPLE_DEMO_PLUGIN_H
-#define PURPLE_DEMO_DOMAIN (g_quark_from_static_string("demo-plugin"))
-#endif /* PURPLE_DEMO_PLUGIN_H */
--- a/libpurple/protocols/demo/purpledemoprotocol.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
- * 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/gi18n-lib.h>
-#include "purpledemoprotocol.h"
-#include "purpledemoconnection.h"
-#include "purpledemoprotocolactions.h"
-#include "purpledemoprotocolclient.h"
-#include "purpledemoprotocolcontacts.h"
-#include "purpledemoprotocolconversation.h"
-#include "purpledemoprotocolmedia.h"
-struct _PurpleDemoProtocol {
-/******************************************************************************
- * PurpleProtocol Implementation
- *****************************************************************************/
-static PurpleConnection *
-purple_demo_protocol_create_connection(PurpleProtocol *protocol,
- PurpleAccount *account,
- G_GNUC_UNUSED GError **error)
- g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL);
- g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
- PURPLE_DEMO_TYPE_CONNECTION,
-purple_demo_protocol_status_types(G_GNUC_UNUSED PurpleProtocol *protocol,
- G_GNUC_UNUSED PurpleAccount *account)
- PurpleStatusType *type = NULL;
- GList *status_types = NULL;
- type = purple_status_type_new_with_attrs(
- PURPLE_STATUS_AVAILABLE, "available", NULL,
- "message", _("Message"), purple_value_new(G_TYPE_STRING),
- status_types = g_list_append(status_types, type);
- type = purple_status_type_new_with_attrs(
- PURPLE_STATUS_AWAY, "away", NULL,
- "message", _("Message"), purple_value_new(G_TYPE_STRING),
- status_types = g_list_append(status_types, type);
- type = purple_status_type_new_with_attrs(
- PURPLE_STATUS_EXTENDED_AWAY, "extended_away", NULL,
- "message", _("Message"), purple_value_new(G_TYPE_STRING),
- status_types = g_list_append(status_types, type);
- type = purple_status_type_new_full(
- PURPLE_STATUS_OFFLINE, NULL, NULL,
- status_types = g_list_append(status_types, type);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_DYNAMIC_TYPE_EXTENDED(
- G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ACTIONS,
- purple_demo_protocol_actions_init)
- G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT,
- purple_demo_protocol_client_init)
- G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONTACTS,
- purple_demo_protocol_contacts_init)
- G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONVERSATION,
- purple_demo_protocol_conversation_init)
- G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_MEDIA,
- purple_demo_protocol_media_init))
-purple_demo_protocol_init(G_GNUC_UNUSED PurpleDemoProtocol *protocol) {
-purple_demo_protocol_class_finalize(G_GNUC_UNUSED PurpleDemoProtocolClass *klass) {
-purple_demo_protocol_class_init(PurpleDemoProtocolClass *klass) {
- PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
- protocol_class->status_types = purple_demo_protocol_status_types;
- protocol_class->create_connection = purple_demo_protocol_create_connection;
-/******************************************************************************
- *****************************************************************************/
-purple_demo_protocol_register(GPluginNativePlugin *plugin) {
- purple_demo_protocol_register_type(G_TYPE_MODULE(plugin));
-purple_demo_protocol_new(void) {
- PURPLE_DEMO_TYPE_PROTOCOL,
- "description", "A protocol plugin with static data to be used in "
- "icon-name", "im-purple-demo",
- "icon-resource-path", "/im/pidgin/purple/demo/icons",
- "options", OPT_PROTO_NO_PASSWORD,
--- a/libpurple/protocols/demo/purpledemoprotocol.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_PROTOCOL_H
-#define PURPLE_DEMO_PROTOCOL_H
-#include <glib-object.h>
-#include <gplugin-native.h>
-#define PURPLE_DEMO_TYPE_PROTOCOL (purple_demo_protocol_get_type())
-G_DECLARE_FINAL_TYPE(PurpleDemoProtocol, purple_demo_protocol, PURPLE_DEMO,
- PROTOCOL, PurpleProtocol)
-G_GNUC_INTERNAL void purple_demo_protocol_register(GPluginNativePlugin *plugin);
-G_GNUC_INTERNAL PurpleProtocol *purple_demo_protocol_new(void);
-#endif /* PURPLE_DEMO_PROTOCOL_H */
--- a/libpurple/protocols/demo/purpledemoprotocolactions.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,950 +0,0 @@
- * 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/gi18n-lib.h>
-#include "purpledemoprotocol.h"
-#include "purpledemoprotocolactions.h"
-#include "purpledemoresource.h"
-/******************************************************************************
- * Connection failure action implementations
- *****************************************************************************/
-#define REAPER_BUDDY_NAME ("Gary")
-#define DEFAULT_REAP_TIME (5) /* seconds */
-#define FATAL_TICK_STR N_("Reaping connection in %d second...")
-#define FATAL_TICK_PLURAL_STR N_("Reaping connection in %d seconds...")
-#define FATAL_DISCONNECT_STR N_("%s reaped the connection")
-#define TEMPORARY_TICK_STR N_("Pruning connection in %d second...")
-#define TEMPORARY_TICK_PLURAL_STR N_("Pruning connection in %d seconds...")
-#define TEMPORARY_DISCONNECT_STR N_("%s pruned the connection")
-purple_demo_protocol_failure_tick(gpointer data,
- PurpleConnectionError error_code,
- const gchar *tick_plural_str,
- const gchar *disconnect_str)
- PurpleConnection *connection = PURPLE_CONNECTION(data);
- PurpleAccount *account = purple_connection_get_account(connection);
- timeout = GPOINTER_TO_INT(g_object_steal_data(G_OBJECT(connection),
- PurpleContact *contact = NULL;
- PurpleContactManager *manager = NULL;
- g_object_set_data(G_OBJECT(connection), "reaping-time",
- GINT_TO_POINTER(timeout));
- manager = purple_contact_manager_get_default();
- contact = purple_contact_manager_find_with_username(manager, account,
- if(PURPLE_IS_CONTACT(contact)) {
- PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact);
- PurplePresence *presence = purple_contact_info_get_presence(info);
- const char *format = ngettext(tick_str, tick_plural_str, timeout);
- message = g_strdup_printf(format, timeout);
- purple_presence_set_message(presence, message);
- return G_SOURCE_CONTINUE;
- message = g_strdup_printf(_(disconnect_str), REAPER_BUDDY_NAME);
- purple_connection_error(connection, error_code, message);
- return G_SOURCE_REMOVE;
-purple_demo_protocol_fatal_failure_cb(gpointer data) {
- return purple_demo_protocol_failure_tick(data,
- PURPLE_CONNECTION_ERROR_CUSTOM_FATAL,
-purple_demo_protocol_temporary_failure_cb(gpointer data) {
- return purple_demo_protocol_failure_tick(data,
- PURPLE_CONNECTION_ERROR_CUSTOM_TEMPORARY,
- TEMPORARY_TICK_PLURAL_STR,
- TEMPORARY_DISCONNECT_STR);
-purple_demo_protocol_failure_action_activate(G_GNUC_UNUSED GSimpleAction *action,
- const gchar *tick_plural_str,
- PurpleAccountManager *account_manager = NULL;
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
- PurpleContact *contact = NULL;
- PurpleContactManager *contact_manager = NULL;
- const char *account_id = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- account_manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(account_manager, account_id);
- connection = purple_account_get_connection(account);
- /* Do nothing if disconnected, or already in process of reaping. */
- if(!PURPLE_IS_CONNECTION(connection)) {
- g_clear_object(&account);
- if(g_object_get_data(G_OBJECT(connection), "reaping-time")) {
- g_clear_object(&account);
- contact_manager = purple_contact_manager_get_default();
- contact = purple_contact_manager_find_with_username(contact_manager,
- if(PURPLE_IS_CONTACT(contact)) {
- PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact);
- PurplePresence *presence = purple_contact_info_get_presence(info);
- const char *format = NULL;
- format = ngettext(tick_str, tick_plural_str, DEFAULT_REAP_TIME);
- message = g_strdup_printf(format, DEFAULT_REAP_TIME);
- purple_presence_set_idle(presence, FALSE, NULL);
- purple_presence_set_message(presence, message);
- g_object_set_data(G_OBJECT(connection), "reaping-time",
- GINT_TO_POINTER(DEFAULT_REAP_TIME));
- g_timeout_add_seconds(1, cb, connection);
- g_clear_object(&account);
-purple_demo_protocol_temporary_failure_action_activate(GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- purple_demo_protocol_failure_action_activate(action, parameter,
- TEMPORARY_TICK_PLURAL_STR,
- purple_demo_protocol_temporary_failure_cb);
-purple_demo_protocol_fatal_failure_action_activate(GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- purple_demo_protocol_failure_action_activate(action, parameter,
- purple_demo_protocol_fatal_failure_cb);
-/******************************************************************************
- * Request API action implementations
- *****************************************************************************/
-purple_demo_protocol_request_input_ok_cb(G_GNUC_UNUSED gpointer data,
- g_message(_("Successfully requested input from UI: %s"), value);
-purple_demo_protocol_request_input_cancel_cb(G_GNUC_UNUSED gpointer data,
- G_GNUC_UNUSED const char *value)
- g_message(_("UI cancelled input request"));
-purple_demo_protocol_request_input_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- gboolean multiline = FALSE, masked = FALSE;
- char *secondary = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- g_clear_object(&account);
- /* Alternate through all four combinations of {masked, multiline}. */
- masked = form % 2 == 1;
- multiline = (form / 2) % 2 == 1;
- secondary = g_strdup_printf(_("The input will be %s %s."),
- masked ? "masked" : "unmasked",
- multiline ? "multiple lines" : "single line");
- purple_request_input(connection, _("Request Input Demo"),
- _("Please input some text…"), secondary, _("default"),
- multiline, masked, NULL,
- G_CALLBACK(purple_demo_protocol_request_input_ok_cb),
- G_CALLBACK(purple_demo_protocol_request_input_cancel_cb),
- purple_request_cpar_from_connection(connection), NULL);
-purple_demo_protocol_request_choice_ok_cb(G_GNUC_UNUSED gpointer data,
- const char *text = value;
- g_message(_("Successfully requested a choice from UI: %s"), text);
-purple_demo_protocol_request_choice_cancel_cb(G_GNUC_UNUSED gpointer data,
- G_GNUC_UNUSED gpointer value)
- g_message(_("UI cancelled choice request"));
-purple_demo_protocol_request_choice_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- g_clear_object(&account);
- purple_request_choice(connection, _("Request Choice Demo"),
- _("Please pick an option…"), NULL, _("foo"),
- G_CALLBACK(purple_demo_protocol_request_choice_ok_cb),
- G_CALLBACK(purple_demo_protocol_request_choice_cancel_cb),
- purple_request_cpar_from_connection(connection),
- NULL, _("foo"), "foo", _("bar"), "bar",
- _("baz"), "baz", NULL);
-purple_demo_protocol_request_action_cb(G_GNUC_UNUSED gpointer data, int action)
- g_message(_("Successfully requested an action from the UI: %d"), action);
-purple_demo_protocol_request_action_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- g_clear_object(&account);
- purple_request_action(connection, _("Request Action Demo"),
- _("Please choose an action…"), NULL, 1,
- purple_request_cpar_from_connection(connection),
- _("foo"), purple_demo_protocol_request_action_cb,
- _("bar"), purple_demo_protocol_request_action_cb,
- _("baz"), purple_demo_protocol_request_action_cb);
-} PurpleDemoProtocolWaitData;
-purple_demo_protocol_request_wait_pulse_cb(gpointer data) {
- PurpleDemoProtocolWaitData *wait = data;
- purple_request_wait_pulse(wait->ui_handle);
- return G_SOURCE_CONTINUE;
-purple_demo_protocol_request_wait_cancel_cb(G_GNUC_UNUSED gpointer data) {
- g_message(_("UI cancelled wait request"));
-purple_demo_protocol_request_wait_close_cb(gpointer data) {
- PurpleDemoProtocolWaitData *wait = data;
- g_source_remove(wait->id);
-purple_demo_protocol_request_wait_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- PurpleDemoProtocolWaitData *wait = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- g_clear_object(&account);
- wait = g_new0(PurpleDemoProtocolWaitData, 1);
- wait->ui_handle = purple_request_wait(connection, _("Request Wait Demo"),
- _("Please wait…"), NULL, TRUE,
- purple_demo_protocol_request_wait_cancel_cb,
- purple_request_cpar_from_connection(connection),
- wait->id = g_timeout_add(250, purple_demo_protocol_request_wait_pulse_cb,
- purple_request_add_close_notify(wait->ui_handle,
- purple_demo_protocol_request_wait_close_cb,
-purple_demo_protocol_request_fields_ok_cb(G_GNUC_UNUSED gpointer data,
- PurpleRequestPage *page)
- PurpleAccount *account = NULL;
- PurpleRequestFieldList *field = NULL;
- const char *tmp = NULL;
- info = g_string_new(_("Basic group:\n"));
- g_string_append_printf(info, _("\tString: %s\n"),
- purple_request_page_get_string(page, "string"));
- g_string_append_printf(info, _("\tMultiline string: %s\n"),
- purple_request_page_get_string(page,
- g_string_append_printf(info, _("\tMasked string: %s\n"),
- purple_request_page_get_string(page,
- g_string_append_printf(info, _("\tAlphanumeric string: %s\n"),
- purple_request_page_get_string(page,
- g_string_append_printf(info, _("\tEmail string: %s\n"),
- purple_request_page_get_string(page, "email"));
- g_string_append_printf(info, _("\tInteger: %d\n"),
- purple_request_page_get_integer(page, "int"));
- g_string_append_printf(info, _("\tBoolean: %s\n"),
- purple_request_page_get_bool(page, "bool") ?
- _("TRUE") : _("FALSE"));
- g_string_append(info, _("Multiple-choice group:\n"));
- tmp = (const char *)purple_request_page_get_choice(page, "choice");
- g_string_append_printf(info, _("\tChoice: %s\n"), tmp);
- field = PURPLE_REQUEST_FIELD_LIST(purple_request_page_get_field(page,
- list = purple_request_field_list_get_selected(field);
- tmp = (const char *)list->data;
- g_string_append_printf(info, _("\tList: %s\n"), tmp);
- field = PURPLE_REQUEST_FIELD_LIST(purple_request_page_get_field(page,
- list = purple_request_field_list_get_selected(field);
- g_string_append(info, _("\tMulti-list: ["));
- tmp = (const char *)list->data;
- g_string_append_printf(info, "%s%s", tmp,
- list->next != NULL ? ", " : "");
- g_string_append(info, _("]\n"));
- g_string_append(info, _("Special group:\n"));
- account = purple_request_page_get_account(page, "account");
- if(PURPLE_IS_ACCOUNT(account)) {
- tmp = purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(account));
- g_string_append_printf(info, _("\tAccount: %s\n"), tmp);
- g_message(_("Successfully requested fields:\n%s"), info->str);
- g_string_free(info, TRUE);
-purple_demo_protocol_request_fields_cancel_cb(G_GNUC_UNUSED gpointer data,
- G_GNUC_UNUSED PurpleRequestPage *page)
- g_message(_("UI cancelled field request"));
-purple_demo_protocol_request_fields_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- PurpleRequestPage *page = NULL;
- PurpleRequestGroup *group = NULL;
- PurpleRequestField *boolfield = NULL;
- PurpleRequestField *field = NULL;
- PurpleRequestFieldChoice *choice_field = NULL;
- PurpleRequestFieldList *list_field = NULL;
- gconstpointer icon_data = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- page = purple_request_page_new();
- /* This group will contain basic fields. */
- group = purple_request_group_new(_("Basic"));
- purple_request_page_add_group(page, group);
- boolfield = purple_request_field_bool_new("bool", _("Sensitive?"), TRUE);
- purple_request_field_set_tooltip(boolfield,
- _("Allow modifying all fields."));
- purple_request_group_add_field(group, boolfield);
- field = purple_request_field_label_new("basic-label",
- _("This group contains basic fields"));
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_string_new("string", _("A string"),
- purple_request_field_set_required(field, TRUE);
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_string_new("multiline-string",
- _("A multiline string"),
- purple_request_group_add_field(group, field);
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- field = purple_request_field_string_new("masked-string",
- _("A masked string"), _("default"),
- purple_request_field_string_set_masked(PURPLE_REQUEST_FIELD_STRING(field),
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_string_new("alphanumeric",
- _("An alphanumeric string"),
- purple_request_field_set_validator(field,
- purple_request_field_alphanumeric_validator,
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_string_new("email", _("An email"),
- _("me@example.com"), FALSE);
- purple_request_field_set_validator(field,
- purple_request_field_email_validator,
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_int_new("int", _("An integer"), 123, -42, 1337);
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- /* This group will contain fields with multiple options. */
- group = purple_request_group_new(_("Multiple"));
- purple_request_page_add_group(page, group);
- field = purple_request_field_label_new("multiple-label",
- _("This group contains fields with multiple options"));
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_choice_new("choice", _("A choice"), "foo");
- choice_field = PURPLE_REQUEST_FIELD_CHOICE(field);
- purple_request_field_choice_add(choice_field, _("foo"), "foo");
- purple_request_field_choice_add(choice_field, _("bar"), "bar");
- purple_request_field_choice_add(choice_field, _("baz"), "baz");
- purple_request_field_choice_add(choice_field, _("quux"), "quux");
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_list_new("list", _("A list"));
- list_field = PURPLE_REQUEST_FIELD_LIST(field);
- purple_request_field_list_add_icon(list_field, _("foo"), NULL, "foo");
- purple_request_field_list_add_icon(list_field, _("bar"), NULL, "bar");
- purple_request_field_list_add_icon(list_field, _("baz"), NULL, "baz");
- purple_request_field_list_add_icon(list_field, _("quux"), NULL, "quux");
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_list_new("multilist", _("A multi-select list"));
- list_field = PURPLE_REQUEST_FIELD_LIST(field);
- purple_request_field_list_set_multi_select(list_field, TRUE);
- purple_request_field_list_add_icon(list_field, _("foo"), NULL, "foo");
- purple_request_field_list_add_icon(list_field, _("bar"), NULL, "bar");
- purple_request_field_list_add_icon(list_field, _("baz"), NULL, "baz");
- purple_request_field_list_add_icon(list_field, _("quux"), NULL, "quux");
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- /* This group will contain specialized fields. */
- group = purple_request_group_new(_("Special"));
- purple_request_page_add_group(page, group);
- field = purple_request_field_label_new("special-label",
- _("This group contains specialized fields"));
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- icon = g_resource_lookup_data(purple_demo_get_resource(),
- "/im/pidgin/purple/demo/icons/scalable/apps/im-purple-demo.svg",
- G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
- icon_data = g_bytes_get_data(icon, &icon_len);
- field = purple_request_field_image_new("image", _("An image"),
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- field = purple_request_field_account_new("account", _("An account"),
- g_object_bind_property(boolfield, "value", field, "sensitive", 0);
- purple_request_group_add_field(group, field);
- purple_request_fields(connection, _("Request Fields Demo"),
- _("Please fill out these fields…"), NULL, page,
- G_CALLBACK(purple_demo_protocol_request_fields_ok_cb),
- G_CALLBACK(purple_demo_protocol_request_fields_cancel_cb),
- purple_request_cpar_from_connection(connection),
- g_clear_object(&account);
-purple_demo_protocol_request_path_ok_cb(gpointer data, const char *filename) {
- const char *type = data;
- g_message(_("Successfully requested %s from UI: %s"), type, filename);
-purple_demo_protocol_request_path_cancel_cb(gpointer data) {
- const char *type = data;
- g_message(_("UI cancelled %s request"), type);
-purple_demo_protocol_request_file_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- g_clear_object(&account);
- purple_request_file(connection, _("Request File Demo"),
- G_CALLBACK(purple_demo_protocol_request_path_ok_cb),
- G_CALLBACK(purple_demo_protocol_request_path_cancel_cb),
- purple_request_cpar_from_connection(connection),
-purple_demo_protocol_request_folder_activate(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleConnection *connection = NULL;
- const gchar *account_id = NULL;
- PurpleAccountManager *manager = NULL;
- PurpleAccount *account = NULL;
- if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) {
- g_critical("Demo failure action parameter is of incorrect type %s",
- g_variant_get_type_string(parameter));
- account_id = g_variant_get_string(parameter, NULL);
- manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(manager, account_id);
- connection = purple_account_get_connection(account);
- g_clear_object(&account);
- purple_request_folder(connection, _("Request Folder Demo"), NULL,
- G_CALLBACK(purple_demo_protocol_request_path_ok_cb),
- G_CALLBACK(purple_demo_protocol_request_path_cancel_cb),
- purple_request_cpar_from_connection(connection),
-/******************************************************************************
- * Contact action implementations
- *****************************************************************************/
-static const gchar *contacts[] = {"Alice", "Bob", "Carlos", "Erin" };
-purple_demo_protocol_remote_add(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleAccount *account = NULL;
- PurpleAccountManager *account_manager = NULL;
- PurpleAddContactRequest *request = NULL;
- PurpleNotification *notification = NULL;
- PurpleNotificationManager *notification_manager = NULL;
- const gchar *account_id = NULL;
- static guint counter = 0;
- account_id = g_variant_get_string(parameter, NULL);
- account_manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(account_manager, account_id);
- request = purple_add_contact_request_new(account, contacts[counter]);
- notification = purple_notification_new_from_add_contact_request(request);
- notification_manager = purple_notification_manager_get_default();
- purple_notification_manager_add(notification_manager, notification);
- if(counter >= G_N_ELEMENTS(contacts)) {
- g_clear_object(¬ification);
- g_clear_object(&account);
-static const char *puns[] = {
- "Toucan play at that game!",
- "As long as it's not too much of a birden...",
- "Sounds like a bit of ostrich...",
- "People can't stop raven!",
- "Have you heard about the bird?",
-purple_demo_protocol_generic_notification(G_GNUC_UNUSED GSimpleAction *action,
- G_GNUC_UNUSED gpointer data)
- PurpleAccount *account = NULL;
- PurpleAccountManager *account_manager = NULL;
- PurpleNotification *notification = NULL;
- PurpleNotificationManager *notification_manager = NULL;
- const char *account_id = NULL;
- static guint counter = 0;
- account_id = g_variant_get_string(parameter, NULL);
- account_manager = purple_account_manager_get_default();
- account = purple_account_manager_find_by_id(account_manager, account_id);
- notification = purple_notification_new(PURPLE_NOTIFICATION_TYPE_GENERIC,
- account, g_strdup(puns[counter]),
- notification_manager = purple_notification_manager_get_default();
- purple_notification_manager_add(notification_manager, notification);
- if(counter >= G_N_ELEMENTS(puns)) {
- g_clear_object(¬ification);
- g_clear_object(&account);
-/******************************************************************************
- * PurpleProtocolActions Implementation
- *****************************************************************************/
-purple_demo_protocol_get_prefix(G_GNUC_UNUSED PurpleProtocolActions *actions) {
-purple_demo_protocol_get_action_group(G_GNUC_UNUSED PurpleProtocolActions *actions,
- G_GNUC_UNUSED PurpleConnection *connection)
- GSimpleActionGroup *group = NULL;
- GActionEntry entries[] = {
- .name = "temporary-failure",
- .activate = purple_demo_protocol_temporary_failure_action_activate,
- .name = "fatal-failure",
- .activate = purple_demo_protocol_fatal_failure_action_activate,
- .name = "generic-notification",
- .activate = purple_demo_protocol_generic_notification,
- .activate = purple_demo_protocol_remote_add,
- .name = "request-input",
- .activate = purple_demo_protocol_request_input_activate,
- .name = "request-choice",
- .activate = purple_demo_protocol_request_choice_activate,
- .name = "request-action",
- .activate = purple_demo_protocol_request_action_activate,
- .name = "request-wait",
- .activate = purple_demo_protocol_request_wait_activate,
- .name = "request-fields",
- .activate = purple_demo_protocol_request_fields_activate,
- .name = "request-file",
- .activate = purple_demo_protocol_request_file_activate,
- .name = "request-folder",
- .activate = purple_demo_protocol_request_folder_activate,
- gsize nentries = G_N_ELEMENTS(entries);
- group = g_simple_action_group_new();
- g_action_map_add_action_entries(G_ACTION_MAP(group), entries, nentries,
- return G_ACTION_GROUP(group);
-purple_demo_protocol_get_menu(G_GNUC_UNUSED PurpleProtocolActions *actions,
- G_GNUC_UNUSED PurpleConnection *connection)
- GMenuItem *item = NULL;
- item = g_menu_item_new(_("Trigger temporary connection failure..."),
- "prpl-demo.temporary-failure");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(menu, item);
- item = g_menu_item_new(_("Trigger fatal connection failure..."),
- "prpl-demo.fatal-failure");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(menu, item);
- item = g_menu_item_new(_("Trigger a generic notification"),
- "prpl-demo.generic-notification");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(menu, item);
- item = g_menu_item_new(_("Trigger a contact adding you"),
- "prpl-demo.remote-add");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(menu, item);
- submenu = g_menu_new();
- item = g_menu_item_new(_("Input"), "prpl-demo.request-input");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- item = g_menu_item_new(_("Choice"), "prpl-demo.request-choice");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- item = g_menu_item_new(_("Action"), "prpl-demo.request-action");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- item = g_menu_item_new(_("Wait"), "prpl-demo.request-wait");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- item = g_menu_item_new(_("Fields"), "prpl-demo.request-fields");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- item = g_menu_item_new(_("File"), "prpl-demo.request-file");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- item = g_menu_item_new(_("Folder"), "prpl-demo.request-folder");
- g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
- g_menu_append_item(submenu, item);
- g_menu_append_submenu(menu, _("Trigger requests"), G_MENU_MODEL(submenu));
- g_object_unref(submenu);
-purple_demo_protocol_actions_init(PurpleProtocolActionsInterface *iface) {
- iface->get_prefix = purple_demo_protocol_get_prefix;
- iface->get_action_group = purple_demo_protocol_get_action_group;
- iface->get_menu = purple_demo_protocol_get_menu;
--- a/libpurple/protocols/demo/purpledemoprotocolactions.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_PROTOCOL_ACTIONS_H
-#define PURPLE_DEMO_PROTOCOL_ACTIONS_H
-G_GNUC_INTERNAL void purple_demo_protocol_actions_init(PurpleProtocolActionsInterface *iface);
-#endif /* PURPLE_DEMO_PROTOCOL_ACTIONS_H */
--- a/libpurple/protocols/demo/purpledemoprotocolclient.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
- * 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/gi18n-lib.h>
-#include "purpledemoprotocol.h"
-#include "purpledemoprotocolclient.h"
-/******************************************************************************
- * PurpleProtocolClient Implementation
- *****************************************************************************/
-purple_demo_protocol_client_init(G_GNUC_UNUSED PurpleProtocolClientInterface *iface) {
--- a/libpurple/protocols/demo/purpledemoprotocolclient.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_PROTOCOL_CLIENT_H
-#define PURPLE_DEMO_PROTOCOL_CLIENT_H
-G_GNUC_INTERNAL void purple_demo_protocol_client_init(PurpleProtocolClientInterface *iface);
-#endif /* PURPLE_DEMO_PROTOCOL_CLIENT_H */
--- a/libpurple/protocols/demo/purpledemoprotocolcontacts.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
- * 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/gi18n-lib.h>
-#include "purpledemoprotocolcontacts.h"
-/******************************************************************************
- * PurpleProtocolContacts Implementation
- *****************************************************************************/
-purple_demo_protocol_contacts_get_profile_finish(G_GNUC_UNUSED PurpleProtocolContacts *contacts,
- g_return_val_if_fail(G_IS_TASK(result), NULL);
- return g_task_propagate_pointer(G_TASK(result), error);
-purple_demo_protocol_contacts_get_profile_async(PurpleProtocolContacts *contacts,
- PurpleContactInfo *info,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- const char *profile = NULL;
- task = g_task_new(contacts, cancellable, callback, data);
- profile = g_object_get_data(G_OBJECT(info), "demo-profile");
- g_task_return_pointer(task, g_strdup(profile), g_free);
-purple_demo_protocol_contacts_init(PurpleProtocolContactsInterface *iface) {
- iface->get_profile_async = purple_demo_protocol_contacts_get_profile_async;
- iface->get_profile_finish = purple_demo_protocol_contacts_get_profile_finish;
--- a/libpurple/protocols/demo/purpledemoprotocolcontacts.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
- * 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/liceng_task_return_pointerses/>.
-#ifndef PURPLE_DEMO_PROTOCOL_CONTACTS_H
-#define PURPLE_DEMO_PROTOCOL_CONTACTS_H
-G_GNUC_INTERNAL void purple_demo_protocol_contacts_init(PurpleProtocolContactsInterface *iface);
-#endif /* PURPLE_DEMO_PROTOCOL_CONTACTS_H */
--- a/libpurple/protocols/demo/purpledemoprotocolconversation.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
- * 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/gi18n-lib.h>
-#include "purpledemoprotocol.h"
-#include "purpledemoprotocolconversation.h"
-/******************************************************************************
- * PurpleProtocolConversation Implementation
- *****************************************************************************/
- PurpleConversation *conversation;
- PurpleMessage *message;
-} PurpleDemoProtocolIMInfo;
-purple_demo_protocol_im_info_free(PurpleDemoProtocolIMInfo *info) {
- g_clear_object(&info->conversation);
- g_clear_object(&info->message);
-purple_demo_protocol_echo_im_cb(gpointer data) {
- PurpleDemoProtocolIMInfo *info = data;
- PurpleMessage *message = NULL;
- PurpleMessageFlags flags;
- const char *who = NULL;
- /* Turn outgoing message back incoming. */
- who = purple_conversation_get_name(info->conversation);
- flags = purple_message_get_flags(info->message);
- flags &= ~PURPLE_MESSAGE_SEND;
- flags |= PURPLE_MESSAGE_RECV;
- message = purple_message_new_incoming(who,
- purple_message_get_contents(info->message),
- purple_conversation_write_message(info->conversation, message);
- g_clear_object(&message);
- return G_SOURCE_REMOVE;
-purple_demo_protocol_send_message_async(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
- PurpleConversation *conversation,
- PurpleMessage *message,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- const char *who = NULL;
- who = purple_conversation_get_name(conversation);
- if(purple_strempty(who)) {
- who = purple_message_get_recipient(message);
- if(purple_strequal(who, "Echo")) {
- PurpleDemoProtocolIMInfo *info = g_new(PurpleDemoProtocolIMInfo, 1);
- info->conversation = g_object_ref(conversation);
- info->message = g_object_ref(message);
- g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
- purple_demo_protocol_echo_im_cb, info,
- (GDestroyNotify)purple_demo_protocol_im_info_free);
- } else if(purple_strequal(who, "Aegina")) {
- PurpleDemoProtocolIMInfo *info = g_new(PurpleDemoProtocolIMInfo, 1);
- const char *author = purple_message_get_author(message);
- const char *contents = NULL;
- if(purple_strequal(author, "Hades")) {
- /* TRANSLATORS: This is a reference to the Cap of Invisibility owned by
- * various Greek gods, such as Hades, as mentioned. */
- contents = _("Don't tell Hades I have his Cap");
- info->conversation = g_object_ref(conversation);
- info->message = purple_message_new_outgoing(author, who, contents,
- g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, purple_demo_protocol_echo_im_cb,
- info, (GDestroyNotify)purple_demo_protocol_im_info_free);
- purple_conversation_write_message(conversation, message);
- task = g_task_new(protocol, cancellable, callback, data);
- g_task_return_boolean(task, TRUE);
-purple_demo_protocol_send_message_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
- g_return_val_if_fail(G_IS_TASK(result), FALSE);
- return g_task_propagate_boolean(G_TASK(result), error);
-purple_demo_protocol_conversation_init(PurpleProtocolConversationInterface *iface) {
- iface->send_message_async = purple_demo_protocol_send_message_async;
- iface->send_message_finish = purple_demo_protocol_send_message_finish;
--- a/libpurple/protocols/demo/purpledemoprotocolconversation.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_PROTOCOL_CONVERSATION_H
-#define PURPLE_DEMO_PROTOCOL_CONVERSATION_H
-G_GNUC_INTERNAL void purple_demo_protocol_conversation_init(PurpleProtocolConversationInterface *iface);
-#endif /* PURPLE_DEMO_PROTOCOL_CONVERSATION_H */
--- a/libpurple/protocols/demo/purpledemoprotocolmedia.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
- * 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/gi18n-lib.h>
-#include "purpledemoprotocol.h"
-#include "purpledemoprotocolmedia.h"
-/******************************************************************************
- * PurpleProtocolMedia Implementation
- *****************************************************************************/
-purple_demo_protocol_media_get_caps(G_GNUC_UNUSED PurpleProtocolMedia *media,
- G_GNUC_UNUSED PurpleAccount *account,
- if(purple_strequal(who, "Echo")) {
- return PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_VIDEO |
- PURPLE_MEDIA_CAPS_AUDIO_VIDEO;
- return PURPLE_MEDIA_CAPS_NONE;
-purple_demo_protocol_media_initiate_session(G_GNUC_UNUSED PurpleProtocolMedia *media,
- PurpleAccount *account,
- PurpleMediaSessionType type)
- PurpleConnection *connection = NULL;
- gchar *session_name = NULL;
- GDateTime *timestamp = NULL;
- connection = purple_account_get_connection(account);
- session_name = g_flags_to_string(PURPLE_MEDIA_TYPE_SESSION_TYPE, type);
- message = g_strdup_printf(_("Initiated demo %s session with %s"),
- timestamp = g_date_time_new_now_utc();
- purple_serv_got_im(connection, "Echo",
- message, PURPLE_MESSAGE_RECV,
- g_date_time_to_unix(timestamp));
- g_date_time_unref(timestamp);
- /* TODO: When libpurple gets a backend, we can implement more of this. */
-purple_demo_protocol_media_send_dtmf(G_GNUC_UNUSED PurpleProtocolMedia *protocol_media,
- PurpleMedia *media, gchar dtmf,
- guint8 volume, guint8 duration)
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
- GDateTime *timestamp = NULL;
- account = purple_media_get_account(media);
- connection = purple_account_get_connection(account);
- message = g_strdup_printf(_("Received DTMF %c at volume %d for %d seconds"),
- dtmf, volume, duration);
- timestamp = g_date_time_new_now_utc();
- purple_serv_got_im(connection, "Echo",
- message, PURPLE_MESSAGE_RECV,
- g_date_time_to_unix(timestamp));
- g_date_time_unref(timestamp);
-purple_demo_protocol_media_init(PurpleProtocolMediaInterface *iface) {
- iface->get_caps = purple_demo_protocol_media_get_caps;
- iface->initiate_session = purple_demo_protocol_media_initiate_session;
- iface->send_dtmf = purple_demo_protocol_media_send_dtmf;
--- a/libpurple/protocols/demo/purpledemoprotocolmedia.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
- * 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/>.
-#ifndef PURPLE_DEMO_PROTOCOL_MEDIA_H
-#define PURPLE_DEMO_PROTOCOL_MEDIA_H
-G_GNUC_INTERNAL void purple_demo_protocol_media_init(PurpleProtocolMediaInterface *iface);
-#endif /* PURPLE_DEMO_PROTOCOL_MEDIA_H */
Binary file libpurple/protocols/demo/resources/buddy_icons/Aegina.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/Echo.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/Eion.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/Elliott.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/Gary.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/John.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/Markus.png has changed
Binary file libpurple/protocols/demo/resources/buddy_icons/Richard.png has changed
--- a/libpurple/protocols/demo/resources/buddy_icons/mkicon.py Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-# Purple - Internet Messaging Library
-# Copyright (C) Pidgin Developers <devel@pidgin.im>
-# Purple is the legal property of its developers, whose names are too numerous
-# to list here. Please refer to the COPYRIGHT file distributed with this
-# 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/>.
-from pathlib import Path
-from PIL import Image, ImageDraw, ImageFont
-parser = argparse.ArgumentParser(
- description='Generate buddy icons from user names.')
-parser.add_argument('name', nargs='+', help='The name(s) to use.')
-parser.add_argument('-f', '--font',
- default='/usr/share/fonts/urw-base35/D050000L.otf',
- help='Path to (TrueType or OpenType) font to use.')
-parser.add_argument('-s', '--size', default=96, type=int,
- help='Size of buddy icons to produce.')
-parser.add_argument('-o', '--output', default='.',
- help='Directory in which to place files.')
-args = parser.parse_args()
-def calculate_colours_for_text(text):
- Calculate the foreground and background colours from text.
- This is based on pidgin_color_calculate_for_text in Pidgin C code.
- # Hash the string and get the first 2 bytes of the digest.
- checksum = hashlib.sha1()
- checksum.update(text.encode('utf-8'))
- digest = checksum.digest()
- # Calculate the hue based on the digest, scaled to 0-1.
- hue = (digest[0] << 8 | digest[1]) / 65535
- # Get the RGB values for the hue at full saturation and value.
- foreground = colorsys.hsv_to_rgb(hue, 1.0, 1.0)
- # Calculate the hue based on the end of the digest, scaled to 0-1.
- hue = (digest[-1] << 8 | digest[-2]) / 65535
- # Get the RGB values for the hue at full saturation and low value.
- background = colorsys.hsv_to_rgb(hue, 1.0, 0.2)
- # Finally calculate the foreground summing 20% of the inverted background
- # with 80% of the foreground.
- (0.2 * (1 - bc)) + (0.8 * fc) for bc, fc in zip(background, foreground)
- # Pillow requires colours in 0-255.
- return (tuple(int(c * 255) for c in foreground),
- tuple(int(c * 255) for c in background))
-output_dir = Path(args.output)
-font = ImageFont.truetype(args.font, size=int(args.size * 96/72))
- fore, back = calculate_colours_for_text(name)
- # Generate an image using the first letter of the name to choose a glyph
- # from the specified font. The default font generates some star-like or
- img = Image.new('RGBA', (args.size, args.size), color=back)
- draw = ImageDraw.Draw(img)
- letter = name[0].upper()
- draw.text((args.size // 2, args.size - 1), letter,
- font=font, anchor='mb', fill=fore)
- img.save(output_dir / f'{name}.png', 'PNG')
--- a/libpurple/protocols/demo/resources/contacts.json Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
- "primitive": "available",
- "message": "💤 Sleeping... 😴",
- "profile": "Pidgin 3 will be ready when it is ready! 🗿"
- "profile": "Idiot Windows sysadmin who used to develop for Pidgin and libpurple in the bright days before Windows 8"
- "primitive": "available"
- "primitive": "available",
- "message": "Writing crazy patches!"
- "primitive": "do-not-disturb",
- "message": "Running all the things in valgrind..."
- "primitive": "available",
- "message": "Cursed to speak the last words spoken to me"
- "primitive": "offline",
- "message": "Stole the Cap of Invisibility from Hades"
Binary file libpurple/protocols/demo/resources/icons/16x16/apps/im-purple-demo.png has changed
Binary file libpurple/protocols/demo/resources/icons/22x22/apps/im-purple-demo.png has changed
Binary file libpurple/protocols/demo/resources/icons/48x48/apps/im-purple-demo.png has changed
--- a/libpurple/protocols/demo/resources/icons/scalable/apps/im-purple-demo.svg Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
- viewBox="0 0 37.851284 39.057049"
- inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
- sodipodi:docname="im-purple-demo.svg"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/">
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:cx="25.178571"
- inkscape:cy="33.571429"
- inkscape:document-units="mm"
- inkscape:current-layer="layer1"
- inkscape:window-width="1916"
- inkscape:window-height="1017"
- inkscape:window-x="1080"
- inkscape:window-y="493"
- inkscape:window-maximized="1"
- inkscape:pagecheckerboard="0" />
- <dc:format>image/svg+xml</dc:format>
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- transform="translate(75.835419,-56.26127)">
- style="isolation:isolate;fill:#4b2854;fill-opacity:1;stroke:#fdfdfd;stroke-width:11.7132;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
- inkscape:connector-curvature="0"
- d="m 137.45776,361.78346 q 9.2531,10.42501 11.91562,19.37813 a 19.687501,19.687501 0 0 0 -9.08437,2.24061 17.090626,17.090626 0 0 0 -6.84376,6.38437 19.312501,19.312501 0 0 0 -2.74688,9.76876 c -0.15944,5.625 1.64063,10.15313 5.44688,13.54687 a 20.831251,20.831251 0 0 0 13.70626,5.33438 19.687501,19.687501 0 0 0 11.61563,-3.02811 20.690626,20.690626 0 0 0 7.36874,-8.22189 23.690626,23.690626 0 0 0 2.65312,-10.14374 39.196877,39.196877 0 0 0 -6.79688,-22.98749 87.590631,87.590631 0 0 0 -18.20626,-19.98751 z"
- transform="matrix(0.37919911,0,0,0.38886781,-123.17216,-79.14694)" />
- style="isolation:isolate;fill:#5c3566;stroke:#fdfdfd;stroke-width:11.7132;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
- inkscape:connector-curvature="0"
- d="m 184.75465,435.07723 q 9.25309,-10.42501 11.90622,-19.37814 a 19.687501,19.687501 0 0 1 -9.08437,-2.24064 17.062501,17.062501 0 0 1 -6.83437,-6.38437 19.312501,19.312501 0 0 1 -2.75626,-9.75936 q -0.23457,-8.4375 5.45626,-13.55627 a 20.812501,20.812501 0 0 1 13.70626,-5.33438 19.687501,19.687501 0 0 1 11.6156,3.0375 20.709376,20.709376 0 0 1 7.36877,8.20315 23.765626,23.765626 0 0 1 2.65312,10.15313 39.281254,39.281254 0 0 1 -6.79688,22.98749 87.590631,87.590631 0 0 1 -18.20626,19.98751 z"
- transform="matrix(0.37919911,0,0,0.38886781,-123.17216,-79.14694)" />
--- a/libpurple/protocols/demo/resources/purpledemo.gresource.xml Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
- <gresource prefix="/im/pidgin/purple/demo">
- <file compressed="true" preprocess="json-stripblanks">contacts.json</file>
- <file>icons/16x16/apps/im-purple-demo.png</file>
- <file>icons/22x22/apps/im-purple-demo.png</file>
- <file>icons/48x48/apps/im-purple-demo.png</file>
- <file>icons/scalable/apps/im-purple-demo.svg</file>
- <file>buddy_icons/Aegina.png</file>
- <file>buddy_icons/Echo.png</file>
- <file>buddy_icons/Eion.png</file>
- <file>buddy_icons/Elliott.png</file>
- <file>buddy_icons/Gary.png</file>
- <file>buddy_icons/John.png</file>
- <file>buddy_icons/Markus.png</file>
- <file>buddy_icons/Richard.png</file>
--- a/libpurple/protocols/ircv3/README.md Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-This is a brand new from-scratch protocol plugin which is the first protocol
-plugin to be 100% code reviewed. It uses regex to tokenize messages.
-We are intending for it to be subclass-able so other networks like Twitch.tv can
-be supported but we're not quite there yet.
-We also are intending to support subclassing in other languages but we've run
-into some issues with dynamic GObject types and GObject introspection that have
-This is a list of capabilities that we currently support. We'll do our best to
-keep this list up to date, but if you notice we've missed something please let
-* sasl (right now just PLAIN works)
--- a/libpurple/protocols/ircv3/ircv3generategir.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <gplugin-introspection.h>
-main(int argc, char *argv[]) {
- return gplugin_introspection_introspect_plugin(&argc, &argv,
--- a/libpurple/protocols/ircv3/meson.build Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
- 'purpleircv3capabilities.c',
- 'purpleircv3connection.c',
- 'purpleircv3formatting.c',
- 'purpleircv3message.c',
- 'purpleircv3messagehandlers.c',
- 'purpleircv3protocol.c',
- 'purpleircv3protocolconversation.c',
- 'purpleircv3capabilities.h',
- 'purpleircv3connection.h',
- 'purpleircv3constants.h',
- 'purpleircv3formatting.h',
- 'purpleircv3message.h',
- 'purpleircv3messagehandlers.h',
- 'purpleircv3protocol.h',
- 'purpleircv3protocolconversation.h',
-ircv3_filebase = f'purple-@purple_major_version@-ircv3'
-ircv3_includes = include_directories('.')
-ircv3_include_base = purple_include_base / 'protocols/ircv3'
-ircv3_resources = gnome.compile_resources('ircv3resource',
- 'resources/ircv3.gresource.xml',
- source_dir : 'resources',
- c_name : 'purple_ircv3')
-IRCV3_SOURCES += ircv3_resources
-foreach header : IRCV3_HEADERS
- ircv3_h_includes += f'#include <@header@>'
-ircv3_h_conf = configuration_data()
-ircv3_h_conf.set('IRCV3_H_INCLUDES', '\n'.join(ircv3_h_includes))
-ircv3_h = configure_file(input : 'purpleircv3.h.in',
- output : 'purpleircv3.h',
- configuration : ircv3_h_conf,
- install_dir : get_option('includedir') / ircv3_include_base)
-install_headers(IRCV3_HEADERS,
- subdir : ircv3_include_base)
-ircv3_prpl = shared_library('ircv3', IRCV3_SOURCES + IRCV3_HEADERS + [ircv3_h],
- c_args : ['-DPURPLE_IRCV3_COMPILATION', '-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Purple-IRCv3"'],
- gnu_symbol_visibility : 'hidden',
- dependencies : [birb_dep, libpurple_dep, glib, gio, hasl],
- install_dir : PURPLE_PLUGINDIR)
-ircv3_dep = declare_dependency(
- sources : [IRCV3_SOURCES, IRCV3_HEADERS],
- include_directories : ircv3_includes,
- dependencies : [birb_dep, libpurple_dep, glib, gio, hasl])
- # we purposely don't put the library here because you should not be
- # linking to the plugin, everything will be resolved during runtime.
- description : 'a purple3 protocol plugin for IRCv3',
- version : meson.project_version(),
- subdirs : [ircv3_include_base],
- filebase : ircv3_filebase,
- libraries : [gio, glib, hasl, libpurple])
-meson.override_dependency(ircv3_filebase, ircv3_dep)
-devenv.append('PURPLE_PLUGIN_PATH', meson.current_build_dir())
-if get_option('introspection')
- GPLUGIN_INTROSPECTION = dependency('gplugin-introspection')
- plugin_filename = ircv3_prpl.full_path()
- ircv3_introspection_stub = executable('ircv3generategir',
- sources : 'ircv3generategir.c',
- dependencies : [ircv3_dep, libpurple_dep, glib, gio, hasl, GPLUGIN_INTROSPECTION],
- c_args : ['-DPURPLE_IRCV3_COMPILATION', f'-DPLUGIN_FILENAME="@plugin_filename@"'],
- ircv3_introspection_stub,
- sources : [IRCV3_SOURCES, IRCV3_HEADERS],
- includes : ['Birb-1.0', 'GLib-2.0', 'GObject-2.0', 'GPlugin-1.0', libpurple_gir[0]],
- namespace : 'PurpleIRCv3',
- symbol_prefix : 'purple_ircv3',
- dependencies: [birb_dep, gplugin_dep],
- export_packages : ['ircv3'],
- extra_args : ['-DPURPLE_IRCV3_COMPILATION', '--verbose'])
--- a/libpurple/protocols/ircv3/purpleircv3.h.in Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#ifndef __GI_SCANNER__ /* hide this bit from g-ir-scanner */
-# ifdef PURPLE_IRCV3_COMPILATION
-# error "ircv3 source files should not be including purpleircv3.h"
-# endif /* PURPLE_IRCV3_COMPILATION */
-#endif /* __GI_SCANNER__ */
-#ifndef PURPLE_IRCV3_GLOBAL_HEADER_INSIDE
-# define PURPLE_IRCV3_GLOBAL_HEADER_INSIDE
-#endif /* PURPLE_IRCV3_GLOBAL_HEADER_INSIDE */
-#undef PURPLE_IRCV3_GLOBAL_HEADER_INSIDE
-#endif /* PURPLE_IRCV3_H */
--- a/libpurple/protocols/ircv3/purpleircv3capabilities.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,685 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include "purpleircv3capabilities.h"
-#include "purpleircv3connection.h"
-#include "purpleircv3core.h"
-#include "purpleircv3sasl.h"
-static GParamSpec *properties[N_PROPERTIES] = {NULL, };
-/* Windows is does something weird with signal handling that includes defining
- * SIG_ACK. We don't care about that here, so we undef it if we find it.
- * See https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-action-constants?view=msvc-170
-static guint signals[N_SIGNALS] = {0, };
-struct _PurpleIRCv3Capabilities {
- PurpleIRCv3Connection *connection;
- gatomicrefcount wait_counters;
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_capabilities_set_connection(PurpleIRCv3Capabilities *capabilities,
- PurpleIRCv3Connection *connection)
- g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
- if(g_set_object(&capabilities->connection, connection)) {
- g_object_notify_by_pspec(G_OBJECT(capabilities),
- properties[PROP_CONNECTION]);
-purple_ircv3_capabilities_finish(PurpleIRCv3Capabilities *capabilities) {
- purple_ircv3_connection_writef(capabilities->connection,
- g_signal_emit(capabilities, signals[SIG_DONE], 0);
-purple_ircv3_capabilities_add(PurpleIRCv3Capabilities *capabilities,
- const char *capability)
- char *equals = g_strstr_len(capability, -1, "=");
- char *key = g_strndup(capability, equals - capability);
- char *value = g_strdup(equals + 1);
- g_hash_table_insert(capabilities->caps, key, value);
- g_hash_table_insert(capabilities->caps, g_strdup(capability), NULL);
-/******************************************************************************
- *****************************************************************************/
-ircv3_capabilities_message_tags_ack_cb(PurpleIRCv3Capabilities *capabilities,
- G_GNUC_UNUSED const char *capability,
- G_GNUC_UNUSED gpointer data)
- /* We have message tags so add the stuff we support that depends on it. */
- purple_ircv3_capabilities_lookup_and_request(capabilities, "msgid");
-/******************************************************************************
- * PurpleIRCv3Capabilities Implementation
- *****************************************************************************/
-purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities)
- PurpleAccount *account = NULL;
- PurpleConnection *purple_connection = NULL;
- purple_connection = PURPLE_CONNECTION(capabilities->connection);
- account = purple_connection_get_account(purple_connection);
- /* Don't request the sasl capability unless the user has selected the
- * require-password option.
- if(purple_account_get_require_password(account)) {
- gboolean found = FALSE;
- purple_ircv3_capabilities_lookup(capabilities, "sasl", &found);
- purple_ircv3_sasl_request(capabilities);
- /* cap-notify is implied when we use CAP LS 302, so this is really just to
- * make sure it's requested.
- purple_ircv3_capabilities_lookup_and_request(capabilities, "cap-notify");
- /* message-tags is used for a lot of stuff so we need to tell everyone we
- * do in fact support it.
- if(purple_ircv3_capabilities_lookup_and_request(capabilities,
- g_signal_connect(capabilities, "ack::message-tags",
- G_CALLBACK(ircv3_capabilities_message_tags_ack_cb),
- /* The server-time capability just tells the server to send a tag to
- * messages, so we just need to request it and then handle the tag when
- * we're processing messages if it exists.
- purple_ircv3_capabilities_lookup_and_request(capabilities,
- PURPLE_IRCV3_CAPABILITY_SERVER_TIME);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Capabilities,
- purple_ircv3_capabilities, G_TYPE_OBJECT,
-purple_ircv3_capabilities_get_property(GObject *obj, guint param_id,
- GValue *value, GParamSpec *pspec)
- PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
- g_value_set_object(value,
- purple_ircv3_capabilities_get_connection(capabilities));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-purple_ircv3_capabilities_set_property(GObject *obj, guint param_id,
- const GValue *value, GParamSpec *pspec)
- PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
- purple_ircv3_capabilities_set_connection(capabilities,
- g_value_get_object(value));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-purple_ircv3_capabilities_dispose(GObject *obj) {
- PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
- g_clear_object(&capabilities->connection);
- G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->dispose(obj);
-purple_ircv3_capabilities_finalize(GObject *obj) {
- PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj);
- g_hash_table_destroy(capabilities->caps);
- g_ptr_array_free(capabilities->requests, TRUE);
- G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->finalize(obj);
-purple_ircv3_capabilities_init(PurpleIRCv3Capabilities *capabilities) {
- capabilities->caps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
- capabilities->requests = g_ptr_array_new_full(0, g_free);
- g_atomic_ref_count_init(&capabilities->wait_counters);
-purple_ircv3_capabilities_class_finalize(G_GNUC_UNUSED PurpleIRCv3CapabilitiesClass *klass) {
-purple_ircv3_capabilities_class_init(PurpleIRCv3CapabilitiesClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
- obj_class->dispose = purple_ircv3_capabilities_dispose;
- obj_class->finalize = purple_ircv3_capabilities_finalize;
- obj_class->get_property = purple_ircv3_capabilities_get_property;
- obj_class->set_property = purple_ircv3_capabilities_set_property;
- * PurpleIRCv3Capabilities:connection:
- * The PurpleIRCv3Connection object that this capabilities was created
- properties[PROP_CONNECTION] = g_param_spec_object(
- "connection", "connection",
- "The connection this capabilities was created for.",
- PURPLE_IRCV3_TYPE_CONNECTION,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
- g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
- * PurpleIRCv3Capabilities::ready:
- * @capabilities: The instance.
- * Emitted when @capabilities has finished receiving the list of
- * capabilities from the server at startup.
- * For dynamically added capabilities see the `added` and `removed`
- signals[SIG_READY] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- G_CALLBACK(purple_ircv3_capabilities_default_ready_cb),
- * PurpleIRCv3Capabilities::ack:
- * @capabilities: The instance.
- * @capability: The capability string.
- * Emitted when the server has acknowledged a `CAP REQ` call from
- * purple_ircv3_capabilities_request.
- * The value of @capability will be the same as the one that was requested.
- signals[SIG_ACK] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
- * PurpleIRCv3Capabilities::nak:
- * @capabilities: The instance.
- * @capability: The capability string.
- * Emitted when the server has nacked a `CAP REQ` call from
- * purple_ircv3_capabilities_request.
- * The value of @capability will be the same as the one that was requested.
- signals[SIG_NAK] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
- * PurpleIRCv3Capabilities::done:
- * @capabilities: The instance.
- * Emitted when all of the requested capabilities have been either ack'd or
- signals[SIG_DONE] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- * PurpleIRCv3Capabilities::new:
- * @capabilities: The instance.
- * @added: The newly added capabilities.
- * Emitted when the server sends the `CAP NEW` command. @added is a
- * [type@GLib.Strv] of the new capabilities the server added.
- * There are two approaches to how you can use this signal. You can check
- * each item in @added for the values you need and parsing their values, or
- * you can call #purple_ircv3_capabilities_lookup to see if the
- * capabilities you're interested in have been added.
- signals[SIG_NEW] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- * PurpleIRCv3Capabilities::del:
- * @capabilities: The instance.
- * @removed: The capabilities that were removed.
- * Emitted when the server sends the `CAP DEL` command. @removed is a
- * [type@GLib.Strv] of the capabilities that the server removed.
- * There are two approaches to how you can use this signal. You can check
- * each item in @removed for the values you care about, or you can call
- * #purple_ircv3_capabilities_lookup to see if the capabilities you're
- * interested in have been removed.
- signals[SIG_DEL] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_capabilities_handle_list(PurpleIRCv3Capabilities *capabilities,
- G_GNUC_UNUSED GError **error)
- /* Check if we have more messages coming. */
- if(n_params > 1 && purple_strequal(params[0], "*")) {
- parts = g_strsplit(params[1], " ", -1);
- parts = g_strsplit(params[0], " ", -1);
- /* Add each capability to our hash table, splitting the keys and values. */
- for(int i = 0; parts[i] != NULL; i++) {
- purple_ircv3_capabilities_add(capabilities, parts[i]);
- g_signal_emit(capabilities, signals[SIG_READY], 0, signals[SIG_READY]);
- /* If no capabilities were requested after we emitted the ready signal
- * we're done with capability negotiation.
- if(capabilities->requests->len == 0) {
- purple_ircv3_capabilities_remove_wait(capabilities);
-purple_ircv3_capabilities_handle_ack_nak(PurpleIRCv3Capabilities *capabilities,
- char *caps = g_strjoinv(" ", params);
- gboolean found = FALSE;
- g_signal_emit(capabilities, sig, g_quark_from_string(caps), caps);
- found = g_ptr_array_find_with_equal_func(capabilities->requests, caps,
- g_ptr_array_remove_index(capabilities->requests, index);
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "received CAP %s for unknown capability %s", method, caps);
- if(capabilities->requests->len == 0) {
- purple_ircv3_capabilities_remove_wait(capabilities);
-purple_ircv3_capabilities_handle_new(PurpleIRCv3Capabilities *capabilities,
- G_GNUC_UNUSED GError **error)
- for(guint i = 0; i < n_params; i++) {
- purple_ircv3_capabilities_add(capabilities, params[i]);
- g_signal_emit(capabilities, signals[SIG_NEW], 0, params);
-purple_ircv3_capabilities_handle_del(PurpleIRCv3Capabilities *capabilities,
- G_GNUC_UNUSED GError **error)
- for(guint i = 0; i < n_params; i++) {
- g_hash_table_remove(capabilities->caps, params[i]);
- g_signal_emit(capabilities, signals[SIG_DEL], 0, params);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_capabilities_register(GPluginNativePlugin *plugin) {
- purple_ircv3_capabilities_register_type(G_TYPE_MODULE(plugin));
-PurpleIRCv3Capabilities *
-purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection) {
- PURPLE_IRCV3_TYPE_CAPABILITIES,
- "connection", connection,
-purple_ircv3_capabilities_start(PurpleIRCv3Capabilities *capabilities) {
- purple_ircv3_connection_writef(capabilities->connection, "CAP LS %s",
- PURPLE_IRCV3_CAPABILITY_CAP_LS_VERSION);
-purple_ircv3_capabilities_message_handler(PurpleIRCv3Message *message,
- PurpleIRCv3Connection *connection = data;
- PurpleIRCv3Capabilities *capabilities = NULL;
- GStrv subparams = NULL;
- const char *subcommand = NULL;
- params = purple_ircv3_message_get_params(message);
- n_params = g_strv_length(params);
- capabilities = purple_ircv3_connection_get_capabilities(connection);
- /* Initialize some variables to make it easier to call our sub command
- * params[0] is the nick or * if it hasn't been negotiated yet, we don't
- * have a need for this, so we ignore it.
- * params[1] is the CAP subcommand sent from the server. We use it here
- * purely for dispatching to our subcommand handlers.
- * params[2] and higher are the parameters to the subcommand. To make the
- * code a bit easier all around, we subtract 2 from n_params to remove
- * references to the nick and subcommand name. Like wise, we add 2 to the
- * params GStrv which will now point to the second item in the array again
- * ignoring the nick and subcommand.
- subcommand = params[1];
- n_subparams = n_params - 2;
- subparams = params + 2;
- /* Dispatch the subcommand. */
- if(purple_strequal(subcommand, "LS") ||
- purple_strequal(subcommand, "LIST"))
- return purple_ircv3_capabilities_handle_list(capabilities, n_subparams,
- } else if(purple_strequal(subcommand, "ACK")) {
- return purple_ircv3_capabilities_handle_ack_nak(capabilities,
- } else if(purple_strequal(subcommand, "NAK")) {
- return purple_ircv3_capabilities_handle_ack_nak(capabilities,
- } else if(purple_strequal(subcommand, "NEW")) {
- return purple_ircv3_capabilities_handle_new(capabilities, n_subparams,
- } else if(purple_strequal(subcommand, "DEL")) {
- return purple_ircv3_capabilities_handle_del(capabilities, n_subparams,
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "No handler for CAP subcommand %s", subcommand);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities)
- g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL);
- return capabilities->connection;
-purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities,
- const char *capability)
- g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
- g_return_if_fail(capability != NULL);
- g_ptr_array_add(capabilities->requests, g_strdup(capability));
- purple_ircv3_connection_writef(capabilities->connection, "CAP REQ :%s",
-purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities,
- const char *name, gboolean *found)
- gboolean real_found = FALSE;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL);
- g_return_val_if_fail(name != NULL, NULL);
- real_found = g_hash_table_lookup_extended(capabilities->caps, name, NULL,
-purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities,
- gboolean found = FALSE;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), FALSE);
- g_return_val_if_fail(name != NULL, FALSE);
- purple_ircv3_capabilities_lookup(capabilities, name, &found);
- purple_ircv3_capabilities_request(capabilities, name);
-purple_ircv3_capabilities_add_wait(PurpleIRCv3Capabilities *capabilities) {
- g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
- g_atomic_ref_count_inc(&capabilities->wait_counters);
-purple_ircv3_capabilities_remove_wait(PurpleIRCv3Capabilities *capabilities) {
- g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities));
- if(g_atomic_ref_count_dec(&capabilities->wait_counters)) {
- purple_ircv3_capabilities_finish(capabilities);
--- a/libpurple/protocols/ircv3/purpleircv3capabilities.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_CAPABILITIES_H
-#define PURPLE_IRCV3_CAPABILITIES_H
-#include <glib-object.h>
-#include <gplugin-native.h>
-#include "purpleircv3version.h"
-/* https://ircv3.net/specs/extensions/capability-negotiation */
-#define PURPLE_IRCV3_CAPABILITY_CAP_LS_VERSION "302"
-/* https://ircv3.net/specs/extensions/sasl-3.2 */
-#define PURPLE_IRCV3_CAPABILITY_SASL "sasl"
-/* https://ircv3.net/specs/extensions/server-time */
-#define PURPLE_IRCV3_CAPABILITY_SERVER_TIME "server-time"
-#define PURPLE_IRCV3_TYPE_CAPABILITIES (purple_ircv3_capabilities_get_type())
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-G_DECLARE_FINAL_TYPE(PurpleIRCv3Capabilities, purple_ircv3_capabilities,
- PURPLE_IRCV3, CAPABILITIES, GObject)
-#include "purpleircv3connection.h"
-#include "purpleircv3message.h"
- * purple_ircv3_capabilities_register: (skip)
- * @plugin: The [class@GPlugin.NativePlugin] instance.
- * Dynamically registers the PurpleIRCv3Capabilities type.
-G_GNUC_INTERNAL void purple_ircv3_capabilities_register(GPluginNativePlugin *plugin);
-G_GNUC_INTERNAL PurpleIRCv3Capabilities *purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection);
-G_GNUC_INTERNAL void purple_ircv3_capabilities_start(PurpleIRCv3Capabilities *capabilities);
-G_GNUC_INTERNAL gboolean purple_ircv3_capabilities_message_handler(PurpleIRCv3Message *message, GError **error, gpointer data);
- * purple_ircv3_capabilities_get_connection:
- * @capabilities: The instance.
- * Gets the PurpleIRCv3Connection object that @capabilities was created with.
- * Returns: (transfer none): The connection instance.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-PurpleIRCv3Connection *purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities);
- * purple_ircv3_capabilities_request:
- * @capabilities: The instance.
- * @capability: The capabilities to request.
- * This method will send `CAP REQ @capability` to the server. Listen to the
- * `::ack` and `::nak` signals which will contain the contents of @capability
- * that was passed in here.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities, const char *capability);
- * purple_ircv3_capabilities_lookup:
- * @capabilities: The instance.
- * @name: The name of the capability to look for.
- * @found: (out) (nullable): A return address for a boolean on whether the
- * capability was advertised or not.
- * Gets the value that the @name capability provided if it was advertised. To
- * determine if the capability was advertised use the @found parameter.
- * Returns: The value of the capability named @name.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-const char *purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities, const char *name, gboolean *found);
- * purple_ircv3_capabilities_lookup_and_request:
- * @capabilities: The instance.
- * @name: The name of the capability to look for.
- * A helper function to call [method@PurpleIRCv3.Capabilities.Lookup] and if
- * found, call [method@PurpleIRCv3.Capabilities.Request].
- * This method ignores the advertised value, so to get that you'll need to call
- * [method@PurpleIRCv3.Capabilities.Lookup] yourself.
- * Also if you need to do something when the server ACK's or NAK's your
- * request, you're probably better off just using the methods yourself.
- * Returns: %TRUE if @name was found and requested, %FALSE otherwise.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-gboolean purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities, const char *name);
- * purple_ircv3_capabilties_add_wait:
- * @capabilities: The instance.
- * Adds a wait counter to @capabilities. This counter is used to delay the
- * call of `CAP END` until all capability negotiation has completed. This is
- * necessary for SASL and may be necessary for other capabilities as well.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_capabilities_add_wait(PurpleIRCv3Capabilities *capabilities);
- * purple_ircv3_capabilties_remove_wait:
- * @capabilities: The instance.
- * Removes a wait counter from @capabilities. Only when this counter reaches 0,
- * will `CAP END` be called and registration completed.
- * This is necessary for SASL and may be necessary for other capabilities as
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_capabilities_remove_wait(PurpleIRCv3Capabilities *capabilities);
-#endif /* PURPLE_IRCV3_CAPABILITIES_H */
--- a/libpurple/protocols/ircv3/purpleircv3connection.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,964 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3connection.h"
-#include "purpleircv3constants.h"
-#include "purpleircv3core.h"
-#include "purpleircv3ctcp.h"
-#include "purpleircv3formatting.h"
-#include "purpleircv3parser.h"
-static GParamSpec *properties[N_PROPERTIES] = {NULL, };
- SIG_REGISTRATION_COMPLETE,
-static guint signals[N_SIGNALS] = {0, };
- GSocketConnection *connection;
- GDataInputStream *input;
- PurpleIRCv3Parser *parser;
- PurpleIRCv3Capabilities *capabilities;
- PurpleConversation *status_conversation;
-} PurpleIRCv3ConnectionPrivate;
-G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Connection,
- purple_ircv3_connection,
- PURPLE_TYPE_CONNECTION,
- G_ADD_PRIVATE_DYNAMIC(PurpleIRCv3Connection))
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_connection_send_pass_command(PurpleIRCv3Connection *connection) {
- PurpleAccount *account = NULL;
- const char *password = NULL;
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- password = purple_account_get_string(account, "server-password", "");
- if(password != NULL && *password != '\0') {
- purple_ircv3_connection_writef(connection, "PASS %s", password);
-purple_ircv3_connection_send_user_command(PurpleIRCv3Connection *connection) {
- PurpleAccount *account = NULL;
- const char *identname = NULL;
- const char *nickname = NULL;
- const char *realname = NULL;
- purple_connection_get_display_name(PURPLE_CONNECTION(connection));
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- /* The stored value could be an empty string, so pass a default of empty
- * string and then if it was empty, set our correct fallback.
- identname = purple_account_get_string(account, "ident", "");
- if(identname == NULL || *identname == '\0') {
- realname = purple_account_get_string(account, "real-name", "");
- if(realname == NULL || *realname == '\0') {
- purple_ircv3_connection_writef(connection, "USER %s 0 * :%s", identname,
-purple_ircv3_connection_send_nick_command(PurpleIRCv3Connection *connection) {
- const char *nickname = NULL;
- purple_connection_get_display_name(PURPLE_CONNECTION(connection));
- purple_ircv3_connection_writef(connection, "NICK %s", nickname);
-purple_ircv3_connection_rejoin_channels(PurpleIRCv3Connection *connection) {
- PurpleAccount *account = NULL;
- PurpleConversationManager *manager = NULL;
- GList *conversations = NULL;
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- manager = purple_conversation_manager_get_default();
- conversations = purple_conversation_manager_get_all(manager);
- while(conversations != NULL) {
- PurpleConversation *conversation = conversations->data;
- PurpleAccount *conv_account = NULL;
- conv_account = purple_conversation_get_account(conversation);
- if(conv_account == account) {
- const char *id = purple_conversation_get_id(conversation);
- purple_ircv3_connection_writef(connection, "%s %s",
- PURPLE_IRCV3_MSG_JOIN, id);
- conversations = g_list_delete_link(conversations, conversations);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_connection_read_cb(GObject *source, GAsyncResult *result,
- PurpleIRCv3Connection *connection = data;
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- GCancellable *cancellable = NULL;
- GDataInputStream *istream = G_DATA_INPUT_STREAM(source);
- gboolean parsed = FALSE;
- line = g_data_input_stream_read_line_finish(istream, result, &length,
- if(line == NULL || error != NULL) {
- if(PURPLE_IS_CONNECTION(connection)) {
- g_set_error_literal(&error, PURPLE_CONNECTION_ERROR,
- PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
- _("Server closed the connection"));
- g_prefix_error(&error, "%s", _("Lost connection with server: "));
- purple_connection_take_error(PURPLE_CONNECTION(connection), error);
- /* In the off chance that line was returned, make sure we free it. */
- priv = purple_ircv3_connection_get_instance_private(connection);
- parsed = purple_ircv3_parser_parse(priv->parser, line, &error,
- g_warning("failed to handle '%s': %s", line,
- error != NULL ? error->message : "unknown error");
- /* Call read_line_async again to continue reading lines. */
- cancellable = purple_connection_get_cancellable(PURPLE_CONNECTION(connection));
- g_data_input_stream_read_line_async(priv->input,
- purple_ircv3_connection_read_cb,
-purple_ircv3_connection_write_cb(GObject *source, GAsyncResult *result,
- PurpleIRCv3Connection *connection = data;
- BirbQueuedOutputStream *stream = BIRB_QUEUED_OUTPUT_STREAM(source);
- gboolean success = FALSE;
- success = birb_queued_output_stream_push_bytes_finish(stream, result,
- birb_queued_output_stream_clear_queue(stream);
- g_prefix_error(&error, "%s", _("Lost connection with server: "));
- purple_connection_take_error(PURPLE_CONNECTION(connection), error);
-purple_ircv3_connection_connected_cb(GObject *source, GAsyncResult *result,
- PurpleIRCv3Connection *connection = data;
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- GCancellable *cancellable = NULL;
- GInputStream *istream = NULL;
- GOutputStream *ostream = NULL;
- GSocketClient *client = G_SOCKET_CLIENT(source);
- GSocketConnection *conn = NULL;
- priv = purple_ircv3_connection_get_instance_private(connection);
- /* Finish the async method. */
- conn = g_socket_client_connect_to_host_finish(client, result, &error);
- if(conn == NULL || error != NULL) {
- g_prefix_error(&error, "%s", _("Unable to connect: "));
- purple_connection_take_error(PURPLE_CONNECTION(connection), error);
- g_message("Successfully connected to %s", priv->server_name);
- /* Save our connection and setup our input and outputs. */
- priv->connection = conn;
- /* Create our parser. */
- priv->parser = purple_ircv3_parser_new();
- purple_ircv3_parser_add_default_handlers(priv->parser);
- ostream = g_io_stream_get_output_stream(G_IO_STREAM(conn));
- priv->output = birb_queued_output_stream_new(ostream);
- istream = g_io_stream_get_input_stream(G_IO_STREAM(conn));
- priv->input = g_data_input_stream_new(istream);
- g_data_input_stream_set_newline_type(G_DATA_INPUT_STREAM(priv->input),
- G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
- cancellable = purple_connection_get_cancellable(PURPLE_CONNECTION(connection));
- /* Add our read callback. */
- g_data_input_stream_read_line_async(priv->input,
- purple_ircv3_connection_read_cb,
- /* Send our registration commands. */
- purple_ircv3_capabilities_start(priv->capabilities);
- purple_ircv3_connection_send_pass_command(connection);
- purple_ircv3_connection_send_user_command(connection);
- purple_ircv3_connection_send_nick_command(connection);
-purple_ircv3_connection_caps_done_cb(G_GNUC_UNUSED PurpleIRCv3Capabilities *caps,
- PurpleIRCv3Connection *connection = data;
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- priv = purple_ircv3_connection_get_instance_private(connection);
- priv->registered = TRUE;
- g_signal_emit(connection, signals[SIG_REGISTRATION_COMPLETE], 0);
- /* Add our supported CTCP commands. */
- purple_ircv3_ctcp_add_default_handlers(connection);
- /* Now that registration is complete, rejoin any channels that the
- * conversation manager has for us.
- purple_ircv3_connection_rejoin_channels(connection);
-/******************************************************************************
- * PurpleConnection Implementation
- *****************************************************************************/
-purple_ircv3_connection_connect(PurpleConnection *purple_connection,
- PurpleIRCv3Connection *connection = NULL;
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- PurpleAccount *account = NULL;
- GCancellable *cancellable = NULL;
- GSocketClient *client = NULL;
- gint default_port = PURPLE_IRCV3_DEFAULT_TLS_PORT;
- gboolean use_tls = TRUE;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE);
- connection = PURPLE_IRCV3_CONNECTION(purple_connection);
- priv = purple_ircv3_connection_get_instance_private(connection);
- account = purple_connection_get_account(purple_connection);
- client = purple_gio_socket_client_new(account, error);
- if(!G_IS_SOCKET_CLIENT(client)) {
- if(error != NULL && *error != NULL) {
- purple_connection_take_error(purple_connection, *error);
- /* Turn on TLS if requested. */
- use_tls = purple_account_get_bool(account, "use-tls", TRUE);
- g_socket_client_set_tls(client, use_tls);
- /* If TLS is not being used, set the default port to the plain port. */
- default_port = PURPLE_IRCV3_DEFAULT_PLAIN_PORT;
- port = purple_account_get_int(account, "port", default_port);
- cancellable = purple_connection_get_cancellable(purple_connection);
- /* Finally start the async connection. */
- g_socket_client_connect_to_host_async(client, priv->server_name,
- purple_ircv3_connection_connected_cb,
- g_clear_object(&client);
-purple_ircv3_connection_disconnect(PurpleConnection *purple_connection,
- PurpleIRCv3Connection *connection = NULL;
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- PurpleConnectionClass *parent_class = NULL;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE);
- /* Chain up to our parent's disconnect to do initial clean up. */
- parent_class = PURPLE_CONNECTION_CLASS(purple_ircv3_connection_parent_class);
- parent_class->disconnect(purple_connection, error);
- connection = PURPLE_IRCV3_CONNECTION(purple_connection);
- priv = purple_ircv3_connection_get_instance_private(connection);
- /* TODO: send QUIT command. */
- if(G_IS_SOCKET_CONNECTION(priv->connection)) {
- GInputStream *istream = G_INPUT_STREAM(priv->input);
- GOutputStream *ostream = G_OUTPUT_STREAM(priv->output);
- purple_gio_graceful_close(G_IO_STREAM(priv->connection),
- g_clear_object(&priv->input);
- g_clear_object(&priv->output);
- g_clear_object(&priv->connection);
-purple_ircv3_connection_registration_complete_cb(PurpleIRCv3Connection *connection) {
- /* Don't set our connection state to connected until we've completed
- * registration as connected implies that we can start chatting or join
- * rooms and other "online" activities.
- purple_connection_set_state(PURPLE_CONNECTION(connection),
- PURPLE_CONNECTION_STATE_CONNECTED);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_connection_ctcp_request_default_handler(G_GNUC_UNUSED PurpleIRCv3Connection *connection,
- G_GNUC_UNUSED PurpleConversation *conversation,
- PurpleMessage *message,
- if(!purple_strempty(params)) {
- contents = g_strdup_printf(_("requested CTCP %s: %s"), command,
- contents = g_strdup_printf(_("requested CTCP %s"),
- purple_message_set_contents(message, contents);
- g_clear_pointer(&contents, g_free);
-purple_ircv3_connection_ctcp_response_default_handler(G_GNUC_UNUSED PurpleIRCv3Connection *connection,
- G_GNUC_UNUSED PurpleConversation *conversation,
- PurpleMessage *message,
- if(!purple_strempty(params)) {
- contents = g_strdup_printf(_("CTCP %s response: %s"), command,
- contents = g_strdup_printf(_("CTCP %s response was empty"),
- purple_message_set_contents(message, contents);
- g_clear_pointer(&contents, g_free);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-purple_ircv3_connection_get_property(GObject *obj, guint param_id,
- GValue *value, GParamSpec *pspec)
- PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
- case PROP_CAPABILITIES:
- g_value_set_object(value,
- purple_ircv3_connection_get_capabilities(connection));
- g_value_set_boolean(value,
- purple_ircv3_connection_get_registered(connection));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-purple_ircv3_connection_dispose(GObject *obj) {
- PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- priv = purple_ircv3_connection_get_instance_private(connection);
- g_clear_object(&priv->input);
- g_clear_object(&priv->output);
- g_clear_object(&priv->connection);
- g_clear_object(&priv->capabilities);
- g_clear_object(&priv->parser);
- g_clear_object(&priv->status_conversation);
- G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->dispose(obj);
-purple_ircv3_connection_finalize(GObject *obj) {
- PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- priv = purple_ircv3_connection_get_instance_private(connection);
- g_clear_pointer(&priv->server_name, g_free);
- G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->finalize(obj);
-purple_ircv3_connection_constructed(GObject *obj) {
- PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj);
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- PurpleAccount *account = NULL;
- PurpleContactInfo *info = NULL;
- PurpleConversationManager *conversation_manager = NULL;
- char **userparts = NULL;
- const char *username = NULL;
- G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->constructed(obj);
- priv = purple_ircv3_connection_get_instance_private(connection);
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- info = PURPLE_CONTACT_INFO(account);
- title = g_strdup_printf(_("status for %s"),
- purple_contact_info_get_username(info));
- /* Split the username into nick and server and store the values. */
- username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account));
- userparts = g_strsplit(username, "@", 2);
- purple_connection_set_display_name(PURPLE_CONNECTION(connection),
- priv->server_name = g_strdup(userparts[1]);
- /* Free the userparts vector. */
- /* Check if we have an existing status conversation. */
- conversation_manager = purple_conversation_manager_get_default();
- priv->status_conversation = purple_conversation_manager_find_with_id(conversation_manager,
- if(!PURPLE_IS_CONVERSATION(priv->status_conversation)) {
- /* Create our status conversation. */
- priv->status_conversation = g_object_new(
- PURPLE_TYPE_CONVERSATION,
- "id", priv->server_name,
- "name", priv->server_name,
- purple_conversation_manager_register(conversation_manager,
- priv->status_conversation);
- /* The conversation existed, so add a reference to it. */
- g_object_ref(priv->status_conversation);
- g_clear_pointer(&title, g_free);
- /* Finally create our objects. */
- priv->capabilities = purple_ircv3_capabilities_new(connection);
- g_signal_connect_object(priv->capabilities, "done",
- G_CALLBACK(purple_ircv3_connection_caps_done_cb),
-purple_ircv3_connection_init(G_GNUC_UNUSED PurpleIRCv3Connection *connection) {
-purple_ircv3_connection_class_finalize(G_GNUC_UNUSED PurpleIRCv3ConnectionClass *klass) {
-purple_ircv3_connection_class_init(PurpleIRCv3ConnectionClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
- PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass);
- obj_class->get_property = purple_ircv3_connection_get_property;
- obj_class->constructed = purple_ircv3_connection_constructed;
- obj_class->dispose = purple_ircv3_connection_dispose;
- obj_class->finalize = purple_ircv3_connection_finalize;
- connection_class->connect = purple_ircv3_connection_connect;
- connection_class->disconnect = purple_ircv3_connection_disconnect;
- * PurpleIRCv3Connection:capabilities:
- * The capabilities that the server supports.
- * This is created during registration of the connection and is useful for
- * troubleshooting or just reporting them to end users.
- properties[PROP_CAPABILITIES] = g_param_spec_object(
- "capabilities", "capabilities",
- "The capabilities that the server supports",
- PURPLE_IRCV3_TYPE_CAPABILITIES,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- * PurpleIRCv3Connection:registered:
- * Whether or not the connection has finished the registration portion of
- properties[PROP_REGISTERED] = g_param_spec_boolean(
- "registered", "registered",
- "Whether or not the connection has finished registration.",
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
- * PurpleIRCv3Connection::registration-complete:
- * @connection: The instance.
- * This signal is emitted after the registration process has been
- * completed. Plugins can use this to perform additional actions before
- * any channels are auto joined or similar.
- signals[SIG_REGISTRATION_COMPLETE] = g_signal_new_class_handler(
- "registration-complete",
- G_OBJECT_CLASS_TYPE(klass),
- G_CALLBACK(purple_ircv3_connection_registration_complete_cb),
- * PurpleIRCv3Connection::ctcp-request:
- * @connection: The instance.
- * @conversation: The conversation.
- * @message: The message.
- * @command: The CTCP command.
- * @params: (nullable): The CTCP parameters.
- * This signal is emitted after a CTCP request has been received.
- * Signal handlers should return TRUE if they fully handled the request and
- * do not want it echoed out to the user.
- * Handlers may also modify the message. For example, the CTCP ACTION
- * command has its message contents replaced with just the CTCP parameters
- * and sets the [property@Message:action] to %TRUE and then returns %TRUE.
- signals[SIG_CTCP_REQUEST] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- G_CALLBACK(purple_ircv3_connection_ctcp_request_default_handler),
- g_signal_accumulator_true_handled,
- PURPLE_TYPE_CONVERSATION,
- * PurpleIRCv3Connection::ctcp-response:
- * @connection: The instance.
- * @conversation: The conversation.
- * @message: The message.
- * @command: The CTCP command.
- * @params: (nullable): The CTCP parameters.
- * This signal is emitted after a CTCP response has been received.
- * Signal handlers should return TRUE if they fully handled the response
- * and do not want it echoed out to the user.
- * Handlers may modify @conversation or @message to depending on their
- signals[SIG_CTCP_RESPONSE] = g_signal_new_class_handler(
- G_OBJECT_CLASS_TYPE(klass),
- G_CALLBACK(purple_ircv3_connection_ctcp_response_default_handler),
- g_signal_accumulator_true_handled,
- PURPLE_TYPE_CONVERSATION,
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_connection_register(GPluginNativePlugin *plugin) {
- GObjectClass *hack = NULL;
- purple_ircv3_connection_register_type(G_TYPE_MODULE(plugin));
- /* Without this hack we get some warnings about no reference on this type
- * when generating the gir file. However, there are no changes to the
- * generated gir file with or without this hack, so this is purely to get
- hack = g_type_class_ref(purple_ircv3_connection_get_type());
- g_type_class_unref(hack);
-purple_ircv3_connection_emit_ctcp_request(PurpleIRCv3Connection *connection,
- PurpleConversation *conversation,
- PurpleMessage *message,
- const char *parameters)
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE);
- g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
- g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE);
- g_return_val_if_fail(!purple_strempty(command), FALSE);
- g_signal_emit(connection, signals[SIG_CTCP_REQUEST], 0, conversation,
- message, command, parameters, &ret);
-purple_ircv3_connection_emit_ctcp_response(PurpleIRCv3Connection *connection,
- PurpleConversation *conversation,
- PurpleMessage *message,
- const char *parameters)
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE);
- g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
- g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE);
- g_return_val_if_fail(!purple_strempty(command), FALSE);
- g_signal_emit(connection, signals[SIG_CTCP_RESPONSE], 0, conversation,
- message, command, parameters, &ret);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_connection_writef(PurpleIRCv3Connection *connection,
- const char *format, ...)
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- GCancellable *cancellable = NULL;
- g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection));
- g_return_if_fail(format != NULL);
- priv = purple_ircv3_connection_get_instance_private(connection);
- /* Create our string and append our format to it. */
- msg = g_string_new("");
- va_start(vargs, format);
- g_string_vprintf(msg, format, vargs);
- /* Next add the trailing carriage return line feed. */
- g_string_append(msg, "\r\n");
- /* Finally turn the string into bytes and send it! */
- bytes = g_string_free_to_bytes(msg);
- cancellable = purple_connection_get_cancellable(PURPLE_CONNECTION(connection));
- birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(priv->output),
- purple_ircv3_connection_write_cb,
-PurpleIRCv3Capabilities *
-purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection) {
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL);
- priv = purple_ircv3_connection_get_instance_private(connection);
- return priv->capabilities;
-purple_ircv3_connection_get_registered(PurpleIRCv3Connection *connection) {
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE);
- priv = purple_ircv3_connection_get_instance_private(connection);
- return priv->registered;
-purple_ircv3_connection_add_status_message(PurpleIRCv3Connection *connection,
- PurpleIRCv3Message *v3_message)
- PurpleIRCv3ConnectionPrivate *priv = NULL;
- PurpleMessage *message = NULL;
- const char *command = NULL;
- g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection));
- g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(v3_message));
- priv = purple_ircv3_connection_get_instance_private(connection);
- command = purple_ircv3_message_get_command(v3_message);
- str = g_string_new(command);
- params = purple_ircv3_message_get_params(v3_message);
- if(params != NULL && params[0] != NULL) {
- char *joined = g_strjoinv(" ", params);
- g_string_append_printf(str, " %s", joined);
- stripped = purple_ircv3_formatting_strip(str->str);
- g_string_free(str, TRUE);
- message = g_object_new(
- "author", purple_ircv3_message_get_source(v3_message),
- purple_conversation_write_message(priv->status_conversation, message);
- g_clear_object(&message);
-purple_ircv3_connection_is_channel(PurpleIRCv3Connection *connection,
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE);
- g_return_val_if_fail(id != NULL, FALSE);
-purple_ircv3_connection_find_or_create_conversation(PurpleIRCv3Connection *connection,
- PurpleAccount *account = NULL;
- PurpleConversation *conversation = NULL;
- PurpleConversationManager *manager = NULL;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL);
- g_return_val_if_fail(id != NULL, NULL);
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- manager = purple_conversation_manager_get_default();
- conversation = purple_conversation_manager_find_with_id(manager, account,
- if(!PURPLE_IS_CONVERSATION(conversation)) {
- PurpleConversationType type = PurpleConversationTypeDM;
- if(purple_ircv3_connection_is_channel(connection, id)) {
- type = PurpleConversationTypeChannel;
- conversation = g_object_new(
- PURPLE_TYPE_CONVERSATION,
- purple_conversation_manager_register(manager, conversation);
- /* The manager creates its own reference on our new conversation, so we
- * borrow it like we do above if it already exists.
- g_object_unref(conversation);
-purple_ircv3_connection_find_or_create_contact(PurpleIRCv3Connection *connection,
- PurpleAccount *account = NULL;
- PurpleContact *contact = NULL;
- PurpleContactManager *manager = NULL;
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- manager = purple_contact_manager_get_default();
- contact = purple_contact_manager_find_with_id(manager, account, nick);
- if(!PURPLE_IS_CONTACT(contact)) {
- contact = purple_contact_new(account, nick);
- purple_contact_info_set_username(PURPLE_CONTACT_INFO(contact), nick);
- purple_contact_manager_add(manager, contact);
--- a/libpurple/protocols/ircv3/purpleircv3connection.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_CONNECTION_H
-#define PURPLE_IRCV3_CONNECTION_H
-#include <glib-object.h>
-#include <gplugin-native.h>
-#include "purpleircv3version.h"
-#define PURPLE_IRCV3_TYPE_CONNECTION (purple_ircv3_connection_get_type())
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-G_DECLARE_DERIVABLE_TYPE(PurpleIRCv3Connection, purple_ircv3_connection,
- PURPLE_IRCV3, CONNECTION, PurpleConnection)
-#include "purpleircv3capabilities.h"
-#include "purpleircv3message.h"
-struct _PurpleIRCv3ConnectionClass {
- PurpleConnectionClass parent;
- * purple_ircv3_connection_register: (skip)
- * @plugin: The GTypeModule
- * Registers the dynamic type using @plugin.
-G_GNUC_INTERNAL void purple_ircv3_connection_register(GPluginNativePlugin *plugin);
- * purple_ircv3_connection_emit_ctcp_request:
- * @connection: The instance.
- * @conversation: The conversation.
- * @message: The message.
- * @command: The CTCP command.
- * @parameters: (nullable): The CTCP parameters.
- * Emits the [signal@Connection:ctcp-request] signal with the given @command
- * and @parameters which originated from @message in @conversation.
- * The message may be modified by a signal handler. For example, the default
- * handler will output an internationalized string that describes what command
- * Returns: %TRUE if the request was handled and the message should not be
- * echoed, otherwise %FALSE.
-G_GNUC_INTERNAL gboolean purple_ircv3_connection_emit_ctcp_request(PurpleIRCv3Connection *connection, PurpleConversation *conversation, PurpleMessage *message, const char *command, const char *parameters);
- * purple_ircv3_connection_emit_ctcp_response:
- * @connection: The instance.
- * @conversation: The conversation.
- * @message: The message.
- * @command: The CTCP command.
- * @parameters: (nullable): The CTCP parameters.
- * Emits the [signal@Connection:ctcp-response] signal with the given @command
- * and @parameters which originated from @message in @conversation.
- * The message may be modified by a signal handler. For example, the default
- * handler will output an internationalized string that describes what the
- * Returns: %TRUE if the request was handled and the message should not be
- * echoed, otherwise %FALSE.
-G_GNUC_INTERNAL gboolean purple_ircv3_connection_emit_ctcp_response(PurpleIRCv3Connection *connection, PurpleConversation *conversation, PurpleMessage *message, const char *command, const char *parameters);
- * purple_ircv3_connection_writef:
- * @connection: The instance.
- * @format: The format string.
- * @...: The arguments for @format.
- * Similar to C `printf()` but writes the format string out to @connection.
- * This will add the proper line termination, so you do not need to worry about
-void purple_ircv3_connection_writef(PurpleIRCv3Connection *connection, const char *format, ...) G_GNUC_PRINTF(2, 3);
- * purple_ircv3_connection_get_capabilities:
- * @connection: The instance.
- * Gets the list of capabilities that the server supplied during registration.
- * Returns: (transfer none): The list of capabilities that the server supports.
-PurpleIRCv3Capabilities *purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection);
- * purple_ircv3_connection_get_registered:
- * @connection: The instance.
- * Gets whether or not the connection has finished the registration process.
- * Returns: %TRUE if registration has been completed otherwise %FALSE.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-gboolean purple_ircv3_connection_get_registered(PurpleIRCv3Connection *connection);
- * purple_ircv3_connection_add_status_message:
- * @connection: The instance.
- * @message: The message.
- * Adds a message to the status conversation/window for @connection.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_connection_add_status_message(PurpleIRCv3Connection *connection, PurpleIRCv3Message *message);
- * purple_ircv3_connection_is_channel:
- * @connection: The instance.
- * @id: The id to check.
- * Checks if @id is a channel.
- * Right now this just checks if @id starts with a `#` but in the future this
- * will be updated to check all channel prefixes that the connection supports.
- * Returns: %TRUE if @id is a channel otherwise %FALSE.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-gboolean purple_ircv3_connection_is_channel(PurpleIRCv3Connection *connection, const char *id);
- * purple_ircv3_connection_find_or_create_conversation:
- * @connection: The instance.
- * @id: The id of the conversation.
- * Looks for an existing conversation belonging to @connection and returns it
- * if found. If not found a new conversation will be created.
- * This will only ever return %NULL if @connection is invalid or @id is %NULL.
- * Note that ownership of the conversation remains with the default
- * [class@Purple.ConversationManager].
- * Returns: (transfer none) (nullable): The conversation.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-PurpleConversation *purple_ircv3_connection_find_or_create_conversation(PurpleIRCv3Connection *connection, const char *id);
- * purple_ircv3_connection_find_or_create_contact:
- * @connection: The instance.
- * @nick: The nickname of the user.
- * Looks for an existing contact belonging to @connection and returns it if
- * found. If not a new contact will be created.
- * This will only ever return %NULL if @connection is invalid or @nick is
- * Note that the ownership of the contact remains with the default
- * [class@Purple.ContactManager].
- * Returns: (transfer none) (nullable): The contact.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-PurpleContact *purple_ircv3_connection_find_or_create_contact(PurpleIRCv3Connection *connection, const char *nick);
-#endif /* PURPLE_IRCV3_CONNECTION_H */
--- a/libpurple/protocols/ircv3/purpleircv3constants.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,328 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_CONSTANTS_H
-#define PURPLE_IRCV3_CONSTANTS_H
- * PURPLE_IRCV3_CTCP_ACTION:
- * A constant representing the CTCP ACTION command.
-#define PURPLE_IRCV3_CTCP_ACTION ("ACTION") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_CTCP_DELIMITER:
- * The delimiter used for CTCP messages.
-#define PURPLE_IRCV3_CTCP_DELIMITER (0x1) PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_CTCP_VERSION:
- * A constant representing the CTCP VERSION command.
-#define PURPLE_IRCV3_CTCP_VERSION ("VERSION") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_ERR_NICKLOCKED:
- * A constant for the IRC %NICKLOCKED error.
-#define PURPLE_IRCV3_ERR_NICKLOCKED ("902") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_ERR_SASLABORTED:
- * A constant for the IRC %SASLABORTED error.
-#define PURPLE_IRCV3_ERR_SASLABORTED ("906") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_ERR_SASLALREADY:
- * A constant for the IRC %SASLALREADY error.
-#define PURPLE_IRCV3_ERR_SASLALREADY ("907") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_ERR_SASLFAIL:
- * A constant for the IRC %SASLFAIL error.
-#define PURPLE_IRCV3_ERR_SASLFAIL ("904") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_ERR_SASLTOOLONG:
- * A constant for the IRC %SASLTOOLONG error.
-#define PURPLE_IRCV3_ERR_SASLTOOLONG ("905") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_AUTHENTICATE:
- * A constant for the IRC %AUTHENTICATE message.
-#define PURPLE_IRCV3_MSG_AUTHENTICATE ("AUTHENTICATE") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_CAP:
- * A constant for the IRC %CAP message.
-#define PURPLE_IRCV3_MSG_CAP ("CAP") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_JOIN:
- * A constant for the IRC %JOIN message.
-#define PURPLE_IRCV3_MSG_JOIN ("JOIN") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_NOTICE:
- * A constant for the IRC %NOTICE message.
-#define PURPLE_IRCV3_MSG_NOTICE ("NOTICE") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_PING:
- * A constant for the IRC %PING message.
-#define PURPLE_IRCV3_MSG_PING ("PING") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_PRIVMSG:
- * A constant for the IRC %PRIVMSG message.
-#define PURPLE_IRCV3_MSG_PRIVMSG ("PRIVMSG") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_MSG_TOPIC:
- * A constant for the IRC %TOPIC message.
-#define PURPLE_IRCV3_MSG_TOPIC ("TOPIC") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_CREATED:
- * A constant for the IRC %CREATED reply.
-#define PURPLE_IRCV3_RPL_CREATED ("003") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_ENDOFMOTD:
- * A constant for the IRC %ENDOFMOTD reply.
-#define PURPLE_IRCV3_RPL_ENDOFMOTD ("376") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LOGGEDIN:
- * A constant for the IRC %LOGGEDIN reply.
-#define PURPLE_IRCV3_RPL_LOGGEDIN ("900") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LOGGEDOUT:
- * A constant for the IRC %LOGGEDOUT reply.
-#define PURPLE_IRCV3_RPL_LOGGEDOUT ("901") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LUSERCHANNELS:
- * A constant for the IRC %LUSERCHANNELS reply.
-#define PURPLE_IRCV3_RPL_LUSERCHANNELS ("254") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LUSERCLIENT:
- * A constant for the IRC %LUSERCLIENT reply.
-#define PURPLE_IRCV3_RPL_LUSERCLIENT ("251") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LUSERME:
- * A constant for the IRC %LUSERME reply.
-#define PURPLE_IRCV3_RPL_LUSERME ("255") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LUSEROP:
- * A constant for the IRC %LUSEROP reply.
-#define PURPLE_IRCV3_RPL_LUSEROP ("252") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_LUSERUNKNOWN:
- * A constant for the IRC %LUSERUNKNOWN reply.
-#define PURPLE_IRCV3_RPL_LUSERUNKNOWN ("253") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_MYINFO:
- * A constant for the IRC %MYINFO reply.
-#define PURPLE_IRCV3_RPL_MYINFO ("004") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_MOTD:
- * A constant for the IRC %MOTD reply.
-#define PURPLE_IRCV3_RPL_MOTD ("372") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_MOTDSTART:
- * A constant for the IRC %MOTDSTART reply.
-#define PURPLE_IRCV3_RPL_MOTDSTART ("375") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_NOTOPIC:
- * A constant for the IRC %NOTOPIC reply.
-#define PURPLE_IRCV3_RPL_NOTOPIC ("331") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_SASLMECHS:
- * A constant for the IRC %SASLMECHS reply.
-#define PURPLE_IRCV3_RPL_SASLMECHS ("908") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_SASLSUCCESS:
- * A constant for the IRC %SASLSUCCESS reply.
-#define PURPLE_IRCV3_RPL_SASLSUCCESS ("903") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_TOPIC:
- * A constant for the IRC %TOPIC reply.
-#define PURPLE_IRCV3_RPL_TOPIC ("332") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_WELCOME:
- * A constant for the IRC %WELCOME reply.
-#define PURPLE_IRCV3_RPL_WELCOME ("001") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
- * PURPLE_IRCV3_RPL_YOURHOST:
- * A constant for the IRC %YOURHOST reply.
-#define PURPLE_IRCV3_RPL_YOURHOST ("002") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
-#endif /* PURPLE_IRCV3_CONSTANTS_H */
--- a/libpurple/protocols/ircv3/purpleircv3core.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include <gplugin-native.h>
-#include "purpleircv3core.h"
-#include "purpleircv3capabilities.h"
-#include "purpleircv3connection.h"
-#include "purpleircv3protocol.h"
-/******************************************************************************
- *****************************************************************************/
-static PurpleProtocol *ircv3_protocol = NULL;
-/******************************************************************************
- *****************************************************************************/
-static GPluginPluginInfo *
-purple_ircv3_query(G_GNUC_UNUSED GError **error) {
- PurplePluginInfoFlags flags = PURPLE_PLUGIN_INFO_FLAGS_INTERNAL |
- PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD;
- const gchar * const authors[] = {
- "Pidgin Developers <devel@pidgin.im>",
- return purple_plugin_info_new(
- "name", "IRCv3 Protocol",
- "version", DISPLAY_VERSION,
- "category", N_("Protocol"),
- "summary", N_("IRCv3 Protocol Plugin"),
- "description", N_("Modern IRC Support"),
- "website", PURPLE_WEBSITE,
- "abi-version", PURPLE_ABI_VERSION,
-purple_ircv3_load(GPluginPlugin *plugin, GError **error) {
- PurpleProtocolManager *manager = NULL;
- if(PURPLE_IS_PROTOCOL(ircv3_protocol)) {
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "plugin was not cleaned up properly");
- purple_ircv3_capabilities_register(GPLUGIN_NATIVE_PLUGIN(plugin));
- purple_ircv3_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin));
- purple_ircv3_protocol_register(GPLUGIN_NATIVE_PLUGIN(plugin));
- ircv3_protocol = purple_ircv3_protocol_new();
- manager = purple_protocol_manager_get_default();
- /* Manager can be NULL when we're generating the GIR stuff. */
- if(PURPLE_IS_PROTOCOL_MANAGER(manager)) {
- if(!purple_protocol_manager_register(manager, ircv3_protocol, error)) {
- g_clear_object(&ircv3_protocol);
-purple_ircv3_unload(G_GNUC_UNUSED GPluginPlugin *plugin,
- G_GNUC_UNUSED gboolean shutdown,
- PurpleProtocolManager *manager = NULL;
- if(!PURPLE_IS_PROTOCOL(ircv3_protocol)) {
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "plugin was not setup properly");
- manager = purple_protocol_manager_get_default();
- /* Manager can be NULL when we're generating the GIR stuff. */
- if(PURPLE_IS_PROTOCOL_MANAGER(manager)) {
- if(!purple_protocol_manager_unregister(manager, ircv3_protocol,
- g_clear_object(&ircv3_protocol);
-GPLUGIN_NATIVE_PLUGIN_DECLARE(purple_ircv3)
--- a/libpurple/protocols/ircv3/purpleircv3core.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_CORE_H
-#define PURPLE_IRCV3_CORE_H
-#define PURPLE_IRCV3_DEFAULT_SERVER "irc.libera.chat"
-#define PURPLE_IRCV3_DEFAULT_PLAIN_PORT 6667
-#define PURPLE_IRCV3_DEFAULT_TLS_PORT 6697
-#define PURPLE_IRCV3_DOMAIN (g_quark_from_static_string("ircv3-plugin"))
-#endif /* PURPLE_IRCV3_CORE_H */
--- a/libpurple/protocols/ircv3/purpleircv3ctcp.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3ctcp.h"
-#include "purpleircv3constants.h"
-#include "purpleircv3source.h"
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_ctcp_respond(PurpleIRCv3Connection *connection,
- PurpleMessage *message,
- const char *author = NULL;
- author = purple_message_get_author(message);
- purple_ircv3_source_parse(author, &nick, NULL, NULL);
- if(!purple_strempty(nick)) {
- PurpleContact *contact = NULL;
- contact = purple_ircv3_connection_find_or_create_contact(connection,
- if(PURPLE_IS_CONTACT(contact)) {
- id = purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact));
- purple_ircv3_connection_writef(connection, "%s %s :%s",
- PURPLE_IRCV3_MSG_NOTICE,
- g_clear_pointer(&nick, g_free);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_ctcp_handler(PurpleIRCv3Connection *connection,
- PurpleConversation *conversation,
- PurpleMessage *message,
- G_GNUC_UNUSED gpointer data)
- if(purple_strequal(command, PURPLE_IRCV3_CTCP_ACTION)) {
- purple_message_set_contents(message, params);
- purple_message_set_action(message, TRUE);
- purple_conversation_write_message(conversation, message);
- } else if(purple_strequal(command, PURPLE_IRCV3_CTCP_VERSION)) {
- purple_ircv3_ctcp_respond(connection, message, "Purple3 IRCv3");
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_ctcp_handle(PurpleIRCv3Connection *connection,
- PurpleConversation *conversation,
- PurpleMessage *message)
- PurpleMessageFlags flags;
- const char *contents = NULL;
- g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE);
- g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
- g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE);
- contents = purple_message_get_contents(message);
- if(contents == NULL || contents[0] != PURPLE_IRCV3_CTCP_DELIMITER) {
- /* Move past the delimiter. */
- /* Find the delimiter for the command. */
- ptr = g_strstr_len(contents, -1, " ");
- /* If we don't have a space, then we have no parameters. */
- size_t len = strlen(contents);
- command = g_strdup(contents);
- if(command[len - 1] == PURPLE_IRCV3_CTCP_DELIMITER) {
- command[len - 1] = '\0';
- command = g_strndup(contents, ptr - contents);
- params = g_strdup(ptr + 1);
- if(params[len - 1] == PURPLE_IRCV3_CTCP_DELIMITER) {
- params[len - 1] = '\0';
- flags = purple_message_get_flags(message);
- if(flags & PURPLE_MESSAGE_NOTIFY) {
- ret = purple_ircv3_connection_emit_ctcp_response(connection,
- ret = purple_ircv3_connection_emit_ctcp_request(connection, conversation,
- g_clear_pointer(&command, g_free);
- g_clear_pointer(¶ms, g_free);
-purple_ircv3_ctcp_add_default_handlers(PurpleIRCv3Connection *connection) {
- g_signal_connect(connection, "ctcp-request",
- G_CALLBACK(purple_ircv3_ctcp_handler), NULL);
--- a/libpurple/protocols/ircv3/purpleircv3ctcp.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_CTCP_H
-#define PURPLE_IRCV3_CTCP_H
-#include <glib-object.h>
-#include "purpleircv3connection.h"
- * purple_ircv3_ctcp_handle: (skip)
- * @connection: The connection.
- * @conversation: The conversation.
- * @message: The message.
- * Check if @message is a CTCP message and handles it accordingly.
- * Returns: %TRUE if the message was a CTCP message and %FALSE otherwise.
-gboolean purple_ircv3_ctcp_handle(PurpleIRCv3Connection *connection, PurpleConversation *conversation, PurpleMessage *message);
- * purple_ircv3_ctcp_add_default_handlers: (skip)
- * @connection: The connection.
- * Adds handlers for the CTCP commands that we support directly.
-void purple_ircv3_ctcp_add_default_handlers(PurpleIRCv3Connection *connection);
-#endif /* PURPLE_IRCV3_CTCP_H */
--- a/libpurple/protocols/ircv3/purpleircv3formatting.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include "purpleircv3formatting.h"
-#define IRCV3_FORMAT_BOLD (0x02)
-#define IRCV3_FORMAT_COLOR (0x03)
-#define IRCV3_FORMAT_HEX_COLOR (0x04)
-#define IRCV3_FORMAT_ITALIC (0x1d)
-#define IRCV3_FORMAT_MONOSPACE (0x11)
-#define IRCV3_FORMAT_RESET (0x0f)
-#define IRCV3_FORMAT_REVERSE (0x16)
-#define IRCV3_FORMAT_STRIKETHROUGH (0x1e)
-#define IRCV3_FORMAT_UNDERLINE (0x1f)
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_formatting_is_hex_color(const char *text) {
- for(int i = 0; i < 6; i++) {
- if(!g_ascii_isxdigit(text[i])) {
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_formatting_strip(const char *text) {
- /* We don't use purple_strempty here because if we're passed an empty
- * string, we should return a newly allocated empty string.
- str = g_string_new("");
- for(int i = 0; text[i] != '\0'; i++) {
- case IRCV3_FORMAT_BOLD:
- case IRCV3_FORMAT_ITALIC:
- case IRCV3_FORMAT_MONOSPACE:
- case IRCV3_FORMAT_RESET:
- case IRCV3_FORMAT_REVERSE:
- case IRCV3_FORMAT_STRIKETHROUGH:
- case IRCV3_FORMAT_UNDERLINE:
- case IRCV3_FORMAT_COLOR:
- if(g_ascii_isdigit(text[i + 1])) {
- if(g_ascii_isdigit(text[i + 1])) {
- if(text[i + 1] == ',' && g_ascii_isdigit(text[i + 2])) {
- if(g_ascii_isdigit(text[i + 1])) {
- case IRCV3_FORMAT_HEX_COLOR:
- if(purple_ircv3_formatting_is_hex_color(&text[i + 1])) {
- if(text[i + 1] == ',' &&
- purple_ircv3_formatting_is_hex_color(&text[i + 2]))
- g_string_append_c(str, text[i]);
- return g_string_free(str, FALSE);
--- a/libpurple/protocols/ircv3/purpleircv3formatting.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_FORMATTING_H
-#define PURPLE_IRCV3_FORMATTING_H
-#include "purpleircv3version.h"
- * purple_ircv3_formatting_strip:
- * @text: (nullable): The text to strip.
- * Removes all formatting from @text and returns the result.
- * Returns: (transfer full) (nullable): The result of stripping @text.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-char *purple_ircv3_formatting_strip(const char *text);
-#endif /* PURPLE_IRCV3_FORMATTING_H */
--- a/libpurple/protocols/ircv3/purpleircv3message.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,284 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3message.h"
-static GParamSpec *properties[N_PROPERTIES] = {NULL, };
-struct _PurpleIRCv3Message {
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_FINAL_TYPE(PurpleIRCv3Message, purple_ircv3_message, G_TYPE_OBJECT)
-purple_ircv3_message_finalize(GObject *obj) {
- PurpleIRCv3Message *message = PURPLE_IRCV3_MESSAGE(obj);
- g_clear_pointer(&message->command, g_free);
- g_clear_pointer(&message->params, g_strfreev);
- g_clear_pointer(&message->source, g_free);
- g_clear_pointer(&message->tags, g_hash_table_unref);
- G_OBJECT_CLASS(purple_ircv3_message_parent_class)->finalize(obj);
-purple_ircv3_message_get_property(GObject *obj, guint param_id, GValue *value,
- PurpleIRCv3Message *message = PURPLE_IRCV3_MESSAGE(obj);
- g_value_set_string(value, purple_ircv3_message_get_command(message));
- g_value_set_boxed(value, purple_ircv3_message_get_params(message));
- g_value_set_string(value, purple_ircv3_message_get_source(message));
- g_value_set_boxed(value, purple_ircv3_message_get_tags(message));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-purple_ircv3_message_set_property(GObject *obj, guint param_id,
- const GValue *value, GParamSpec *pspec)
- PurpleIRCv3Message *message = PURPLE_IRCV3_MESSAGE(obj);
- purple_ircv3_message_set_command(message, g_value_get_string(value));
- purple_ircv3_message_set_params(message, g_value_get_boxed(value));
- purple_ircv3_message_set_source(message, g_value_get_string(value));
- purple_ircv3_message_set_tags(message, g_value_get_boxed(value));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-purple_ircv3_message_init(G_GNUC_UNUSED PurpleIRCv3Message *message) {
-purple_ircv3_message_class_init(PurpleIRCv3MessageClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
- obj_class->finalize = purple_ircv3_message_finalize;
- obj_class->get_property = purple_ircv3_message_get_property;
- obj_class->set_property = purple_ircv3_message_set_property;
- * PurpleIRCv3Message:command:
- * The command of this message.
- * This could be something like JOIN or a server reply numeric like 005.
- properties[PROP_COMMAND] = g_param_spec_string(
- "The command of the message.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- * PurpleIRCv3Message:params:
- * The parameters of the message.
- * When serialized, the last item will be prefixed with a :.
- properties[PROP_PARAMS] = g_param_spec_boxed(
- "The parameters of this message.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- * PurpleIRCv3Message:source:
- * The source of the message.
- * This could be a nickname, a full nick!ident@server, a server name, or
- properties[PROP_SOURCE] = g_param_spec_string(
- "The source of the message.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- * PurpleIRCv3Message:tags:
- * The [ircv3 message tags](https://ircv3.net/specs/extensions/message-tags)
- properties[PROP_TAGS] = g_param_spec_boxed(
- "The ircv3 message tags from the message.",
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_message_new(const char *command) {
- PURPLE_IRCV3_TYPE_MESSAGE,
-purple_ircv3_message_get_command(PurpleIRCv3Message *message) {
- g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL);
- return message->command;
-purple_ircv3_message_set_command(PurpleIRCv3Message *message,
- g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message));
- g_return_if_fail(!purple_strempty(command));
- if(!purple_strequal(message->command, command)) {
- g_free(message->command);
- message->command = g_strdup(command);
- g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_COMMAND]);
-purple_ircv3_message_get_source(PurpleIRCv3Message *message) {
- g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL);
- return message->source;
-purple_ircv3_message_set_source(PurpleIRCv3Message *message,
- g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message));
- if(!purple_strequal(message->source, source)) {
- g_free(message->source);
- message->source = g_strdup(source);
- g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_SOURCE]);
-purple_ircv3_message_get_tags(PurpleIRCv3Message *message) {
- g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL);
-purple_ircv3_message_set_tags(PurpleIRCv3Message *message, GHashTable *tags) {
- g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message));
- if(message->tags != tags) {
- g_clear_pointer(&message->tags, g_hash_table_unref);
- message->tags = g_hash_table_ref(tags);
- g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_TAGS]);
-purple_ircv3_message_get_params(PurpleIRCv3Message *message) {
- g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL);
- return message->params;
-purple_ircv3_message_set_params(PurpleIRCv3Message *message, GStrv params) {
- g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message));
- if(message->params != params) {
- g_clear_pointer(&message->params, g_strfreev);
- message->params = g_strdupv(params);
- g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_PARAMS]);
--- a/libpurple/protocols/ircv3/purpleircv3message.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_MESSAGE_H
-#define PURPLE_IRCV3_MESSAGE_H
-#include <gplugin-native.h>
-#include "purpleircv3version.h"
-#define PURPLE_IRCV3_TYPE_MESSAGE (purple_ircv3_message_get_type())
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-G_DECLARE_FINAL_TYPE(PurpleIRCv3Message, purple_ircv3_message, PURPLE_IRCV3,
- * purple_ircv3_message_new:
- * @command: (not nullable): The command of the message.
- * Creates a new message with @command.
- * Returns: (transfer full): The new message.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-PurpleIRCv3Message *purple_ircv3_message_new(const char *command);
- * purple_ircv3_message_get_command:
- * @message: The instance.
- * Gets the command of the message.
- * Returns: The command of the message.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-const char *purple_ircv3_message_get_command(PurpleIRCv3Message *message);
- * purple_ircv3_message_set_command:
- * @message: The instance.
- * @command: The new command.
- * Sets the command for @message to @command.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_message_set_command(PurpleIRCv3Message *message, const char *command);
- * purple_ircv3_message_get_params:
- * @message: The instance.
- * Gets the parameters from @message.
- * Returns: (transfer none) (nullable): The parameters from @message.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-GStrv purple_ircv3_message_get_params(PurpleIRCv3Message *message);
- * purple_ircv3_message_set_params:
- * @message: The instance.
- * @params: (transfer none) (nullable): The new parameters.
- * Sets the parameters of @message to @params.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_message_set_params(PurpleIRCv3Message *message, GStrv params);
- * purple_ircv3_message_get_source:
- * @message: The instance.
- * Gets the the source of @message.
- * Returns: (nullable): The source of @message.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-const char *purple_ircv3_message_get_source(PurpleIRCv3Message *message);
- * purple_ircv3_message_set_source:
- * @message: The instance.
- * @source: (nullable): The new source.
- * Sets the source of @message to @source.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_message_set_source(PurpleIRCv3Message *message, const char *source);
- * purple_ircv3_message_get_tags:
- * @message: The instance.
- * Gets the tags from @message.
- * Returns: (transfer none) (element-type utf8 utf8) (nullable): The tags from
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-GHashTable *purple_ircv3_message_get_tags(PurpleIRCv3Message *message);
- * purple_ircv3_message_set_tags:
- * @message: The instance.
- * @tags: (transfer none) (element-type utf8 utf8) (nullable): The new tags.
- * Sets the tags of @message to @tags.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_message_set_tags(PurpleIRCv3Message *message, GHashTable *tags);
-#endif /* PURPLE_IRCV3_MESSAGE_H */
--- a/libpurple/protocols/ircv3/purpleircv3messagehandlers.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,315 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3messagehandlers.h"
-#include "purpleircv3connection.h"
-#include "purpleircv3constants.h"
-#include "purpleircv3core.h"
-#include "purpleircv3ctcp.h"
-#include "purpleircv3formatting.h"
-#include "purpleircv3source.h"
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_message_handler_fallback(PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = data;
- char *new_command = NULL;
- const char *command = NULL;
- command = purple_ircv3_message_get_command(message);
- new_command = g_strdup_printf(_("unknown command '%s'"), command);
- purple_ircv3_message_set_command(message, new_command);
- purple_ircv3_connection_add_status_message(connection, message);
- g_clear_pointer(&new_command, g_free);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_message_handler_status(PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- purple_ircv3_connection_add_status_message(data, message);
-purple_ircv3_message_handler_status_ignore_param0(PurpleIRCv3Message *message,
- GStrv new_params = NULL;
- params = purple_ircv3_message_get_params(message);
- n_params = g_strv_length(params);
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "expected n_params > 1, got %u", n_params);
- /* We need to make a copy because otherwise we'd get a use after free in
- new_params = g_strdupv(params + 1);
- purple_ircv3_message_set_params(message, new_params);
- g_clear_pointer(&new_params, g_strfreev);
- purple_ircv3_connection_add_status_message(data, message);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_message_handler_ping(PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = data;
- params = purple_ircv3_message_get_params(message);
- if(params != NULL && g_strv_length(params) == 1) {
- purple_ircv3_connection_writef(connection, "PONG %s", params[0]);
- purple_ircv3_connection_writef(connection, "PONG");
-purple_ircv3_message_handler_privmsg(PurpleIRCv3Message *v3_message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = data;
- PurpleContact *contact = NULL;
- PurpleConversation *conversation = NULL;
- PurpleMessage *message = NULL;
- PurpleMessageFlags flags = PURPLE_MESSAGE_RECV;
- GHashTable *tags = NULL;
- gpointer raw_id = NULL;
- gpointer raw_timestamp = NULL;
- const char *command = NULL;
- const char *source = NULL;
- const char *target = NULL;
- command = purple_ircv3_message_get_command(v3_message);
- params = purple_ircv3_message_get_params(v3_message);
- source = purple_ircv3_message_get_source(v3_message);
- tags = purple_ircv3_message_get_tags(v3_message);
- g_warning("privmsg received with no parameters");
- if(g_strv_length(params) != 2) {
- char *body = g_strjoinv(" ", params);
- g_warning("unknown privmsg message format: '%s'", body);
- purple_ircv3_source_parse(source, &nick, NULL, NULL);
- /* Find or create the conversation. */
- if(!purple_ircv3_connection_is_channel(connection, target)) {
- conversation = purple_ircv3_connection_find_or_create_conversation(connection,
- /* Find or create the contact. */
- contact = purple_ircv3_connection_find_or_create_contact(connection, nick);
- if(PURPLE_IS_CONTACT(contact)) {
- PurpleConversationMember *member = NULL;
- /* Update the contact's sid as it may have changed. */
- purple_contact_info_set_sid(PURPLE_CONTACT_INFO(contact), source);
- /* Make sure the contact is in the conversation. */
- member = purple_conversation_find_member(conversation,
- PURPLE_CONTACT_INFO(contact));
- if(!PURPLE_IS_CONVERSATION_MEMBER(member)) {
- purple_conversation_add_member(conversation,
- PURPLE_CONTACT_INFO(contact),
- /* Grab the msgid if one was provided. */
- if(g_hash_table_lookup_extended(tags, "msgid", NULL, &raw_id)) {
- if(!purple_strempty(raw_id)) {
- if(purple_strequal(command, PURPLE_IRCV3_MSG_NOTICE)) {
- flags |= PURPLE_MESSAGE_NOTIFY;
- /* Determine the timestamp of the message. */
- if(g_hash_table_lookup_extended(tags, "time", NULL, &raw_timestamp)) {
- const char *timestamp = raw_timestamp;
- if(!purple_strempty(timestamp)) {
- GTimeZone *tz = g_time_zone_new_utc();
- dt = g_date_time_new_from_iso8601(timestamp, tz);
- /* If the server didn't provide a time, use the current local time. */
- dt = g_date_time_new_now_local();
- stripped = purple_ircv3_formatting_strip(params[1]);
- message = g_object_new(
- /* Check if this is a CTCP message. */
- if(!purple_ircv3_ctcp_handle(connection, conversation, message)) {
- purple_conversation_write_message(conversation, message);
- g_clear_pointer(&nick, g_free);
- g_clear_object(&message);
-purple_ircv3_message_handler_topic(PurpleIRCv3Message *message,
- PurpleIRCv3Connection *connection = data;
- PurpleConversation *conversation = NULL;
- const char *channel = NULL;
- const char *command = NULL;
- const char *topic = NULL;
- command = purple_ircv3_message_get_command(message);
- params = purple_ircv3_message_get_params(message);
- n_params = g_strv_length(params);
- if(purple_strequal(command, PURPLE_IRCV3_MSG_TOPIC)) {
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "received TOPIC with %u parameters, expected 2",
- } else if(purple_strequal(command, PURPLE_IRCV3_RPL_NOTOPIC)) {
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "received RPL_NOTOPIC with %u parameters, expected 3",
- } else if(purple_strequal(command, PURPLE_IRCV3_RPL_TOPIC)) {
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "received RPL_TOPIC with %u parameters, expected 3",
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, "unexpected command %s",
- conversation = purple_ircv3_connection_find_or_create_conversation(connection,
- if(!PURPLE_IS_CONVERSATION(conversation)) {
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "failed to find or create channel '%s'", channel);
- purple_conversation_set_topic(conversation, topic);
--- a/libpurple/protocols/ircv3/purpleircv3messagehandlers.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_MESSAGE_HANDLERS_H
-#define PURPLE_IRCV3_MESSAGE_HANDLERS_H
-#include "purpleircv3message.h"
-#include "purpleircv3version.h"
- * PurpleIRCv3MessageHandler:
- * @message: The [class@Message] to handle.
- * @error: (nullable): A return address for a [type@GLib.Error].
- * @data: The user data that was passed to [method@PurpleIRCv3.Parser.parse].
- * Defines the type of a function that will be called to handle @message.
- * Returns: %TRUE if the command was handled properly, otherwise %FALSE and
-PURPLE_IRCV3_AVAILABLE_TYPE_IN_3_0
-typedef gboolean (*PurpleIRCv3MessageHandler)(PurpleIRCv3Message *message,
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_fallback(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_status(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_status_ignore_param0(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_ping(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_privmsg(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_topic(PurpleIRCv3Message *message, GError **error, gpointer data);
-#endif /* PURPLE_IRCV3_MESSAGE_HANDLERS_H */
--- a/libpurple/protocols/ircv3/purpleircv3parser.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,507 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include "purpleircv3parser.h"
-#include "purpleircv3capabilities.h"
-#include "purpleircv3constants.h"
-#include "purpleircv3core.h"
-#include "purpleircv3message.h"
-#include "purpleircv3messagehandlers.h"
-#include "purpleircv3sasl.h"
-struct _PurpleIRCv3Parser {
- PurpleIRCv3MessageHandler fallback_handler;
-G_DEFINE_FINAL_TYPE(PurpleIRCv3Parser, purple_ircv3_parser, G_TYPE_OBJECT)
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_parser_unescape_tag_value(const char *value) {
- GString *unescaped = g_string_new("");
- gboolean escaping = FALSE;
- /* Walk the string and replace escaped values according to
- * https://ircv3.net/specs/extensions/message-tags.html#escaping-values
- for(int i = 0; value[i] != '\0'; i++) {
- /* Set the replacement to the current character which will fall
- * through for everything, including '\\'
- char replacement = value[i];
- } else if(value[i] == 's') {
- } else if(value[i] == 'r') {
- } else if(value[i] == 'n') {
- g_string_append_c(unescaped, replacement);
- g_string_append_c(unescaped, value[i]);
- return g_string_free(unescaped, FALSE);
-purple_ircv3_parser_parse_tags(PurpleIRCv3Parser *parser,
- const gchar *tags_string, GError **error)
- GError *local_error = NULL;
- GHashTable *tags = NULL;
- GMatchInfo *info = NULL;
- gboolean matches = FALSE;
- tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
- /* tags_string can never be NULL, because g_match_info_fetch_named always
- * returns a string. So if we were passed an empty string, just return the
- if(*tags_string == '\0') {
- matches = g_regex_match_full(parser->regex_tags, tags_string, -1, 0, 0,
- if(local_error != NULL) {
- g_propagate_error(error, local_error);
- g_match_info_unref(info);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "failed to parse tags: unknown error");
- g_match_info_unref(info);
- while(g_match_info_matches(info)) {
- char *unescaped = NULL;
- key = g_match_info_fetch_named(info, "key");
- value = g_match_info_fetch_named(info, "value");
- unescaped = purple_ircv3_parser_unescape_tag_value(value);
- /* the hash table is created with destroy notifies for both key and
- * value, so there's no need to free the allocated memory right now.
- g_hash_table_insert(tags, key, unescaped);
- g_match_info_next(info, &local_error);
- if(local_error != NULL) {
- g_propagate_error(error, local_error);
- g_match_info_unref(info);
-purple_ircv3_parser_extract_params(G_GNUC_UNUSED PurpleIRCv3Parser *parser,
- GStrvBuilder *builder, const gchar *str)
- /* Loop through str finding each space separated string. */
- while(str != NULL && *str != '\0') {
- /* Look for a space. */
- ptr = strchr(str, ' ');
- /* If we found one, set it to null terminator and add the string to our
- g_strv_builder_add(builder, str);
- /* Move str to the next character as we know there's another
- * character which might be another null terminator.
- /* And don't forget to increment the count... ah ah ah! */
- /* Add the remaining string. */
- g_strv_builder_add(builder, str);
- /* Give the count another one, ah ah ah! */
- /* Finally break out of the loop. */
-purple_ircv3_parser_build_params(PurpleIRCv3Parser *parser,
- const gchar *middle, const gchar *coda,
- const gchar *trailing, guint *n_params)
- GStrvBuilder *builder = g_strv_builder_new();
- purple_ircv3_parser_extract_params(parser, builder, middle);
- g_strv_builder_add(builder, trailing);
- result = g_strv_builder_end(builder);
- g_strv_builder_unref(builder);
- if(result != NULL && n_params != NULL) {
- *n_params = g_strv_length(result);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_fallback_handler(PurpleIRCv3Message *message,
- G_GNUC_UNUSED gpointer data)
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, "no handler for command %s",
- purple_ircv3_message_get_command(message));
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-purple_ircv3_parser_finalize(GObject *obj) {
- PurpleIRCv3Parser *parser = PURPLE_IRCV3_PARSER(obj);
- g_clear_pointer(&parser->regex_message, g_regex_unref);
- g_clear_pointer(&parser->regex_tags, g_regex_unref);
- g_hash_table_destroy(parser->handlers);
- G_OBJECT_CLASS(purple_ircv3_parser_parent_class)->finalize(obj);
-purple_ircv3_parser_init(PurpleIRCv3Parser *parser) {
- parser->regex_message = g_regex_new("(?:@(?<tags>[^ ]+) )?"
- "(?::(?<source>[^ ]+) +)?"
- "(?: +(?<middle>(?:[^ :]+(?: +[^ :]+)*)))*"
- "(?<coda> +:(?<trailing>.*)?)?",
- g_assert(parser->regex_message != NULL);
- parser->regex_tags = g_regex_new("(?<key>(?<client_prefix>\\+?)"
- "(?:(?<vendor>[A-Za-z0-9-\\.]+)\\/)?"
- "(?<key_name>[A-Za-z0-9-]+)"
- "(?:=(?<value>[^\r\n;]*))?(?:;|$)",
- g_assert(parser->regex_tags != NULL);
- parser->fallback_handler = purple_ircv3_fallback_handler;
- parser->handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-purple_ircv3_parser_class_init(PurpleIRCv3ParserClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
- obj_class->finalize = purple_ircv3_parser_finalize;
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_parser_new(void) {
- return g_object_new(PURPLE_IRCV3_TYPE_PARSER, NULL);
-purple_ircv3_parser_set_fallback_handler(PurpleIRCv3Parser *parser,
- PurpleIRCv3MessageHandler handler)
- g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
- parser->fallback_handler = handler;
-purple_ircv3_parser_parse(PurpleIRCv3Parser *parser, const gchar *buffer,
- GError **error, gpointer data)
- PurpleIRCv3Message *message = NULL;
- PurpleIRCv3MessageHandler handler = NULL;
- GError *local_error = NULL;
- GHashTable *tags = NULL;
- GMatchInfo *info = NULL;
- gchar *tags_string = NULL;
- gchar *trailing = NULL;
- gboolean matches = FALSE;
- gboolean result = FALSE;
- g_return_val_if_fail(PURPLE_IRCV3_IS_PARSER(parser), FALSE);
- g_return_val_if_fail(buffer != NULL, FALSE);
- /* Check if the buffer matches our regex for messages. */
- matches = g_regex_match(parser->regex_message, buffer, 0, &info);
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "failed to parser buffer '%s'", buffer);
- g_match_info_unref(info);
- /* Extract the command from the buffer, so we can find the handler. */
- command = g_match_info_fetch_named(info, "command");
- handler = g_hash_table_lookup(parser->handlers, command);
- if(parser->fallback_handler == NULL) {
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "no handler found for command %s and no default "
- "handler set.", command);
- g_match_info_unref(info);
- handler = parser->fallback_handler;
- /* If we made it this far, we have our handler, so lets get the rest of the
- * parameters and call the handler.
- message = purple_ircv3_message_new(command);
- tags_string = g_match_info_fetch_named(info, "tags");
- tags = purple_ircv3_parser_parse_tags(parser, tags_string, &local_error);
- if(local_error != NULL) {
- g_propagate_error(error, local_error);
- g_clear_object(&message);
- g_hash_table_destroy(tags);
- g_match_info_unref(info);
- purple_ircv3_message_set_tags(message, tags);
- g_hash_table_unref(tags);
- source = g_match_info_fetch_named(info, "source");
- if(!purple_strempty(source)) {
- purple_ircv3_message_set_source(message, source);
- middle = g_match_info_fetch_named(info, "middle");
- coda = g_match_info_fetch_named(info, "coda");
- trailing = g_match_info_fetch_named(info, "trailing");
- params = purple_ircv3_parser_build_params(parser, middle, coda, trailing,
- purple_ircv3_message_set_params(message, params);
- /* Call the handler. */
- result = handler(message, error, data);
- /* Cleanup the left overs. */
- g_match_info_unref(info);
- g_clear_object(&message);
-purple_ircv3_parser_add_handler(PurpleIRCv3Parser *parser,
- PurpleIRCv3MessageHandler handler)
- g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
- g_return_if_fail(command != NULL);
- g_return_if_fail(handler != NULL);
- g_hash_table_insert(parser->handlers, g_strdup(command), handler);
-purple_ircv3_parser_add_handlers(PurpleIRCv3Parser *parser,
- PurpleIRCv3MessageHandler handler,
- const char *command = NULL;
- g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
- g_return_if_fail(handler != NULL);
- va_start(vargs, handler);
- while((command = va_arg(vargs, const char *)) != NULL) {
- purple_ircv3_parser_add_handler(parser, command, handler);
-purple_ircv3_parser_add_default_handlers(PurpleIRCv3Parser *parser) {
- g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser));
- purple_ircv3_parser_set_fallback_handler(parser,
- purple_ircv3_message_handler_fallback);
- /* Core functionality. */
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_CAP,
- purple_ircv3_capabilities_message_handler);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_NOTICE,
- purple_ircv3_message_handler_privmsg);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_PING,
- purple_ircv3_message_handler_ping);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_PRIVMSG,
- purple_ircv3_message_handler_privmsg);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_TOPIC,
- purple_ircv3_message_handler_topic);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_NOTOPIC,
- purple_ircv3_message_handler_topic);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_TOPIC,
- purple_ircv3_message_handler_topic);
- /* Post Registration Greetings */
- purple_ircv3_parser_add_handlers(parser,
- purple_ircv3_message_handler_status_ignore_param0,
- PURPLE_IRCV3_RPL_WELCOME,
- PURPLE_IRCV3_RPL_YOURHOST,
- PURPLE_IRCV3_RPL_CREATED,
- PURPLE_IRCV3_RPL_MYINFO,
- purple_ircv3_parser_add_handlers(parser,
- purple_ircv3_message_handler_status_ignore_param0,
- PURPLE_IRCV3_RPL_LUSERCLIENT,
- PURPLE_IRCV3_RPL_LUSEROP,
- PURPLE_IRCV3_RPL_LUSERUNKNOWN,
- PURPLE_IRCV3_RPL_LUSERCHANNELS,
- PURPLE_IRCV3_RPL_LUSERME,
- purple_ircv3_parser_add_handlers(parser,
- purple_ircv3_message_handler_status_ignore_param0,
- PURPLE_IRCV3_RPL_MOTDSTART,
- PURPLE_IRCV3_RPL_ENDOFMOTD,
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_LOGGEDIN,
- purple_ircv3_sasl_logged_in);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_LOGGEDOUT,
- purple_ircv3_sasl_logged_out);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_NICKLOCKED,
- purple_ircv3_sasl_nick_locked);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_SASLSUCCESS,
- purple_ircv3_sasl_success);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLFAIL,
- purple_ircv3_sasl_failed);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLTOOLONG,
- purple_ircv3_sasl_message_too_long);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLABORTED,
- purple_ircv3_sasl_aborted);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLALREADY,
- purple_ircv3_sasl_already_authed);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_SASLMECHS,
- purple_ircv3_sasl_mechanisms);
- purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_AUTHENTICATE,
- purple_ircv3_sasl_authenticate);
--- a/libpurple/protocols/ircv3/purpleircv3parser.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,126 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_PARSER_H
-#define PURPLE_IRCV3_PARSER_H
-#include <glib-object.h>
-#include "purpleircv3messagehandlers.h"
-#include "purpleircv3version.h"
-#define PURPLE_IRCV3_TYPE_PARSER (purple_ircv3_parser_get_type())
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-G_DECLARE_FINAL_TYPE(PurpleIRCv3Parser, purple_ircv3_parser, PURPLE_IRCV3,
- * purple_ircv3_parser_new:
- * Creates a new instance.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-PurpleIRCv3Parser *purple_ircv3_parser_new(void);
- * purple_ircv3_parser_set_fallback_handler: (skip):
- * @parser: The instance.
- * @handler: A [func@PurpleIRCv3.MessageHandler].
- * Sets @handler to be called for any messages that @parser doesn't know how to
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_parser_set_fallback_handler(PurpleIRCv3Parser *parser, PurpleIRCv3MessageHandler handler);
- * purple_ircv3_parser_parse:
- * @parser: The instance.
- * @buffer: The buffer to parse.
- * @error: Return address for a #GError, or %NULL.
- * @data: (nullable): Optional data to pass to the handler.
- * Parses @buffer with @parser.
- * Returns: %TRUE if the buffer was parsed correctly or %FALSE with @error set.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-gboolean purple_ircv3_parser_parse(PurpleIRCv3Parser *parser, const gchar *buffer, GError **error, gpointer data);
- * purple_ircv3_parser_add_handler:
- * @parser: The instance.
- * @command: The command string.
- * @handler: (scope forever): The handler to call.
- * Calls @handler every time @parser finds the command named @command.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_parser_add_handler(PurpleIRCv3Parser *parser, const char *command, PurpleIRCv3MessageHandler handler);
- * purple_ircv3_parser_add_handlers:
- * @parser: The instance.
- * @handler: (scope forever): The handler to call when the command is received.
- * @...: A %NULL terminated list of string command names.
- * Like [method@Parser.add_handler] but allows you to add multiple commands at
- * once that share a handler.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_parser_add_handlers(PurpleIRCv3Parser *parser, PurpleIRCv3MessageHandler handler, ...) G_GNUC_NULL_TERMINATED;
- * purple_ircv3_parser_add_default_handlers:
- * @parser: The instance.
- * Adds all of the default handlers to @parser.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_parser_add_default_handlers(PurpleIRCv3Parser *parser);
-#endif /* PURPLE_IRCV3_PARSER_H */
--- a/libpurple/protocols/ircv3/purpleircv3protocol.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,279 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3protocol.h"
-#include "purpleircv3connection.h"
-#include "purpleircv3core.h"
-#include "purpleircv3protocolconversation.h"
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_protocol_can_reach_cb(GObject *self, GAsyncResult *result,
- gboolean can_reach = FALSE;
- /* task and result share a cancellable, so either can be used here. */
- if(g_task_return_error_if_cancelled(task)) {
- can_reach = g_network_monitor_can_reach_finish(G_NETWORK_MONITOR(self), result,
- g_task_return_error(task, error);
- } else if(!can_reach) {
- g_task_return_new_error(task, PURPLE_IRCV3_DOMAIN, 0,
- _("Unknown network error."));
- g_task_return_boolean(task, TRUE);
-/******************************************************************************
- * PurpleProtocol Implementation
- *****************************************************************************/
-purple_ircv3_protocol_get_user_splits(G_GNUC_UNUSED PurpleProtocol *protocol) {
- PurpleAccountUserSplit *split = NULL;
- split = purple_account_user_split_new(_("Server"),
- PURPLE_IRCV3_DEFAULT_SERVER,
- splits = g_list_append(splits, split);
-purple_ircv3_protocol_get_account_options(G_GNUC_UNUSED PurpleProtocol *protocol)
- PurpleAccountOption *option;
- option = purple_account_option_int_new(_("Port"), "port",
- PURPLE_IRCV3_DEFAULT_TLS_PORT);
- options = g_list_append(options, option);
- option = purple_account_option_bool_new(_("Use TLS"), "use-tls", TRUE);
- options = g_list_append(options, option);
- option = purple_account_option_string_new(_("Server password"),
- "server-password", "");
- purple_account_option_string_set_masked(option, TRUE);
- options = g_list_append(options, option);
- option = purple_account_option_string_new(_("Ident name"), "ident", "");
- options = g_list_append(options, option);
- option = purple_account_option_string_new(_("Real name"), "real-name", "");
- options = g_list_append(options, option);
- option = purple_account_option_string_new(_("SASL login name"),
- "sasl-login-name", "");
- options = g_list_append(options, option);
- option = purple_account_option_string_new(_("SASL mechanisms"),
- "sasl-mechanisms", "");
- options = g_list_append(options, option);
- option = purple_account_option_bool_new(_("Allow plaintext SASL auth over "
- "unencrypted connection"),
- "plain-sasl-in-clear", FALSE);
- options = g_list_append(options, option);
- option = purple_account_option_int_new(_("Seconds between sending "
- "rate-limit-interval", 2);
- options = g_list_append(options, option);
- option = purple_account_option_int_new(_("Maximum messages to send at "
- "rate-limit-burst", 5);
- options = g_list_append(options, option);
-static PurpleConnection *
-purple_ircv3_protocol_create_connection(PurpleProtocol *protocol,
- PurpleAccount *account,
- const char *username = NULL;
- g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL);
- g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL);
- /* Make sure the username (which includes the servername via usersplits),
- * does not contain any whitespace.
- username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account));
- if(strpbrk(username, " \t\v\r\n") != NULL) {
- PURPLE_CONNECTION_ERROR,
- PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
- _("IRC nick and server may not contain whitespace"));
- PURPLE_IRCV3_TYPE_CONNECTION,
-purple_ircv3_protocol_status_types(G_GNUC_UNUSED PurpleProtocol *protocol,
- G_GNUC_UNUSED PurpleAccount *account)
- PurpleStatusType *type = NULL;
- type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE);
- types = g_list_append(types, type);
- type = purple_status_type_new_with_attrs(
- PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
- "message", _("Message"), purple_value_new(G_TYPE_STRING),
- types = g_list_append(types, type);
- type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE);
- types = g_list_append(types, type);
-purple_ircv3_protocol_can_connect_async(PurpleProtocol *protocol,
- PurpleAccount *account,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- GNetworkMonitor *monitor = NULL;
- GSocketConnectable *connectable = NULL;
- const char *username = NULL;
- task = g_task_new(protocol, cancellable, callback, data);
- monitor = g_network_monitor_get_default();
- username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account));
- parts = g_strsplit(username, "@", 2);
- port = purple_account_get_int(account, "port",
- PURPLE_IRCV3_DEFAULT_TLS_PORT);
- connectable = g_network_address_new(parts[1], (guint16)port);
- g_network_monitor_can_reach_async(monitor, connectable, cancellable,
- purple_ircv3_protocol_can_reach_cb,
- g_clear_object(&connectable);
-purple_ircv3_protocol_can_connect_finish(G_GNUC_UNUSED PurpleProtocol *protocol,
- return g_task_propagate_boolean(G_TASK(result), error);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_DYNAMIC_TYPE_EXTENDED(
- G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONVERSATION,
- purple_ircv3_protocol_conversation_init))
-purple_ircv3_protocol_init(G_GNUC_UNUSED PurpleIRCv3Protocol *protocol) {
-purple_ircv3_protocol_class_finalize(G_GNUC_UNUSED PurpleIRCv3ProtocolClass *klass) {
-purple_ircv3_protocol_class_init(PurpleIRCv3ProtocolClass *klass) {
- PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass);
- protocol_class->get_user_splits = purple_ircv3_protocol_get_user_splits;
- protocol_class->get_account_options =
- purple_ircv3_protocol_get_account_options;
- protocol_class->create_connection =
- purple_ircv3_protocol_create_connection;
- protocol_class->status_types = purple_ircv3_protocol_status_types;
- protocol_class->can_connect_async =
- purple_ircv3_protocol_can_connect_async;
- protocol_class->can_connect_finish =
- purple_ircv3_protocol_can_connect_finish;
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-purple_ircv3_protocol_register(GPluginNativePlugin *plugin) {
- purple_ircv3_protocol_register_type(G_TYPE_MODULE(plugin));
-purple_ircv3_protocol_new(void) {
- PURPLE_IRCV3_TYPE_PROTOCOL,
- "description", _("Version 3 of Internet Relay Chat (IRC)."),
- "icon-name", "im-ircv3",
- "icon-resource-path", "/im/pidgin/libpurple/ircv3/icons",
- "options", OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL |
- OPT_PROTO_SLASH_COMMANDS_NATIVE,
--- a/libpurple/protocols/ircv3/purpleircv3protocol.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_PROTOCOL_H
-#define PURPLE_IRCV3_PROTOCOL_H
-#include <glib-object.h>
-#include <gplugin-native.h>
-#include "purpleircv3version.h"
-#define PURPLE_IRCV3_TYPE_PROTOCOL (purple_ircv3_protocol_get_type())
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-G_DECLARE_DERIVABLE_TYPE(PurpleIRCv3Protocol, purple_ircv3_protocol,
- PURPLE_IRCV3, PROTOCOL, PurpleProtocol)
-struct _PurpleIRCv3ProtocolClass {
- PurpleProtocolClass parent;
- * purple_ircv3_protocol_register: (skip)
- * @plugin: The GTypeModule
- * Registers the dynamic type using @plugin.
-G_GNUC_INTERNAL void purple_ircv3_protocol_register(GPluginNativePlugin *plugin);
-G_GNUC_INTERNAL PurpleProtocol *purple_ircv3_protocol_new(void);
-#endif /* PURPLE_IRCV3_PROTOCOL_H */
--- a/libpurple/protocols/ircv3/purpleircv3protocolconversation.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3protocolconversation.h"
-#include "purpleircv3connection.h"
-#include "purpleircv3constants.h"
-#include "purpleircv3core.h"
-/******************************************************************************
- * PurpleProtocolConversation Implementation
- *****************************************************************************/
-purple_ircv3_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol,
- PurpleConversation *conversation,
- PurpleMessage *message,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- PurpleIRCv3Connection *v3_connection = NULL;
- PurpleAccount *account = NULL;
- PurpleConnection *connection = NULL;
- account = purple_conversation_get_account(conversation);
- connection = purple_account_get_connection(account);
- v3_connection = PURPLE_IRCV3_CONNECTION(connection);
- id = purple_conversation_get_id(conversation);
- /* TODO: the new message dialog sets the name but not the id and we want to
- * use the id only, so for now if id is NULL we grab the name.
- if(purple_strempty(id)) {
- id = purple_conversation_get_name(conversation);
- purple_ircv3_connection_writef(v3_connection, "PRIVMSG %s :%s", id,
- purple_message_get_contents(message));
- task = g_task_new(protocol, cancellable, callback, data);
- g_task_return_boolean(task, TRUE);
- /* This will be made conditional when we add echo-message support.
- * https://ircv3.net/specs/extensions/echo-message
- purple_conversation_write_message(conversation, message);
-purple_ircv3_protocol_conversation_send_message_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
- return g_task_propagate_boolean(G_TASK(result), error);
-static PurpleChannelJoinDetails *
-purple_ircv3_protocol_conversation_get_channel_join_details(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
- G_GNUC_UNUSED PurpleAccount *account)
- return purple_channel_join_details_new(FALSE, TRUE);
-purple_ircv3_protocol_conversation_join_channel_async(PurpleProtocolConversation *protocol,
- PurpleAccount *account,
- PurpleChannelJoinDetails* details,
- GCancellable* cancellable,
- GAsyncReadyCallback callback,
- PurpleIRCv3Connection *v3_connection = NULL;
- PurpleConnection *connection = NULL;
- PurpleConversation *conversation = NULL;
- PurpleConversationManager *manager = NULL;
- const char *name = NULL;
- const char *password = NULL;
- connection = purple_account_get_connection(account);
- v3_connection = PURPLE_IRCV3_CONNECTION(connection);
- task = g_task_new(protocol, cancellable, callback, data);
- /* Validate that the name isn't empty. */
- /* TODO: check that name match the ISUPPORT channel prefixes. */
- name = purple_channel_join_details_get_name(details);
- if(purple_strempty(name)) {
- g_task_return_new_error(task, PURPLE_IRCV3_DOMAIN, 0,
- "channel name is empty");
- manager = purple_conversation_manager_get_default();
- conversation = purple_conversation_manager_find_with_id(manager, account,
- /* If the conversation already exists, just return TRUE. */
- if(PURPLE_IS_CONVERSATION(conversation)) {
- g_task_return_boolean(task, TRUE);
- /* Build our join string. */
- cmd = g_string_new(NULL);
- g_string_append_printf(cmd, "%s %s", PURPLE_IRCV3_MSG_JOIN, name);
- password = purple_channel_join_details_get_password(details);
- if(!purple_strempty(password)) {
- g_string_append_printf(cmd, " %s", password);
- conversation = g_object_new(
- PURPLE_TYPE_CONVERSATION,
- "type", PurpleConversationTypeChannel,
- purple_conversation_manager_register(manager, conversation);
- g_clear_object(&conversation);
- purple_ircv3_connection_writef(v3_connection, "%s", cmd->str);
- g_string_free(cmd, TRUE);
- g_task_return_boolean(task, TRUE);
-purple_ircv3_protocol_conversation_join_channel_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol,
- return g_task_propagate_boolean(G_TASK(result), error);
-purple_ircv3_protocol_conversation_init(PurpleProtocolConversationInterface *iface) {
- iface->send_message_async =
- purple_ircv3_protocol_conversation_send_message_async;
- iface->send_message_finish =
- purple_ircv3_protocol_conversation_send_message_finish;
- iface->get_channel_join_details =
- purple_ircv3_protocol_conversation_get_channel_join_details;
- iface->join_channel_async =
- purple_ircv3_protocol_conversation_join_channel_async;
- iface->join_channel_finish =
- purple_ircv3_protocol_conversation_join_channel_finish;
--- a/libpurple/protocols/ircv3/purpleircv3protocolconversation.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_PROTOCOL_CONVERSATION_H
-#define PURPLE_IRCV3_PROTOCOL_CONVERSATION_H
-G_GNUC_INTERNAL void purple_ircv3_protocol_conversation_init(PurpleProtocolConversationInterface *iface);
-#endif /* PURPLE_IRCV3_PROTOCOL_CONVERSATION_H */
--- a/libpurple/protocols/ircv3/purpleircv3sasl.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,563 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <glib/gi18n-lib.h>
-#include "purpleircv3sasl.h"
-#include "purpleircv3capabilities.h"
-#include "purpleircv3connection.h"
-#include "purpleircv3constants.h"
-#include "purpleircv3core.h"
-#define PURPLE_IRCV3_SASL_DATA_KEY ("sasl-data")
- PurpleConnection *connection;
- GString *server_in_buffer;
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_sasl_get_username(PurpleConnection *connection) {
- PurpleAccount *account = NULL;
- const char *username = NULL;
- account = purple_connection_get_account(connection);
- username = purple_account_get_string(account, "sasl-login-name", "");
- if(username != NULL && username[0] != '\0') {
- return purple_connection_get_display_name(connection);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_sasl_data_free(PurpleIRCv3SASLData *data) {
- g_clear_object(&data->ctx);
- g_string_free(data->server_in_buffer, TRUE);
-purple_ircv3_sasl_data_add(PurpleConnection *connection, HaslContext *ctx) {
- PurpleIRCv3SASLData *data = NULL;
- data = g_new0(PurpleIRCv3SASLData, 1);
- g_object_set_data_full(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY,
- data, (GDestroyNotify)purple_ircv3_sasl_data_free);
- /* We don't reference this because the life cycle of this data is tied
- * directly to the connection and adding a reference to the connection
- * would keep both alive forever.
- data->connection = connection;
- /* We truncate the server_in_buffer when we need to so that we can minimize
- * allocations and simplify the logic involved with it.
- data->server_in_buffer = g_string_new("");
-purple_ircv3_sasl_attempt(PurpleIRCv3Connection *connection) {
- PurpleIRCv3SASLData *data = NULL;
- const char *next_mechanism = NULL;
- const char *current = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- current = hasl_context_get_current_mechanism(data->ctx);
- g_message("SASL '%s' mechanism failed", current);
- next_mechanism = hasl_context_next(data->ctx);
- if(next_mechanism == NULL) {
- GError *error = g_error_new(PURPLE_CONNECTION_ERROR,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("No valid SASL mechanisms found"));
- purple_connection_take_error(PURPLE_CONNECTION(connection), error);
- g_message("trying SASL '%s' mechanism", next_mechanism);
- purple_ircv3_connection_writef(connection, "%s %s",
- PURPLE_IRCV3_MSG_AUTHENTICATE,
-purple_ircv3_sasl_start(PurpleIRCv3Capabilities *caps) {
- PurpleIRCv3Connection *connection = NULL;
- PurpleAccount *account = NULL;
- PurpleConnection *purple_connection = NULL;
- HaslContext *ctx = NULL;
- const char *mechanisms = NULL;
- gboolean toggle = FALSE;
- connection = purple_ircv3_capabilities_get_connection(caps);
- purple_connection = PURPLE_CONNECTION(connection);
- account = purple_connection_get_account(purple_connection);
- ctx = hasl_context_new();
- /* At this point we are ready to start our SASL negotiation, so add a wait
- * counter to the capabilities and start the negotiations!
- purple_ircv3_capabilities_add_wait(caps);
- /* Determine what mechanisms we're allowing and tell the context. */
- mechanisms = purple_account_get_string(account, "sasl-mechanisms", "");
- if(purple_strempty(mechanisms)) {
- /* If the user didn't specify any mechanisms, grab the mechanisms that
- * the server advertised.
- mechanisms = purple_ircv3_capabilities_lookup(caps, "sasl", NULL);
- hasl_context_set_allowed_mechanisms(ctx, mechanisms);
- /* Add the values we know to the context. */
- hasl_context_set_username(ctx, purple_ircv3_sasl_get_username(purple_connection));
- hasl_context_set_password(ctx, purple_connection_get_password(purple_connection));
- toggle = purple_account_get_bool(account, "use-tls", TRUE);
- hasl_context_set_tls(ctx, toggle);
- toggle = purple_account_get_bool(account, "plain-sasl-in-clear", FALSE);
- hasl_context_set_allow_clear_text(ctx, toggle);
- /* Create our SASLData object, add it to the connection. */
- purple_ircv3_sasl_data_add(purple_connection, ctx);
- purple_ircv3_sasl_attempt(connection);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_sasl_ack_cb(PurpleIRCv3Capabilities *caps,
- G_GNUC_UNUSED const char *capability,
- G_GNUC_UNUSED gpointer data)
- purple_ircv3_sasl_start(caps);
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_sasl_request(PurpleIRCv3Capabilities *capabilities) {
- purple_ircv3_capabilities_request(capabilities,
- PURPLE_IRCV3_CAPABILITY_SASL);
- g_signal_connect(capabilities, "ack::" PURPLE_IRCV3_CAPABILITY_SASL,
- G_CALLBACK(purple_ircv3_sasl_ack_cb), NULL);
-purple_ircv3_sasl_logged_in(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- PurpleAccount *account = NULL;
- const char *sasl_name = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "RPL_LOGGEDIN received with no SASL data "
- /* Check if the SASL login name is not set. If it is not set, set it to the
- * current nick as it was successful.
- account = purple_connection_get_account(PURPLE_CONNECTION(connection));
- sasl_name = purple_account_get_string(account, "sasl-login-name", "");
- if(purple_strempty(sasl_name)) {
- char **userparts = NULL;
- const char *username = NULL;
- username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account));
- userparts = g_strsplit(username, "@", 2);
- purple_account_set_string(account, "sasl-login-name", userparts[0]);
-purple_ircv3_sasl_logged_out(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "RPL_LOGGEDOUT received with no SASL data "
- /* Not sure how to trigger this or what we should do in this case to be
- * honest, so just note it for now.
- g_warning("Server sent SASL logged out");
-purple_ircv3_sasl_nick_locked(PurpleIRCv3Message *v3_message,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "ERR_NICKLOCKED received with no SASL data "
- params = purple_ircv3_message_get_params(v3_message);
- message = g_strjoinv(" ", params);
- g_set_error(error, PURPLE_CONNECTION_ERROR,
- PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
- _("Nick name is locked: %s"), message);
-purple_ircv3_sasl_success(G_GNUC_UNUSED PurpleIRCv3Message *message,
- PurpleIRCv3Capabilities *capabilities = NULL;
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- capabilities = purple_ircv3_connection_get_capabilities(connection);
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "RPL_SASLSUCCESS received with no SASL data "
- /* This needs to be after we've checked the SASL data otherwise we might
- * end up removing a wait that we don't own.
- purple_ircv3_capabilities_remove_wait(capabilities);
- g_message("successfully authenticated with SASL '%s' mechanism.",
- hasl_context_get_current_mechanism(data->ctx));
-purple_ircv3_sasl_failed(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "ERR_SASLFAIL received with no SASL data present");
- purple_ircv3_sasl_attempt(connection);
-purple_ircv3_sasl_message_too_long(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "ERR_SASLTOOLONG received with no SASL data "
-purple_ircv3_sasl_aborted(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "ERR_SASLABORTED received with no SASL data "
- /* This is supposed to get sent, when the client sends `AUTHENTICATE *`,
- * but we don't do this, so I guess we'll note it for now...?
- g_warning("The server claims we aborted SASL authentication.");
-purple_ircv3_sasl_already_authed(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "ERR_SASLALREADY received with no SASL data "
- /* Similar to aborted above, we don't allow this, so just note that it
- g_warning("Server claims we tried to SASL authenticate again.");
-purple_ircv3_sasl_mechanisms(PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "RPL_SASLMECHS received with no SASL data "
- params = purple_ircv3_message_get_params(message);
- n_params = g_strv_length(params);
- /* We need to find a server that sends this message. The specification says
- * it _may_ be sent when the client sends AUTHENTICATE with an unknown
- * mechanism, but ergo doesn't.
- * We can't just blindly accept the new list either, as depending on how
- * the server implements it, we'll need to remove mechanisms we've already
- * tried in the event the server just dumps the entire list. As we're not
- * currently tracking which mechanisms we've tried, this will have to be
- char *message = g_strjoinv(" ", params);
- g_message("Server sent the following SASL mechanisms: %s", message);
- g_message("Server sent an empty list of SASL mechanisms");
-purple_ircv3_sasl_authenticate(PurpleIRCv3Message *message,
- PurpleIRCv3Connection *connection = user_data;
- PurpleIRCv3SASLData *data = NULL;
- params = purple_ircv3_message_get_params(message);
- n_params = g_strv_length(params);
- g_set_error(error, PURPLE_IRCV3_DOMAIN, 0,
- "ignoring AUTHENTICATE with %d parameters", n_params);
- data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY);
- g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0,
- "AUTHENTICATE received with no SASL data present");
- /* If the server sent us a payload, combine the chunks. */
- if(payload[0] != '+') {
- g_string_append(data->server_in_buffer, payload);
- if(strlen(payload) < 400) {
- /* The server sent a + which is an empty message or the final message
- * ended on a 400 byte barrier. */
- HaslMechanismResult res = 0;
- GError *local_error = NULL;
- guint8 *server_in = NULL;
- guint8 *client_out = NULL;
- gsize server_in_length = 0;
- size_t client_out_length = 0;
- /* If we have a buffer, base64 decode it, and then truncate it. */
- if(data->server_in_buffer->len > 0) {
- server_in = g_base64_decode(data->server_in_buffer->str,
- g_string_truncate(data->server_in_buffer, 0);
- /* Try to move to the next step of the sasl client. */
- res = hasl_context_step(data->ctx, server_in, server_in_length,
- &client_out, &client_out_length, &local_error);
- /* We should be done with server_in, so free it.*/
- g_clear_pointer(&server_in, g_free);
- if(res == HASL_MECHANISM_RESULT_ERROR) {
- g_propagate_error(error, local_error);
- if(local_error != NULL) {
- g_warning("hasl_context_step returned an error without an error "
- "status: %s", local_error->message);
- g_clear_error(&local_error);
- /* If we got an output for the client, write it out. */
- if(client_out_length > 0) {
- encoded = g_base64_encode(client_out, client_out_length);
- g_clear_pointer(&client_out, g_free);
- purple_ircv3_connection_writef(connection, "%s %s",
- PURPLE_IRCV3_MSG_AUTHENTICATE,
- purple_ircv3_connection_writef(connection, "%s +",
- PURPLE_IRCV3_MSG_AUTHENTICATE);
--- a/libpurple/protocols/ircv3/purpleircv3sasl.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_SASL_H
-#define PURPLE_IRCV3_SASL_H
-#include "purpleircv3capabilities.h"
-#include "purpleircv3message.h"
-G_GNUC_INTERNAL void purple_ircv3_sasl_request(PurpleIRCv3Capabilities *capabilities);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_logged_in(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_logged_out(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_nick_locked(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_success(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_failed(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_message_too_long(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_aborted(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_already_authed(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_mechanisms(PurpleIRCv3Message *message, GError **error, gpointer data);
-G_GNUC_INTERNAL gboolean purple_ircv3_sasl_authenticate(PurpleIRCv3Message *message, GError **error, gpointer data);
-#endif /* PURPLE_IRCV3_SASL_H */
--- a/libpurple/protocols/ircv3/purpleircv3source.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include "purpleircv3source.h"
-/******************************************************************************
- *****************************************************************************/
-purple_ircv3_source_parse(const char *source, char **nick, char **user,
- GMatchInfo *info = NULL;
- gboolean matched = FALSE;
- g_return_if_fail(!purple_strempty(source));
- g_return_if_fail(nick != NULL || user != NULL || host != NULL);
- /* If we find any \r \n or spaces in our source, it's invalid so we just
- matched = g_regex_match_simple("[\r\n ]", source, G_REGEX_DEFAULT,
- G_REGEX_MATCH_DEFAULT);
- regex = g_regex_new("^(?P<nick>[^ \r\n!]+)(?:!(?P<user>[^@]+)(?:@(?P<host>[^!@]+))?)?$",
- G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, NULL);
- matched = g_regex_match(regex, source, G_REGEX_MATCH_DEFAULT, &info);
- *nick = g_strdup(source);
- g_clear_pointer(&info, g_match_info_unref);
- g_clear_pointer(®ex, g_regex_unref);
- *nick = g_match_info_fetch_named(info, "nick");
- char *tmp = g_match_info_fetch_named(info, "user");
- if(!purple_strempty(tmp)) {
- char *tmp = g_match_info_fetch_named(info, "host");
- if(!purple_strempty(tmp)) {
- g_clear_pointer(&info, g_match_info_unref);
- g_clear_pointer(®ex, g_regex_unref);
--- a/libpurple/protocols/ircv3/purpleircv3source.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_SOURCE_H
-#define PURPLE_IRCV3_SOURCE_H
-#include <glib-object.h>
-#include "purpleircv3version.h"
- * purple_ircv3_source_parse:
- * @source: The source to parse.
- * @nick: (out) (transfer full): A return address for the nick.
- * @user: (out) (transfer full): A return address for the user.
- * @host: (out) (transfer full): A return address for the host.
- * Parses a `source` string like `pidgy!~u@53unc8n42i868.irc` into its nick,
- * user, and host parts per https://modern.ircdocs.horse/#source.
- * If the user or host aren't present in @source, but a return address is
- * provided for them, that pointer will be set to %NULL.
-PURPLE_IRCV3_AVAILABLE_IN_ALL
-void purple_ircv3_source_parse(const char *source, char **nick, char **user, char **host);
-#endif /* PURPLE_IRCV3_SOURCE_H */
--- a/libpurple/protocols/ircv3/purpleircv3version.h Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * This library 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 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 General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \
- !defined(PURPLE_IRCV3_COMPILATION)
-# error "only <libpurple/protocols/ircv3.h> may be included directly"
-#ifndef PURPLE_IRCV3_VERSION_H
-#define PURPLE_IRCV3_VERSION_H
-#if (defined(_WIN32) || defined(__CYGWIN__)) && \
- !defined(PURPLE_IRCV3_STATIC_COMPILATION)
-#define _PURPLE_IRCV3_EXPORT __declspec(dllexport)
-#define _PURPLE_IRCV3_IMPORT __declspec(dllimport)
-#define _PURPLE_IRCV3_EXPORT __attribute__((visibility("default")))
-#define _PURPLE_IRCV3_IMPORT
-#define _PURPLE_IRCV3_EXPORT
-#define _PURPLE_IRCV3_IMPORT
-#ifdef PURPLE_IRCV3_COMPILATION
-#define _PURPLE_IRCV3_API _PURPLE_IRCV3_EXPORT
-#define _PURPLE_IRCV3_API _PURPLE_IRCV3_IMPORT
-#define _PURPLE_IRCV3_EXTERN _PURPLE_IRCV3_API extern
-#ifdef PURPLE_IRCV3_DISABLE_DEPRECATION_WARNINGS
-#define PURPLE_IRCV3_DEPRECATED _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_DEPRECATED_FOR(f) _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_UNAVAILABLE(maj, min) _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_UNAVAILABLE_STATIC_INLINE(maj, min)
-#define PURPLE_IRCV3_UNAVAILABLE_TYPE(maj, min)
-#define PURPLE_IRCV3_DEPRECATED G_DEPRECATED _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_UNAVAILABLE(maj, min) G_UNAVAILABLE(maj, min) _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_UNAVAILABLE_STATIC_INLINE(maj, min) G_UNAVAILABLE(maj, min)
-#define PURPLE_IRCV3_UNAVAILABLE_TYPE(maj, min) G_UNAVAILABLE(maj, min)
- * PURPLE_IRCV3_VERSION_CUR_STABLE:
- * A macro that evaluates to the current stable version of the IRCv3 protocol
- * plugin, in a format that can be used by the C pre-processor.
-#define PURPLE_IRCV3_VERSION_CUR_STABLE \
- (G_ENCODE_VERSION(PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION))
-/* If the package sets PURPLE_IRCV3_VERSION_MIN_REQUIRED to some future
- * PURPLE_IRCV3_VERSION_X_Y value that we don't know about, it will compare as 0 in
-#ifndef PURPLE_IRCV3_VERSION_MIN_REQUIRED
-#define PURPLE_IRCV3_VERSION_MIN_REQUIRED (PURPLE_IRCV3_VERSION_CUR_STABLE)
-#elif PURPLE_IRCV3_VERSION_MIN_REQUIRED == 0
-#undef PURPLE_IRCV3_VERSION_MIN_REQUIRED
-#define PURPLE_IRCV3_VERSION_MIN_REQUIRED (PURPLE_IRCV3_VERSION_CUR_STABLE + 1)
-#endif /* PURPLE_IRCV3_VERSION_MIN_REQUIRED */
-#if !defined(PURPLE_IRCV3_VERSION_MAX_ALLOWED) || (PURPLE_IRCV3_VERSION_MAX_ALLOWED == 0)
-#undef PURPLE_IRCV3_VERSION_MAX_ALLOWED
-#define PURPLE_IRCV3_VERSION_MAX_ALLOWED (PURPLE_IRCV3_VERSION_CUR_STABLE)
-#endif /* PURPLE_IRCV3_VERSION_MAX_ALLOWED */
-#if PURPLE_IRCV3_VERSION_MIN_REQUIRED > PURPLE_IRCV3_VERSION_CUR_STABLE
-#error "PURPLE_IRCV3_VERSION_MIN_REQUIRED must be <= PURPLE_IRCV3_VERSION_CUR_STABLE"
-#if PURPLE_IRCV3_VERSION_MAX_ALLOWED < PURPLE_IRCV3_VERSION_MIN_REQUIRED
-#error "PURPLE_IRCV3_VERSION_MAX_ALLOWED must be >= PURPLE_IRCV3_VERSION_MIN_REQUIRED"
-#if PURPLE_IRCV3_VERSION_MIN_REQUIRED < G_ENCODE_VERSION(3, 0)
-#error "PURPLE_IRCV3_VERSION_MIN_REQUIRED must be >= PURPLE_IRCV3_VERSION_3_0"
-#define PURPLE_IRCV3_VAR _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_AVAILABLE_IN_ALL _PURPLE_IRCV3_EXTERN
- * PURPLE_IRCV3_VERSION_3_0:
- * A macro that evaluates to the 3.0 version of the IRCv3 protocol plugin, in a
- * format that can be used by the C pre-processor.
-#define PURPLE_IRCV3_VERSION_3_0 (G_ENCODE_VERSION(3, 0))
-#if PURPLE_IRCV3_VERSION_MAX_ALLOWED < PURPLE_IRCV3_VERSION_3_0
-#define PURPLE_IRCV3_AVAILABLE_IN_3_0 PURPLE_IRCV3_UNAVAILABLE(3, 0)
-#define PURPLE_IRCV3_AVAILABLE_STATIC_INLINE_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_STATIC_INLINE(3, 0)
-#define PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_MACRO(3, 0)
-#define PURPLE_IRCV3_AVAILABLE_ENUMERATOR_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_ENUMERATOR(3, 0)
-#define PURPLE_IRCV3_AVAILABLE_TYPE_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_TYPE(3, 0)
-#define PURPLE_IRCV3_AVAILABLE_IN_3_0 _PURPLE_IRCV3_EXTERN
-#define PURPLE_IRCV3_AVAILABLE_STATIC_INLINE_IN_3_0
-#define PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0
-#define PURPLE_IRCV3_AVAILABLE_ENUMERATOR_IN_3_0
-#define PURPLE_IRCV3_AVAILABLE_TYPE_IN_3_0
-#endif /* PURPLE_IRCV3_VERSION_H */
Binary file libpurple/protocols/ircv3/resources/icons/16x16/apps/im-ircv3.png has changed
Binary file libpurple/protocols/ircv3/resources/icons/22x22/apps/im-ircv3.png has changed
Binary file libpurple/protocols/ircv3/resources/icons/48x48/apps/im-ircv3.png has changed
--- a/libpurple/protocols/ircv3/resources/ircv3.gresource.xml Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
- <gresource prefix="/im/pidgin/libpurple/ircv3">
- <file>icons/16x16/apps/im-ircv3.png</file>
- <file>icons/22x22/apps/im-ircv3.png</file>
- <file>icons/48x48/apps/im-ircv3.png</file>
--- a/libpurple/protocols/ircv3/tests/meson.build Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
- f'test_ircv3_@prog@', f'test_ircv3_@prog@.c',
- dependencies : [birb_dep, libpurple_dep, glib, hasl],
- objects : ircv3_prpl.extract_all_objects(),
- c_args : ['-DPURPLE_IRCV3_COMPILATION'])
- test(f'ircv3_@prog@', e)
--- a/libpurple/protocols/ircv3/tests/test_ircv3_formatting.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
- * 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 "../purpleircv3formatting.h"
-/******************************************************************************
- *****************************************************************************/
-test_ircv3_formatting_strip(const char *input, const char *expected) {
- actual = purple_ircv3_formatting_strip(input);
- g_assert_cmpstr(actual, ==, expected);
- g_clear_pointer(&actual, g_free);
-test_ircv3_formatting_strip_null(void) {
- test_ircv3_formatting_strip(NULL, NULL);
-test_ircv3_formatting_strip_empty(void) {
- test_ircv3_formatting_strip("", "");
-test_ircv3_formatting_strip_color_comma(void) {
- test_ircv3_formatting_strip("\003,", ",");
-test_ircv3_formatting_strip_color_foreground_comma(void) {
- test_ircv3_formatting_strip("\0033,", ",");
-test_ircv3_formatting_strip_color_full(void) {
- test_ircv3_formatting_strip("\0033,9wee", "wee");
-test_ircv3_formatting_strip_color_foreground_3_digit(void) {
- test_ircv3_formatting_strip("\003314", "4");
-test_ircv3_formatting_strip_color_background_3_digit(void) {
- test_ircv3_formatting_strip("\0031,234", "4");
-test_ircv3_formatting_strip_hex_color(void) {
- test_ircv3_formatting_strip("\004FF00FFwoo!", "woo!");
-test_ircv3_formatting_strip_hex_color_full(void) {
- test_ircv3_formatting_strip("\004FF00FF,00FF00woo!", "woo!");
-test_ircv3_formatting_strip_hex_color_comma(void) {
- test_ircv3_formatting_strip("\004,", ",");
-test_ircv3_formatting_strip_hex_color_foreground_comma(void) {
- test_ircv3_formatting_strip("\004FEFEFE,", ",");
-test_ircv3_formatting_strip_hex_color_foreground_7_characters(void) {
- test_ircv3_formatting_strip("\004FEFEFEF", "F");
-test_ircv3_formatting_strip_hex_color_background_7_characters(void) {
- test_ircv3_formatting_strip("\004FEFEFE,2222223", "3");
-test_ircv3_formatting_strip_bold(void) {
- test_ircv3_formatting_strip("this is \002bold\002!", "this is bold!");
-test_ircv3_formatting_strip_italic(void) {
- test_ircv3_formatting_strip("what do you \035mean\035?!",
-test_ircv3_formatting_strip_monospace(void) {
- test_ircv3_formatting_strip("\021i++;\021", "i++;");
-test_ircv3_formatting_strip_reset(void) {
- test_ircv3_formatting_strip("end of formatting\017", "end of formatting");
-test_ircv3_formatting_strip_reverse(void) {
- test_ircv3_formatting_strip("re\026ver\026se", "reverse");
-test_ircv3_formatting_strip_strikethrough(void) {
- test_ircv3_formatting_strip("\036I could be wrong\036",
-test_ircv3_formatting_strip_underline(void) {
- test_ircv3_formatting_strip("You can't handle the \037truth\037!",
- "You can't handle the truth!");
-test_ircv3_formatting_strip_spec_example1(void) {
- test_ircv3_formatting_strip(
- "I love \0033IRC! \003It is the \0037best protocol ever!",
- "I love IRC! It is the best protocol ever!");
-test_ircv3_formatting_strip_spec_example2(void) {
- test_ircv3_formatting_strip(
- "This is a \035\00313,9cool \003message",
- "This is a cool message");
-test_ircv3_formatting_strip_spec_example3(void) {
- test_ircv3_formatting_strip(
- "IRC \002is \0034,12so \003great\017!",
-test_ircv3_formatting_strip_spec_example4(void) {
- test_ircv3_formatting_strip(
- "Rules: Don't spam 5\00313,8,6\003,7,8, and especially not \0029\002\035!",
- "Rules: Don't spam 5,6,7,8, and especially not 9!");
-/******************************************************************************
- *****************************************************************************/
-main(int argc, char *argv[]) {
- g_test_init(&argc, &argv, NULL);
- g_test_add_func("/ircv3/formatting/strip/null",
- test_ircv3_formatting_strip_null);
- g_test_add_func("/ircv3/formatting/strip/empty",
- test_ircv3_formatting_strip_empty);
- g_test_add_func("/ircv3/formatting/strip/color-comma",
- test_ircv3_formatting_strip_color_comma);
- g_test_add_func("/ircv3/formatting/strip/color-full",
- test_ircv3_formatting_strip_color_full);
- g_test_add_func("/ircv3/formatting/strip/color-foreground-comma",
- test_ircv3_formatting_strip_color_foreground_comma);
- g_test_add_func("/ircv3/formatting/strip/color-foreground-3-digit",
- test_ircv3_formatting_strip_color_foreground_3_digit);
- g_test_add_func("/ircv3/formatting/strip/color-background-3-digit",
- test_ircv3_formatting_strip_color_background_3_digit);
- g_test_add_func("/ircv3/formatting/strip/hex-color",
- test_ircv3_formatting_strip_hex_color);
- g_test_add_func("/ircv3/formatting/strip/hex-color-full",
- test_ircv3_formatting_strip_hex_color_full);
- g_test_add_func("/ircv3/formatting/strip/hex-color-comma",
- test_ircv3_formatting_strip_hex_color_comma);
- g_test_add_func("/ircv3/formatting/strip/hex-color-foreground-comma",
- test_ircv3_formatting_strip_hex_color_foreground_comma);
- g_test_add_func("/ircv3/formatting/strip/hex-color-foreground-7-characters",
- test_ircv3_formatting_strip_hex_color_foreground_7_characters);
- g_test_add_func("/ircv3/formatting/strip/hex-color-background-7-characters",
- test_ircv3_formatting_strip_hex_color_background_7_characters);
- g_test_add_func("/ircv3/formatting/strip/bold",
- test_ircv3_formatting_strip_bold);
- g_test_add_func("/ircv3/formatting/strip/italic",
- test_ircv3_formatting_strip_italic);
- g_test_add_func("/ircv3/formatting/strip/monospace",
- test_ircv3_formatting_strip_monospace);
- g_test_add_func("/ircv3/formatting/strip/reset",
- test_ircv3_formatting_strip_reset);
- g_test_add_func("/ircv3/formatting/strip/reverse",
- test_ircv3_formatting_strip_reverse);
- g_test_add_func("/ircv3/formatting/strip/strikethrough",
- test_ircv3_formatting_strip_strikethrough);
- g_test_add_func("/ircv3/formatting/strip/underline",
- test_ircv3_formatting_strip_underline);
- /* These tests are based on the examples here
- * https://modern.ircdocs.horse/formatting.html#examples
- g_test_add_func("/ircv3/formatting/strip/spec-example1",
- test_ircv3_formatting_strip_spec_example1);
- g_test_add_func("/ircv3/formatting/strip/spec-example2",
- test_ircv3_formatting_strip_spec_example2);
- g_test_add_func("/ircv3/formatting/strip/spec-example3",
- test_ircv3_formatting_strip_spec_example3);
- g_test_add_func("/ircv3/formatting/strip/spec-example4",
- test_ircv3_formatting_strip_spec_example4);
--- a/libpurple/protocols/ircv3/tests/test_ircv3_parser.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,822 +0,0 @@
- * 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 "../purpleircv3constants.h"
-#include "../purpleircv3message.h"
-#include "../purpleircv3parser.h"
-#define TEST_IRCV3_PARSER_DOMAIN (g_quark_from_static_string("test-ircv3-parser"))
- const gchar * const params[16];
-} TestPurpleIRCv3ParserData;
-/******************************************************************************
- *****************************************************************************/
-test_purple_ircv3_test_handler(PurpleIRCv3Message *message,
- G_GNUC_UNUSED GError **error,
- TestPurpleIRCv3ParserData *d = data;
- GHashTable *tags = NULL;
- const char *command = NULL;
- const char *source = NULL;
- command = purple_ircv3_message_get_command(message);
- params = purple_ircv3_message_get_params(message);
- source = purple_ircv3_message_get_source(message);
- tags = purple_ircv3_message_get_tags(message);
- n_params = g_strv_length(params);
- /* Make sure we have an expected tags hash table before checking them. */
- gpointer expected_value;
- /* Make sure the tag hash tables have the same size. */
- expected_size = g_hash_table_size(d->tags);
- actual_size = g_hash_table_size(tags);
- g_assert_cmpuint(actual_size, ==, expected_size);
- /* Since the tables have the same size, we can walk through the expected
- * table and use it to verify the actual table.
- g_hash_table_iter_init(&iter, d->tags);
- while(g_hash_table_iter_next(&iter, &expected_key, &expected_value)) {
- gpointer actual_value = NULL;
- gboolean found = FALSE;
- found = g_hash_table_lookup_extended(tags, expected_key, NULL,
- g_assert_cmpstr(actual_value, ==, expected_value);
- /* Walk through the params checking against the expected values. */
- g_assert_cmpuint(n_params, ==, d->n_params);
- for(guint i = 0; i < d->n_params; i++) {
- g_assert_cmpstr(params[i], ==, d->params[i]);
- /* Validate all the string parameters. */
- g_assert_cmpstr(source, ==, d->source);
- g_assert_cmpstr(command, ==, d->command);
- /* Cleanup everything the caller allocated. */
- g_clear_pointer(&d->tags, g_hash_table_destroy);
- /* Return the return value the caller asked for. */
-test_purple_ircv3_test_handler_error(G_GNUC_UNUSED PurpleIRCv3Message *message,
- G_GNUC_UNUSED gpointer data)
- g_set_error(error, TEST_IRCV3_PARSER_DOMAIN, 0, "test error");
-/******************************************************************************
- *****************************************************************************/
-test_purple_ircv3_parser(const gchar *source, TestPurpleIRCv3ParserData *d) {
- PurpleIRCv3Parser *parser = purple_ircv3_parser_new();
- gboolean result = FALSE;
- purple_ircv3_parser_set_fallback_handler(parser,
- test_purple_ircv3_test_handler);
- result = purple_ircv3_parser_parse(parser, source, &error, d);
- g_assert_no_error(error);
- g_clear_object(&parser);
-/******************************************************************************
- *****************************************************************************/
-test_purple_ircv3_parser_propagates_errors(void) {
- PurpleIRCv3Parser *parser = purple_ircv3_parser_new();
- gboolean result = FALSE;
- purple_ircv3_parser_set_fallback_handler(parser,
- test_purple_ircv3_test_handler_error);
- result = purple_ircv3_parser_parse(parser, "COMMAND", &error, NULL);
- g_assert_error(error, TEST_IRCV3_PARSER_DOMAIN, 0);
- g_assert_false(result);
- g_clear_object(&parser);
-test_purple_ircv3_parser_simple(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", "asdf"},
- test_purple_ircv3_parser("foo bar baz asdf", &data);
-test_purple_ircv3_parser_with_source(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", "asdf"},
- test_purple_ircv3_parser(":coolguy foo bar baz asdf", &data);
-test_purple_ircv3_parser_with_trailing(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", "asdf quux"},
- test_purple_ircv3_parser("foo bar baz :asdf quux", &data);
-test_purple_ircv3_parser_with_empty_trailing(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", ""},
- test_purple_ircv3_parser("foo bar baz :", &data);
-test_purple_ircv3_parser_with_trailing_starting_colon(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", ":asdf"},
- test_purple_ircv3_parser("foo bar baz ::asdf", &data);
-test_purple_ircv3_parser_with_source_and_trailing(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", "asdf quux"},
- test_purple_ircv3_parser(":coolguy foo bar baz :asdf quux", &data);
-test_purple_ircv3_parser_with_source_and_trailing_whitespace(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", " asdf quux "},
- test_purple_ircv3_parser(":coolguy foo bar baz : asdf quux ", &data);
-test_purple_ircv3_parser_with_source_and_trailing_colon(void) {
- TestPurpleIRCv3ParserData data = {
- .command = PURPLE_IRCV3_MSG_PRIVMSG,
- .params = {"bar", "lol :) "},
- test_purple_ircv3_parser(":coolguy PRIVMSG bar :lol :) ", &data);
-test_purple_ircv3_parser_with_source_and_empty_trailing(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", ""},
- test_purple_ircv3_parser(":coolguy foo bar baz :", &data);
-test_purple_ircv3_parser_with_source_and_trailing_only_whitespace(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz", " "},
- test_purple_ircv3_parser(":coolguy foo bar baz : ", &data);
-test_purple_ircv3_parser_with_tags(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "a", "b");
- g_hash_table_insert(data.tags, "c", "32");
- g_hash_table_insert(data.tags, "k", "");
- g_hash_table_insert(data.tags, "rt", "ql7");
- test_purple_ircv3_parser("@a=b;c=32;k;rt=ql7 foo", &data);
-test_purple_ircv3_parser_with_escaped_tags(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "a", "b\\and\nk");
- g_hash_table_insert(data.tags, "c", "72 45");
- g_hash_table_insert(data.tags, "d", "gh;764");
- test_purple_ircv3_parser("@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo",
-test_purple_ircv3_with_tags_and_source(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "c", "");
- g_hash_table_insert(data.tags, "h", "");
- g_hash_table_insert(data.tags, "a", "b");
- test_purple_ircv3_parser("@c;h=;a=b :quux ab cd", &data);
-test_purple_ircv3_last_param_no_colon(void) {
- TestPurpleIRCv3ParserData data = {
- .command = PURPLE_IRCV3_MSG_JOIN,
- test_purple_ircv3_parser(":src JOIN #chan", &data);
-test_purple_ircv3_last_param_with_colon(void) {
- TestPurpleIRCv3ParserData data = {
- .command = PURPLE_IRCV3_MSG_JOIN,
- test_purple_ircv3_parser(":src JOIN :#chan", &data);
-test_purple_ircv3_without_last_param(void) {
- TestPurpleIRCv3ParserData data = {
- test_purple_ircv3_parser(":src AWAY", &data);
-test_purple_ircv3_with_last_param(void) {
- TestPurpleIRCv3ParserData data = {
- test_purple_ircv3_parser(":src AWAY ", &data);
-test_purple_ircv3_tab_is_not_space(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"bar", "baz"},
- test_purple_ircv3_parser(":cool\tguy foo bar baz", &data);
-test_purple_ircv3_source_control_characters_1(void) {
- /* Break each string after the hex escape as they are supposed to only be
- * a single byte, but the c compiler will keep unescaping unless we break
- TestPurpleIRCv3ParserData data = {
- .source = "coolguy!ag@net\x03" "5w\x03" "ork.admin",
- .command = PURPLE_IRCV3_MSG_PRIVMSG,
- .params = {"foo", "bar baz"},
- const gchar *msg = NULL;
- msg = ":coolguy!ag@net\x03" "5w\x03" "ork.admin PRIVMSG foo :bar baz";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_source_control_characters_2(void) {
- /* Break each string after the hex escape as they are supposed to only be
- * a single byte, but the c compiler will keep unescaping unless we break
- TestPurpleIRCv3ParserData data = {
- .source = "coolguy!~ag@n\x02" "et\x03" "05w\x0f" "ork.admin",
- .command = PURPLE_IRCV3_MSG_PRIVMSG,
- .params = {"foo", "bar baz"},
- const gchar *msg = NULL;
- msg = ":coolguy!~ag@n\x02" "et\x03" "05w\x0f" "ork.admin PRIVMSG foo :bar "
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_everything(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "irc.example.com",
- .params = {"param1", "param2", "param3 param3"},
- const gchar *msg = NULL;
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "value1");
- g_hash_table_insert(data.tags, "tag2", "");
- g_hash_table_insert(data.tags, "vendor1/tag3", "value2");
- g_hash_table_insert(data.tags, "vendor2/tag4", "");
- msg = "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= "
- ":irc.example.com COMMAND param1 param2 :param3 param3";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_everything_but_tags(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "irc.example.com",
- .params = {"param1", "param2", "param3 param3"},
- const gchar *msg = NULL;
- msg = ":irc.example.com COMMAND param1 param2 :param3 param3";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_everything_but_source(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"param1", "param2", "param3 param3"},
- const gchar *msg = NULL;
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "value1");
- g_hash_table_insert(data.tags, "tag2", "");
- g_hash_table_insert(data.tags, "vendor1/tag3", "value2");
- g_hash_table_insert(data.tags, "vendor2/tag4", "");
- msg = "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 "
- "COMMAND param1 param2 :param3 param3";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_command_only(void) {
- TestPurpleIRCv3ParserData data = {
- test_purple_ircv3_parser("COMMAND", &data);
-test_purple_ircv3_slashes_are_fun(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "foo", "\\\\;\\s \r\n");
- test_purple_ircv3_parser("@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND", &data);
-test_purple_ircv3_unreal_broken_1(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "gravel.mozilla.org",
- .params = {"#momo", "Erroneous Nickname: Illegal characters"},
- const gchar *msg = NULL;
- msg = ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal "
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_unreal_broken_2(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "gravel.mozilla.org",
- .params = {"#tckk", "+n"},
- test_purple_ircv3_parser(":gravel.mozilla.org MODE #tckk +n ", &data);
-test_purple_ircv3_unreal_broken_3(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "services.esper.net",
- .params = {"#foo-bar", "+o", "foobar"},
- test_purple_ircv3_parser(":services.esper.net MODE #foo-bar +o foobar ",
-test_purple_ircv3_tag_escape_char_at_a_time(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "value\\ntest");
- test_purple_ircv3_parser("@tag1=value\\\\ntest COMMAND", &data);
-test_purple_ircv3_tag_drop_unnecessary_escapes(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "value1");
- test_purple_ircv3_parser("@tag1=value\\1 COMMAND", &data);
-test_purple_ircv3_tag_drop_trailing_slash(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "value1");
- test_purple_ircv3_parser("@tag1=value1\\ COMMAND", &data);
-test_purple_ircv3_duplicate_tags(void) {
- TestPurpleIRCv3ParserData data = {
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "5");
- g_hash_table_insert(data.tags, "tag2", "3");
- g_hash_table_insert(data.tags, "tag3", "4");
- test_purple_ircv3_parser("@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND", &data);
-test_purple_ircv3_vendor_tags_are_namespaced(void) {
- TestPurpleIRCv3ParserData data = {
- const gchar *msg = NULL;
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "tag1", "5");
- g_hash_table_insert(data.tags, "tag2", "3");
- g_hash_table_insert(data.tags, "tag3", "4");
- g_hash_table_insert(data.tags, "vendor/tag2", "8");
- msg = "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_special_mode_1(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"#channel", "+i"},
- test_purple_ircv3_parser(":SomeOp MODE #channel :+i", &data);
-test_purple_ircv3_special_mode_2(void) {
- TestPurpleIRCv3ParserData data = {
- .params = {"#channel", "+oo", "SomeUser", "AnotherUser"},
- test_purple_ircv3_parser(":SomeOp MODE #channel +oo SomeUser :AnotherUser",
-/******************************************************************************
- * Message tags examples
- *****************************************************************************/
-test_purple_ircv3_parser_message_tags_none(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "nick!ident@host.com",
- .command = PURPLE_IRCV3_MSG_PRIVMSG,
- .params = {"me", "Hello"},
- const char *msg = NULL;
- msg = ":nick!ident@host.com PRIVMSG me :Hello";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_parser_message_tags_3_tags(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "nick!ident@host.com",
- .command = PURPLE_IRCV3_MSG_PRIVMSG,
- .params = {"me", "Hello"},
- const char *msg = NULL;
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "aaa", "bbb");
- g_hash_table_insert(data.tags, "ccc", "");
- g_hash_table_insert(data.tags, "example.com/ddd", "eee");
- msg = "@aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me "
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_parser_message_tags_client_only(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "url_bot!bot@example.com",
- .command = PURPLE_IRCV3_MSG_PRIVMSG,
- .params = {"#channel", "Example.com: A News Story"},
- const char *msg = NULL;
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "+icon", "https://example.com/favicon.png");
- msg = "@+icon=https://example.com/favicon.png :url_bot!bot@example.com "
- "PRIVMSG #channel :Example.com: A News Story";
- test_purple_ircv3_parser(msg, &data);
-test_purple_ircv3_parser_message_tags_labeled_response(void) {
- TestPurpleIRCv3ParserData data = {
- .source = "nick!user@example.com",
- .params = {"#channel"},
- const char *msg = NULL;
- data.tags = g_hash_table_new(g_str_hash, g_str_equal);
- g_hash_table_insert(data.tags, "label", "123");
- g_hash_table_insert(data.tags, "msgid", "abc");
- g_hash_table_insert(data.tags, "+example-client-tag", "example-value");
- msg = "@label=123;msgid=abc;+example-client-tag=example-value "
- ":nick!user@example.com TAGMSG #channel";
- test_purple_ircv3_parser(msg, &data);
-/******************************************************************************
- *****************************************************************************/
-main(gint argc, gchar *argv[]) {
- g_test_init(&argc, &argv, NULL);
- /* Make sure an error in the handler is propagated back up to the calling
- g_test_add_func("/ircv3/parser/propagates-errors",
- test_purple_ircv3_parser_propagates_errors);
- /* These tests are based on the msg-split tests from
- * https://github.com/ircdocs/parser-tests/blob/master/tests/msg-split.yaml
- g_test_add_func("/ircv3/parser/simple",
- test_purple_ircv3_parser_simple);
- g_test_add_func("/ircv3/parser/with-source",
- test_purple_ircv3_parser_with_source);
- g_test_add_func("/ircv3/parser/with-trailing",
- test_purple_ircv3_parser_with_trailing);
- g_test_add_func("/ircv3/parser/with-empty-trailing",
- test_purple_ircv3_parser_with_empty_trailing);
- g_test_add_func("/ircv3/parser/with-trailing-starting-colon",
- test_purple_ircv3_parser_with_trailing_starting_colon);
- g_test_add_func("/ircv3/parser/with-source-and-trailing",
- test_purple_ircv3_parser_with_source_and_trailing);
- g_test_add_func("/ircv3/parser/with-source-and-trailing-whitespace",
- test_purple_ircv3_parser_with_source_and_trailing_whitespace);
- g_test_add_func("/ircv3/parser/with-source-and-trailing-colon",
- test_purple_ircv3_parser_with_source_and_trailing_colon);
- g_test_add_func("/ircv3/parser/with-source-and-empty-trailing",
- test_purple_ircv3_parser_with_source_and_empty_trailing);
- g_test_add_func("/ircv3/parser/with-source-and-trailing-only-whitespace",
- test_purple_ircv3_parser_with_source_and_trailing_only_whitespace);
- g_test_add_func("/ircv3/parser/with-tags",
- test_purple_ircv3_parser_with_tags);
- g_test_add_func("/ircv3/parser/with-escaped-tags",
- test_purple_ircv3_parser_with_escaped_tags);
- g_test_add_func("/ircv3/parser/with-tags-and-source",
- test_purple_ircv3_with_tags_and_source);
- g_test_add_func("/ircv3/parser/last-param-no-colon",
- test_purple_ircv3_last_param_no_colon);
- g_test_add_func("/ircv3/parser/last-param-with-colon",
- test_purple_ircv3_last_param_with_colon);
- g_test_add_func("/ircv3/parser/without-last-param",
- test_purple_ircv3_without_last_param);
- g_test_add_func("/ircv3/parser/with-last-parsm",
- test_purple_ircv3_with_last_param);
- g_test_add_func("/ircv3/parser/tab-is-not-space",
- test_purple_ircv3_tab_is_not_space);
- g_test_add_func("/ircv3/parser/source_control_characters_1",
- test_purple_ircv3_source_control_characters_1);
- g_test_add_func("/ircv3/parser/source_control_characters_2",
- test_purple_ircv3_source_control_characters_2);
- g_test_add_func("/ircv3/parser/everything",
- test_purple_ircv3_everything);
- g_test_add_func("/ircv3/parser/everything-but-tags",
- test_purple_ircv3_everything_but_tags);
- g_test_add_func("/ircv3/parser/everything-but-source",
- test_purple_ircv3_everything_but_source);
- g_test_add_func("/ircv3/parser/command-only",
- test_purple_ircv3_command_only);
- g_test_add_func("/ircv3/parser/slashes-are-fun",
- test_purple_ircv3_slashes_are_fun);
- g_test_add_func("/ircv3/parser/unreal-broken-1",
- test_purple_ircv3_unreal_broken_1);
- g_test_add_func("/ircv3/parser/unreal-broken-2",
- test_purple_ircv3_unreal_broken_2);
- g_test_add_func("/ircv3/parser/unreal-broken-3",
- test_purple_ircv3_unreal_broken_3);
- g_test_add_func("/ircv3/parser/tag-escape-char-at-a-time",
- test_purple_ircv3_tag_escape_char_at_a_time);
- g_test_add_func("/ircv3/parser/tag-drop-unnecessary-escapes",
- test_purple_ircv3_tag_drop_unnecessary_escapes);
- g_test_add_func("/ircv3/parser/tag-drop-trailing-slash",
- test_purple_ircv3_tag_drop_trailing_slash);
- g_test_add_func("/ircv3/parser/duplicate-tags",
- test_purple_ircv3_duplicate_tags);
- g_test_add_func("/ircv3/parser/vendor-tags-are-namespaced",
- test_purple_ircv3_vendor_tags_are_namespaced);
- g_test_add_func("/ircv3/parser/special-mode-1",
- test_purple_ircv3_special_mode_1);
- g_test_add_func("/ircv3/parser/special-mode-2",
- test_purple_ircv3_special_mode_2);
- /* These tests are the examples from the message-tags specification,
- * https://ircv3.net/specs/extensions/message-tags.html.
- g_test_add_func("/ircv3/parser/message-tags/none",
- test_purple_ircv3_parser_message_tags_none);
- g_test_add_func("/ircv3/parser/message-tags/3-tags",
- test_purple_ircv3_parser_message_tags_3_tags);
- g_test_add_func("/ircv3/parser/message-tags/client-only",
- test_purple_ircv3_parser_message_tags_client_only);
- g_test_add_func("/ircv3/parser/message-tags/labeled-message",
- test_purple_ircv3_parser_message_tags_labeled_response);
--- a/libpurple/protocols/ircv3/tests/test_ircv3_source.c Thu Mar 21 22:20:50 2024 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
- * 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 "../purpleircv3source.h"
-/******************************************************************************
- *****************************************************************************/
-test_ircv3_source_parse_return_address_required(void) {
- if(g_test_subprocess()) {
- purple_ircv3_source_parse("pidgy", NULL, NULL, NULL);
- g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
- g_test_trap_assert_stderr("*nick != NULL || user != NULL || host != NULL' failed*");
-test_ircv3_source_parse_nick(void) {
- /* Once to make sure that user and host are null. */
- purple_ircv3_source_parse("pidgy", &nick, &user, &host);
- g_assert_cmpstr(nick, ==, "pidgy");
- g_clear_pointer(&nick, g_free);
- /* Again to make sure it works without user and host. */
- purple_ircv3_source_parse("pidgy", &nick, NULL, NULL);
- g_assert_cmpstr(nick, ==, "pidgy");
- g_clear_pointer(&nick, g_free);
-test_ircv3_source_parse_user(void) {
- /* Once to make sure host is null. */
- purple_ircv3_source_parse("pidgy!~u", &nick, &user, &host);
- g_assert_cmpstr(nick, ==, "pidgy");
- g_clear_pointer(&nick, g_free);
- g_assert_cmpstr(user, ==, "~u");
- g_clear_pointer(&user, g_free);
- /* Again to make sure nick and host aren't required. */
- purple_ircv3_source_parse("pidgy!~u", NULL, &user, NULL);
- g_assert_cmpstr(user, ==, "~u");
- g_clear_pointer(&user, g_free);
-test_ircv3_source_parse_host(void) {
- /* Once to make sure everything works. */
- purple_ircv3_source_parse("pidgy!~u@53unc8n42i868.irc", &nick, &user,
- g_assert_cmpstr(nick, ==, "pidgy");
- g_clear_pointer(&nick, g_free);
- g_assert_cmpstr(user, ==, "~u");
- g_clear_pointer(&user, g_free);
- g_assert_cmpstr(host, ==, "53unc8n42i868.irc");
- g_clear_pointer(&host, g_free);
- /* Again to make sure nick and host aren't required. */
- purple_ircv3_source_parse("pidgy!~u@53unc8n42i868.irc", NULL, NULL, &host);
- g_assert_cmpstr(host, ==, "53unc8n42i868.irc");
- g_clear_pointer(&host, g_free);
-test_ircv3_source_parse_baddies(void) {
- purple_ircv3_source_parse("\n", &nick, NULL, NULL);
- purple_ircv3_source_parse("a\nb", &nick, NULL, NULL);
- purple_ircv3_source_parse("\r", &nick, NULL, NULL);
- purple_ircv3_source_parse("c\rd", &nick, NULL, NULL);
- purple_ircv3_source_parse(" ", &nick, NULL, NULL);
- purple_ircv3_source_parse("e f", &nick, NULL, NULL);
- purple_ircv3_source_parse("nick@foo!user@server", &nick, &user, &server);
- g_assert_cmpstr(nick, ==, "nick@foo");
- g_clear_pointer(&nick, g_free);
- g_assert_cmpstr(user, ==, "user");
- g_clear_pointer(&user, g_free);
- g_assert_cmpstr(server, ==, "server");
- g_clear_pointer(&server, g_free);
- purple_ircv3_source_parse("nick!user@server!foo", &nick, &user, &server);
- g_assert_cmpstr(nick, ==, "nick!user@server!foo");
-/******************************************************************************
- *****************************************************************************/
-main(int argc, char *argv[]) {
- g_test_init(&argc, &argv, NULL);
- g_test_add_func("/ircv3/source/parse/return-address-required",
- test_ircv3_source_parse_return_address_required);
- g_test_add_func("/ircv3/source/parse/nick", test_ircv3_source_parse_nick);
- g_test_add_func("/ircv3/source/parse/user", test_ircv3_source_parse_user);
- g_test_add_func("/ircv3/source/parse/host", test_ircv3_source_parse_host);
- g_test_add_func("/ircv3/source/parse/baddies",
- test_ircv3_source_parse_baddies);
\ No newline at end of file
--- a/libpurple/protocols/meson.build Thu Mar 21 22:20:50 2024 -0500
+++ b/libpurple/protocols/meson.build Mon Mar 25 21:43:28 2024 -0500
@@ -1,5 +1,3 @@
--- a/po/POTFILES.in Thu Mar 21 22:20:50 2024 -0500
+++ b/po/POTFILES.in Mon Mar 25 21:43:28 2024 -0500
@@ -72,9 +72,6 @@
libpurple/protocols/bonjour/parser.c
libpurple/protocols/bonjour/xmpp.c
-libpurple/protocols/demo/purpledemocontacts.c
-libpurple/protocols/demo/purpledemoplugin.c
-libpurple/protocols/demo/purpledemoprotocol.c
libpurple/protocols/gg/avatar.c
libpurple/protocols/gg/blist.c
libpurple/protocols/gg/chat.c
@@ -316,6 +313,9 @@
protocols/bonjour/purplebonjourcore.c
protocols/bonjour/purplebonjourprotocol.c
+protocols/demo/purpledemocontacts.c +protocols/demo/purpledemoplugin.c +protocols/demo/purpledemoprotocol.c protocols/xmpp/purplexmppconnection.c
protocols/xmpp/purplexmppcore.c
protocols/xmpp/purplexmppprotocol.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/meson.build Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,37 @@
+ 'purpledemoconnection.c', + 'purpledemoconnection.h', + 'purpledemocontacts.c', + 'purpledemocontacts.h', + 'purpledemoprotocol.c', + 'purpledemoprotocol.h', + 'purpledemoprotocolactions.c', + 'purpledemoprotocolactions.h', + 'purpledemoprotocolclient.c', + 'purpledemoprotocolclient.h', + 'purpledemoprotocolcontacts.c', + 'purpledemoprotocolcontacts.h', + 'purpledemoprotocolconversation.c', + 'purpledemoprotocolconversation.h', + 'purpledemoprotocolmedia.c', + 'purpledemoprotocolmedia.h', + demo_resources = gnome.compile_resources('purpledemoresource', + 'resources/purpledemo.gresource.xml', + source_dir : 'resources', + c_name : 'purple_demo') + DEMO_SOURCES += demo_resources + shared_library('demo', DEMO_SOURCES, + c_args : ['-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Purple-Demo"'], + gnu_symbol_visibility : 'hidden', + dependencies : [glib, json, libpurple_dep], + install_dir : PURPLE_PLUGINDIR) + devenv.append('PURPLE_PLUGIN_PATH', meson.current_build_dir()) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoconnection.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,80 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpledemoconnection.h" +#include "purpledemocontacts.h" +struct _PurpleDemoConnection { + PurpleConnection parent; +G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleDemoConnection, purple_demo_connection, + PURPLE_TYPE_CONNECTION, G_TYPE_FLAG_FINAL, {}) +/****************************************************************************** + * PurpleConnection Implementation + *****************************************************************************/ +purple_demo_connection_connect(PurpleConnection *connection, + G_GNUC_UNUSED GError **error) + PurpleAccount *account = purple_connection_get_account(connection); + purple_connection_set_state(connection, PURPLE_CONNECTION_STATE_CONNECTED); + purple_demo_contacts_load(account); +purple_demo_connection_disconnect(G_GNUC_UNUSED PurpleConnection *connection, + G_GNUC_UNUSED GError **error) +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +purple_demo_connection_init(G_GNUC_UNUSED PurpleDemoConnection *connection) { +purple_demo_connection_class_finalize(G_GNUC_UNUSED PurpleDemoConnectionClass *klass) { +purple_demo_connection_class_init(PurpleDemoConnectionClass *klass) { + PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass); + connection_class->connect = purple_demo_connection_connect; + connection_class->disconnect = purple_demo_connection_disconnect; +/****************************************************************************** + *****************************************************************************/ +purple_demo_connection_register(GPluginNativePlugin *plugin) { + purple_demo_connection_register_type(G_TYPE_MODULE(plugin)); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoconnection.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,36 @@
+ * 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/>. +#ifndef PURPLE_DEMO_CONNECTION_H +#define PURPLE_DEMO_CONNECTION_H +#define PURPLE_DEMO_TYPE_CONNECTION (purple_demo_connection_get_type()) +G_DECLARE_FINAL_TYPE(PurpleDemoConnection, purple_demo_connection, PURPLE_DEMO, + CONNECTION, PurpleConnection) +G_GNUC_INTERNAL void purple_demo_connection_register(GPluginNativePlugin *plugin); +#endif /* PURPLE_DEMO_CONNECTION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemocontacts.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,357 @@
+ * 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 <json-glib/json-glib.h> +#include "purpledemocontacts.h" +#include "purpledemoresource.h" +/****************************************************************************** + *****************************************************************************/ +purple_demo_contacts_load_contact_icon(PurpleContactInfo *info, + PurpleAvatar *avatar = NULL; + path = g_strdup_printf("/im/pidgin/purple/demo/buddy_icons/%s.png", name); + avatar = purple_avatar_new_from_resource(path, &error); + g_message("Failed to load find an icon for %s: %s", path, + if(PURPLE_IS_AVATAR(avatar)) { + purple_contact_info_set_avatar(info, avatar); + g_clear_object(&avatar); +purple_demo_contacts_load_contact_person(JsonObject *person_object, + PurpleContactInfo *info) + PurplePerson *person = NULL; + gboolean new_person = FALSE; + const char *value = NULL; + /* If the person has an id, grab it so we can use it when constructing the + if(json_object_has_member(person_object, "id")) { + value = json_object_get_string_member(person_object, "id"); + /* See if the contact has an existing person. */ + person = purple_contact_info_get_person(info); + if(PURPLE_IS_PERSON(person)) { + const char *existing_id = NULL; + /* If the existing person's id doesn't match the new one, NULL out + * person so it'll be recreated with the new id. + existing_id = purple_person_get_id(person); + if(!purple_strequal(existing_id, value)) { + /* If the person didn't exist or it had a different id, create a new person + if(!PURPLE_IS_PERSON(person)) { + person = g_object_new(PURPLE_TYPE_PERSON, "id", value, NULL); + if(json_object_has_member(person_object, "alias")) { + value = json_object_get_string_member(person_object, "alias"); + if(!purple_strempty(value)) { + purple_person_set_alias(person, value); + /* Create the link between the person and the contact info. */ + purple_person_add_contact_info(person, info); + purple_contact_info_set_person(info, person); + g_clear_object(&person); +purple_demo_contacts_load_contact_presence(JsonObject *presence_object, + PurpleContactInfo *info) + PurplePresence *presence = NULL; + const gchar *value = NULL; + presence = purple_contact_info_get_presence(info); + if(json_object_has_member(presence_object, "emoji")) { + value = json_object_get_string_member(presence_object, "emoji"); + if(!purple_strempty(value)) { + purple_presence_set_emoji(presence, value); + if(json_object_has_member(presence_object, "idle")) { + GDateTime *idle_since = NULL; + ivalue = json_object_get_int_member(presence_object, "idle"); + now = g_date_time_new_now_local(); + idle_since = g_date_time_add_minutes(now, -1 * ivalue); + purple_presence_set_idle(presence, TRUE, idle_since); + g_date_time_unref(idle_since); + g_date_time_unref(now); + if(json_object_has_member(presence_object, "message")) { + value = json_object_get_string_member(presence_object, "message"); + if(!purple_strempty(value)) { + purple_presence_set_message(presence, value); + if(json_object_has_member(presence_object, "mobile")) { + gboolean bvalue = FALSE; + bvalue = json_object_get_boolean_member(presence_object, "mobile"); + purple_presence_set_mobile(presence, bvalue); + if(json_object_has_member(presence_object, "primitive")) { + PurplePresencePrimitive primitive = PURPLE_PRESENCE_PRIMITIVE_OFFLINE; + value = json_object_get_string_member(presence_object, "primitive"); + if(!purple_strempty(value)) { + GEnumClass *enum_class = NULL; + GEnumValue *enum_value = NULL; + enum_class = g_type_class_ref(PURPLE_TYPE_PRESENCE_PRIMITIVE); + enum_value = g_enum_get_value_by_nick(enum_class, value); + if(enum_value != NULL) { + primitive = enum_value->value; + g_type_class_unref(enum_class); + purple_presence_set_primitive(presence, primitive); +purple_demo_contacts_load_contact(PurpleContactManager *manager, + PurpleAccount *account, + JsonObject *contact_object) + PurpleContact *contact = NULL; + PurpleContactInfo *info = NULL; + gboolean new_contact = FALSE; + const char *value = NULL; + /* If we have an id, grab so we can create the contact with it. */ + if(json_object_has_member(contact_object, "id")) { + id = json_object_get_string_member(contact_object, "id"); + /* Look for an existing contact before creating a new one. This stops us + * from getting multiples when we trigger connection errors. + if(!purple_strempty(id)) { + contact = purple_contact_manager_find_with_id(manager, account, id); + /* If we didn't find an existing contact, create it now with the provided + if(!PURPLE_IS_CONTACT(contact)) { + contact = purple_contact_new(account, id); + info = PURPLE_CONTACT_INFO(contact); + if(json_object_has_member(contact_object, "alias")) { + value = json_object_get_string_member(contact_object, "alias"); + if(!purple_strempty(value)) { + purple_contact_info_set_alias(info, value); + if(json_object_has_member(contact_object, "color")) { + value = json_object_get_string_member(contact_object, "color"); + if(!purple_strempty(value)) { + purple_contact_info_set_color(info, value); + if(json_object_has_member(contact_object, "display_name")) { + value = json_object_get_string_member(contact_object, "display_name"); + if(!purple_strempty(value)) { + purple_contact_info_set_display_name(info, value); + if(json_object_has_member(contact_object, "username")) { + value = json_object_get_string_member(contact_object, "username"); + if(!purple_strempty(value)) { + purple_contact_info_set_username(info, value); + purple_demo_contacts_load_contact_icon(info, value); + /* Load the profile if it exists. */ + if(json_object_has_member(contact_object, "profile")) { + value = json_object_get_string_member(contact_object, "profile"); + if(!purple_strempty(value)) { + g_object_set_data_full(G_OBJECT(info), "demo-profile", + g_strdup(value), g_free); + if(json_object_has_member(contact_object, "tags")) { + PurpleTags *tags = purple_contact_info_get_tags(info); + JsonArray *array = NULL; + GList *elements = NULL; + array = json_object_get_array_member(contact_object, "tags"); + elements = json_array_get_elements(array); + while(elements != NULL) { + JsonNode *tag_node = elements->data; + const char *tag = json_node_get_string(tag_node); + purple_tags_add(tags, tag); + elements = g_list_delete_link(elements, elements); + if(json_object_has_member(contact_object, "person")) { + JsonObject *person_object = NULL; + person_object = json_object_get_object_member(contact_object, + purple_demo_contacts_load_contact_person(person_object, info); + /* Load the presence. */ + if(json_object_has_member(contact_object, "presence")) { + JsonObject *presence_object = NULL; + presence_object = json_object_get_object_member(contact_object, + purple_demo_contacts_load_contact_presence(presence_object, info); + /* Finally add the contact to the contact manager if it's new. */ + purple_contact_manager_add(manager, contact); + g_clear_object(&contact); +/****************************************************************************** + *****************************************************************************/ +purple_demo_contacts_load(PurpleAccount *account) { + PurpleContactManager *manager = NULL; + GInputStream *istream = NULL; + GList *contacts = NULL; + JsonArray *contacts_array = NULL; + JsonNode *root_node = NULL; + JsonParser *parser = NULL; + /* get a stream to the contacts.json resource */ + istream = g_resource_open_stream(purple_demo_get_resource(), + "/im/pidgin/purple/demo/contacts.json", + G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); + /* create our parser */ + parser = json_parser_new(); + if(!json_parser_load_from_stream(parser, istream, NULL, &error)) { + g_critical("%s", error->message); + root_node = json_parser_get_root(parser); + manager = purple_contact_manager_get_default(); + /* Load the contacts! */ + contacts_array = json_node_get_array(root_node); + contacts = json_array_get_elements(contacts_array); + while(contacts != NULL) { + JsonNode *contact_node = NULL; + JsonObject *contact_object = NULL; + contact_node = contacts->data; + contact_object = json_node_get_object(contact_node); + purple_demo_contacts_load_contact(manager, account, contact_object); + contacts = g_list_delete_link(contacts, contacts); + /* Clean up everything else... */ + g_clear_object(&parser); + g_input_stream_close(istream, NULL, NULL); + g_object_unref(istream); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemocontacts.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,32 @@
+ * 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/>. +#ifndef PURPLE_DEMO_CONTACTS_H +#define PURPLE_DEMO_CONTACTS_H +G_GNUC_INTERNAL void purple_demo_contacts_load(PurpleAccount *account); +#endif /* PURPLE_DEMO_CONTACTS_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoplugin.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,113 @@
+ * 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/gi18n-lib.h> +#include <gplugin-native.h> +#include "purpledemoplugin.h" +#include "purpledemoconnection.h" +#include "purpledemoprotocol.h" +/****************************************************************************** + *****************************************************************************/ +static PurpleProtocol *demo_protocol = NULL; +/****************************************************************************** + * GPlugin Implementation + *****************************************************************************/ +static GPluginPluginInfo * +purple_demo_plugin_query(G_GNUC_UNUSED GError **error) { + const gchar *authors[] = { + "Pidgin Developers <devel@pidgin.im>", + return purple_plugin_info_new( + "name", "Demo Protocol Plugin", + "version", PURPLE_VERSION, + "category", N_("Protocol"), + "summary", N_("A protocol plugin used for demos."), + "description", N_("A protocol plugin that helps to demonstrate " + "features of libpurple and clients."), + "website", PURPLE_WEBSITE, + "abi-version", PURPLE_ABI_VERSION, + "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | + PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD, +purple_demo_plugin_load(GPluginPlugin *plugin, GError **error) { + PurpleProtocolManager *manager = NULL; + if(PURPLE_IS_PROTOCOL(demo_protocol)) { + g_set_error_literal(error, PURPLE_DEMO_DOMAIN, 0, + "plugin was not cleaned up properly"); + purple_demo_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin)); + purple_demo_protocol_register(GPLUGIN_NATIVE_PLUGIN(plugin)); + manager = purple_protocol_manager_get_default(); + demo_protocol = purple_demo_protocol_new(); + if(!purple_protocol_manager_register(manager, demo_protocol, error)) { + g_clear_object(&demo_protocol); +purple_demo_plugin_unload(G_GNUC_UNUSED GPluginPlugin *plugin, + G_GNUC_UNUSED gboolean shutdown, + PurpleProtocolManager *manager = purple_protocol_manager_get_default(); + if(!PURPLE_IS_PROTOCOL(demo_protocol)) { + g_set_error_literal(error, PURPLE_DEMO_DOMAIN, 0, + "plugin was not setup properly"); + if(!purple_protocol_manager_unregister(manager, demo_protocol, error)) { + g_clear_object(&demo_protocol); +GPLUGIN_NATIVE_PLUGIN_DECLARE(purple_demo_plugin) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoplugin.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,29 @@
+ * 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/>. +#ifndef PURPLE_DEMO_PLUGIN_H +#define PURPLE_DEMO_PLUGIN_H +#define PURPLE_DEMO_DOMAIN (g_quark_from_static_string("demo-plugin")) +#endif /* PURPLE_DEMO_PLUGIN_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocol.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,146 @@
+ * 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/gi18n-lib.h> +#include "purpledemoprotocol.h" +#include "purpledemoconnection.h" +#include "purpledemoprotocolactions.h" +#include "purpledemoprotocolclient.h" +#include "purpledemoprotocolcontacts.h" +#include "purpledemoprotocolconversation.h" +#include "purpledemoprotocolmedia.h" +struct _PurpleDemoProtocol { +/****************************************************************************** + * PurpleProtocol Implementation + *****************************************************************************/ +static PurpleConnection * +purple_demo_protocol_create_connection(PurpleProtocol *protocol, + PurpleAccount *account, + G_GNUC_UNUSED GError **error) + g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + PURPLE_DEMO_TYPE_CONNECTION, +purple_demo_protocol_status_types(G_GNUC_UNUSED PurpleProtocol *protocol, + G_GNUC_UNUSED PurpleAccount *account) + PurpleStatusType *type = NULL; + GList *status_types = NULL; + type = purple_status_type_new_with_attrs( + PURPLE_STATUS_AVAILABLE, "available", NULL, + "message", _("Message"), purple_value_new(G_TYPE_STRING), + status_types = g_list_append(status_types, type); + type = purple_status_type_new_with_attrs( + PURPLE_STATUS_AWAY, "away", NULL, + "message", _("Message"), purple_value_new(G_TYPE_STRING), + status_types = g_list_append(status_types, type); + type = purple_status_type_new_with_attrs( + PURPLE_STATUS_EXTENDED_AWAY, "extended_away", NULL, + "message", _("Message"), purple_value_new(G_TYPE_STRING), + status_types = g_list_append(status_types, type); + type = purple_status_type_new_full( + PURPLE_STATUS_OFFLINE, NULL, NULL, + status_types = g_list_append(status_types, type); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_DYNAMIC_TYPE_EXTENDED( + G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_ACTIONS, + purple_demo_protocol_actions_init) + G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT, + purple_demo_protocol_client_init) + G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONTACTS, + purple_demo_protocol_contacts_init) + G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONVERSATION, + purple_demo_protocol_conversation_init) + G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_MEDIA, + purple_demo_protocol_media_init)) +purple_demo_protocol_init(G_GNUC_UNUSED PurpleDemoProtocol *protocol) { +purple_demo_protocol_class_finalize(G_GNUC_UNUSED PurpleDemoProtocolClass *klass) { +purple_demo_protocol_class_init(PurpleDemoProtocolClass *klass) { + PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass); + protocol_class->status_types = purple_demo_protocol_status_types; + protocol_class->create_connection = purple_demo_protocol_create_connection; +/****************************************************************************** + *****************************************************************************/ +purple_demo_protocol_register(GPluginNativePlugin *plugin) { + purple_demo_protocol_register_type(G_TYPE_MODULE(plugin)); +purple_demo_protocol_new(void) { + PURPLE_DEMO_TYPE_PROTOCOL, + "description", "A protocol plugin with static data to be used in " + "icon-name", "im-purple-demo", + "icon-resource-path", "/im/pidgin/purple/demo/icons", + "options", OPT_PROTO_NO_PASSWORD, --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocol.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,41 @@
+ * 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/>. +#ifndef PURPLE_DEMO_PROTOCOL_H +#define PURPLE_DEMO_PROTOCOL_H +#include <glib-object.h> +#include <gplugin-native.h> +#define PURPLE_DEMO_TYPE_PROTOCOL (purple_demo_protocol_get_type()) +G_DECLARE_FINAL_TYPE(PurpleDemoProtocol, purple_demo_protocol, PURPLE_DEMO, + PROTOCOL, PurpleProtocol) +G_GNUC_INTERNAL void purple_demo_protocol_register(GPluginNativePlugin *plugin); +G_GNUC_INTERNAL PurpleProtocol *purple_demo_protocol_new(void); +#endif /* PURPLE_DEMO_PROTOCOL_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolactions.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,950 @@
+ * 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/gi18n-lib.h> +#include "purpledemoprotocol.h" +#include "purpledemoprotocolactions.h" +#include "purpledemoresource.h" +/****************************************************************************** + * Connection failure action implementations + *****************************************************************************/ +#define REAPER_BUDDY_NAME ("Gary") +#define DEFAULT_REAP_TIME (5) /* seconds */ +#define FATAL_TICK_STR N_("Reaping connection in %d second...") +#define FATAL_TICK_PLURAL_STR N_("Reaping connection in %d seconds...") +#define FATAL_DISCONNECT_STR N_("%s reaped the connection") +#define TEMPORARY_TICK_STR N_("Pruning connection in %d second...") +#define TEMPORARY_TICK_PLURAL_STR N_("Pruning connection in %d seconds...") +#define TEMPORARY_DISCONNECT_STR N_("%s pruned the connection") +purple_demo_protocol_failure_tick(gpointer data, + PurpleConnectionError error_code, + const gchar *tick_plural_str, + const gchar *disconnect_str) + PurpleConnection *connection = PURPLE_CONNECTION(data); + PurpleAccount *account = purple_connection_get_account(connection); + timeout = GPOINTER_TO_INT(g_object_steal_data(G_OBJECT(connection), + PurpleContact *contact = NULL; + PurpleContactManager *manager = NULL; + g_object_set_data(G_OBJECT(connection), "reaping-time", + GINT_TO_POINTER(timeout)); + manager = purple_contact_manager_get_default(); + contact = purple_contact_manager_find_with_username(manager, account, + if(PURPLE_IS_CONTACT(contact)) { + PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact); + PurplePresence *presence = purple_contact_info_get_presence(info); + const char *format = ngettext(tick_str, tick_plural_str, timeout); + message = g_strdup_printf(format, timeout); + purple_presence_set_message(presence, message); + return G_SOURCE_CONTINUE; + message = g_strdup_printf(_(disconnect_str), REAPER_BUDDY_NAME); + purple_connection_error(connection, error_code, message); + return G_SOURCE_REMOVE; +purple_demo_protocol_fatal_failure_cb(gpointer data) { + return purple_demo_protocol_failure_tick(data, + PURPLE_CONNECTION_ERROR_CUSTOM_FATAL, +purple_demo_protocol_temporary_failure_cb(gpointer data) { + return purple_demo_protocol_failure_tick(data, + PURPLE_CONNECTION_ERROR_CUSTOM_TEMPORARY, + TEMPORARY_TICK_PLURAL_STR, + TEMPORARY_DISCONNECT_STR); +purple_demo_protocol_failure_action_activate(G_GNUC_UNUSED GSimpleAction *action, + const gchar *tick_plural_str, + PurpleAccountManager *account_manager = NULL; + PurpleAccount *account = NULL; + PurpleConnection *connection = NULL; + PurpleContact *contact = NULL; + PurpleContactManager *contact_manager = NULL; + const char *account_id = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + account_manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(account_manager, account_id); + connection = purple_account_get_connection(account); + /* Do nothing if disconnected, or already in process of reaping. */ + if(!PURPLE_IS_CONNECTION(connection)) { + g_clear_object(&account); + if(g_object_get_data(G_OBJECT(connection), "reaping-time")) { + g_clear_object(&account); + contact_manager = purple_contact_manager_get_default(); + contact = purple_contact_manager_find_with_username(contact_manager, + if(PURPLE_IS_CONTACT(contact)) { + PurpleContactInfo *info = PURPLE_CONTACT_INFO(contact); + PurplePresence *presence = purple_contact_info_get_presence(info); + const char *format = NULL; + format = ngettext(tick_str, tick_plural_str, DEFAULT_REAP_TIME); + message = g_strdup_printf(format, DEFAULT_REAP_TIME); + purple_presence_set_idle(presence, FALSE, NULL); + purple_presence_set_message(presence, message); + g_object_set_data(G_OBJECT(connection), "reaping-time", + GINT_TO_POINTER(DEFAULT_REAP_TIME)); + g_timeout_add_seconds(1, cb, connection); + g_clear_object(&account); +purple_demo_protocol_temporary_failure_action_activate(GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + purple_demo_protocol_failure_action_activate(action, parameter, + TEMPORARY_TICK_PLURAL_STR, + purple_demo_protocol_temporary_failure_cb); +purple_demo_protocol_fatal_failure_action_activate(GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + purple_demo_protocol_failure_action_activate(action, parameter, + purple_demo_protocol_fatal_failure_cb); +/****************************************************************************** + * Request API action implementations + *****************************************************************************/ +purple_demo_protocol_request_input_ok_cb(G_GNUC_UNUSED gpointer data, + g_message(_("Successfully requested input from UI: %s"), value); +purple_demo_protocol_request_input_cancel_cb(G_GNUC_UNUSED gpointer data, + G_GNUC_UNUSED const char *value) + g_message(_("UI cancelled input request")); +purple_demo_protocol_request_input_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + gboolean multiline = FALSE, masked = FALSE; + char *secondary = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + g_clear_object(&account); + /* Alternate through all four combinations of {masked, multiline}. */ + masked = form % 2 == 1; + multiline = (form / 2) % 2 == 1; + secondary = g_strdup_printf(_("The input will be %s %s."), + masked ? "masked" : "unmasked", + multiline ? "multiple lines" : "single line"); + purple_request_input(connection, _("Request Input Demo"), + _("Please input some text…"), secondary, _("default"), + multiline, masked, NULL, + G_CALLBACK(purple_demo_protocol_request_input_ok_cb), + G_CALLBACK(purple_demo_protocol_request_input_cancel_cb), + purple_request_cpar_from_connection(connection), NULL); +purple_demo_protocol_request_choice_ok_cb(G_GNUC_UNUSED gpointer data, + const char *text = value; + g_message(_("Successfully requested a choice from UI: %s"), text); +purple_demo_protocol_request_choice_cancel_cb(G_GNUC_UNUSED gpointer data, + G_GNUC_UNUSED gpointer value) + g_message(_("UI cancelled choice request")); +purple_demo_protocol_request_choice_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + g_clear_object(&account); + purple_request_choice(connection, _("Request Choice Demo"), + _("Please pick an option…"), NULL, _("foo"), + G_CALLBACK(purple_demo_protocol_request_choice_ok_cb), + G_CALLBACK(purple_demo_protocol_request_choice_cancel_cb), + purple_request_cpar_from_connection(connection), + NULL, _("foo"), "foo", _("bar"), "bar", + _("baz"), "baz", NULL); +purple_demo_protocol_request_action_cb(G_GNUC_UNUSED gpointer data, int action) + g_message(_("Successfully requested an action from the UI: %d"), action); +purple_demo_protocol_request_action_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + g_clear_object(&account); + purple_request_action(connection, _("Request Action Demo"), + _("Please choose an action…"), NULL, 1, + purple_request_cpar_from_connection(connection), + _("foo"), purple_demo_protocol_request_action_cb, + _("bar"), purple_demo_protocol_request_action_cb, + _("baz"), purple_demo_protocol_request_action_cb); +} PurpleDemoProtocolWaitData; +purple_demo_protocol_request_wait_pulse_cb(gpointer data) { + PurpleDemoProtocolWaitData *wait = data; + purple_request_wait_pulse(wait->ui_handle); + return G_SOURCE_CONTINUE; +purple_demo_protocol_request_wait_cancel_cb(G_GNUC_UNUSED gpointer data) { + g_message(_("UI cancelled wait request")); +purple_demo_protocol_request_wait_close_cb(gpointer data) { + PurpleDemoProtocolWaitData *wait = data; + g_source_remove(wait->id); +purple_demo_protocol_request_wait_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + PurpleDemoProtocolWaitData *wait = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + g_clear_object(&account); + wait = g_new0(PurpleDemoProtocolWaitData, 1); + wait->ui_handle = purple_request_wait(connection, _("Request Wait Demo"), + _("Please wait…"), NULL, TRUE, + purple_demo_protocol_request_wait_cancel_cb, + purple_request_cpar_from_connection(connection), + wait->id = g_timeout_add(250, purple_demo_protocol_request_wait_pulse_cb, + purple_request_add_close_notify(wait->ui_handle, + purple_demo_protocol_request_wait_close_cb, +purple_demo_protocol_request_fields_ok_cb(G_GNUC_UNUSED gpointer data, + PurpleRequestPage *page) + PurpleAccount *account = NULL; + PurpleRequestFieldList *field = NULL; + const char *tmp = NULL; + info = g_string_new(_("Basic group:\n")); + g_string_append_printf(info, _("\tString: %s\n"), + purple_request_page_get_string(page, "string")); + g_string_append_printf(info, _("\tMultiline string: %s\n"), + purple_request_page_get_string(page, + g_string_append_printf(info, _("\tMasked string: %s\n"), + purple_request_page_get_string(page, + g_string_append_printf(info, _("\tAlphanumeric string: %s\n"), + purple_request_page_get_string(page, + g_string_append_printf(info, _("\tEmail string: %s\n"), + purple_request_page_get_string(page, "email")); + g_string_append_printf(info, _("\tInteger: %d\n"), + purple_request_page_get_integer(page, "int")); + g_string_append_printf(info, _("\tBoolean: %s\n"), + purple_request_page_get_bool(page, "bool") ? + _("TRUE") : _("FALSE")); + g_string_append(info, _("Multiple-choice group:\n")); + tmp = (const char *)purple_request_page_get_choice(page, "choice"); + g_string_append_printf(info, _("\tChoice: %s\n"), tmp); + field = PURPLE_REQUEST_FIELD_LIST(purple_request_page_get_field(page, + list = purple_request_field_list_get_selected(field); + tmp = (const char *)list->data; + g_string_append_printf(info, _("\tList: %s\n"), tmp); + field = PURPLE_REQUEST_FIELD_LIST(purple_request_page_get_field(page, + list = purple_request_field_list_get_selected(field); + g_string_append(info, _("\tMulti-list: [")); + tmp = (const char *)list->data; + g_string_append_printf(info, "%s%s", tmp, + list->next != NULL ? ", " : ""); + g_string_append(info, _("]\n")); + g_string_append(info, _("Special group:\n")); + account = purple_request_page_get_account(page, "account"); + if(PURPLE_IS_ACCOUNT(account)) { + tmp = purple_contact_info_get_name_for_display(PURPLE_CONTACT_INFO(account)); + g_string_append_printf(info, _("\tAccount: %s\n"), tmp); + g_message(_("Successfully requested fields:\n%s"), info->str); + g_string_free(info, TRUE); +purple_demo_protocol_request_fields_cancel_cb(G_GNUC_UNUSED gpointer data, + G_GNUC_UNUSED PurpleRequestPage *page) + g_message(_("UI cancelled field request")); +purple_demo_protocol_request_fields_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + PurpleRequestPage *page = NULL; + PurpleRequestGroup *group = NULL; + PurpleRequestField *boolfield = NULL; + PurpleRequestField *field = NULL; + PurpleRequestFieldChoice *choice_field = NULL; + PurpleRequestFieldList *list_field = NULL; + gconstpointer icon_data = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + page = purple_request_page_new(); + /* This group will contain basic fields. */ + group = purple_request_group_new(_("Basic")); + purple_request_page_add_group(page, group); + boolfield = purple_request_field_bool_new("bool", _("Sensitive?"), TRUE); + purple_request_field_set_tooltip(boolfield, + _("Allow modifying all fields.")); + purple_request_group_add_field(group, boolfield); + field = purple_request_field_label_new("basic-label", + _("This group contains basic fields")); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_string_new("string", _("A string"), + purple_request_field_set_required(field, TRUE); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_string_new("multiline-string", + _("A multiline string"), + purple_request_group_add_field(group, field); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + field = purple_request_field_string_new("masked-string", + _("A masked string"), _("default"), + purple_request_field_string_set_masked(PURPLE_REQUEST_FIELD_STRING(field), + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_string_new("alphanumeric", + _("An alphanumeric string"), + purple_request_field_set_validator(field, + purple_request_field_alphanumeric_validator, + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_string_new("email", _("An email"), + _("me@example.com"), FALSE); + purple_request_field_set_validator(field, + purple_request_field_email_validator, + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_int_new("int", _("An integer"), 123, -42, 1337); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + /* This group will contain fields with multiple options. */ + group = purple_request_group_new(_("Multiple")); + purple_request_page_add_group(page, group); + field = purple_request_field_label_new("multiple-label", + _("This group contains fields with multiple options")); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_choice_new("choice", _("A choice"), "foo"); + choice_field = PURPLE_REQUEST_FIELD_CHOICE(field); + purple_request_field_choice_add(choice_field, _("foo"), "foo"); + purple_request_field_choice_add(choice_field, _("bar"), "bar"); + purple_request_field_choice_add(choice_field, _("baz"), "baz"); + purple_request_field_choice_add(choice_field, _("quux"), "quux"); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_list_new("list", _("A list")); + list_field = PURPLE_REQUEST_FIELD_LIST(field); + purple_request_field_list_add_icon(list_field, _("foo"), NULL, "foo"); + purple_request_field_list_add_icon(list_field, _("bar"), NULL, "bar"); + purple_request_field_list_add_icon(list_field, _("baz"), NULL, "baz"); + purple_request_field_list_add_icon(list_field, _("quux"), NULL, "quux"); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_list_new("multilist", _("A multi-select list")); + list_field = PURPLE_REQUEST_FIELD_LIST(field); + purple_request_field_list_set_multi_select(list_field, TRUE); + purple_request_field_list_add_icon(list_field, _("foo"), NULL, "foo"); + purple_request_field_list_add_icon(list_field, _("bar"), NULL, "bar"); + purple_request_field_list_add_icon(list_field, _("baz"), NULL, "baz"); + purple_request_field_list_add_icon(list_field, _("quux"), NULL, "quux"); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + /* This group will contain specialized fields. */ + group = purple_request_group_new(_("Special")); + purple_request_page_add_group(page, group); + field = purple_request_field_label_new("special-label", + _("This group contains specialized fields")); + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + icon = g_resource_lookup_data(purple_demo_get_resource(), + "/im/pidgin/purple/demo/icons/scalable/apps/im-purple-demo.svg", + G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); + icon_data = g_bytes_get_data(icon, &icon_len); + field = purple_request_field_image_new("image", _("An image"), + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + field = purple_request_field_account_new("account", _("An account"), + g_object_bind_property(boolfield, "value", field, "sensitive", 0); + purple_request_group_add_field(group, field); + purple_request_fields(connection, _("Request Fields Demo"), + _("Please fill out these fields…"), NULL, page, + G_CALLBACK(purple_demo_protocol_request_fields_ok_cb), + G_CALLBACK(purple_demo_protocol_request_fields_cancel_cb), + purple_request_cpar_from_connection(connection), + g_clear_object(&account); +purple_demo_protocol_request_path_ok_cb(gpointer data, const char *filename) { + const char *type = data; + g_message(_("Successfully requested %s from UI: %s"), type, filename); +purple_demo_protocol_request_path_cancel_cb(gpointer data) { + const char *type = data; + g_message(_("UI cancelled %s request"), type); +purple_demo_protocol_request_file_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + g_clear_object(&account); + purple_request_file(connection, _("Request File Demo"), + G_CALLBACK(purple_demo_protocol_request_path_ok_cb), + G_CALLBACK(purple_demo_protocol_request_path_cancel_cb), + purple_request_cpar_from_connection(connection), +purple_demo_protocol_request_folder_activate(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleConnection *connection = NULL; + const gchar *account_id = NULL; + PurpleAccountManager *manager = NULL; + PurpleAccount *account = NULL; + if(!g_variant_is_of_type(parameter, G_VARIANT_TYPE_STRING)) { + g_critical("Demo failure action parameter is of incorrect type %s", + g_variant_get_type_string(parameter)); + account_id = g_variant_get_string(parameter, NULL); + manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(manager, account_id); + connection = purple_account_get_connection(account); + g_clear_object(&account); + purple_request_folder(connection, _("Request Folder Demo"), NULL, + G_CALLBACK(purple_demo_protocol_request_path_ok_cb), + G_CALLBACK(purple_demo_protocol_request_path_cancel_cb), + purple_request_cpar_from_connection(connection), +/****************************************************************************** + * Contact action implementations + *****************************************************************************/ +static const gchar *contacts[] = {"Alice", "Bob", "Carlos", "Erin" }; +purple_demo_protocol_remote_add(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleAccount *account = NULL; + PurpleAccountManager *account_manager = NULL; + PurpleAddContactRequest *request = NULL; + PurpleNotification *notification = NULL; + PurpleNotificationManager *notification_manager = NULL; + const gchar *account_id = NULL; + static guint counter = 0; + account_id = g_variant_get_string(parameter, NULL); + account_manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(account_manager, account_id); + request = purple_add_contact_request_new(account, contacts[counter]); + notification = purple_notification_new_from_add_contact_request(request); + notification_manager = purple_notification_manager_get_default(); + purple_notification_manager_add(notification_manager, notification); + if(counter >= G_N_ELEMENTS(contacts)) { + g_clear_object(¬ification); + g_clear_object(&account); +static const char *puns[] = { + "Toucan play at that game!", + "As long as it's not too much of a birden...", + "Sounds like a bit of ostrich...", + "People can't stop raven!", + "Have you heard about the bird?", +purple_demo_protocol_generic_notification(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED gpointer data) + PurpleAccount *account = NULL; + PurpleAccountManager *account_manager = NULL; + PurpleNotification *notification = NULL; + PurpleNotificationManager *notification_manager = NULL; + const char *account_id = NULL; + static guint counter = 0; + account_id = g_variant_get_string(parameter, NULL); + account_manager = purple_account_manager_get_default(); + account = purple_account_manager_find_by_id(account_manager, account_id); + notification = purple_notification_new(PURPLE_NOTIFICATION_TYPE_GENERIC, + account, g_strdup(puns[counter]), + notification_manager = purple_notification_manager_get_default(); + purple_notification_manager_add(notification_manager, notification); + if(counter >= G_N_ELEMENTS(puns)) { + g_clear_object(¬ification); + g_clear_object(&account); +/****************************************************************************** + * PurpleProtocolActions Implementation + *****************************************************************************/ +purple_demo_protocol_get_prefix(G_GNUC_UNUSED PurpleProtocolActions *actions) { +purple_demo_protocol_get_action_group(G_GNUC_UNUSED PurpleProtocolActions *actions, + G_GNUC_UNUSED PurpleConnection *connection) + GSimpleActionGroup *group = NULL; + GActionEntry entries[] = { + .name = "temporary-failure", + .activate = purple_demo_protocol_temporary_failure_action_activate, + .name = "fatal-failure", + .activate = purple_demo_protocol_fatal_failure_action_activate, + .name = "generic-notification", + .activate = purple_demo_protocol_generic_notification, + .activate = purple_demo_protocol_remote_add, + .name = "request-input", + .activate = purple_demo_protocol_request_input_activate, + .name = "request-choice", + .activate = purple_demo_protocol_request_choice_activate, + .name = "request-action", + .activate = purple_demo_protocol_request_action_activate, + .name = "request-wait", + .activate = purple_demo_protocol_request_wait_activate, + .name = "request-fields", + .activate = purple_demo_protocol_request_fields_activate, + .name = "request-file", + .activate = purple_demo_protocol_request_file_activate, + .name = "request-folder", + .activate = purple_demo_protocol_request_folder_activate, + gsize nentries = G_N_ELEMENTS(entries); + group = g_simple_action_group_new(); + g_action_map_add_action_entries(G_ACTION_MAP(group), entries, nentries, + return G_ACTION_GROUP(group); +purple_demo_protocol_get_menu(G_GNUC_UNUSED PurpleProtocolActions *actions, + G_GNUC_UNUSED PurpleConnection *connection) + GMenuItem *item = NULL; + item = g_menu_item_new(_("Trigger temporary connection failure..."), + "prpl-demo.temporary-failure"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(menu, item); + item = g_menu_item_new(_("Trigger fatal connection failure..."), + "prpl-demo.fatal-failure"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(menu, item); + item = g_menu_item_new(_("Trigger a generic notification"), + "prpl-demo.generic-notification"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(menu, item); + item = g_menu_item_new(_("Trigger a contact adding you"), + "prpl-demo.remote-add"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(menu, item); + submenu = g_menu_new(); + item = g_menu_item_new(_("Input"), "prpl-demo.request-input"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + item = g_menu_item_new(_("Choice"), "prpl-demo.request-choice"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + item = g_menu_item_new(_("Action"), "prpl-demo.request-action"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + item = g_menu_item_new(_("Wait"), "prpl-demo.request-wait"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + item = g_menu_item_new(_("Fields"), "prpl-demo.request-fields"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + item = g_menu_item_new(_("File"), "prpl-demo.request-file"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + item = g_menu_item_new(_("Folder"), "prpl-demo.request-folder"); + g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s", + g_menu_append_item(submenu, item); + g_menu_append_submenu(menu, _("Trigger requests"), G_MENU_MODEL(submenu)); + g_object_unref(submenu); +purple_demo_protocol_actions_init(PurpleProtocolActionsInterface *iface) { + iface->get_prefix = purple_demo_protocol_get_prefix; + iface->get_action_group = purple_demo_protocol_get_action_group; + iface->get_menu = purple_demo_protocol_get_menu; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolactions.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,28 @@
+ * 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/>. +#ifndef PURPLE_DEMO_PROTOCOL_ACTIONS_H +#define PURPLE_DEMO_PROTOCOL_ACTIONS_H +G_GNUC_INTERNAL void purple_demo_protocol_actions_init(PurpleProtocolActionsInterface *iface); +#endif /* PURPLE_DEMO_PROTOCOL_ACTIONS_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolclient.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,29 @@
+ * 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/gi18n-lib.h> +#include "purpledemoprotocol.h" +#include "purpledemoprotocolclient.h" +/****************************************************************************** + * PurpleProtocolClient Implementation + *****************************************************************************/ +purple_demo_protocol_client_init(G_GNUC_UNUSED PurpleProtocolClientInterface *iface) { --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolclient.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,28 @@
+ * 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/>. +#ifndef PURPLE_DEMO_PROTOCOL_CLIENT_H +#define PURPLE_DEMO_PROTOCOL_CLIENT_H +G_GNUC_INTERNAL void purple_demo_protocol_client_init(PurpleProtocolClientInterface *iface); +#endif /* PURPLE_DEMO_PROTOCOL_CLIENT_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolcontacts.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,57 @@
+ * 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/gi18n-lib.h> +#include "purpledemoprotocolcontacts.h" +/****************************************************************************** + * PurpleProtocolContacts Implementation + *****************************************************************************/ +purple_demo_protocol_contacts_get_profile_finish(G_GNUC_UNUSED PurpleProtocolContacts *contacts, + g_return_val_if_fail(G_IS_TASK(result), NULL); + return g_task_propagate_pointer(G_TASK(result), error); +purple_demo_protocol_contacts_get_profile_async(PurpleProtocolContacts *contacts, + PurpleContactInfo *info, + GCancellable *cancellable, + GAsyncReadyCallback callback, + const char *profile = NULL; + task = g_task_new(contacts, cancellable, callback, data); + profile = g_object_get_data(G_OBJECT(info), "demo-profile"); + g_task_return_pointer(task, g_strdup(profile), g_free); +purple_demo_protocol_contacts_init(PurpleProtocolContactsInterface *iface) { + iface->get_profile_async = purple_demo_protocol_contacts_get_profile_async; + iface->get_profile_finish = purple_demo_protocol_contacts_get_profile_finish; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolcontacts.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,28 @@
+ * 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/liceng_task_return_pointerses/>. +#ifndef PURPLE_DEMO_PROTOCOL_CONTACTS_H +#define PURPLE_DEMO_PROTOCOL_CONTACTS_H +G_GNUC_INTERNAL void purple_demo_protocol_contacts_init(PurpleProtocolContactsInterface *iface); +#endif /* PURPLE_DEMO_PROTOCOL_CONTACTS_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolconversation.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,131 @@
+ * 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/gi18n-lib.h> +#include "purpledemoprotocol.h" +#include "purpledemoprotocolconversation.h" +/****************************************************************************** + * PurpleProtocolConversation Implementation + *****************************************************************************/ + PurpleConversation *conversation; + PurpleMessage *message; +} PurpleDemoProtocolIMInfo; +purple_demo_protocol_im_info_free(PurpleDemoProtocolIMInfo *info) { + g_clear_object(&info->conversation); + g_clear_object(&info->message); +purple_demo_protocol_echo_im_cb(gpointer data) { + PurpleDemoProtocolIMInfo *info = data; + PurpleMessage *message = NULL; + PurpleMessageFlags flags; + const char *who = NULL; + /* Turn outgoing message back incoming. */ + who = purple_conversation_get_name(info->conversation); + flags = purple_message_get_flags(info->message); + flags &= ~PURPLE_MESSAGE_SEND; + flags |= PURPLE_MESSAGE_RECV; + message = purple_message_new_incoming(who, + purple_message_get_contents(info->message), + purple_conversation_write_message(info->conversation, message); + g_clear_object(&message); + return G_SOURCE_REMOVE; +purple_demo_protocol_send_message_async(G_GNUC_UNUSED PurpleProtocolConversation *protocol, + PurpleConversation *conversation, + PurpleMessage *message, + GCancellable *cancellable, + GAsyncReadyCallback callback, + const char *who = NULL; + who = purple_conversation_get_name(conversation); + if(purple_strempty(who)) { + who = purple_message_get_recipient(message); + if(purple_strequal(who, "Echo")) { + PurpleDemoProtocolIMInfo *info = g_new(PurpleDemoProtocolIMInfo, 1); + info->conversation = g_object_ref(conversation); + info->message = g_object_ref(message); + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + purple_demo_protocol_echo_im_cb, info, + (GDestroyNotify)purple_demo_protocol_im_info_free); + } else if(purple_strequal(who, "Aegina")) { + PurpleDemoProtocolIMInfo *info = g_new(PurpleDemoProtocolIMInfo, 1); + const char *author = purple_message_get_author(message); + const char *contents = NULL; + if(purple_strequal(author, "Hades")) { + /* TRANSLATORS: This is a reference to the Cap of Invisibility owned by + * various Greek gods, such as Hades, as mentioned. */ + contents = _("Don't tell Hades I have his Cap"); + info->conversation = g_object_ref(conversation); + info->message = purple_message_new_outgoing(author, who, contents, + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, purple_demo_protocol_echo_im_cb, + info, (GDestroyNotify)purple_demo_protocol_im_info_free); + purple_conversation_write_message(conversation, message); + task = g_task_new(protocol, cancellable, callback, data); + g_task_return_boolean(task, TRUE); +purple_demo_protocol_send_message_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol, + g_return_val_if_fail(G_IS_TASK(result), FALSE); + return g_task_propagate_boolean(G_TASK(result), error); +purple_demo_protocol_conversation_init(PurpleProtocolConversationInterface *iface) { + iface->send_message_async = purple_demo_protocol_send_message_async; + iface->send_message_finish = purple_demo_protocol_send_message_finish; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolconversation.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,28 @@
+ * 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/>. +#ifndef PURPLE_DEMO_PROTOCOL_CONVERSATION_H +#define PURPLE_DEMO_PROTOCOL_CONVERSATION_H +G_GNUC_INTERNAL void purple_demo_protocol_conversation_init(PurpleProtocolConversationInterface *iface); +#endif /* PURPLE_DEMO_PROTOCOL_CONVERSATION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolmedia.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,102 @@
+ * 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/gi18n-lib.h> +#include "purpledemoprotocol.h" +#include "purpledemoprotocolmedia.h" +/****************************************************************************** + * PurpleProtocolMedia Implementation + *****************************************************************************/ +purple_demo_protocol_media_get_caps(G_GNUC_UNUSED PurpleProtocolMedia *media, + G_GNUC_UNUSED PurpleAccount *account, + if(purple_strequal(who, "Echo")) { + return PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_VIDEO | + PURPLE_MEDIA_CAPS_AUDIO_VIDEO; + return PURPLE_MEDIA_CAPS_NONE; +purple_demo_protocol_media_initiate_session(G_GNUC_UNUSED PurpleProtocolMedia *media, + PurpleAccount *account, + PurpleMediaSessionType type) + PurpleConnection *connection = NULL; + gchar *session_name = NULL; + GDateTime *timestamp = NULL; + connection = purple_account_get_connection(account); + session_name = g_flags_to_string(PURPLE_MEDIA_TYPE_SESSION_TYPE, type); + message = g_strdup_printf(_("Initiated demo %s session with %s"), + timestamp = g_date_time_new_now_utc(); + purple_serv_got_im(connection, "Echo", + message, PURPLE_MESSAGE_RECV, + g_date_time_to_unix(timestamp)); + g_date_time_unref(timestamp); + /* TODO: When libpurple gets a backend, we can implement more of this. */ +purple_demo_protocol_media_send_dtmf(G_GNUC_UNUSED PurpleProtocolMedia *protocol_media, + PurpleMedia *media, gchar dtmf, + guint8 volume, guint8 duration) + PurpleAccount *account = NULL; + PurpleConnection *connection = NULL; + GDateTime *timestamp = NULL; + account = purple_media_get_account(media); + connection = purple_account_get_connection(account); + message = g_strdup_printf(_("Received DTMF %c at volume %d for %d seconds"), + dtmf, volume, duration); + timestamp = g_date_time_new_now_utc(); + purple_serv_got_im(connection, "Echo", + message, PURPLE_MESSAGE_RECV, + g_date_time_to_unix(timestamp)); + g_date_time_unref(timestamp); +purple_demo_protocol_media_init(PurpleProtocolMediaInterface *iface) { + iface->get_caps = purple_demo_protocol_media_get_caps; + iface->initiate_session = purple_demo_protocol_media_initiate_session; + iface->send_dtmf = purple_demo_protocol_media_send_dtmf; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/purpledemoprotocolmedia.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,28 @@
+ * 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/>. +#ifndef PURPLE_DEMO_PROTOCOL_MEDIA_H +#define PURPLE_DEMO_PROTOCOL_MEDIA_H +G_GNUC_INTERNAL void purple_demo_protocol_media_init(PurpleProtocolMediaInterface *iface); +#endif /* PURPLE_DEMO_PROTOCOL_MEDIA_H */ Binary file protocols/demo/resources/buddy_icons/Aegina.png has changed
Binary file protocols/demo/resources/buddy_icons/Echo.png has changed
Binary file protocols/demo/resources/buddy_icons/Eion.png has changed
Binary file protocols/demo/resources/buddy_icons/Elliott.png has changed
Binary file protocols/demo/resources/buddy_icons/Gary.png has changed
Binary file protocols/demo/resources/buddy_icons/John.png has changed
Binary file protocols/demo/resources/buddy_icons/Markus.png has changed
Binary file protocols/demo/resources/buddy_icons/Richard.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/resources/buddy_icons/mkicon.py Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,90 @@
+# Purple - Internet Messaging Library +# Copyright (C) Pidgin Developers <devel@pidgin.im> +# Purple is the legal property of its developers, whose names are too numerous +# to list here. Please refer to the COPYRIGHT file distributed with this +# 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/>. +from pathlib import Path +from PIL import Image, ImageDraw, ImageFont +parser = argparse.ArgumentParser( + description='Generate buddy icons from user names.') +parser.add_argument('name', nargs='+', help='The name(s) to use.') +parser.add_argument('-f', '--font', + default='/usr/share/fonts/urw-base35/D050000L.otf', + help='Path to (TrueType or OpenType) font to use.') +parser.add_argument('-s', '--size', default=96, type=int, + help='Size of buddy icons to produce.') +parser.add_argument('-o', '--output', default='.', + help='Directory in which to place files.') +args = parser.parse_args() +def calculate_colours_for_text(text): + Calculate the foreground and background colours from text. + This is based on pidgin_color_calculate_for_text in Pidgin C code. + # Hash the string and get the first 2 bytes of the digest. + checksum = hashlib.sha1() + checksum.update(text.encode('utf-8')) + digest = checksum.digest() + # Calculate the hue based on the digest, scaled to 0-1. + hue = (digest[0] << 8 | digest[1]) / 65535 + # Get the RGB values for the hue at full saturation and value. + foreground = colorsys.hsv_to_rgb(hue, 1.0, 1.0) + # Calculate the hue based on the end of the digest, scaled to 0-1. + hue = (digest[-1] << 8 | digest[-2]) / 65535 + # Get the RGB values for the hue at full saturation and low value. + background = colorsys.hsv_to_rgb(hue, 1.0, 0.2) + # Finally calculate the foreground summing 20% of the inverted background + # with 80% of the foreground. + (0.2 * (1 - bc)) + (0.8 * fc) for bc, fc in zip(background, foreground) + # Pillow requires colours in 0-255. + return (tuple(int(c * 255) for c in foreground), + tuple(int(c * 255) for c in background)) +output_dir = Path(args.output) +font = ImageFont.truetype(args.font, size=int(args.size * 96/72)) + fore, back = calculate_colours_for_text(name) + # Generate an image using the first letter of the name to choose a glyph + # from the specified font. The default font generates some star-like or + img = Image.new('RGBA', (args.size, args.size), color=back) + draw = ImageDraw.Draw(img) + letter = name[0].upper() + draw.text((args.size // 2, args.size - 1), letter, + font=font, anchor='mb', fill=fore) + img.save(output_dir / f'{name}.png', 'PNG') --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/resources/contacts.json Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,109 @@
+ "primitive": "available", + "message": "💤 Sleeping... 😴", + "profile": "Pidgin 3 will be ready when it is ready! 🗿" + "profile": "Idiot Windows sysadmin who used to develop for Pidgin and libpurple in the bright days before Windows 8" + "primitive": "available" + "primitive": "available", + "message": "Writing crazy patches!" + "primitive": "do-not-disturb", + "message": "Running all the things in valgrind..." + "primitive": "available", + "message": "Cursed to speak the last words spoken to me" + "primitive": "offline", + "message": "Stole the Cap of Invisibility from Hades" Binary file protocols/demo/resources/icons/16x16/apps/im-purple-demo.png has changed
Binary file protocols/demo/resources/icons/22x22/apps/im-purple-demo.png has changed
Binary file protocols/demo/resources/icons/48x48/apps/im-purple-demo.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/resources/icons/scalable/apps/im-purple-demo.svg Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + viewBox="0 0 37.851284 39.057049" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + sodipodi:docname="im-purple-demo.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:cx="25.178571" + inkscape:cy="33.571429" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + inkscape:window-width="1916" + inkscape:window-height="1017" + inkscape:window-x="1080" + inkscape:window-y="493" + inkscape:window-maximized="1" + inkscape:pagecheckerboard="0" /> + <dc:format>image/svg+xml</dc:format> + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + inkscape:label="Layer 1" + inkscape:groupmode="layer" + transform="translate(75.835419,-56.26127)"> + style="isolation:isolate;fill:#4b2854;fill-opacity:1;stroke:#fdfdfd;stroke-width:11.7132;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + inkscape:connector-curvature="0" + d="m 137.45776,361.78346 q 9.2531,10.42501 11.91562,19.37813 a 19.687501,19.687501 0 0 0 -9.08437,2.24061 17.090626,17.090626 0 0 0 -6.84376,6.38437 19.312501,19.312501 0 0 0 -2.74688,9.76876 c -0.15944,5.625 1.64063,10.15313 5.44688,13.54687 a 20.831251,20.831251 0 0 0 13.70626,5.33438 19.687501,19.687501 0 0 0 11.61563,-3.02811 20.690626,20.690626 0 0 0 7.36874,-8.22189 23.690626,23.690626 0 0 0 2.65312,-10.14374 39.196877,39.196877 0 0 0 -6.79688,-22.98749 87.590631,87.590631 0 0 0 -18.20626,-19.98751 z" + transform="matrix(0.37919911,0,0,0.38886781,-123.17216,-79.14694)" /> + style="isolation:isolate;fill:#5c3566;stroke:#fdfdfd;stroke-width:11.7132;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" + inkscape:connector-curvature="0" + d="m 184.75465,435.07723 q 9.25309,-10.42501 11.90622,-19.37814 a 19.687501,19.687501 0 0 1 -9.08437,-2.24064 17.062501,17.062501 0 0 1 -6.83437,-6.38437 19.312501,19.312501 0 0 1 -2.75626,-9.75936 q -0.23457,-8.4375 5.45626,-13.55627 a 20.812501,20.812501 0 0 1 13.70626,-5.33438 19.687501,19.687501 0 0 1 11.6156,3.0375 20.709376,20.709376 0 0 1 7.36877,8.20315 23.765626,23.765626 0 0 1 2.65312,10.15313 39.281254,39.281254 0 0 1 -6.79688,22.98749 87.590631,87.590631 0 0 1 -18.20626,19.98751 z" + transform="matrix(0.37919911,0,0,0.38886781,-123.17216,-79.14694)" /> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/demo/resources/purpledemo.gresource.xml Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?> + <gresource prefix="/im/pidgin/purple/demo"> + <file compressed="true" preprocess="json-stripblanks">contacts.json</file> + <file>icons/16x16/apps/im-purple-demo.png</file> + <file>icons/22x22/apps/im-purple-demo.png</file> + <file>icons/48x48/apps/im-purple-demo.png</file> + <file>icons/scalable/apps/im-purple-demo.svg</file> + <file>buddy_icons/Aegina.png</file> + <file>buddy_icons/Echo.png</file> + <file>buddy_icons/Eion.png</file> + <file>buddy_icons/Elliott.png</file> + <file>buddy_icons/Gary.png</file> + <file>buddy_icons/John.png</file> + <file>buddy_icons/Markus.png</file> + <file>buddy_icons/Richard.png</file> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/README.md Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,22 @@
+This is a brand new from-scratch protocol plugin which is the first protocol +plugin to be 100% code reviewed. It uses regex to tokenize messages. +We are intending for it to be subclass-able so other networks like Twitch.tv can +be supported but we're not quite there yet. +We also are intending to support subclassing in other languages but we've run +into some issues with dynamic GObject types and GObject introspection that have +This is a list of capabilities that we currently support. We'll do our best to +keep this list up to date, but if you notice we've missed something please let +* sasl (right now just PLAIN works) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/ircv3generategir.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,32 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <gplugin-introspection.h> +main(int argc, char *argv[]) { + return gplugin_introspection_introspect_plugin(&argc, &argv, --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/meson.build Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,114 @@
+ 'purpleircv3capabilities.c', + 'purpleircv3connection.c', + 'purpleircv3formatting.c', + 'purpleircv3message.c', + 'purpleircv3messagehandlers.c', + 'purpleircv3protocol.c', + 'purpleircv3protocolconversation.c', + 'purpleircv3capabilities.h', + 'purpleircv3connection.h', + 'purpleircv3constants.h', + 'purpleircv3formatting.h', + 'purpleircv3message.h', + 'purpleircv3messagehandlers.h', + 'purpleircv3protocol.h', + 'purpleircv3protocolconversation.h', +ircv3_filebase = f'purple-@purple_major_version@-ircv3' +ircv3_includes = include_directories('.') +ircv3_include_base = purple_include_base / 'protocols/ircv3' +ircv3_resources = gnome.compile_resources('ircv3resource', + 'resources/ircv3.gresource.xml', + source_dir : 'resources', + c_name : 'purple_ircv3') +IRCV3_SOURCES += ircv3_resources +foreach header : IRCV3_HEADERS + ircv3_h_includes += f'#include <@header@>' +ircv3_h_conf = configuration_data() +ircv3_h_conf.set('IRCV3_H_INCLUDES', '\n'.join(ircv3_h_includes)) +ircv3_h = configure_file(input : 'purpleircv3.h.in', + output : 'purpleircv3.h', + configuration : ircv3_h_conf, + install_dir : get_option('includedir') / ircv3_include_base) +install_headers(IRCV3_HEADERS, + subdir : ircv3_include_base) +ircv3_prpl = shared_library('ircv3', IRCV3_SOURCES + IRCV3_HEADERS + [ircv3_h], + c_args : ['-DPURPLE_IRCV3_COMPILATION', '-DG_LOG_USE_STRUCTURED', '-DG_LOG_DOMAIN="Purple-IRCv3"'], + gnu_symbol_visibility : 'hidden', + dependencies : [birb_dep, libpurple_dep, glib, gio, hasl], + install_dir : PURPLE_PLUGINDIR) +ircv3_dep = declare_dependency( + sources : [IRCV3_SOURCES, IRCV3_HEADERS], + include_directories : ircv3_includes, + dependencies : [birb_dep, libpurple_dep, glib, gio, hasl]) + # we purposely don't put the library here because you should not be + # linking to the plugin, everything will be resolved during runtime. + description : 'a purple3 protocol plugin for IRCv3', + version : meson.project_version(), + subdirs : [ircv3_include_base], + filebase : ircv3_filebase, + libraries : [gio, glib, hasl, libpurple]) +meson.override_dependency(ircv3_filebase, ircv3_dep) +devenv.append('PURPLE_PLUGIN_PATH', meson.current_build_dir()) +if get_option('introspection') + GPLUGIN_INTROSPECTION = dependency('gplugin-introspection') + plugin_filename = ircv3_prpl.full_path() + ircv3_introspection_stub = executable('ircv3generategir', + sources : 'ircv3generategir.c', + dependencies : [ircv3_dep, libpurple_dep, glib, gio, hasl, GPLUGIN_INTROSPECTION], + c_args : ['-DPURPLE_IRCV3_COMPILATION', f'-DPLUGIN_FILENAME="@plugin_filename@"'], + ircv3_introspection_stub, + sources : [IRCV3_SOURCES, IRCV3_HEADERS], + includes : ['Birb-1.0', 'GLib-2.0', 'GObject-2.0', 'GPlugin-1.0', libpurple_gir[0]], + namespace : 'PurpleIRCv3', + symbol_prefix : 'purple_ircv3', + dependencies: [birb_dep, gplugin_dep], + export_packages : ['ircv3'], + extra_args : ['-DPURPLE_IRCV3_COMPILATION', '--verbose']) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3.h.in Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,40 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#ifndef __GI_SCANNER__ /* hide this bit from g-ir-scanner */ +# ifdef PURPLE_IRCV3_COMPILATION +# error "ircv3 source files should not be including purpleircv3.h" +# endif /* PURPLE_IRCV3_COMPILATION */ +#endif /* __GI_SCANNER__ */ +#ifndef PURPLE_IRCV3_GLOBAL_HEADER_INSIDE +# define PURPLE_IRCV3_GLOBAL_HEADER_INSIDE +#endif /* PURPLE_IRCV3_GLOBAL_HEADER_INSIDE */ +#undef PURPLE_IRCV3_GLOBAL_HEADER_INSIDE +#endif /* PURPLE_IRCV3_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3capabilities.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,685 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include "purpleircv3capabilities.h" +#include "purpleircv3connection.h" +#include "purpleircv3core.h" +#include "purpleircv3sasl.h" +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; +/* Windows is does something weird with signal handling that includes defining + * SIG_ACK. We don't care about that here, so we undef it if we find it. + * See https://learn.microsoft.com/en-us/cpp/c-runtime-library/signal-action-constants?view=msvc-170 +static guint signals[N_SIGNALS] = {0, }; +struct _PurpleIRCv3Capabilities { + PurpleIRCv3Connection *connection; + gatomicrefcount wait_counters; +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_set_connection(PurpleIRCv3Capabilities *capabilities, + PurpleIRCv3Connection *connection) + g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities)); + if(g_set_object(&capabilities->connection, connection)) { + g_object_notify_by_pspec(G_OBJECT(capabilities), + properties[PROP_CONNECTION]); +purple_ircv3_capabilities_finish(PurpleIRCv3Capabilities *capabilities) { + purple_ircv3_connection_writef(capabilities->connection, + g_signal_emit(capabilities, signals[SIG_DONE], 0); +purple_ircv3_capabilities_add(PurpleIRCv3Capabilities *capabilities, + const char *capability) + char *equals = g_strstr_len(capability, -1, "="); + char *key = g_strndup(capability, equals - capability); + char *value = g_strdup(equals + 1); + g_hash_table_insert(capabilities->caps, key, value); + g_hash_table_insert(capabilities->caps, g_strdup(capability), NULL); +/****************************************************************************** + *****************************************************************************/ +ircv3_capabilities_message_tags_ack_cb(PurpleIRCv3Capabilities *capabilities, + G_GNUC_UNUSED const char *capability, + G_GNUC_UNUSED gpointer data) + /* We have message tags so add the stuff we support that depends on it. */ + purple_ircv3_capabilities_lookup_and_request(capabilities, "msgid"); +/****************************************************************************** + * PurpleIRCv3Capabilities Implementation + *****************************************************************************/ +purple_ircv3_capabilities_default_ready_cb(PurpleIRCv3Capabilities *capabilities) + PurpleAccount *account = NULL; + PurpleConnection *purple_connection = NULL; + purple_connection = PURPLE_CONNECTION(capabilities->connection); + account = purple_connection_get_account(purple_connection); + /* Don't request the sasl capability unless the user has selected the + * require-password option. + if(purple_account_get_require_password(account)) { + gboolean found = FALSE; + purple_ircv3_capabilities_lookup(capabilities, "sasl", &found); + purple_ircv3_sasl_request(capabilities); + /* cap-notify is implied when we use CAP LS 302, so this is really just to + * make sure it's requested. + purple_ircv3_capabilities_lookup_and_request(capabilities, "cap-notify"); + /* message-tags is used for a lot of stuff so we need to tell everyone we + * do in fact support it. + if(purple_ircv3_capabilities_lookup_and_request(capabilities, + g_signal_connect(capabilities, "ack::message-tags", + G_CALLBACK(ircv3_capabilities_message_tags_ack_cb), + /* The server-time capability just tells the server to send a tag to + * messages, so we just need to request it and then handle the tag when + * we're processing messages if it exists. + purple_ircv3_capabilities_lookup_and_request(capabilities, + PURPLE_IRCV3_CAPABILITY_SERVER_TIME); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Capabilities, + purple_ircv3_capabilities, G_TYPE_OBJECT, +purple_ircv3_capabilities_get_property(GObject *obj, guint param_id, + GValue *value, GParamSpec *pspec) + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + g_value_set_object(value, + purple_ircv3_capabilities_get_connection(capabilities)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_capabilities_set_property(GObject *obj, guint param_id, + const GValue *value, GParamSpec *pspec) + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + purple_ircv3_capabilities_set_connection(capabilities, + g_value_get_object(value)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_capabilities_dispose(GObject *obj) { + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + g_clear_object(&capabilities->connection); + G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->dispose(obj); +purple_ircv3_capabilities_finalize(GObject *obj) { + PurpleIRCv3Capabilities *capabilities = PURPLE_IRCV3_CAPABILITIES(obj); + g_hash_table_destroy(capabilities->caps); + g_ptr_array_free(capabilities->requests, TRUE); + G_OBJECT_CLASS(purple_ircv3_capabilities_parent_class)->finalize(obj); +purple_ircv3_capabilities_init(PurpleIRCv3Capabilities *capabilities) { + capabilities->caps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + capabilities->requests = g_ptr_array_new_full(0, g_free); + g_atomic_ref_count_init(&capabilities->wait_counters); +purple_ircv3_capabilities_class_finalize(G_GNUC_UNUSED PurpleIRCv3CapabilitiesClass *klass) { +purple_ircv3_capabilities_class_init(PurpleIRCv3CapabilitiesClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->dispose = purple_ircv3_capabilities_dispose; + obj_class->finalize = purple_ircv3_capabilities_finalize; + obj_class->get_property = purple_ircv3_capabilities_get_property; + obj_class->set_property = purple_ircv3_capabilities_set_property; + * PurpleIRCv3Capabilities:connection: + * The PurpleIRCv3Connection object that this capabilities was created + properties[PROP_CONNECTION] = g_param_spec_object( + "connection", "connection", + "The connection this capabilities was created for.", + PURPLE_IRCV3_TYPE_CONNECTION, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); + * PurpleIRCv3Capabilities::ready: + * @capabilities: The instance. + * Emitted when @capabilities has finished receiving the list of + * capabilities from the server at startup. + * For dynamically added capabilities see the `added` and `removed` + signals[SIG_READY] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + G_CALLBACK(purple_ircv3_capabilities_default_ready_cb), + * PurpleIRCv3Capabilities::ack: + * @capabilities: The instance. + * @capability: The capability string. + * Emitted when the server has acknowledged a `CAP REQ` call from + * purple_ircv3_capabilities_request. + * The value of @capability will be the same as the one that was requested. + signals[SIG_ACK] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST, + * PurpleIRCv3Capabilities::nak: + * @capabilities: The instance. + * @capability: The capability string. + * Emitted when the server has nacked a `CAP REQ` call from + * purple_ircv3_capabilities_request. + * The value of @capability will be the same as the one that was requested. + signals[SIG_NAK] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST, + * PurpleIRCv3Capabilities::done: + * @capabilities: The instance. + * Emitted when all of the requested capabilities have been either ack'd or + signals[SIG_DONE] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + * PurpleIRCv3Capabilities::new: + * @capabilities: The instance. + * @added: The newly added capabilities. + * Emitted when the server sends the `CAP NEW` command. @added is a + * [type@GLib.Strv] of the new capabilities the server added. + * There are two approaches to how you can use this signal. You can check + * each item in @added for the values you need and parsing their values, or + * you can call #purple_ircv3_capabilities_lookup to see if the + * capabilities you're interested in have been added. + signals[SIG_NEW] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + * PurpleIRCv3Capabilities::del: + * @capabilities: The instance. + * @removed: The capabilities that were removed. + * Emitted when the server sends the `CAP DEL` command. @removed is a + * [type@GLib.Strv] of the capabilities that the server removed. + * There are two approaches to how you can use this signal. You can check + * each item in @removed for the values you care about, or you can call + * #purple_ircv3_capabilities_lookup to see if the capabilities you're + * interested in have been removed. + signals[SIG_DEL] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_handle_list(PurpleIRCv3Capabilities *capabilities, + G_GNUC_UNUSED GError **error) + /* Check if we have more messages coming. */ + if(n_params > 1 && purple_strequal(params[0], "*")) { + parts = g_strsplit(params[1], " ", -1); + parts = g_strsplit(params[0], " ", -1); + /* Add each capability to our hash table, splitting the keys and values. */ + for(int i = 0; parts[i] != NULL; i++) { + purple_ircv3_capabilities_add(capabilities, parts[i]); + g_signal_emit(capabilities, signals[SIG_READY], 0, signals[SIG_READY]); + /* If no capabilities were requested after we emitted the ready signal + * we're done with capability negotiation. + if(capabilities->requests->len == 0) { + purple_ircv3_capabilities_remove_wait(capabilities); +purple_ircv3_capabilities_handle_ack_nak(PurpleIRCv3Capabilities *capabilities, + char *caps = g_strjoinv(" ", params); + gboolean found = FALSE; + g_signal_emit(capabilities, sig, g_quark_from_string(caps), caps); + found = g_ptr_array_find_with_equal_func(capabilities->requests, caps, + g_ptr_array_remove_index(capabilities->requests, index); + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "received CAP %s for unknown capability %s", method, caps); + if(capabilities->requests->len == 0) { + purple_ircv3_capabilities_remove_wait(capabilities); +purple_ircv3_capabilities_handle_new(PurpleIRCv3Capabilities *capabilities, + G_GNUC_UNUSED GError **error) + for(guint i = 0; i < n_params; i++) { + purple_ircv3_capabilities_add(capabilities, params[i]); + g_signal_emit(capabilities, signals[SIG_NEW], 0, params); +purple_ircv3_capabilities_handle_del(PurpleIRCv3Capabilities *capabilities, + G_GNUC_UNUSED GError **error) + for(guint i = 0; i < n_params; i++) { + g_hash_table_remove(capabilities->caps, params[i]); + g_signal_emit(capabilities, signals[SIG_DEL], 0, params); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_register(GPluginNativePlugin *plugin) { + purple_ircv3_capabilities_register_type(G_TYPE_MODULE(plugin)); +PurpleIRCv3Capabilities * +purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection) { + PURPLE_IRCV3_TYPE_CAPABILITIES, + "connection", connection, +purple_ircv3_capabilities_start(PurpleIRCv3Capabilities *capabilities) { + purple_ircv3_connection_writef(capabilities->connection, "CAP LS %s", + PURPLE_IRCV3_CAPABILITY_CAP_LS_VERSION); +purple_ircv3_capabilities_message_handler(PurpleIRCv3Message *message, + PurpleIRCv3Connection *connection = data; + PurpleIRCv3Capabilities *capabilities = NULL; + GStrv subparams = NULL; + const char *subcommand = NULL; + params = purple_ircv3_message_get_params(message); + n_params = g_strv_length(params); + capabilities = purple_ircv3_connection_get_capabilities(connection); + /* Initialize some variables to make it easier to call our sub command + * params[0] is the nick or * if it hasn't been negotiated yet, we don't + * have a need for this, so we ignore it. + * params[1] is the CAP subcommand sent from the server. We use it here + * purely for dispatching to our subcommand handlers. + * params[2] and higher are the parameters to the subcommand. To make the + * code a bit easier all around, we subtract 2 from n_params to remove + * references to the nick and subcommand name. Like wise, we add 2 to the + * params GStrv which will now point to the second item in the array again + * ignoring the nick and subcommand. + subcommand = params[1]; + n_subparams = n_params - 2; + subparams = params + 2; + /* Dispatch the subcommand. */ + if(purple_strequal(subcommand, "LS") || + purple_strequal(subcommand, "LIST")) + return purple_ircv3_capabilities_handle_list(capabilities, n_subparams, + } else if(purple_strequal(subcommand, "ACK")) { + return purple_ircv3_capabilities_handle_ack_nak(capabilities, + } else if(purple_strequal(subcommand, "NAK")) { + return purple_ircv3_capabilities_handle_ack_nak(capabilities, + } else if(purple_strequal(subcommand, "NEW")) { + return purple_ircv3_capabilities_handle_new(capabilities, n_subparams, + } else if(purple_strequal(subcommand, "DEL")) { + return purple_ircv3_capabilities_handle_del(capabilities, n_subparams, + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "No handler for CAP subcommand %s", subcommand); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities) + g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL); + return capabilities->connection; +purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities, + const char *capability) + g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities)); + g_return_if_fail(capability != NULL); + g_ptr_array_add(capabilities->requests, g_strdup(capability)); + purple_ircv3_connection_writef(capabilities->connection, "CAP REQ :%s", +purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities, + const char *name, gboolean *found) + gboolean real_found = FALSE; + g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), NULL); + g_return_val_if_fail(name != NULL, NULL); + real_found = g_hash_table_lookup_extended(capabilities->caps, name, NULL, +purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities, + gboolean found = FALSE; + g_return_val_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities), FALSE); + g_return_val_if_fail(name != NULL, FALSE); + purple_ircv3_capabilities_lookup(capabilities, name, &found); + purple_ircv3_capabilities_request(capabilities, name); +purple_ircv3_capabilities_add_wait(PurpleIRCv3Capabilities *capabilities) { + g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities)); + g_atomic_ref_count_inc(&capabilities->wait_counters); +purple_ircv3_capabilities_remove_wait(PurpleIRCv3Capabilities *capabilities) { + g_return_if_fail(PURPLE_IRCV3_IS_CAPABILITIES(capabilities)); + if(g_atomic_ref_count_dec(&capabilities->wait_counters)) { + purple_ircv3_capabilities_finish(capabilities); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3capabilities.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,172 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_CAPABILITIES_H +#define PURPLE_IRCV3_CAPABILITIES_H +#include <glib-object.h> +#include <gplugin-native.h> +#include "purpleircv3version.h" +/* https://ircv3.net/specs/extensions/capability-negotiation */ +#define PURPLE_IRCV3_CAPABILITY_CAP_LS_VERSION "302" +/* https://ircv3.net/specs/extensions/sasl-3.2 */ +#define PURPLE_IRCV3_CAPABILITY_SASL "sasl" +/* https://ircv3.net/specs/extensions/server-time */ +#define PURPLE_IRCV3_CAPABILITY_SERVER_TIME "server-time" +#define PURPLE_IRCV3_TYPE_CAPABILITIES (purple_ircv3_capabilities_get_type()) +PURPLE_IRCV3_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE(PurpleIRCv3Capabilities, purple_ircv3_capabilities, + PURPLE_IRCV3, CAPABILITIES, GObject) +#include "purpleircv3connection.h" +#include "purpleircv3message.h" + * purple_ircv3_capabilities_register: (skip) + * @plugin: The [class@GPlugin.NativePlugin] instance. + * Dynamically registers the PurpleIRCv3Capabilities type. +G_GNUC_INTERNAL void purple_ircv3_capabilities_register(GPluginNativePlugin *plugin); +G_GNUC_INTERNAL PurpleIRCv3Capabilities *purple_ircv3_capabilities_new(PurpleIRCv3Connection *connection); +G_GNUC_INTERNAL void purple_ircv3_capabilities_start(PurpleIRCv3Capabilities *capabilities); +G_GNUC_INTERNAL gboolean purple_ircv3_capabilities_message_handler(PurpleIRCv3Message *message, GError **error, gpointer data); + * purple_ircv3_capabilities_get_connection: + * @capabilities: The instance. + * Gets the PurpleIRCv3Connection object that @capabilities was created with. + * Returns: (transfer none): The connection instance. +PURPLE_IRCV3_AVAILABLE_IN_ALL +PurpleIRCv3Connection *purple_ircv3_capabilities_get_connection(PurpleIRCv3Capabilities *capabilities); + * purple_ircv3_capabilities_request: + * @capabilities: The instance. + * @capability: The capabilities to request. + * This method will send `CAP REQ @capability` to the server. Listen to the + * `::ack` and `::nak` signals which will contain the contents of @capability + * that was passed in here. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_capabilities_request(PurpleIRCv3Capabilities *capabilities, const char *capability); + * purple_ircv3_capabilities_lookup: + * @capabilities: The instance. + * @name: The name of the capability to look for. + * @found: (out) (nullable): A return address for a boolean on whether the + * capability was advertised or not. + * Gets the value that the @name capability provided if it was advertised. To + * determine if the capability was advertised use the @found parameter. + * Returns: The value of the capability named @name. +PURPLE_IRCV3_AVAILABLE_IN_ALL +const char *purple_ircv3_capabilities_lookup(PurpleIRCv3Capabilities *capabilities, const char *name, gboolean *found); + * purple_ircv3_capabilities_lookup_and_request: + * @capabilities: The instance. + * @name: The name of the capability to look for. + * A helper function to call [method@PurpleIRCv3.Capabilities.Lookup] and if + * found, call [method@PurpleIRCv3.Capabilities.Request]. + * This method ignores the advertised value, so to get that you'll need to call + * [method@PurpleIRCv3.Capabilities.Lookup] yourself. + * Also if you need to do something when the server ACK's or NAK's your + * request, you're probably better off just using the methods yourself. + * Returns: %TRUE if @name was found and requested, %FALSE otherwise. +PURPLE_IRCV3_AVAILABLE_IN_ALL +gboolean purple_ircv3_capabilities_lookup_and_request(PurpleIRCv3Capabilities *capabilities, const char *name); + * purple_ircv3_capabilties_add_wait: + * @capabilities: The instance. + * Adds a wait counter to @capabilities. This counter is used to delay the + * call of `CAP END` until all capability negotiation has completed. This is + * necessary for SASL and may be necessary for other capabilities as well. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_capabilities_add_wait(PurpleIRCv3Capabilities *capabilities); + * purple_ircv3_capabilties_remove_wait: + * @capabilities: The instance. + * Removes a wait counter from @capabilities. Only when this counter reaches 0, + * will `CAP END` be called and registration completed. + * This is necessary for SASL and may be necessary for other capabilities as +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_capabilities_remove_wait(PurpleIRCv3Capabilities *capabilities); +#endif /* PURPLE_IRCV3_CAPABILITIES_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3connection.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,964 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3connection.h" +#include "purpleircv3constants.h" +#include "purpleircv3core.h" +#include "purpleircv3ctcp.h" +#include "purpleircv3formatting.h" +#include "purpleircv3parser.h" +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; + SIG_REGISTRATION_COMPLETE, +static guint signals[N_SIGNALS] = {0, }; + GSocketConnection *connection; + GDataInputStream *input; + PurpleIRCv3Parser *parser; + PurpleIRCv3Capabilities *capabilities; + PurpleConversation *status_conversation; +} PurpleIRCv3ConnectionPrivate; +G_DEFINE_DYNAMIC_TYPE_EXTENDED(PurpleIRCv3Connection, + purple_ircv3_connection, + PURPLE_TYPE_CONNECTION, + G_ADD_PRIVATE_DYNAMIC(PurpleIRCv3Connection)) +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_connection_send_pass_command(PurpleIRCv3Connection *connection) { + PurpleAccount *account = NULL; + const char *password = NULL; + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + password = purple_account_get_string(account, "server-password", ""); + if(password != NULL && *password != '\0') { + purple_ircv3_connection_writef(connection, "PASS %s", password); +purple_ircv3_connection_send_user_command(PurpleIRCv3Connection *connection) { + PurpleAccount *account = NULL; + const char *identname = NULL; + const char *nickname = NULL; + const char *realname = NULL; + purple_connection_get_display_name(PURPLE_CONNECTION(connection)); + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + /* The stored value could be an empty string, so pass a default of empty + * string and then if it was empty, set our correct fallback. + identname = purple_account_get_string(account, "ident", ""); + if(identname == NULL || *identname == '\0') { + realname = purple_account_get_string(account, "real-name", ""); + if(realname == NULL || *realname == '\0') { + purple_ircv3_connection_writef(connection, "USER %s 0 * :%s", identname, +purple_ircv3_connection_send_nick_command(PurpleIRCv3Connection *connection) { + const char *nickname = NULL; + purple_connection_get_display_name(PURPLE_CONNECTION(connection)); + purple_ircv3_connection_writef(connection, "NICK %s", nickname); +purple_ircv3_connection_rejoin_channels(PurpleIRCv3Connection *connection) { + PurpleAccount *account = NULL; + PurpleConversationManager *manager = NULL; + GList *conversations = NULL; + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + manager = purple_conversation_manager_get_default(); + conversations = purple_conversation_manager_get_all(manager); + while(conversations != NULL) { + PurpleConversation *conversation = conversations->data; + PurpleAccount *conv_account = NULL; + conv_account = purple_conversation_get_account(conversation); + if(conv_account == account) { + const char *id = purple_conversation_get_id(conversation); + purple_ircv3_connection_writef(connection, "%s %s", + PURPLE_IRCV3_MSG_JOIN, id); + conversations = g_list_delete_link(conversations, conversations); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_connection_read_cb(GObject *source, GAsyncResult *result, + PurpleIRCv3Connection *connection = data; + PurpleIRCv3ConnectionPrivate *priv = NULL; + GCancellable *cancellable = NULL; + GDataInputStream *istream = G_DATA_INPUT_STREAM(source); + gboolean parsed = FALSE; + line = g_data_input_stream_read_line_finish(istream, result, &length, + if(line == NULL || error != NULL) { + if(PURPLE_IS_CONNECTION(connection)) { + g_set_error_literal(&error, PURPLE_CONNECTION_ERROR, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection")); + g_prefix_error(&error, "%s", _("Lost connection with server: ")); + purple_connection_take_error(PURPLE_CONNECTION(connection), error); + /* In the off chance that line was returned, make sure we free it. */ + priv = purple_ircv3_connection_get_instance_private(connection); + parsed = purple_ircv3_parser_parse(priv->parser, line, &error, + g_warning("failed to handle '%s': %s", line, + error != NULL ? error->message : "unknown error"); + /* Call read_line_async again to continue reading lines. */ + cancellable = purple_connection_get_cancellable(PURPLE_CONNECTION(connection)); + g_data_input_stream_read_line_async(priv->input, + purple_ircv3_connection_read_cb, +purple_ircv3_connection_write_cb(GObject *source, GAsyncResult *result, + PurpleIRCv3Connection *connection = data; + BirbQueuedOutputStream *stream = BIRB_QUEUED_OUTPUT_STREAM(source); + gboolean success = FALSE; + success = birb_queued_output_stream_push_bytes_finish(stream, result, + birb_queued_output_stream_clear_queue(stream); + g_prefix_error(&error, "%s", _("Lost connection with server: ")); + purple_connection_take_error(PURPLE_CONNECTION(connection), error); +purple_ircv3_connection_connected_cb(GObject *source, GAsyncResult *result, + PurpleIRCv3Connection *connection = data; + PurpleIRCv3ConnectionPrivate *priv = NULL; + GCancellable *cancellable = NULL; + GInputStream *istream = NULL; + GOutputStream *ostream = NULL; + GSocketClient *client = G_SOCKET_CLIENT(source); + GSocketConnection *conn = NULL; + priv = purple_ircv3_connection_get_instance_private(connection); + /* Finish the async method. */ + conn = g_socket_client_connect_to_host_finish(client, result, &error); + if(conn == NULL || error != NULL) { + g_prefix_error(&error, "%s", _("Unable to connect: ")); + purple_connection_take_error(PURPLE_CONNECTION(connection), error); + g_message("Successfully connected to %s", priv->server_name); + /* Save our connection and setup our input and outputs. */ + priv->connection = conn; + /* Create our parser. */ + priv->parser = purple_ircv3_parser_new(); + purple_ircv3_parser_add_default_handlers(priv->parser); + ostream = g_io_stream_get_output_stream(G_IO_STREAM(conn)); + priv->output = birb_queued_output_stream_new(ostream); + istream = g_io_stream_get_input_stream(G_IO_STREAM(conn)); + priv->input = g_data_input_stream_new(istream); + g_data_input_stream_set_newline_type(G_DATA_INPUT_STREAM(priv->input), + G_DATA_STREAM_NEWLINE_TYPE_CR_LF); + cancellable = purple_connection_get_cancellable(PURPLE_CONNECTION(connection)); + /* Add our read callback. */ + g_data_input_stream_read_line_async(priv->input, + purple_ircv3_connection_read_cb, + /* Send our registration commands. */ + purple_ircv3_capabilities_start(priv->capabilities); + purple_ircv3_connection_send_pass_command(connection); + purple_ircv3_connection_send_user_command(connection); + purple_ircv3_connection_send_nick_command(connection); +purple_ircv3_connection_caps_done_cb(G_GNUC_UNUSED PurpleIRCv3Capabilities *caps, + PurpleIRCv3Connection *connection = data; + PurpleIRCv3ConnectionPrivate *priv = NULL; + priv = purple_ircv3_connection_get_instance_private(connection); + priv->registered = TRUE; + g_signal_emit(connection, signals[SIG_REGISTRATION_COMPLETE], 0); + /* Add our supported CTCP commands. */ + purple_ircv3_ctcp_add_default_handlers(connection); + /* Now that registration is complete, rejoin any channels that the + * conversation manager has for us. + purple_ircv3_connection_rejoin_channels(connection); +/****************************************************************************** + * PurpleConnection Implementation + *****************************************************************************/ +purple_ircv3_connection_connect(PurpleConnection *purple_connection, + PurpleIRCv3Connection *connection = NULL; + PurpleIRCv3ConnectionPrivate *priv = NULL; + PurpleAccount *account = NULL; + GCancellable *cancellable = NULL; + GSocketClient *client = NULL; + gint default_port = PURPLE_IRCV3_DEFAULT_TLS_PORT; + gboolean use_tls = TRUE; + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE); + connection = PURPLE_IRCV3_CONNECTION(purple_connection); + priv = purple_ircv3_connection_get_instance_private(connection); + account = purple_connection_get_account(purple_connection); + client = purple_gio_socket_client_new(account, error); + if(!G_IS_SOCKET_CLIENT(client)) { + if(error != NULL && *error != NULL) { + purple_connection_take_error(purple_connection, *error); + /* Turn on TLS if requested. */ + use_tls = purple_account_get_bool(account, "use-tls", TRUE); + g_socket_client_set_tls(client, use_tls); + /* If TLS is not being used, set the default port to the plain port. */ + default_port = PURPLE_IRCV3_DEFAULT_PLAIN_PORT; + port = purple_account_get_int(account, "port", default_port); + cancellable = purple_connection_get_cancellable(purple_connection); + /* Finally start the async connection. */ + g_socket_client_connect_to_host_async(client, priv->server_name, + purple_ircv3_connection_connected_cb, + g_clear_object(&client); +purple_ircv3_connection_disconnect(PurpleConnection *purple_connection, + PurpleIRCv3Connection *connection = NULL; + PurpleIRCv3ConnectionPrivate *priv = NULL; + PurpleConnectionClass *parent_class = NULL; + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(purple_connection), FALSE); + /* Chain up to our parent's disconnect to do initial clean up. */ + parent_class = PURPLE_CONNECTION_CLASS(purple_ircv3_connection_parent_class); + parent_class->disconnect(purple_connection, error); + connection = PURPLE_IRCV3_CONNECTION(purple_connection); + priv = purple_ircv3_connection_get_instance_private(connection); + /* TODO: send QUIT command. */ + if(G_IS_SOCKET_CONNECTION(priv->connection)) { + GInputStream *istream = G_INPUT_STREAM(priv->input); + GOutputStream *ostream = G_OUTPUT_STREAM(priv->output); + purple_gio_graceful_close(G_IO_STREAM(priv->connection), + g_clear_object(&priv->input); + g_clear_object(&priv->output); + g_clear_object(&priv->connection); +purple_ircv3_connection_registration_complete_cb(PurpleIRCv3Connection *connection) { + /* Don't set our connection state to connected until we've completed + * registration as connected implies that we can start chatting or join + * rooms and other "online" activities. + purple_connection_set_state(PURPLE_CONNECTION(connection), + PURPLE_CONNECTION_STATE_CONNECTED); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_connection_ctcp_request_default_handler(G_GNUC_UNUSED PurpleIRCv3Connection *connection, + G_GNUC_UNUSED PurpleConversation *conversation, + PurpleMessage *message, + if(!purple_strempty(params)) { + contents = g_strdup_printf(_("requested CTCP %s: %s"), command, + contents = g_strdup_printf(_("requested CTCP %s"), + purple_message_set_contents(message, contents); + g_clear_pointer(&contents, g_free); +purple_ircv3_connection_ctcp_response_default_handler(G_GNUC_UNUSED PurpleIRCv3Connection *connection, + G_GNUC_UNUSED PurpleConversation *conversation, + PurpleMessage *message, + if(!purple_strempty(params)) { + contents = g_strdup_printf(_("CTCP %s response: %s"), command, + contents = g_strdup_printf(_("CTCP %s response was empty"), + purple_message_set_contents(message, contents); + g_clear_pointer(&contents, g_free); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +purple_ircv3_connection_get_property(GObject *obj, guint param_id, + GValue *value, GParamSpec *pspec) + PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); + case PROP_CAPABILITIES: + g_value_set_object(value, + purple_ircv3_connection_get_capabilities(connection)); + g_value_set_boolean(value, + purple_ircv3_connection_get_registered(connection)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_connection_dispose(GObject *obj) { + PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); + PurpleIRCv3ConnectionPrivate *priv = NULL; + priv = purple_ircv3_connection_get_instance_private(connection); + g_clear_object(&priv->input); + g_clear_object(&priv->output); + g_clear_object(&priv->connection); + g_clear_object(&priv->capabilities); + g_clear_object(&priv->parser); + g_clear_object(&priv->status_conversation); + G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->dispose(obj); +purple_ircv3_connection_finalize(GObject *obj) { + PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); + PurpleIRCv3ConnectionPrivate *priv = NULL; + priv = purple_ircv3_connection_get_instance_private(connection); + g_clear_pointer(&priv->server_name, g_free); + G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->finalize(obj); +purple_ircv3_connection_constructed(GObject *obj) { + PurpleIRCv3Connection *connection = PURPLE_IRCV3_CONNECTION(obj); + PurpleIRCv3ConnectionPrivate *priv = NULL; + PurpleAccount *account = NULL; + PurpleContactInfo *info = NULL; + PurpleConversationManager *conversation_manager = NULL; + char **userparts = NULL; + const char *username = NULL; + G_OBJECT_CLASS(purple_ircv3_connection_parent_class)->constructed(obj); + priv = purple_ircv3_connection_get_instance_private(connection); + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + info = PURPLE_CONTACT_INFO(account); + title = g_strdup_printf(_("status for %s"), + purple_contact_info_get_username(info)); + /* Split the username into nick and server and store the values. */ + username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account)); + userparts = g_strsplit(username, "@", 2); + purple_connection_set_display_name(PURPLE_CONNECTION(connection), + priv->server_name = g_strdup(userparts[1]); + /* Free the userparts vector. */ + /* Check if we have an existing status conversation. */ + conversation_manager = purple_conversation_manager_get_default(); + priv->status_conversation = purple_conversation_manager_find_with_id(conversation_manager, + if(!PURPLE_IS_CONVERSATION(priv->status_conversation)) { + /* Create our status conversation. */ + priv->status_conversation = g_object_new( + PURPLE_TYPE_CONVERSATION, + "id", priv->server_name, + "name", priv->server_name, + purple_conversation_manager_register(conversation_manager, + priv->status_conversation); + /* The conversation existed, so add a reference to it. */ + g_object_ref(priv->status_conversation); + g_clear_pointer(&title, g_free); + /* Finally create our objects. */ + priv->capabilities = purple_ircv3_capabilities_new(connection); + g_signal_connect_object(priv->capabilities, "done", + G_CALLBACK(purple_ircv3_connection_caps_done_cb), +purple_ircv3_connection_init(G_GNUC_UNUSED PurpleIRCv3Connection *connection) { +purple_ircv3_connection_class_finalize(G_GNUC_UNUSED PurpleIRCv3ConnectionClass *klass) { +purple_ircv3_connection_class_init(PurpleIRCv3ConnectionClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + PurpleConnectionClass *connection_class = PURPLE_CONNECTION_CLASS(klass); + obj_class->get_property = purple_ircv3_connection_get_property; + obj_class->constructed = purple_ircv3_connection_constructed; + obj_class->dispose = purple_ircv3_connection_dispose; + obj_class->finalize = purple_ircv3_connection_finalize; + connection_class->connect = purple_ircv3_connection_connect; + connection_class->disconnect = purple_ircv3_connection_disconnect; + * PurpleIRCv3Connection:capabilities: + * The capabilities that the server supports. + * This is created during registration of the connection and is useful for + * troubleshooting or just reporting them to end users. + properties[PROP_CAPABILITIES] = g_param_spec_object( + "capabilities", "capabilities", + "The capabilities that the server supports", + PURPLE_IRCV3_TYPE_CAPABILITIES, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + * PurpleIRCv3Connection:registered: + * Whether or not the connection has finished the registration portion of + properties[PROP_REGISTERED] = g_param_spec_boolean( + "registered", "registered", + "Whether or not the connection has finished registration.", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); + * PurpleIRCv3Connection::registration-complete: + * @connection: The instance. + * This signal is emitted after the registration process has been + * completed. Plugins can use this to perform additional actions before + * any channels are auto joined or similar. + signals[SIG_REGISTRATION_COMPLETE] = g_signal_new_class_handler( + "registration-complete", + G_OBJECT_CLASS_TYPE(klass), + G_CALLBACK(purple_ircv3_connection_registration_complete_cb), + * PurpleIRCv3Connection::ctcp-request: + * @connection: The instance. + * @conversation: The conversation. + * @message: The message. + * @command: The CTCP command. + * @params: (nullable): The CTCP parameters. + * This signal is emitted after a CTCP request has been received. + * Signal handlers should return TRUE if they fully handled the request and + * do not want it echoed out to the user. + * Handlers may also modify the message. For example, the CTCP ACTION + * command has its message contents replaced with just the CTCP parameters + * and sets the [property@Message:action] to %TRUE and then returns %TRUE. + signals[SIG_CTCP_REQUEST] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + G_CALLBACK(purple_ircv3_connection_ctcp_request_default_handler), + g_signal_accumulator_true_handled, + PURPLE_TYPE_CONVERSATION, + * PurpleIRCv3Connection::ctcp-response: + * @connection: The instance. + * @conversation: The conversation. + * @message: The message. + * @command: The CTCP command. + * @params: (nullable): The CTCP parameters. + * This signal is emitted after a CTCP response has been received. + * Signal handlers should return TRUE if they fully handled the response + * and do not want it echoed out to the user. + * Handlers may modify @conversation or @message to depending on their + signals[SIG_CTCP_RESPONSE] = g_signal_new_class_handler( + G_OBJECT_CLASS_TYPE(klass), + G_CALLBACK(purple_ircv3_connection_ctcp_response_default_handler), + g_signal_accumulator_true_handled, + PURPLE_TYPE_CONVERSATION, +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_connection_register(GPluginNativePlugin *plugin) { + GObjectClass *hack = NULL; + purple_ircv3_connection_register_type(G_TYPE_MODULE(plugin)); + /* Without this hack we get some warnings about no reference on this type + * when generating the gir file. However, there are no changes to the + * generated gir file with or without this hack, so this is purely to get + hack = g_type_class_ref(purple_ircv3_connection_get_type()); + g_type_class_unref(hack); +purple_ircv3_connection_emit_ctcp_request(PurpleIRCv3Connection *connection, + PurpleConversation *conversation, + PurpleMessage *message, + const char *parameters) + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE); + g_return_val_if_fail(!purple_strempty(command), FALSE); + g_signal_emit(connection, signals[SIG_CTCP_REQUEST], 0, conversation, + message, command, parameters, &ret); +purple_ircv3_connection_emit_ctcp_response(PurpleIRCv3Connection *connection, + PurpleConversation *conversation, + PurpleMessage *message, + const char *parameters) + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE); + g_return_val_if_fail(!purple_strempty(command), FALSE); + g_signal_emit(connection, signals[SIG_CTCP_RESPONSE], 0, conversation, + message, command, parameters, &ret); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_connection_writef(PurpleIRCv3Connection *connection, + const char *format, ...) + PurpleIRCv3ConnectionPrivate *priv = NULL; + GCancellable *cancellable = NULL; + g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection)); + g_return_if_fail(format != NULL); + priv = purple_ircv3_connection_get_instance_private(connection); + /* Create our string and append our format to it. */ + msg = g_string_new(""); + va_start(vargs, format); + g_string_vprintf(msg, format, vargs); + /* Next add the trailing carriage return line feed. */ + g_string_append(msg, "\r\n"); + /* Finally turn the string into bytes and send it! */ + bytes = g_string_free_to_bytes(msg); + cancellable = purple_connection_get_cancellable(PURPLE_CONNECTION(connection)); + birb_queued_output_stream_push_bytes_async(BIRB_QUEUED_OUTPUT_STREAM(priv->output), + purple_ircv3_connection_write_cb, +PurpleIRCv3Capabilities * +purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection) { + PurpleIRCv3ConnectionPrivate *priv = NULL; + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); + priv = purple_ircv3_connection_get_instance_private(connection); + return priv->capabilities; +purple_ircv3_connection_get_registered(PurpleIRCv3Connection *connection) { + PurpleIRCv3ConnectionPrivate *priv = NULL; + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE); + priv = purple_ircv3_connection_get_instance_private(connection); + return priv->registered; +purple_ircv3_connection_add_status_message(PurpleIRCv3Connection *connection, + PurpleIRCv3Message *v3_message) + PurpleIRCv3ConnectionPrivate *priv = NULL; + PurpleMessage *message = NULL; + const char *command = NULL; + g_return_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection)); + g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(v3_message)); + priv = purple_ircv3_connection_get_instance_private(connection); + command = purple_ircv3_message_get_command(v3_message); + str = g_string_new(command); + params = purple_ircv3_message_get_params(v3_message); + if(params != NULL && params[0] != NULL) { + char *joined = g_strjoinv(" ", params); + g_string_append_printf(str, " %s", joined); + stripped = purple_ircv3_formatting_strip(str->str); + g_string_free(str, TRUE); + message = g_object_new( + "author", purple_ircv3_message_get_source(v3_message), + purple_conversation_write_message(priv->status_conversation, message); + g_clear_object(&message); +purple_ircv3_connection_is_channel(PurpleIRCv3Connection *connection, + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(id != NULL, FALSE); +purple_ircv3_connection_find_or_create_conversation(PurpleIRCv3Connection *connection, + PurpleAccount *account = NULL; + PurpleConversation *conversation = NULL; + PurpleConversationManager *manager = NULL; + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), NULL); + g_return_val_if_fail(id != NULL, NULL); + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + manager = purple_conversation_manager_get_default(); + conversation = purple_conversation_manager_find_with_id(manager, account, + if(!PURPLE_IS_CONVERSATION(conversation)) { + PurpleConversationType type = PurpleConversationTypeDM; + if(purple_ircv3_connection_is_channel(connection, id)) { + type = PurpleConversationTypeChannel; + conversation = g_object_new( + PURPLE_TYPE_CONVERSATION, + purple_conversation_manager_register(manager, conversation); + /* The manager creates its own reference on our new conversation, so we + * borrow it like we do above if it already exists. + g_object_unref(conversation); +purple_ircv3_connection_find_or_create_contact(PurpleIRCv3Connection *connection, + PurpleAccount *account = NULL; + PurpleContact *contact = NULL; + PurpleContactManager *manager = NULL; + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + manager = purple_contact_manager_get_default(); + contact = purple_contact_manager_find_with_id(manager, account, nick); + if(!PURPLE_IS_CONTACT(contact)) { + contact = purple_contact_new(account, nick); + purple_contact_info_set_username(PURPLE_CONTACT_INFO(contact), nick); + purple_contact_manager_add(manager, contact); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3connection.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,222 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_CONNECTION_H +#define PURPLE_IRCV3_CONNECTION_H +#include <glib-object.h> +#include <gplugin-native.h> +#include "purpleircv3version.h" +#define PURPLE_IRCV3_TYPE_CONNECTION (purple_ircv3_connection_get_type()) +PURPLE_IRCV3_AVAILABLE_IN_ALL +G_DECLARE_DERIVABLE_TYPE(PurpleIRCv3Connection, purple_ircv3_connection, + PURPLE_IRCV3, CONNECTION, PurpleConnection) +#include "purpleircv3capabilities.h" +#include "purpleircv3message.h" +struct _PurpleIRCv3ConnectionClass { + PurpleConnectionClass parent; + * purple_ircv3_connection_register: (skip) + * @plugin: The GTypeModule + * Registers the dynamic type using @plugin. +G_GNUC_INTERNAL void purple_ircv3_connection_register(GPluginNativePlugin *plugin); + * purple_ircv3_connection_emit_ctcp_request: + * @connection: The instance. + * @conversation: The conversation. + * @message: The message. + * @command: The CTCP command. + * @parameters: (nullable): The CTCP parameters. + * Emits the [signal@Connection:ctcp-request] signal with the given @command + * and @parameters which originated from @message in @conversation. + * The message may be modified by a signal handler. For example, the default + * handler will output an internationalized string that describes what command + * Returns: %TRUE if the request was handled and the message should not be + * echoed, otherwise %FALSE. +G_GNUC_INTERNAL gboolean purple_ircv3_connection_emit_ctcp_request(PurpleIRCv3Connection *connection, PurpleConversation *conversation, PurpleMessage *message, const char *command, const char *parameters); + * purple_ircv3_connection_emit_ctcp_response: + * @connection: The instance. + * @conversation: The conversation. + * @message: The message. + * @command: The CTCP command. + * @parameters: (nullable): The CTCP parameters. + * Emits the [signal@Connection:ctcp-response] signal with the given @command + * and @parameters which originated from @message in @conversation. + * The message may be modified by a signal handler. For example, the default + * handler will output an internationalized string that describes what the + * Returns: %TRUE if the request was handled and the message should not be + * echoed, otherwise %FALSE. +G_GNUC_INTERNAL gboolean purple_ircv3_connection_emit_ctcp_response(PurpleIRCv3Connection *connection, PurpleConversation *conversation, PurpleMessage *message, const char *command, const char *parameters); + * purple_ircv3_connection_writef: + * @connection: The instance. + * @format: The format string. + * @...: The arguments for @format. + * Similar to C `printf()` but writes the format string out to @connection. + * This will add the proper line termination, so you do not need to worry about +void purple_ircv3_connection_writef(PurpleIRCv3Connection *connection, const char *format, ...) G_GNUC_PRINTF(2, 3); + * purple_ircv3_connection_get_capabilities: + * @connection: The instance. + * Gets the list of capabilities that the server supplied during registration. + * Returns: (transfer none): The list of capabilities that the server supports. +PurpleIRCv3Capabilities *purple_ircv3_connection_get_capabilities(PurpleIRCv3Connection *connection); + * purple_ircv3_connection_get_registered: + * @connection: The instance. + * Gets whether or not the connection has finished the registration process. + * Returns: %TRUE if registration has been completed otherwise %FALSE. +PURPLE_IRCV3_AVAILABLE_IN_ALL +gboolean purple_ircv3_connection_get_registered(PurpleIRCv3Connection *connection); + * purple_ircv3_connection_add_status_message: + * @connection: The instance. + * @message: The message. + * Adds a message to the status conversation/window for @connection. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_connection_add_status_message(PurpleIRCv3Connection *connection, PurpleIRCv3Message *message); + * purple_ircv3_connection_is_channel: + * @connection: The instance. + * @id: The id to check. + * Checks if @id is a channel. + * Right now this just checks if @id starts with a `#` but in the future this + * will be updated to check all channel prefixes that the connection supports. + * Returns: %TRUE if @id is a channel otherwise %FALSE. +PURPLE_IRCV3_AVAILABLE_IN_ALL +gboolean purple_ircv3_connection_is_channel(PurpleIRCv3Connection *connection, const char *id); + * purple_ircv3_connection_find_or_create_conversation: + * @connection: The instance. + * @id: The id of the conversation. + * Looks for an existing conversation belonging to @connection and returns it + * if found. If not found a new conversation will be created. + * This will only ever return %NULL if @connection is invalid or @id is %NULL. + * Note that ownership of the conversation remains with the default + * [class@Purple.ConversationManager]. + * Returns: (transfer none) (nullable): The conversation. +PURPLE_IRCV3_AVAILABLE_IN_ALL +PurpleConversation *purple_ircv3_connection_find_or_create_conversation(PurpleIRCv3Connection *connection, const char *id); + * purple_ircv3_connection_find_or_create_contact: + * @connection: The instance. + * @nick: The nickname of the user. + * Looks for an existing contact belonging to @connection and returns it if + * found. If not a new contact will be created. + * This will only ever return %NULL if @connection is invalid or @nick is + * Note that the ownership of the contact remains with the default + * [class@Purple.ContactManager]. + * Returns: (transfer none) (nullable): The contact. +PURPLE_IRCV3_AVAILABLE_IN_ALL +PurpleContact *purple_ircv3_connection_find_or_create_contact(PurpleIRCv3Connection *connection, const char *nick); +#endif /* PURPLE_IRCV3_CONNECTION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3constants.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,328 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_CONSTANTS_H +#define PURPLE_IRCV3_CONSTANTS_H + * PURPLE_IRCV3_CTCP_ACTION: + * A constant representing the CTCP ACTION command. +#define PURPLE_IRCV3_CTCP_ACTION ("ACTION") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_CTCP_DELIMITER: + * The delimiter used for CTCP messages. +#define PURPLE_IRCV3_CTCP_DELIMITER (0x1) PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_CTCP_VERSION: + * A constant representing the CTCP VERSION command. +#define PURPLE_IRCV3_CTCP_VERSION ("VERSION") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_ERR_NICKLOCKED: + * A constant for the IRC %NICKLOCKED error. +#define PURPLE_IRCV3_ERR_NICKLOCKED ("902") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_ERR_SASLABORTED: + * A constant for the IRC %SASLABORTED error. +#define PURPLE_IRCV3_ERR_SASLABORTED ("906") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_ERR_SASLALREADY: + * A constant for the IRC %SASLALREADY error. +#define PURPLE_IRCV3_ERR_SASLALREADY ("907") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_ERR_SASLFAIL: + * A constant for the IRC %SASLFAIL error. +#define PURPLE_IRCV3_ERR_SASLFAIL ("904") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_ERR_SASLTOOLONG: + * A constant for the IRC %SASLTOOLONG error. +#define PURPLE_IRCV3_ERR_SASLTOOLONG ("905") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_AUTHENTICATE: + * A constant for the IRC %AUTHENTICATE message. +#define PURPLE_IRCV3_MSG_AUTHENTICATE ("AUTHENTICATE") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_CAP: + * A constant for the IRC %CAP message. +#define PURPLE_IRCV3_MSG_CAP ("CAP") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_JOIN: + * A constant for the IRC %JOIN message. +#define PURPLE_IRCV3_MSG_JOIN ("JOIN") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_NOTICE: + * A constant for the IRC %NOTICE message. +#define PURPLE_IRCV3_MSG_NOTICE ("NOTICE") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_PING: + * A constant for the IRC %PING message. +#define PURPLE_IRCV3_MSG_PING ("PING") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_PRIVMSG: + * A constant for the IRC %PRIVMSG message. +#define PURPLE_IRCV3_MSG_PRIVMSG ("PRIVMSG") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_MSG_TOPIC: + * A constant for the IRC %TOPIC message. +#define PURPLE_IRCV3_MSG_TOPIC ("TOPIC") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_CREATED: + * A constant for the IRC %CREATED reply. +#define PURPLE_IRCV3_RPL_CREATED ("003") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_ENDOFMOTD: + * A constant for the IRC %ENDOFMOTD reply. +#define PURPLE_IRCV3_RPL_ENDOFMOTD ("376") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LOGGEDIN: + * A constant for the IRC %LOGGEDIN reply. +#define PURPLE_IRCV3_RPL_LOGGEDIN ("900") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LOGGEDOUT: + * A constant for the IRC %LOGGEDOUT reply. +#define PURPLE_IRCV3_RPL_LOGGEDOUT ("901") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LUSERCHANNELS: + * A constant for the IRC %LUSERCHANNELS reply. +#define PURPLE_IRCV3_RPL_LUSERCHANNELS ("254") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LUSERCLIENT: + * A constant for the IRC %LUSERCLIENT reply. +#define PURPLE_IRCV3_RPL_LUSERCLIENT ("251") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LUSERME: + * A constant for the IRC %LUSERME reply. +#define PURPLE_IRCV3_RPL_LUSERME ("255") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LUSEROP: + * A constant for the IRC %LUSEROP reply. +#define PURPLE_IRCV3_RPL_LUSEROP ("252") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_LUSERUNKNOWN: + * A constant for the IRC %LUSERUNKNOWN reply. +#define PURPLE_IRCV3_RPL_LUSERUNKNOWN ("253") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_MYINFO: + * A constant for the IRC %MYINFO reply. +#define PURPLE_IRCV3_RPL_MYINFO ("004") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_MOTD: + * A constant for the IRC %MOTD reply. +#define PURPLE_IRCV3_RPL_MOTD ("372") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_MOTDSTART: + * A constant for the IRC %MOTDSTART reply. +#define PURPLE_IRCV3_RPL_MOTDSTART ("375") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_NOTOPIC: + * A constant for the IRC %NOTOPIC reply. +#define PURPLE_IRCV3_RPL_NOTOPIC ("331") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_SASLMECHS: + * A constant for the IRC %SASLMECHS reply. +#define PURPLE_IRCV3_RPL_SASLMECHS ("908") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_SASLSUCCESS: + * A constant for the IRC %SASLSUCCESS reply. +#define PURPLE_IRCV3_RPL_SASLSUCCESS ("903") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_TOPIC: + * A constant for the IRC %TOPIC reply. +#define PURPLE_IRCV3_RPL_TOPIC ("332") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_WELCOME: + * A constant for the IRC %WELCOME reply. +#define PURPLE_IRCV3_RPL_WELCOME ("001") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 + * PURPLE_IRCV3_RPL_YOURHOST: + * A constant for the IRC %YOURHOST reply. +#define PURPLE_IRCV3_RPL_YOURHOST ("002") PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 +#endif /* PURPLE_IRCV3_CONSTANTS_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3core.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,128 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include <gplugin-native.h> +#include "purpleircv3core.h" +#include "purpleircv3capabilities.h" +#include "purpleircv3connection.h" +#include "purpleircv3protocol.h" +/****************************************************************************** + *****************************************************************************/ +static PurpleProtocol *ircv3_protocol = NULL; +/****************************************************************************** + *****************************************************************************/ +static GPluginPluginInfo * +purple_ircv3_query(G_GNUC_UNUSED GError **error) { + PurplePluginInfoFlags flags = PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | + PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD; + const gchar * const authors[] = { + "Pidgin Developers <devel@pidgin.im>", + return purple_plugin_info_new( + "name", "IRCv3 Protocol", + "version", DISPLAY_VERSION, + "category", N_("Protocol"), + "summary", N_("IRCv3 Protocol Plugin"), + "description", N_("Modern IRC Support"), + "website", PURPLE_WEBSITE, + "abi-version", PURPLE_ABI_VERSION, +purple_ircv3_load(GPluginPlugin *plugin, GError **error) { + PurpleProtocolManager *manager = NULL; + if(PURPLE_IS_PROTOCOL(ircv3_protocol)) { + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "plugin was not cleaned up properly"); + purple_ircv3_capabilities_register(GPLUGIN_NATIVE_PLUGIN(plugin)); + purple_ircv3_connection_register(GPLUGIN_NATIVE_PLUGIN(plugin)); + purple_ircv3_protocol_register(GPLUGIN_NATIVE_PLUGIN(plugin)); + ircv3_protocol = purple_ircv3_protocol_new(); + manager = purple_protocol_manager_get_default(); + /* Manager can be NULL when we're generating the GIR stuff. */ + if(PURPLE_IS_PROTOCOL_MANAGER(manager)) { + if(!purple_protocol_manager_register(manager, ircv3_protocol, error)) { + g_clear_object(&ircv3_protocol); +purple_ircv3_unload(G_GNUC_UNUSED GPluginPlugin *plugin, + G_GNUC_UNUSED gboolean shutdown, + PurpleProtocolManager *manager = NULL; + if(!PURPLE_IS_PROTOCOL(ircv3_protocol)) { + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "plugin was not setup properly"); + manager = purple_protocol_manager_get_default(); + /* Manager can be NULL when we're generating the GIR stuff. */ + if(PURPLE_IS_PROTOCOL_MANAGER(manager)) { + if(!purple_protocol_manager_unregister(manager, ircv3_protocol, + g_clear_object(&ircv3_protocol); +GPLUGIN_NATIVE_PLUGIN_DECLARE(purple_ircv3) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3core.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,39 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_CORE_H +#define PURPLE_IRCV3_CORE_H +#define PURPLE_IRCV3_DEFAULT_SERVER "irc.libera.chat" +#define PURPLE_IRCV3_DEFAULT_PLAIN_PORT 6667 +#define PURPLE_IRCV3_DEFAULT_TLS_PORT 6697 +#define PURPLE_IRCV3_DOMAIN (g_quark_from_static_string("ircv3-plugin")) +#endif /* PURPLE_IRCV3_CORE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3ctcp.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,160 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3ctcp.h" +#include "purpleircv3constants.h" +#include "purpleircv3source.h" +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_ctcp_respond(PurpleIRCv3Connection *connection, + PurpleMessage *message, + const char *author = NULL; + author = purple_message_get_author(message); + purple_ircv3_source_parse(author, &nick, NULL, NULL); + if(!purple_strempty(nick)) { + PurpleContact *contact = NULL; + contact = purple_ircv3_connection_find_or_create_contact(connection, + if(PURPLE_IS_CONTACT(contact)) { + id = purple_contact_info_get_id(PURPLE_CONTACT_INFO(contact)); + purple_ircv3_connection_writef(connection, "%s %s :%s", + PURPLE_IRCV3_MSG_NOTICE, + g_clear_pointer(&nick, g_free); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_ctcp_handler(PurpleIRCv3Connection *connection, + PurpleConversation *conversation, + PurpleMessage *message, + G_GNUC_UNUSED gpointer data) + if(purple_strequal(command, PURPLE_IRCV3_CTCP_ACTION)) { + purple_message_set_contents(message, params); + purple_message_set_action(message, TRUE); + purple_conversation_write_message(conversation, message); + } else if(purple_strequal(command, PURPLE_IRCV3_CTCP_VERSION)) { + purple_ircv3_ctcp_respond(connection, message, "Purple3 IRCv3"); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_ctcp_handle(PurpleIRCv3Connection *connection, + PurpleConversation *conversation, + PurpleMessage *message) + PurpleMessageFlags flags; + const char *contents = NULL; + g_return_val_if_fail(PURPLE_IRCV3_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + g_return_val_if_fail(PURPLE_IS_MESSAGE(message), FALSE); + contents = purple_message_get_contents(message); + if(contents == NULL || contents[0] != PURPLE_IRCV3_CTCP_DELIMITER) { + /* Move past the delimiter. */ + /* Find the delimiter for the command. */ + ptr = g_strstr_len(contents, -1, " "); + /* If we don't have a space, then we have no parameters. */ + size_t len = strlen(contents); + command = g_strdup(contents); + if(command[len - 1] == PURPLE_IRCV3_CTCP_DELIMITER) { + command[len - 1] = '\0'; + command = g_strndup(contents, ptr - contents); + params = g_strdup(ptr + 1); + if(params[len - 1] == PURPLE_IRCV3_CTCP_DELIMITER) { + params[len - 1] = '\0'; + flags = purple_message_get_flags(message); + if(flags & PURPLE_MESSAGE_NOTIFY) { + ret = purple_ircv3_connection_emit_ctcp_response(connection, + ret = purple_ircv3_connection_emit_ctcp_request(connection, conversation, + g_clear_pointer(&command, g_free); + g_clear_pointer(¶ms, g_free); +purple_ircv3_ctcp_add_default_handlers(PurpleIRCv3Connection *connection) { + g_signal_connect(connection, "ctcp-request", + G_CALLBACK(purple_ircv3_ctcp_handler), NULL); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3ctcp.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,68 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_CTCP_H +#define PURPLE_IRCV3_CTCP_H +#include <glib-object.h> +#include "purpleircv3connection.h" + * purple_ircv3_ctcp_handle: (skip) + * @connection: The connection. + * @conversation: The conversation. + * @message: The message. + * Check if @message is a CTCP message and handles it accordingly. + * Returns: %TRUE if the message was a CTCP message and %FALSE otherwise. +gboolean purple_ircv3_ctcp_handle(PurpleIRCv3Connection *connection, PurpleConversation *conversation, PurpleMessage *message); + * purple_ircv3_ctcp_add_default_handlers: (skip) + * @connection: The connection. + * Adds handlers for the CTCP commands that we support directly. +void purple_ircv3_ctcp_add_default_handlers(PurpleIRCv3Connection *connection); +#endif /* PURPLE_IRCV3_CTCP_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3formatting.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,118 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include "purpleircv3formatting.h" +#define IRCV3_FORMAT_BOLD (0x02) +#define IRCV3_FORMAT_COLOR (0x03) +#define IRCV3_FORMAT_HEX_COLOR (0x04) +#define IRCV3_FORMAT_ITALIC (0x1d) +#define IRCV3_FORMAT_MONOSPACE (0x11) +#define IRCV3_FORMAT_RESET (0x0f) +#define IRCV3_FORMAT_REVERSE (0x16) +#define IRCV3_FORMAT_STRIKETHROUGH (0x1e) +#define IRCV3_FORMAT_UNDERLINE (0x1f) +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_formatting_is_hex_color(const char *text) { + for(int i = 0; i < 6; i++) { + if(!g_ascii_isxdigit(text[i])) { +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_formatting_strip(const char *text) { + /* We don't use purple_strempty here because if we're passed an empty + * string, we should return a newly allocated empty string. + str = g_string_new(""); + for(int i = 0; text[i] != '\0'; i++) { + case IRCV3_FORMAT_BOLD: + case IRCV3_FORMAT_ITALIC: + case IRCV3_FORMAT_MONOSPACE: + case IRCV3_FORMAT_RESET: + case IRCV3_FORMAT_REVERSE: + case IRCV3_FORMAT_STRIKETHROUGH: + case IRCV3_FORMAT_UNDERLINE: + case IRCV3_FORMAT_COLOR: + if(g_ascii_isdigit(text[i + 1])) { + if(g_ascii_isdigit(text[i + 1])) { + if(text[i + 1] == ',' && g_ascii_isdigit(text[i + 2])) { + if(g_ascii_isdigit(text[i + 1])) { + case IRCV3_FORMAT_HEX_COLOR: + if(purple_ircv3_formatting_is_hex_color(&text[i + 1])) { + if(text[i + 1] == ',' && + purple_ircv3_formatting_is_hex_color(&text[i + 2])) + g_string_append_c(str, text[i]); + return g_string_free(str, FALSE); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3formatting.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,52 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_FORMATTING_H +#define PURPLE_IRCV3_FORMATTING_H +#include "purpleircv3version.h" + * purple_ircv3_formatting_strip: + * @text: (nullable): The text to strip. + * Removes all formatting from @text and returns the result. + * Returns: (transfer full) (nullable): The result of stripping @text. +PURPLE_IRCV3_AVAILABLE_IN_ALL +char *purple_ircv3_formatting_strip(const char *text); +#endif /* PURPLE_IRCV3_FORMATTING_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3message.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,284 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3message.h" +static GParamSpec *properties[N_PROPERTIES] = {NULL, }; +struct _PurpleIRCv3Message { +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_FINAL_TYPE(PurpleIRCv3Message, purple_ircv3_message, G_TYPE_OBJECT) +purple_ircv3_message_finalize(GObject *obj) { + PurpleIRCv3Message *message = PURPLE_IRCV3_MESSAGE(obj); + g_clear_pointer(&message->command, g_free); + g_clear_pointer(&message->params, g_strfreev); + g_clear_pointer(&message->source, g_free); + g_clear_pointer(&message->tags, g_hash_table_unref); + G_OBJECT_CLASS(purple_ircv3_message_parent_class)->finalize(obj); +purple_ircv3_message_get_property(GObject *obj, guint param_id, GValue *value, + PurpleIRCv3Message *message = PURPLE_IRCV3_MESSAGE(obj); + g_value_set_string(value, purple_ircv3_message_get_command(message)); + g_value_set_boxed(value, purple_ircv3_message_get_params(message)); + g_value_set_string(value, purple_ircv3_message_get_source(message)); + g_value_set_boxed(value, purple_ircv3_message_get_tags(message)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_message_set_property(GObject *obj, guint param_id, + const GValue *value, GParamSpec *pspec) + PurpleIRCv3Message *message = PURPLE_IRCV3_MESSAGE(obj); + purple_ircv3_message_set_command(message, g_value_get_string(value)); + purple_ircv3_message_set_params(message, g_value_get_boxed(value)); + purple_ircv3_message_set_source(message, g_value_get_string(value)); + purple_ircv3_message_set_tags(message, g_value_get_boxed(value)); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +purple_ircv3_message_init(G_GNUC_UNUSED PurpleIRCv3Message *message) { +purple_ircv3_message_class_init(PurpleIRCv3MessageClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->finalize = purple_ircv3_message_finalize; + obj_class->get_property = purple_ircv3_message_get_property; + obj_class->set_property = purple_ircv3_message_set_property; + * PurpleIRCv3Message:command: + * The command of this message. + * This could be something like JOIN or a server reply numeric like 005. + properties[PROP_COMMAND] = g_param_spec_string( + "The command of the message.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * PurpleIRCv3Message:params: + * The parameters of the message. + * When serialized, the last item will be prefixed with a :. + properties[PROP_PARAMS] = g_param_spec_boxed( + "The parameters of this message.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * PurpleIRCv3Message:source: + * The source of the message. + * This could be a nickname, a full nick!ident@server, a server name, or + properties[PROP_SOURCE] = g_param_spec_string( + "The source of the message.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + * PurpleIRCv3Message:tags: + * The [ircv3 message tags](https://ircv3.net/specs/extensions/message-tags) + properties[PROP_TAGS] = g_param_spec_boxed( + "The ircv3 message tags from the message.", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_message_new(const char *command) { + PURPLE_IRCV3_TYPE_MESSAGE, +purple_ircv3_message_get_command(PurpleIRCv3Message *message) { + g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL); + return message->command; +purple_ircv3_message_set_command(PurpleIRCv3Message *message, + g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message)); + g_return_if_fail(!purple_strempty(command)); + if(!purple_strequal(message->command, command)) { + g_free(message->command); + message->command = g_strdup(command); + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_COMMAND]); +purple_ircv3_message_get_source(PurpleIRCv3Message *message) { + g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL); + return message->source; +purple_ircv3_message_set_source(PurpleIRCv3Message *message, + g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message)); + if(!purple_strequal(message->source, source)) { + g_free(message->source); + message->source = g_strdup(source); + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_SOURCE]); +purple_ircv3_message_get_tags(PurpleIRCv3Message *message) { + g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL); +purple_ircv3_message_set_tags(PurpleIRCv3Message *message, GHashTable *tags) { + g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message)); + if(message->tags != tags) { + g_clear_pointer(&message->tags, g_hash_table_unref); + message->tags = g_hash_table_ref(tags); + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_TAGS]); +purple_ircv3_message_get_params(PurpleIRCv3Message *message) { + g_return_val_if_fail(PURPLE_IRCV3_IS_MESSAGE(message), NULL); + return message->params; +purple_ircv3_message_set_params(PurpleIRCv3Message *message, GStrv params) { + g_return_if_fail(PURPLE_IRCV3_IS_MESSAGE(message)); + if(message->params != params) { + g_clear_pointer(&message->params, g_strfreev); + message->params = g_strdupv(params); + g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_PARAMS]); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3message.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,164 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_MESSAGE_H +#define PURPLE_IRCV3_MESSAGE_H +#include <gplugin-native.h> +#include "purpleircv3version.h" +#define PURPLE_IRCV3_TYPE_MESSAGE (purple_ircv3_message_get_type()) +PURPLE_IRCV3_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE(PurpleIRCv3Message, purple_ircv3_message, PURPLE_IRCV3, + * purple_ircv3_message_new: + * @command: (not nullable): The command of the message. + * Creates a new message with @command. + * Returns: (transfer full): The new message. +PURPLE_IRCV3_AVAILABLE_IN_ALL +PurpleIRCv3Message *purple_ircv3_message_new(const char *command); + * purple_ircv3_message_get_command: + * @message: The instance. + * Gets the command of the message. + * Returns: The command of the message. +PURPLE_IRCV3_AVAILABLE_IN_ALL +const char *purple_ircv3_message_get_command(PurpleIRCv3Message *message); + * purple_ircv3_message_set_command: + * @message: The instance. + * @command: The new command. + * Sets the command for @message to @command. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_message_set_command(PurpleIRCv3Message *message, const char *command); + * purple_ircv3_message_get_params: + * @message: The instance. + * Gets the parameters from @message. + * Returns: (transfer none) (nullable): The parameters from @message. +PURPLE_IRCV3_AVAILABLE_IN_ALL +GStrv purple_ircv3_message_get_params(PurpleIRCv3Message *message); + * purple_ircv3_message_set_params: + * @message: The instance. + * @params: (transfer none) (nullable): The new parameters. + * Sets the parameters of @message to @params. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_message_set_params(PurpleIRCv3Message *message, GStrv params); + * purple_ircv3_message_get_source: + * @message: The instance. + * Gets the the source of @message. + * Returns: (nullable): The source of @message. +PURPLE_IRCV3_AVAILABLE_IN_ALL +const char *purple_ircv3_message_get_source(PurpleIRCv3Message *message); + * purple_ircv3_message_set_source: + * @message: The instance. + * @source: (nullable): The new source. + * Sets the source of @message to @source. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_message_set_source(PurpleIRCv3Message *message, const char *source); + * purple_ircv3_message_get_tags: + * @message: The instance. + * Gets the tags from @message. + * Returns: (transfer none) (element-type utf8 utf8) (nullable): The tags from +PURPLE_IRCV3_AVAILABLE_IN_ALL +GHashTable *purple_ircv3_message_get_tags(PurpleIRCv3Message *message); + * purple_ircv3_message_set_tags: + * @message: The instance. + * @tags: (transfer none) (element-type utf8 utf8) (nullable): The new tags. + * Sets the tags of @message to @tags. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_message_set_tags(PurpleIRCv3Message *message, GHashTable *tags); +#endif /* PURPLE_IRCV3_MESSAGE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3messagehandlers.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,315 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3messagehandlers.h" +#include "purpleircv3connection.h" +#include "purpleircv3constants.h" +#include "purpleircv3core.h" +#include "purpleircv3ctcp.h" +#include "purpleircv3formatting.h" +#include "purpleircv3source.h" +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_message_handler_fallback(PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = data; + char *new_command = NULL; + const char *command = NULL; + command = purple_ircv3_message_get_command(message); + new_command = g_strdup_printf(_("unknown command '%s'"), command); + purple_ircv3_message_set_command(message, new_command); + purple_ircv3_connection_add_status_message(connection, message); + g_clear_pointer(&new_command, g_free); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_message_handler_status(PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + purple_ircv3_connection_add_status_message(data, message); +purple_ircv3_message_handler_status_ignore_param0(PurpleIRCv3Message *message, + GStrv new_params = NULL; + params = purple_ircv3_message_get_params(message); + n_params = g_strv_length(params); + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "expected n_params > 1, got %u", n_params); + /* We need to make a copy because otherwise we'd get a use after free in + new_params = g_strdupv(params + 1); + purple_ircv3_message_set_params(message, new_params); + g_clear_pointer(&new_params, g_strfreev); + purple_ircv3_connection_add_status_message(data, message); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_message_handler_ping(PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = data; + params = purple_ircv3_message_get_params(message); + if(params != NULL && g_strv_length(params) == 1) { + purple_ircv3_connection_writef(connection, "PONG %s", params[0]); + purple_ircv3_connection_writef(connection, "PONG"); +purple_ircv3_message_handler_privmsg(PurpleIRCv3Message *v3_message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = data; + PurpleContact *contact = NULL; + PurpleConversation *conversation = NULL; + PurpleMessage *message = NULL; + PurpleMessageFlags flags = PURPLE_MESSAGE_RECV; + GHashTable *tags = NULL; + gpointer raw_id = NULL; + gpointer raw_timestamp = NULL; + const char *command = NULL; + const char *source = NULL; + const char *target = NULL; + command = purple_ircv3_message_get_command(v3_message); + params = purple_ircv3_message_get_params(v3_message); + source = purple_ircv3_message_get_source(v3_message); + tags = purple_ircv3_message_get_tags(v3_message); + g_warning("privmsg received with no parameters"); + if(g_strv_length(params) != 2) { + char *body = g_strjoinv(" ", params); + g_warning("unknown privmsg message format: '%s'", body); + purple_ircv3_source_parse(source, &nick, NULL, NULL); + /* Find or create the conversation. */ + if(!purple_ircv3_connection_is_channel(connection, target)) { + conversation = purple_ircv3_connection_find_or_create_conversation(connection, + /* Find or create the contact. */ + contact = purple_ircv3_connection_find_or_create_contact(connection, nick); + if(PURPLE_IS_CONTACT(contact)) { + PurpleConversationMember *member = NULL; + /* Update the contact's sid as it may have changed. */ + purple_contact_info_set_sid(PURPLE_CONTACT_INFO(contact), source); + /* Make sure the contact is in the conversation. */ + member = purple_conversation_find_member(conversation, + PURPLE_CONTACT_INFO(contact)); + if(!PURPLE_IS_CONVERSATION_MEMBER(member)) { + purple_conversation_add_member(conversation, + PURPLE_CONTACT_INFO(contact), + /* Grab the msgid if one was provided. */ + if(g_hash_table_lookup_extended(tags, "msgid", NULL, &raw_id)) { + if(!purple_strempty(raw_id)) { + if(purple_strequal(command, PURPLE_IRCV3_MSG_NOTICE)) { + flags |= PURPLE_MESSAGE_NOTIFY; + /* Determine the timestamp of the message. */ + if(g_hash_table_lookup_extended(tags, "time", NULL, &raw_timestamp)) { + const char *timestamp = raw_timestamp; + if(!purple_strempty(timestamp)) { + GTimeZone *tz = g_time_zone_new_utc(); + dt = g_date_time_new_from_iso8601(timestamp, tz); + /* If the server didn't provide a time, use the current local time. */ + dt = g_date_time_new_now_local(); + stripped = purple_ircv3_formatting_strip(params[1]); + message = g_object_new( + /* Check if this is a CTCP message. */ + if(!purple_ircv3_ctcp_handle(connection, conversation, message)) { + purple_conversation_write_message(conversation, message); + g_clear_pointer(&nick, g_free); + g_clear_object(&message); +purple_ircv3_message_handler_topic(PurpleIRCv3Message *message, + PurpleIRCv3Connection *connection = data; + PurpleConversation *conversation = NULL; + const char *channel = NULL; + const char *command = NULL; + const char *topic = NULL; + command = purple_ircv3_message_get_command(message); + params = purple_ircv3_message_get_params(message); + n_params = g_strv_length(params); + if(purple_strequal(command, PURPLE_IRCV3_MSG_TOPIC)) { + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "received TOPIC with %u parameters, expected 2", + } else if(purple_strequal(command, PURPLE_IRCV3_RPL_NOTOPIC)) { + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "received RPL_NOTOPIC with %u parameters, expected 3", + } else if(purple_strequal(command, PURPLE_IRCV3_RPL_TOPIC)) { + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "received RPL_TOPIC with %u parameters, expected 3", + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, "unexpected command %s", + conversation = purple_ircv3_connection_find_or_create_conversation(connection, + if(!PURPLE_IS_CONVERSATION(conversation)) { + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "failed to find or create channel '%s'", channel); + purple_conversation_set_topic(conversation, topic); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3messagehandlers.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,67 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_MESSAGE_HANDLERS_H +#define PURPLE_IRCV3_MESSAGE_HANDLERS_H +#include "purpleircv3message.h" +#include "purpleircv3version.h" + * PurpleIRCv3MessageHandler: + * @message: The [class@Message] to handle. + * @error: (nullable): A return address for a [type@GLib.Error]. + * @data: The user data that was passed to [method@PurpleIRCv3.Parser.parse]. + * Defines the type of a function that will be called to handle @message. + * Returns: %TRUE if the command was handled properly, otherwise %FALSE and +PURPLE_IRCV3_AVAILABLE_TYPE_IN_3_0 +typedef gboolean (*PurpleIRCv3MessageHandler)(PurpleIRCv3Message *message, +G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_fallback(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_status(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_status_ignore_param0(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_ping(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_privmsg(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_message_handler_topic(PurpleIRCv3Message *message, GError **error, gpointer data); +#endif /* PURPLE_IRCV3_MESSAGE_HANDLERS_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3parser.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,507 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include "purpleircv3parser.h" +#include "purpleircv3capabilities.h" +#include "purpleircv3constants.h" +#include "purpleircv3core.h" +#include "purpleircv3message.h" +#include "purpleircv3messagehandlers.h" +#include "purpleircv3sasl.h" +struct _PurpleIRCv3Parser { + PurpleIRCv3MessageHandler fallback_handler; +G_DEFINE_FINAL_TYPE(PurpleIRCv3Parser, purple_ircv3_parser, G_TYPE_OBJECT) +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_parser_unescape_tag_value(const char *value) { + GString *unescaped = g_string_new(""); + gboolean escaping = FALSE; + /* Walk the string and replace escaped values according to + * https://ircv3.net/specs/extensions/message-tags.html#escaping-values + for(int i = 0; value[i] != '\0'; i++) { + /* Set the replacement to the current character which will fall + * through for everything, including '\\' + char replacement = value[i]; + } else if(value[i] == 's') { + } else if(value[i] == 'r') { + } else if(value[i] == 'n') { + g_string_append_c(unescaped, replacement); + g_string_append_c(unescaped, value[i]); + return g_string_free(unescaped, FALSE); +purple_ircv3_parser_parse_tags(PurpleIRCv3Parser *parser, + const gchar *tags_string, GError **error) + GError *local_error = NULL; + GHashTable *tags = NULL; + GMatchInfo *info = NULL; + gboolean matches = FALSE; + tags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + /* tags_string can never be NULL, because g_match_info_fetch_named always + * returns a string. So if we were passed an empty string, just return the + if(*tags_string == '\0') { + matches = g_regex_match_full(parser->regex_tags, tags_string, -1, 0, 0, + if(local_error != NULL) { + g_propagate_error(error, local_error); + g_match_info_unref(info); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "failed to parse tags: unknown error"); + g_match_info_unref(info); + while(g_match_info_matches(info)) { + char *unescaped = NULL; + key = g_match_info_fetch_named(info, "key"); + value = g_match_info_fetch_named(info, "value"); + unescaped = purple_ircv3_parser_unescape_tag_value(value); + /* the hash table is created with destroy notifies for both key and + * value, so there's no need to free the allocated memory right now. + g_hash_table_insert(tags, key, unescaped); + g_match_info_next(info, &local_error); + if(local_error != NULL) { + g_propagate_error(error, local_error); + g_match_info_unref(info); +purple_ircv3_parser_extract_params(G_GNUC_UNUSED PurpleIRCv3Parser *parser, + GStrvBuilder *builder, const gchar *str) + /* Loop through str finding each space separated string. */ + while(str != NULL && *str != '\0') { + /* Look for a space. */ + ptr = strchr(str, ' '); + /* If we found one, set it to null terminator and add the string to our + g_strv_builder_add(builder, str); + /* Move str to the next character as we know there's another + * character which might be another null terminator. + /* And don't forget to increment the count... ah ah ah! */ + /* Add the remaining string. */ + g_strv_builder_add(builder, str); + /* Give the count another one, ah ah ah! */ + /* Finally break out of the loop. */ +purple_ircv3_parser_build_params(PurpleIRCv3Parser *parser, + const gchar *middle, const gchar *coda, + const gchar *trailing, guint *n_params) + GStrvBuilder *builder = g_strv_builder_new(); + purple_ircv3_parser_extract_params(parser, builder, middle); + g_strv_builder_add(builder, trailing); + result = g_strv_builder_end(builder); + g_strv_builder_unref(builder); + if(result != NULL && n_params != NULL) { + *n_params = g_strv_length(result); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_fallback_handler(PurpleIRCv3Message *message, + G_GNUC_UNUSED gpointer data) + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, "no handler for command %s", + purple_ircv3_message_get_command(message)); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +purple_ircv3_parser_finalize(GObject *obj) { + PurpleIRCv3Parser *parser = PURPLE_IRCV3_PARSER(obj); + g_clear_pointer(&parser->regex_message, g_regex_unref); + g_clear_pointer(&parser->regex_tags, g_regex_unref); + g_hash_table_destroy(parser->handlers); + G_OBJECT_CLASS(purple_ircv3_parser_parent_class)->finalize(obj); +purple_ircv3_parser_init(PurpleIRCv3Parser *parser) { + parser->regex_message = g_regex_new("(?:@(?<tags>[^ ]+) )?" + "(?::(?<source>[^ ]+) +)?" + "(?: +(?<middle>(?:[^ :]+(?: +[^ :]+)*)))*" + "(?<coda> +:(?<trailing>.*)?)?", + g_assert(parser->regex_message != NULL); + parser->regex_tags = g_regex_new("(?<key>(?<client_prefix>\\+?)" + "(?:(?<vendor>[A-Za-z0-9-\\.]+)\\/)?" + "(?<key_name>[A-Za-z0-9-]+)" + "(?:=(?<value>[^\r\n;]*))?(?:;|$)", + g_assert(parser->regex_tags != NULL); + parser->fallback_handler = purple_ircv3_fallback_handler; + parser->handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, +purple_ircv3_parser_class_init(PurpleIRCv3ParserClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->finalize = purple_ircv3_parser_finalize; +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_parser_new(void) { + return g_object_new(PURPLE_IRCV3_TYPE_PARSER, NULL); +purple_ircv3_parser_set_fallback_handler(PurpleIRCv3Parser *parser, + PurpleIRCv3MessageHandler handler) + g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser)); + parser->fallback_handler = handler; +purple_ircv3_parser_parse(PurpleIRCv3Parser *parser, const gchar *buffer, + GError **error, gpointer data) + PurpleIRCv3Message *message = NULL; + PurpleIRCv3MessageHandler handler = NULL; + GError *local_error = NULL; + GHashTable *tags = NULL; + GMatchInfo *info = NULL; + gchar *tags_string = NULL; + gchar *trailing = NULL; + gboolean matches = FALSE; + gboolean result = FALSE; + g_return_val_if_fail(PURPLE_IRCV3_IS_PARSER(parser), FALSE); + g_return_val_if_fail(buffer != NULL, FALSE); + /* Check if the buffer matches our regex for messages. */ + matches = g_regex_match(parser->regex_message, buffer, 0, &info); + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "failed to parser buffer '%s'", buffer); + g_match_info_unref(info); + /* Extract the command from the buffer, so we can find the handler. */ + command = g_match_info_fetch_named(info, "command"); + handler = g_hash_table_lookup(parser->handlers, command); + if(parser->fallback_handler == NULL) { + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "no handler found for command %s and no default " + "handler set.", command); + g_match_info_unref(info); + handler = parser->fallback_handler; + /* If we made it this far, we have our handler, so lets get the rest of the + * parameters and call the handler. + message = purple_ircv3_message_new(command); + tags_string = g_match_info_fetch_named(info, "tags"); + tags = purple_ircv3_parser_parse_tags(parser, tags_string, &local_error); + if(local_error != NULL) { + g_propagate_error(error, local_error); + g_clear_object(&message); + g_hash_table_destroy(tags); + g_match_info_unref(info); + purple_ircv3_message_set_tags(message, tags); + g_hash_table_unref(tags); + source = g_match_info_fetch_named(info, "source"); + if(!purple_strempty(source)) { + purple_ircv3_message_set_source(message, source); + middle = g_match_info_fetch_named(info, "middle"); + coda = g_match_info_fetch_named(info, "coda"); + trailing = g_match_info_fetch_named(info, "trailing"); + params = purple_ircv3_parser_build_params(parser, middle, coda, trailing, + purple_ircv3_message_set_params(message, params); + /* Call the handler. */ + result = handler(message, error, data); + /* Cleanup the left overs. */ + g_match_info_unref(info); + g_clear_object(&message); +purple_ircv3_parser_add_handler(PurpleIRCv3Parser *parser, + PurpleIRCv3MessageHandler handler) + g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser)); + g_return_if_fail(command != NULL); + g_return_if_fail(handler != NULL); + g_hash_table_insert(parser->handlers, g_strdup(command), handler); +purple_ircv3_parser_add_handlers(PurpleIRCv3Parser *parser, + PurpleIRCv3MessageHandler handler, + const char *command = NULL; + g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser)); + g_return_if_fail(handler != NULL); + va_start(vargs, handler); + while((command = va_arg(vargs, const char *)) != NULL) { + purple_ircv3_parser_add_handler(parser, command, handler); +purple_ircv3_parser_add_default_handlers(PurpleIRCv3Parser *parser) { + g_return_if_fail(PURPLE_IRCV3_IS_PARSER(parser)); + purple_ircv3_parser_set_fallback_handler(parser, + purple_ircv3_message_handler_fallback); + /* Core functionality. */ + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_CAP, + purple_ircv3_capabilities_message_handler); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_NOTICE, + purple_ircv3_message_handler_privmsg); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_PING, + purple_ircv3_message_handler_ping); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_PRIVMSG, + purple_ircv3_message_handler_privmsg); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_TOPIC, + purple_ircv3_message_handler_topic); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_NOTOPIC, + purple_ircv3_message_handler_topic); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_TOPIC, + purple_ircv3_message_handler_topic); + /* Post Registration Greetings */ + purple_ircv3_parser_add_handlers(parser, + purple_ircv3_message_handler_status_ignore_param0, + PURPLE_IRCV3_RPL_WELCOME, + PURPLE_IRCV3_RPL_YOURHOST, + PURPLE_IRCV3_RPL_CREATED, + PURPLE_IRCV3_RPL_MYINFO, + purple_ircv3_parser_add_handlers(parser, + purple_ircv3_message_handler_status_ignore_param0, + PURPLE_IRCV3_RPL_LUSERCLIENT, + PURPLE_IRCV3_RPL_LUSEROP, + PURPLE_IRCV3_RPL_LUSERUNKNOWN, + PURPLE_IRCV3_RPL_LUSERCHANNELS, + PURPLE_IRCV3_RPL_LUSERME, + purple_ircv3_parser_add_handlers(parser, + purple_ircv3_message_handler_status_ignore_param0, + PURPLE_IRCV3_RPL_MOTDSTART, + PURPLE_IRCV3_RPL_ENDOFMOTD, + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_LOGGEDIN, + purple_ircv3_sasl_logged_in); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_LOGGEDOUT, + purple_ircv3_sasl_logged_out); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_NICKLOCKED, + purple_ircv3_sasl_nick_locked); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_SASLSUCCESS, + purple_ircv3_sasl_success); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLFAIL, + purple_ircv3_sasl_failed); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLTOOLONG, + purple_ircv3_sasl_message_too_long); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLABORTED, + purple_ircv3_sasl_aborted); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_ERR_SASLALREADY, + purple_ircv3_sasl_already_authed); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_RPL_SASLMECHS, + purple_ircv3_sasl_mechanisms); + purple_ircv3_parser_add_handler(parser, PURPLE_IRCV3_MSG_AUTHENTICATE, + purple_ircv3_sasl_authenticate); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3parser.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,126 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_PARSER_H +#define PURPLE_IRCV3_PARSER_H +#include <glib-object.h> +#include "purpleircv3messagehandlers.h" +#include "purpleircv3version.h" +#define PURPLE_IRCV3_TYPE_PARSER (purple_ircv3_parser_get_type()) +PURPLE_IRCV3_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE(PurpleIRCv3Parser, purple_ircv3_parser, PURPLE_IRCV3, + * purple_ircv3_parser_new: + * Creates a new instance. +PURPLE_IRCV3_AVAILABLE_IN_ALL +PurpleIRCv3Parser *purple_ircv3_parser_new(void); + * purple_ircv3_parser_set_fallback_handler: (skip): + * @parser: The instance. + * @handler: A [func@PurpleIRCv3.MessageHandler]. + * Sets @handler to be called for any messages that @parser doesn't know how to +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_parser_set_fallback_handler(PurpleIRCv3Parser *parser, PurpleIRCv3MessageHandler handler); + * purple_ircv3_parser_parse: + * @parser: The instance. + * @buffer: The buffer to parse. + * @error: Return address for a #GError, or %NULL. + * @data: (nullable): Optional data to pass to the handler. + * Parses @buffer with @parser. + * Returns: %TRUE if the buffer was parsed correctly or %FALSE with @error set. +PURPLE_IRCV3_AVAILABLE_IN_ALL +gboolean purple_ircv3_parser_parse(PurpleIRCv3Parser *parser, const gchar *buffer, GError **error, gpointer data); + * purple_ircv3_parser_add_handler: + * @parser: The instance. + * @command: The command string. + * @handler: (scope forever): The handler to call. + * Calls @handler every time @parser finds the command named @command. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_parser_add_handler(PurpleIRCv3Parser *parser, const char *command, PurpleIRCv3MessageHandler handler); + * purple_ircv3_parser_add_handlers: + * @parser: The instance. + * @handler: (scope forever): The handler to call when the command is received. + * @...: A %NULL terminated list of string command names. + * Like [method@Parser.add_handler] but allows you to add multiple commands at + * once that share a handler. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_parser_add_handlers(PurpleIRCv3Parser *parser, PurpleIRCv3MessageHandler handler, ...) G_GNUC_NULL_TERMINATED; + * purple_ircv3_parser_add_default_handlers: + * @parser: The instance. + * Adds all of the default handlers to @parser. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_parser_add_default_handlers(PurpleIRCv3Parser *parser); +#endif /* PURPLE_IRCV3_PARSER_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3protocol.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,279 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3protocol.h" +#include "purpleircv3connection.h" +#include "purpleircv3core.h" +#include "purpleircv3protocolconversation.h" +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_protocol_can_reach_cb(GObject *self, GAsyncResult *result, + gboolean can_reach = FALSE; + /* task and result share a cancellable, so either can be used here. */ + if(g_task_return_error_if_cancelled(task)) { + can_reach = g_network_monitor_can_reach_finish(G_NETWORK_MONITOR(self), result, + g_task_return_error(task, error); + } else if(!can_reach) { + g_task_return_new_error(task, PURPLE_IRCV3_DOMAIN, 0, + _("Unknown network error.")); + g_task_return_boolean(task, TRUE); +/****************************************************************************** + * PurpleProtocol Implementation + *****************************************************************************/ +purple_ircv3_protocol_get_user_splits(G_GNUC_UNUSED PurpleProtocol *protocol) { + PurpleAccountUserSplit *split = NULL; + split = purple_account_user_split_new(_("Server"), + PURPLE_IRCV3_DEFAULT_SERVER, + splits = g_list_append(splits, split); +purple_ircv3_protocol_get_account_options(G_GNUC_UNUSED PurpleProtocol *protocol) + PurpleAccountOption *option; + option = purple_account_option_int_new(_("Port"), "port", + PURPLE_IRCV3_DEFAULT_TLS_PORT); + options = g_list_append(options, option); + option = purple_account_option_bool_new(_("Use TLS"), "use-tls", TRUE); + options = g_list_append(options, option); + option = purple_account_option_string_new(_("Server password"), + "server-password", ""); + purple_account_option_string_set_masked(option, TRUE); + options = g_list_append(options, option); + option = purple_account_option_string_new(_("Ident name"), "ident", ""); + options = g_list_append(options, option); + option = purple_account_option_string_new(_("Real name"), "real-name", ""); + options = g_list_append(options, option); + option = purple_account_option_string_new(_("SASL login name"), + "sasl-login-name", ""); + options = g_list_append(options, option); + option = purple_account_option_string_new(_("SASL mechanisms"), + "sasl-mechanisms", ""); + options = g_list_append(options, option); + option = purple_account_option_bool_new(_("Allow plaintext SASL auth over " + "unencrypted connection"), + "plain-sasl-in-clear", FALSE); + options = g_list_append(options, option); + option = purple_account_option_int_new(_("Seconds between sending " + "rate-limit-interval", 2); + options = g_list_append(options, option); + option = purple_account_option_int_new(_("Maximum messages to send at " + "rate-limit-burst", 5); + options = g_list_append(options, option); +static PurpleConnection * +purple_ircv3_protocol_create_connection(PurpleProtocol *protocol, + PurpleAccount *account, + const char *username = NULL; + g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + /* Make sure the username (which includes the servername via usersplits), + * does not contain any whitespace. + username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account)); + if(strpbrk(username, " \t\v\r\n") != NULL) { + PURPLE_CONNECTION_ERROR, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("IRC nick and server may not contain whitespace")); + PURPLE_IRCV3_TYPE_CONNECTION, +purple_ircv3_protocol_status_types(G_GNUC_UNUSED PurpleProtocol *protocol, + G_GNUC_UNUSED PurpleAccount *account) + PurpleStatusType *type = NULL; + type = purple_status_type_new(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE); + types = g_list_append(types, type); + type = purple_status_type_new_with_attrs( + PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE, + "message", _("Message"), purple_value_new(G_TYPE_STRING), + types = g_list_append(types, type); + type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE); + types = g_list_append(types, type); +purple_ircv3_protocol_can_connect_async(PurpleProtocol *protocol, + PurpleAccount *account, + GCancellable *cancellable, + GAsyncReadyCallback callback, + GNetworkMonitor *monitor = NULL; + GSocketConnectable *connectable = NULL; + const char *username = NULL; + task = g_task_new(protocol, cancellable, callback, data); + monitor = g_network_monitor_get_default(); + username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account)); + parts = g_strsplit(username, "@", 2); + port = purple_account_get_int(account, "port", + PURPLE_IRCV3_DEFAULT_TLS_PORT); + connectable = g_network_address_new(parts[1], (guint16)port); + g_network_monitor_can_reach_async(monitor, connectable, cancellable, + purple_ircv3_protocol_can_reach_cb, + g_clear_object(&connectable); +purple_ircv3_protocol_can_connect_finish(G_GNUC_UNUSED PurpleProtocol *protocol, + return g_task_propagate_boolean(G_TASK(result), error); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_DYNAMIC_TYPE_EXTENDED( + G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CONVERSATION, + purple_ircv3_protocol_conversation_init)) +purple_ircv3_protocol_init(G_GNUC_UNUSED PurpleIRCv3Protocol *protocol) { +purple_ircv3_protocol_class_finalize(G_GNUC_UNUSED PurpleIRCv3ProtocolClass *klass) { +purple_ircv3_protocol_class_init(PurpleIRCv3ProtocolClass *klass) { + PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass); + protocol_class->get_user_splits = purple_ircv3_protocol_get_user_splits; + protocol_class->get_account_options = + purple_ircv3_protocol_get_account_options; + protocol_class->create_connection = + purple_ircv3_protocol_create_connection; + protocol_class->status_types = purple_ircv3_protocol_status_types; + protocol_class->can_connect_async = + purple_ircv3_protocol_can_connect_async; + protocol_class->can_connect_finish = + purple_ircv3_protocol_can_connect_finish; +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +purple_ircv3_protocol_register(GPluginNativePlugin *plugin) { + purple_ircv3_protocol_register_type(G_TYPE_MODULE(plugin)); +purple_ircv3_protocol_new(void) { + PURPLE_IRCV3_TYPE_PROTOCOL, + "description", _("Version 3 of Internet Relay Chat (IRC)."), + "icon-name", "im-ircv3", + "icon-resource-path", "/im/pidgin/libpurple/ircv3/icons", + "options", OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL | + OPT_PROTO_SLASH_COMMANDS_NATIVE, --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3protocol.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,71 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_PROTOCOL_H +#define PURPLE_IRCV3_PROTOCOL_H +#include <glib-object.h> +#include <gplugin-native.h> +#include "purpleircv3version.h" +#define PURPLE_IRCV3_TYPE_PROTOCOL (purple_ircv3_protocol_get_type()) +PURPLE_IRCV3_AVAILABLE_IN_ALL +G_DECLARE_DERIVABLE_TYPE(PurpleIRCv3Protocol, purple_ircv3_protocol, + PURPLE_IRCV3, PROTOCOL, PurpleProtocol) +struct _PurpleIRCv3ProtocolClass { + PurpleProtocolClass parent; + * purple_ircv3_protocol_register: (skip) + * @plugin: The GTypeModule + * Registers the dynamic type using @plugin. +G_GNUC_INTERNAL void purple_ircv3_protocol_register(GPluginNativePlugin *plugin); +G_GNUC_INTERNAL PurpleProtocol *purple_ircv3_protocol_new(void); +#endif /* PURPLE_IRCV3_PROTOCOL_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3protocolconversation.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,180 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3protocolconversation.h" +#include "purpleircv3connection.h" +#include "purpleircv3constants.h" +#include "purpleircv3core.h" +/****************************************************************************** + * PurpleProtocolConversation Implementation + *****************************************************************************/ +purple_ircv3_protocol_conversation_send_message_async(PurpleProtocolConversation *protocol, + PurpleConversation *conversation, + PurpleMessage *message, + GCancellable *cancellable, + GAsyncReadyCallback callback, + PurpleIRCv3Connection *v3_connection = NULL; + PurpleAccount *account = NULL; + PurpleConnection *connection = NULL; + account = purple_conversation_get_account(conversation); + connection = purple_account_get_connection(account); + v3_connection = PURPLE_IRCV3_CONNECTION(connection); + id = purple_conversation_get_id(conversation); + /* TODO: the new message dialog sets the name but not the id and we want to + * use the id only, so for now if id is NULL we grab the name. + if(purple_strempty(id)) { + id = purple_conversation_get_name(conversation); + purple_ircv3_connection_writef(v3_connection, "PRIVMSG %s :%s", id, + purple_message_get_contents(message)); + task = g_task_new(protocol, cancellable, callback, data); + g_task_return_boolean(task, TRUE); + /* This will be made conditional when we add echo-message support. + * https://ircv3.net/specs/extensions/echo-message + purple_conversation_write_message(conversation, message); +purple_ircv3_protocol_conversation_send_message_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol, + return g_task_propagate_boolean(G_TASK(result), error); +static PurpleChannelJoinDetails * +purple_ircv3_protocol_conversation_get_channel_join_details(G_GNUC_UNUSED PurpleProtocolConversation *protocol, + G_GNUC_UNUSED PurpleAccount *account) + return purple_channel_join_details_new(FALSE, TRUE); +purple_ircv3_protocol_conversation_join_channel_async(PurpleProtocolConversation *protocol, + PurpleAccount *account, + PurpleChannelJoinDetails* details, + GCancellable* cancellable, + GAsyncReadyCallback callback, + PurpleIRCv3Connection *v3_connection = NULL; + PurpleConnection *connection = NULL; + PurpleConversation *conversation = NULL; + PurpleConversationManager *manager = NULL; + const char *name = NULL; + const char *password = NULL; + connection = purple_account_get_connection(account); + v3_connection = PURPLE_IRCV3_CONNECTION(connection); + task = g_task_new(protocol, cancellable, callback, data); + /* Validate that the name isn't empty. */ + /* TODO: check that name match the ISUPPORT channel prefixes. */ + name = purple_channel_join_details_get_name(details); + if(purple_strempty(name)) { + g_task_return_new_error(task, PURPLE_IRCV3_DOMAIN, 0, + "channel name is empty"); + manager = purple_conversation_manager_get_default(); + conversation = purple_conversation_manager_find_with_id(manager, account, + /* If the conversation already exists, just return TRUE. */ + if(PURPLE_IS_CONVERSATION(conversation)) { + g_task_return_boolean(task, TRUE); + /* Build our join string. */ + cmd = g_string_new(NULL); + g_string_append_printf(cmd, "%s %s", PURPLE_IRCV3_MSG_JOIN, name); + password = purple_channel_join_details_get_password(details); + if(!purple_strempty(password)) { + g_string_append_printf(cmd, " %s", password); + conversation = g_object_new( + PURPLE_TYPE_CONVERSATION, + "type", PurpleConversationTypeChannel, + purple_conversation_manager_register(manager, conversation); + g_clear_object(&conversation); + purple_ircv3_connection_writef(v3_connection, "%s", cmd->str); + g_string_free(cmd, TRUE); + g_task_return_boolean(task, TRUE); +purple_ircv3_protocol_conversation_join_channel_finish(G_GNUC_UNUSED PurpleProtocolConversation *protocol, + return g_task_propagate_boolean(G_TASK(result), error); +purple_ircv3_protocol_conversation_init(PurpleProtocolConversationInterface *iface) { + iface->send_message_async = + purple_ircv3_protocol_conversation_send_message_async; + iface->send_message_finish = + purple_ircv3_protocol_conversation_send_message_finish; + iface->get_channel_join_details = + purple_ircv3_protocol_conversation_get_channel_join_details; + iface->join_channel_async = + purple_ircv3_protocol_conversation_join_channel_async; + iface->join_channel_finish = + purple_ircv3_protocol_conversation_join_channel_finish; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3protocolconversation.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,41 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_PROTOCOL_CONVERSATION_H +#define PURPLE_IRCV3_PROTOCOL_CONVERSATION_H +G_GNUC_INTERNAL void purple_ircv3_protocol_conversation_init(PurpleProtocolConversationInterface *iface); +#endif /* PURPLE_IRCV3_PROTOCOL_CONVERSATION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3sasl.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,563 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include "purpleircv3sasl.h" +#include "purpleircv3capabilities.h" +#include "purpleircv3connection.h" +#include "purpleircv3constants.h" +#include "purpleircv3core.h" +#define PURPLE_IRCV3_SASL_DATA_KEY ("sasl-data") + PurpleConnection *connection; + GString *server_in_buffer; +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_get_username(PurpleConnection *connection) { + PurpleAccount *account = NULL; + const char *username = NULL; + account = purple_connection_get_account(connection); + username = purple_account_get_string(account, "sasl-login-name", ""); + if(username != NULL && username[0] != '\0') { + return purple_connection_get_display_name(connection); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_data_free(PurpleIRCv3SASLData *data) { + g_clear_object(&data->ctx); + g_string_free(data->server_in_buffer, TRUE); +purple_ircv3_sasl_data_add(PurpleConnection *connection, HaslContext *ctx) { + PurpleIRCv3SASLData *data = NULL; + data = g_new0(PurpleIRCv3SASLData, 1); + g_object_set_data_full(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY, + data, (GDestroyNotify)purple_ircv3_sasl_data_free); + /* We don't reference this because the life cycle of this data is tied + * directly to the connection and adding a reference to the connection + * would keep both alive forever. + data->connection = connection; + /* We truncate the server_in_buffer when we need to so that we can minimize + * allocations and simplify the logic involved with it. + data->server_in_buffer = g_string_new(""); +purple_ircv3_sasl_attempt(PurpleIRCv3Connection *connection) { + PurpleIRCv3SASLData *data = NULL; + const char *next_mechanism = NULL; + const char *current = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + current = hasl_context_get_current_mechanism(data->ctx); + g_message("SASL '%s' mechanism failed", current); + next_mechanism = hasl_context_next(data->ctx); + if(next_mechanism == NULL) { + GError *error = g_error_new(PURPLE_CONNECTION_ERROR, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("No valid SASL mechanisms found")); + purple_connection_take_error(PURPLE_CONNECTION(connection), error); + g_message("trying SASL '%s' mechanism", next_mechanism); + purple_ircv3_connection_writef(connection, "%s %s", + PURPLE_IRCV3_MSG_AUTHENTICATE, +purple_ircv3_sasl_start(PurpleIRCv3Capabilities *caps) { + PurpleIRCv3Connection *connection = NULL; + PurpleAccount *account = NULL; + PurpleConnection *purple_connection = NULL; + HaslContext *ctx = NULL; + const char *mechanisms = NULL; + gboolean toggle = FALSE; + connection = purple_ircv3_capabilities_get_connection(caps); + purple_connection = PURPLE_CONNECTION(connection); + account = purple_connection_get_account(purple_connection); + ctx = hasl_context_new(); + /* At this point we are ready to start our SASL negotiation, so add a wait + * counter to the capabilities and start the negotiations! + purple_ircv3_capabilities_add_wait(caps); + /* Determine what mechanisms we're allowing and tell the context. */ + mechanisms = purple_account_get_string(account, "sasl-mechanisms", ""); + if(purple_strempty(mechanisms)) { + /* If the user didn't specify any mechanisms, grab the mechanisms that + * the server advertised. + mechanisms = purple_ircv3_capabilities_lookup(caps, "sasl", NULL); + hasl_context_set_allowed_mechanisms(ctx, mechanisms); + /* Add the values we know to the context. */ + hasl_context_set_username(ctx, purple_ircv3_sasl_get_username(purple_connection)); + hasl_context_set_password(ctx, purple_connection_get_password(purple_connection)); + toggle = purple_account_get_bool(account, "use-tls", TRUE); + hasl_context_set_tls(ctx, toggle); + toggle = purple_account_get_bool(account, "plain-sasl-in-clear", FALSE); + hasl_context_set_allow_clear_text(ctx, toggle); + /* Create our SASLData object, add it to the connection. */ + purple_ircv3_sasl_data_add(purple_connection, ctx); + purple_ircv3_sasl_attempt(connection); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_ack_cb(PurpleIRCv3Capabilities *caps, + G_GNUC_UNUSED const char *capability, + G_GNUC_UNUSED gpointer data) + purple_ircv3_sasl_start(caps); +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_sasl_request(PurpleIRCv3Capabilities *capabilities) { + purple_ircv3_capabilities_request(capabilities, + PURPLE_IRCV3_CAPABILITY_SASL); + g_signal_connect(capabilities, "ack::" PURPLE_IRCV3_CAPABILITY_SASL, + G_CALLBACK(purple_ircv3_sasl_ack_cb), NULL); +purple_ircv3_sasl_logged_in(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + PurpleAccount *account = NULL; + const char *sasl_name = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_LOGGEDIN received with no SASL data " + /* Check if the SASL login name is not set. If it is not set, set it to the + * current nick as it was successful. + account = purple_connection_get_account(PURPLE_CONNECTION(connection)); + sasl_name = purple_account_get_string(account, "sasl-login-name", ""); + if(purple_strempty(sasl_name)) { + char **userparts = NULL; + const char *username = NULL; + username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(account)); + userparts = g_strsplit(username, "@", 2); + purple_account_set_string(account, "sasl-login-name", userparts[0]); +purple_ircv3_sasl_logged_out(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_LOGGEDOUT received with no SASL data " + /* Not sure how to trigger this or what we should do in this case to be + * honest, so just note it for now. + g_warning("Server sent SASL logged out"); +purple_ircv3_sasl_nick_locked(PurpleIRCv3Message *v3_message, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_NICKLOCKED received with no SASL data " + params = purple_ircv3_message_get_params(v3_message); + message = g_strjoinv(" ", params); + g_set_error(error, PURPLE_CONNECTION_ERROR, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Nick name is locked: %s"), message); +purple_ircv3_sasl_success(G_GNUC_UNUSED PurpleIRCv3Message *message, + PurpleIRCv3Capabilities *capabilities = NULL; + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + capabilities = purple_ircv3_connection_get_capabilities(connection); + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_SASLSUCCESS received with no SASL data " + /* This needs to be after we've checked the SASL data otherwise we might + * end up removing a wait that we don't own. + purple_ircv3_capabilities_remove_wait(capabilities); + g_message("successfully authenticated with SASL '%s' mechanism.", + hasl_context_get_current_mechanism(data->ctx)); +purple_ircv3_sasl_failed(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLFAIL received with no SASL data present"); + purple_ircv3_sasl_attempt(connection); +purple_ircv3_sasl_message_too_long(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLTOOLONG received with no SASL data " +purple_ircv3_sasl_aborted(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLABORTED received with no SASL data " + /* This is supposed to get sent, when the client sends `AUTHENTICATE *`, + * but we don't do this, so I guess we'll note it for now...? + g_warning("The server claims we aborted SASL authentication."); +purple_ircv3_sasl_already_authed(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "ERR_SASLALREADY received with no SASL data " + /* Similar to aborted above, we don't allow this, so just note that it + g_warning("Server claims we tried to SASL authenticate again."); +purple_ircv3_sasl_mechanisms(PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "RPL_SASLMECHS received with no SASL data " + params = purple_ircv3_message_get_params(message); + n_params = g_strv_length(params); + /* We need to find a server that sends this message. The specification says + * it _may_ be sent when the client sends AUTHENTICATE with an unknown + * mechanism, but ergo doesn't. + * We can't just blindly accept the new list either, as depending on how + * the server implements it, we'll need to remove mechanisms we've already + * tried in the event the server just dumps the entire list. As we're not + * currently tracking which mechanisms we've tried, this will have to be + char *message = g_strjoinv(" ", params); + g_message("Server sent the following SASL mechanisms: %s", message); + g_message("Server sent an empty list of SASL mechanisms"); +purple_ircv3_sasl_authenticate(PurpleIRCv3Message *message, + PurpleIRCv3Connection *connection = user_data; + PurpleIRCv3SASLData *data = NULL; + params = purple_ircv3_message_get_params(message); + n_params = g_strv_length(params); + g_set_error(error, PURPLE_IRCV3_DOMAIN, 0, + "ignoring AUTHENTICATE with %d parameters", n_params); + data = g_object_get_data(G_OBJECT(connection), PURPLE_IRCV3_SASL_DATA_KEY); + g_set_error_literal(error, PURPLE_IRCV3_DOMAIN, 0, + "AUTHENTICATE received with no SASL data present"); + /* If the server sent us a payload, combine the chunks. */ + if(payload[0] != '+') { + g_string_append(data->server_in_buffer, payload); + if(strlen(payload) < 400) { + /* The server sent a + which is an empty message or the final message + * ended on a 400 byte barrier. */ + HaslMechanismResult res = 0; + GError *local_error = NULL; + guint8 *server_in = NULL; + guint8 *client_out = NULL; + gsize server_in_length = 0; + size_t client_out_length = 0; + /* If we have a buffer, base64 decode it, and then truncate it. */ + if(data->server_in_buffer->len > 0) { + server_in = g_base64_decode(data->server_in_buffer->str, + g_string_truncate(data->server_in_buffer, 0); + /* Try to move to the next step of the sasl client. */ + res = hasl_context_step(data->ctx, server_in, server_in_length, + &client_out, &client_out_length, &local_error); + /* We should be done with server_in, so free it.*/ + g_clear_pointer(&server_in, g_free); + if(res == HASL_MECHANISM_RESULT_ERROR) { + g_propagate_error(error, local_error); + if(local_error != NULL) { + g_warning("hasl_context_step returned an error without an error " + "status: %s", local_error->message); + g_clear_error(&local_error); + /* If we got an output for the client, write it out. */ + if(client_out_length > 0) { + encoded = g_base64_encode(client_out, client_out_length); + g_clear_pointer(&client_out, g_free); + purple_ircv3_connection_writef(connection, "%s %s", + PURPLE_IRCV3_MSG_AUTHENTICATE, + purple_ircv3_connection_writef(connection, "%s +", + PURPLE_IRCV3_MSG_AUTHENTICATE); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3sasl.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,55 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_SASL_H +#define PURPLE_IRCV3_SASL_H +#include "purpleircv3capabilities.h" +#include "purpleircv3message.h" +G_GNUC_INTERNAL void purple_ircv3_sasl_request(PurpleIRCv3Capabilities *capabilities); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_logged_in(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_logged_out(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_nick_locked(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_success(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_failed(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_message_too_long(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_aborted(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_already_authed(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_mechanisms(PurpleIRCv3Message *message, GError **error, gpointer data); +G_GNUC_INTERNAL gboolean purple_ircv3_sasl_authenticate(PurpleIRCv3Message *message, GError **error, gpointer data); +#endif /* PURPLE_IRCV3_SASL_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3source.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,91 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include "purpleircv3source.h" +/****************************************************************************** + *****************************************************************************/ +purple_ircv3_source_parse(const char *source, char **nick, char **user, + GMatchInfo *info = NULL; + gboolean matched = FALSE; + g_return_if_fail(!purple_strempty(source)); + g_return_if_fail(nick != NULL || user != NULL || host != NULL); + /* If we find any \r \n or spaces in our source, it's invalid so we just + matched = g_regex_match_simple("[\r\n ]", source, G_REGEX_DEFAULT, + G_REGEX_MATCH_DEFAULT); + regex = g_regex_new("^(?P<nick>[^ \r\n!]+)(?:!(?P<user>[^@]+)(?:@(?P<host>[^!@]+))?)?$", + G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, NULL); + matched = g_regex_match(regex, source, G_REGEX_MATCH_DEFAULT, &info); + *nick = g_strdup(source); + g_clear_pointer(&info, g_match_info_unref); + g_clear_pointer(®ex, g_regex_unref); + *nick = g_match_info_fetch_named(info, "nick"); + char *tmp = g_match_info_fetch_named(info, "user"); + if(!purple_strempty(tmp)) { + char *tmp = g_match_info_fetch_named(info, "host"); + if(!purple_strempty(tmp)) { + g_clear_pointer(&info, g_match_info_unref); + g_clear_pointer(®ex, g_regex_unref); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3source.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,58 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_SOURCE_H +#define PURPLE_IRCV3_SOURCE_H +#include <glib-object.h> +#include "purpleircv3version.h" + * purple_ircv3_source_parse: + * @source: The source to parse. + * @nick: (out) (transfer full): A return address for the nick. + * @user: (out) (transfer full): A return address for the user. + * @host: (out) (transfer full): A return address for the host. + * Parses a `source` string like `pidgy!~u@53unc8n42i868.irc` into its nick, + * user, and host parts per https://modern.ircdocs.horse/#source. + * If the user or host aren't present in @source, but a return address is + * provided for them, that pointer will be set to %NULL. +PURPLE_IRCV3_AVAILABLE_IN_ALL +void purple_ircv3_source_parse(const char *source, char **nick, char **user, char **host); +#endif /* PURPLE_IRCV3_SOURCE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/purpleircv3version.h Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,133 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * This library 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 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_IRCV3_GLOBAL_HEADER_INSIDE) && \ + !defined(PURPLE_IRCV3_COMPILATION) +# error "only <libpurple/protocols/ircv3.h> may be included directly" +#ifndef PURPLE_IRCV3_VERSION_H +#define PURPLE_IRCV3_VERSION_H +#if (defined(_WIN32) || defined(__CYGWIN__)) && \ + !defined(PURPLE_IRCV3_STATIC_COMPILATION) +#define _PURPLE_IRCV3_EXPORT __declspec(dllexport) +#define _PURPLE_IRCV3_IMPORT __declspec(dllimport) +#define _PURPLE_IRCV3_EXPORT __attribute__((visibility("default"))) +#define _PURPLE_IRCV3_IMPORT +#define _PURPLE_IRCV3_EXPORT +#define _PURPLE_IRCV3_IMPORT +#ifdef PURPLE_IRCV3_COMPILATION +#define _PURPLE_IRCV3_API _PURPLE_IRCV3_EXPORT +#define _PURPLE_IRCV3_API _PURPLE_IRCV3_IMPORT +#define _PURPLE_IRCV3_EXTERN _PURPLE_IRCV3_API extern +#ifdef PURPLE_IRCV3_DISABLE_DEPRECATION_WARNINGS +#define PURPLE_IRCV3_DEPRECATED _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_DEPRECATED_FOR(f) _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_UNAVAILABLE(maj, min) _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_UNAVAILABLE_STATIC_INLINE(maj, min) +#define PURPLE_IRCV3_UNAVAILABLE_TYPE(maj, min) +#define PURPLE_IRCV3_DEPRECATED G_DEPRECATED _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_UNAVAILABLE(maj, min) G_UNAVAILABLE(maj, min) _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_UNAVAILABLE_STATIC_INLINE(maj, min) G_UNAVAILABLE(maj, min) +#define PURPLE_IRCV3_UNAVAILABLE_TYPE(maj, min) G_UNAVAILABLE(maj, min) + * PURPLE_IRCV3_VERSION_CUR_STABLE: + * A macro that evaluates to the current stable version of the IRCv3 protocol + * plugin, in a format that can be used by the C pre-processor. +#define PURPLE_IRCV3_VERSION_CUR_STABLE \ + (G_ENCODE_VERSION(PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION)) +/* If the package sets PURPLE_IRCV3_VERSION_MIN_REQUIRED to some future + * PURPLE_IRCV3_VERSION_X_Y value that we don't know about, it will compare as 0 in +#ifndef PURPLE_IRCV3_VERSION_MIN_REQUIRED +#define PURPLE_IRCV3_VERSION_MIN_REQUIRED (PURPLE_IRCV3_VERSION_CUR_STABLE) +#elif PURPLE_IRCV3_VERSION_MIN_REQUIRED == 0 +#undef PURPLE_IRCV3_VERSION_MIN_REQUIRED +#define PURPLE_IRCV3_VERSION_MIN_REQUIRED (PURPLE_IRCV3_VERSION_CUR_STABLE + 1) +#endif /* PURPLE_IRCV3_VERSION_MIN_REQUIRED */ +#if !defined(PURPLE_IRCV3_VERSION_MAX_ALLOWED) || (PURPLE_IRCV3_VERSION_MAX_ALLOWED == 0) +#undef PURPLE_IRCV3_VERSION_MAX_ALLOWED +#define PURPLE_IRCV3_VERSION_MAX_ALLOWED (PURPLE_IRCV3_VERSION_CUR_STABLE) +#endif /* PURPLE_IRCV3_VERSION_MAX_ALLOWED */ +#if PURPLE_IRCV3_VERSION_MIN_REQUIRED > PURPLE_IRCV3_VERSION_CUR_STABLE +#error "PURPLE_IRCV3_VERSION_MIN_REQUIRED must be <= PURPLE_IRCV3_VERSION_CUR_STABLE" +#if PURPLE_IRCV3_VERSION_MAX_ALLOWED < PURPLE_IRCV3_VERSION_MIN_REQUIRED +#error "PURPLE_IRCV3_VERSION_MAX_ALLOWED must be >= PURPLE_IRCV3_VERSION_MIN_REQUIRED" +#if PURPLE_IRCV3_VERSION_MIN_REQUIRED < G_ENCODE_VERSION(3, 0) +#error "PURPLE_IRCV3_VERSION_MIN_REQUIRED must be >= PURPLE_IRCV3_VERSION_3_0" +#define PURPLE_IRCV3_VAR _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_AVAILABLE_IN_ALL _PURPLE_IRCV3_EXTERN + * PURPLE_IRCV3_VERSION_3_0: + * A macro that evaluates to the 3.0 version of the IRCv3 protocol plugin, in a + * format that can be used by the C pre-processor. +#define PURPLE_IRCV3_VERSION_3_0 (G_ENCODE_VERSION(3, 0)) +#if PURPLE_IRCV3_VERSION_MAX_ALLOWED < PURPLE_IRCV3_VERSION_3_0 +#define PURPLE_IRCV3_AVAILABLE_IN_3_0 PURPLE_IRCV3_UNAVAILABLE(3, 0) +#define PURPLE_IRCV3_AVAILABLE_STATIC_INLINE_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_STATIC_INLINE(3, 0) +#define PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_MACRO(3, 0) +#define PURPLE_IRCV3_AVAILABLE_ENUMERATOR_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_ENUMERATOR(3, 0) +#define PURPLE_IRCV3_AVAILABLE_TYPE_IN_3_0 PURPLE_IRCV3_UNAVAILABLE_TYPE(3, 0) +#define PURPLE_IRCV3_AVAILABLE_IN_3_0 _PURPLE_IRCV3_EXTERN +#define PURPLE_IRCV3_AVAILABLE_STATIC_INLINE_IN_3_0 +#define PURPLE_IRCV3_AVAILABLE_MACRO_IN_3_0 +#define PURPLE_IRCV3_AVAILABLE_ENUMERATOR_IN_3_0 +#define PURPLE_IRCV3_AVAILABLE_TYPE_IN_3_0 +#endif /* PURPLE_IRCV3_VERSION_H */ Binary file protocols/ircv3/resources/icons/16x16/apps/im-ircv3.png has changed
Binary file protocols/ircv3/resources/icons/22x22/apps/im-ircv3.png has changed
Binary file protocols/ircv3/resources/icons/48x48/apps/im-ircv3.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/resources/ircv3.gresource.xml Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?> + <gresource prefix="/im/pidgin/libpurple/ircv3"> + <file>icons/16x16/apps/im-ircv3.png</file> + <file>icons/22x22/apps/im-ircv3.png</file> + <file>icons/48x48/apps/im-ircv3.png</file> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/tests/meson.build Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,15 @@
+ f'test_ircv3_@prog@', f'test_ircv3_@prog@.c', + dependencies : [birb_dep, libpurple_dep, glib, hasl], + objects : ircv3_prpl.extract_all_objects(), + c_args : ['-DPURPLE_IRCV3_COMPILATION']) + test(f'ircv3_@prog@', e) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/tests/test_ircv3_formatting.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,232 @@
+ * 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 "../purpleircv3formatting.h" +/****************************************************************************** + *****************************************************************************/ +test_ircv3_formatting_strip(const char *input, const char *expected) { + actual = purple_ircv3_formatting_strip(input); + g_assert_cmpstr(actual, ==, expected); + g_clear_pointer(&actual, g_free); +test_ircv3_formatting_strip_null(void) { + test_ircv3_formatting_strip(NULL, NULL); +test_ircv3_formatting_strip_empty(void) { + test_ircv3_formatting_strip("", ""); +test_ircv3_formatting_strip_color_comma(void) { + test_ircv3_formatting_strip("\003,", ","); +test_ircv3_formatting_strip_color_foreground_comma(void) { + test_ircv3_formatting_strip("\0033,", ","); +test_ircv3_formatting_strip_color_full(void) { + test_ircv3_formatting_strip("\0033,9wee", "wee"); +test_ircv3_formatting_strip_color_foreground_3_digit(void) { + test_ircv3_formatting_strip("\003314", "4"); +test_ircv3_formatting_strip_color_background_3_digit(void) { + test_ircv3_formatting_strip("\0031,234", "4"); +test_ircv3_formatting_strip_hex_color(void) { + test_ircv3_formatting_strip("\004FF00FFwoo!", "woo!"); +test_ircv3_formatting_strip_hex_color_full(void) { + test_ircv3_formatting_strip("\004FF00FF,00FF00woo!", "woo!"); +test_ircv3_formatting_strip_hex_color_comma(void) { + test_ircv3_formatting_strip("\004,", ","); +test_ircv3_formatting_strip_hex_color_foreground_comma(void) { + test_ircv3_formatting_strip("\004FEFEFE,", ","); +test_ircv3_formatting_strip_hex_color_foreground_7_characters(void) { + test_ircv3_formatting_strip("\004FEFEFEF", "F"); +test_ircv3_formatting_strip_hex_color_background_7_characters(void) { + test_ircv3_formatting_strip("\004FEFEFE,2222223", "3"); +test_ircv3_formatting_strip_bold(void) { + test_ircv3_formatting_strip("this is \002bold\002!", "this is bold!"); +test_ircv3_formatting_strip_italic(void) { + test_ircv3_formatting_strip("what do you \035mean\035?!", +test_ircv3_formatting_strip_monospace(void) { + test_ircv3_formatting_strip("\021i++;\021", "i++;"); +test_ircv3_formatting_strip_reset(void) { + test_ircv3_formatting_strip("end of formatting\017", "end of formatting"); +test_ircv3_formatting_strip_reverse(void) { + test_ircv3_formatting_strip("re\026ver\026se", "reverse"); +test_ircv3_formatting_strip_strikethrough(void) { + test_ircv3_formatting_strip("\036I could be wrong\036", +test_ircv3_formatting_strip_underline(void) { + test_ircv3_formatting_strip("You can't handle the \037truth\037!", + "You can't handle the truth!"); +test_ircv3_formatting_strip_spec_example1(void) { + test_ircv3_formatting_strip( + "I love \0033IRC! \003It is the \0037best protocol ever!", + "I love IRC! It is the best protocol ever!"); +test_ircv3_formatting_strip_spec_example2(void) { + test_ircv3_formatting_strip( + "This is a \035\00313,9cool \003message", + "This is a cool message"); +test_ircv3_formatting_strip_spec_example3(void) { + test_ircv3_formatting_strip( + "IRC \002is \0034,12so \003great\017!", +test_ircv3_formatting_strip_spec_example4(void) { + test_ircv3_formatting_strip( + "Rules: Don't spam 5\00313,8,6\003,7,8, and especially not \0029\002\035!", + "Rules: Don't spam 5,6,7,8, and especially not 9!"); +/****************************************************************************** + *****************************************************************************/ +main(int argc, char *argv[]) { + g_test_init(&argc, &argv, NULL); + g_test_add_func("/ircv3/formatting/strip/null", + test_ircv3_formatting_strip_null); + g_test_add_func("/ircv3/formatting/strip/empty", + test_ircv3_formatting_strip_empty); + g_test_add_func("/ircv3/formatting/strip/color-comma", + test_ircv3_formatting_strip_color_comma); + g_test_add_func("/ircv3/formatting/strip/color-full", + test_ircv3_formatting_strip_color_full); + g_test_add_func("/ircv3/formatting/strip/color-foreground-comma", + test_ircv3_formatting_strip_color_foreground_comma); + g_test_add_func("/ircv3/formatting/strip/color-foreground-3-digit", + test_ircv3_formatting_strip_color_foreground_3_digit); + g_test_add_func("/ircv3/formatting/strip/color-background-3-digit", + test_ircv3_formatting_strip_color_background_3_digit); + g_test_add_func("/ircv3/formatting/strip/hex-color", + test_ircv3_formatting_strip_hex_color); + g_test_add_func("/ircv3/formatting/strip/hex-color-full", + test_ircv3_formatting_strip_hex_color_full); + g_test_add_func("/ircv3/formatting/strip/hex-color-comma", + test_ircv3_formatting_strip_hex_color_comma); + g_test_add_func("/ircv3/formatting/strip/hex-color-foreground-comma", + test_ircv3_formatting_strip_hex_color_foreground_comma); + g_test_add_func("/ircv3/formatting/strip/hex-color-foreground-7-characters", + test_ircv3_formatting_strip_hex_color_foreground_7_characters); + g_test_add_func("/ircv3/formatting/strip/hex-color-background-7-characters", + test_ircv3_formatting_strip_hex_color_background_7_characters); + g_test_add_func("/ircv3/formatting/strip/bold", + test_ircv3_formatting_strip_bold); + g_test_add_func("/ircv3/formatting/strip/italic", + test_ircv3_formatting_strip_italic); + g_test_add_func("/ircv3/formatting/strip/monospace", + test_ircv3_formatting_strip_monospace); + g_test_add_func("/ircv3/formatting/strip/reset", + test_ircv3_formatting_strip_reset); + g_test_add_func("/ircv3/formatting/strip/reverse", + test_ircv3_formatting_strip_reverse); + g_test_add_func("/ircv3/formatting/strip/strikethrough", + test_ircv3_formatting_strip_strikethrough); + g_test_add_func("/ircv3/formatting/strip/underline", + test_ircv3_formatting_strip_underline); + /* These tests are based on the examples here + * https://modern.ircdocs.horse/formatting.html#examples + g_test_add_func("/ircv3/formatting/strip/spec-example1", + test_ircv3_formatting_strip_spec_example1); + g_test_add_func("/ircv3/formatting/strip/spec-example2", + test_ircv3_formatting_strip_spec_example2); + g_test_add_func("/ircv3/formatting/strip/spec-example3", + test_ircv3_formatting_strip_spec_example3); + g_test_add_func("/ircv3/formatting/strip/spec-example4", + test_ircv3_formatting_strip_spec_example4); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/tests/test_ircv3_parser.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,822 @@
+ * 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 "../purpleircv3constants.h" +#include "../purpleircv3message.h" +#include "../purpleircv3parser.h" +#define TEST_IRCV3_PARSER_DOMAIN (g_quark_from_static_string("test-ircv3-parser")) + const gchar * const params[16]; +} TestPurpleIRCv3ParserData; +/****************************************************************************** + *****************************************************************************/ +test_purple_ircv3_test_handler(PurpleIRCv3Message *message, + G_GNUC_UNUSED GError **error, + TestPurpleIRCv3ParserData *d = data; + GHashTable *tags = NULL; + const char *command = NULL; + const char *source = NULL; + command = purple_ircv3_message_get_command(message); + params = purple_ircv3_message_get_params(message); + source = purple_ircv3_message_get_source(message); + tags = purple_ircv3_message_get_tags(message); + n_params = g_strv_length(params); + /* Make sure we have an expected tags hash table before checking them. */ + gpointer expected_value; + /* Make sure the tag hash tables have the same size. */ + expected_size = g_hash_table_size(d->tags); + actual_size = g_hash_table_size(tags); + g_assert_cmpuint(actual_size, ==, expected_size); + /* Since the tables have the same size, we can walk through the expected + * table and use it to verify the actual table. + g_hash_table_iter_init(&iter, d->tags); + while(g_hash_table_iter_next(&iter, &expected_key, &expected_value)) { + gpointer actual_value = NULL; + gboolean found = FALSE; + found = g_hash_table_lookup_extended(tags, expected_key, NULL, + g_assert_cmpstr(actual_value, ==, expected_value); + /* Walk through the params checking against the expected values. */ + g_assert_cmpuint(n_params, ==, d->n_params); + for(guint i = 0; i < d->n_params; i++) { + g_assert_cmpstr(params[i], ==, d->params[i]); + /* Validate all the string parameters. */ + g_assert_cmpstr(source, ==, d->source); + g_assert_cmpstr(command, ==, d->command); + /* Cleanup everything the caller allocated. */ + g_clear_pointer(&d->tags, g_hash_table_destroy); + /* Return the return value the caller asked for. */ +test_purple_ircv3_test_handler_error(G_GNUC_UNUSED PurpleIRCv3Message *message, + G_GNUC_UNUSED gpointer data) + g_set_error(error, TEST_IRCV3_PARSER_DOMAIN, 0, "test error"); +/****************************************************************************** + *****************************************************************************/ +test_purple_ircv3_parser(const gchar *source, TestPurpleIRCv3ParserData *d) { + PurpleIRCv3Parser *parser = purple_ircv3_parser_new(); + gboolean result = FALSE; + purple_ircv3_parser_set_fallback_handler(parser, + test_purple_ircv3_test_handler); + result = purple_ircv3_parser_parse(parser, source, &error, d); + g_assert_no_error(error); + g_clear_object(&parser); +/****************************************************************************** + *****************************************************************************/ +test_purple_ircv3_parser_propagates_errors(void) { + PurpleIRCv3Parser *parser = purple_ircv3_parser_new(); + gboolean result = FALSE; + purple_ircv3_parser_set_fallback_handler(parser, + test_purple_ircv3_test_handler_error); + result = purple_ircv3_parser_parse(parser, "COMMAND", &error, NULL); + g_assert_error(error, TEST_IRCV3_PARSER_DOMAIN, 0); + g_assert_false(result); + g_clear_object(&parser); +test_purple_ircv3_parser_simple(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", "asdf"}, + test_purple_ircv3_parser("foo bar baz asdf", &data); +test_purple_ircv3_parser_with_source(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", "asdf"}, + test_purple_ircv3_parser(":coolguy foo bar baz asdf", &data); +test_purple_ircv3_parser_with_trailing(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", "asdf quux"}, + test_purple_ircv3_parser("foo bar baz :asdf quux", &data); +test_purple_ircv3_parser_with_empty_trailing(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", ""}, + test_purple_ircv3_parser("foo bar baz :", &data); +test_purple_ircv3_parser_with_trailing_starting_colon(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", ":asdf"}, + test_purple_ircv3_parser("foo bar baz ::asdf", &data); +test_purple_ircv3_parser_with_source_and_trailing(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", "asdf quux"}, + test_purple_ircv3_parser(":coolguy foo bar baz :asdf quux", &data); +test_purple_ircv3_parser_with_source_and_trailing_whitespace(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", " asdf quux "}, + test_purple_ircv3_parser(":coolguy foo bar baz : asdf quux ", &data); +test_purple_ircv3_parser_with_source_and_trailing_colon(void) { + TestPurpleIRCv3ParserData data = { + .command = PURPLE_IRCV3_MSG_PRIVMSG, + .params = {"bar", "lol :) "}, + test_purple_ircv3_parser(":coolguy PRIVMSG bar :lol :) ", &data); +test_purple_ircv3_parser_with_source_and_empty_trailing(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", ""}, + test_purple_ircv3_parser(":coolguy foo bar baz :", &data); +test_purple_ircv3_parser_with_source_and_trailing_only_whitespace(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz", " "}, + test_purple_ircv3_parser(":coolguy foo bar baz : ", &data); +test_purple_ircv3_parser_with_tags(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "a", "b"); + g_hash_table_insert(data.tags, "c", "32"); + g_hash_table_insert(data.tags, "k", ""); + g_hash_table_insert(data.tags, "rt", "ql7"); + test_purple_ircv3_parser("@a=b;c=32;k;rt=ql7 foo", &data); +test_purple_ircv3_parser_with_escaped_tags(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "a", "b\\and\nk"); + g_hash_table_insert(data.tags, "c", "72 45"); + g_hash_table_insert(data.tags, "d", "gh;764"); + test_purple_ircv3_parser("@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo", +test_purple_ircv3_with_tags_and_source(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "c", ""); + g_hash_table_insert(data.tags, "h", ""); + g_hash_table_insert(data.tags, "a", "b"); + test_purple_ircv3_parser("@c;h=;a=b :quux ab cd", &data); +test_purple_ircv3_last_param_no_colon(void) { + TestPurpleIRCv3ParserData data = { + .command = PURPLE_IRCV3_MSG_JOIN, + test_purple_ircv3_parser(":src JOIN #chan", &data); +test_purple_ircv3_last_param_with_colon(void) { + TestPurpleIRCv3ParserData data = { + .command = PURPLE_IRCV3_MSG_JOIN, + test_purple_ircv3_parser(":src JOIN :#chan", &data); +test_purple_ircv3_without_last_param(void) { + TestPurpleIRCv3ParserData data = { + test_purple_ircv3_parser(":src AWAY", &data); +test_purple_ircv3_with_last_param(void) { + TestPurpleIRCv3ParserData data = { + test_purple_ircv3_parser(":src AWAY ", &data); +test_purple_ircv3_tab_is_not_space(void) { + TestPurpleIRCv3ParserData data = { + .params = {"bar", "baz"}, + test_purple_ircv3_parser(":cool\tguy foo bar baz", &data); +test_purple_ircv3_source_control_characters_1(void) { + /* Break each string after the hex escape as they are supposed to only be + * a single byte, but the c compiler will keep unescaping unless we break + TestPurpleIRCv3ParserData data = { + .source = "coolguy!ag@net\x03" "5w\x03" "ork.admin", + .command = PURPLE_IRCV3_MSG_PRIVMSG, + .params = {"foo", "bar baz"}, + const gchar *msg = NULL; + msg = ":coolguy!ag@net\x03" "5w\x03" "ork.admin PRIVMSG foo :bar baz"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_source_control_characters_2(void) { + /* Break each string after the hex escape as they are supposed to only be + * a single byte, but the c compiler will keep unescaping unless we break + TestPurpleIRCv3ParserData data = { + .source = "coolguy!~ag@n\x02" "et\x03" "05w\x0f" "ork.admin", + .command = PURPLE_IRCV3_MSG_PRIVMSG, + .params = {"foo", "bar baz"}, + const gchar *msg = NULL; + msg = ":coolguy!~ag@n\x02" "et\x03" "05w\x0f" "ork.admin PRIVMSG foo :bar " + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_everything(void) { + TestPurpleIRCv3ParserData data = { + .source = "irc.example.com", + .params = {"param1", "param2", "param3 param3"}, + const gchar *msg = NULL; + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "value1"); + g_hash_table_insert(data.tags, "tag2", ""); + g_hash_table_insert(data.tags, "vendor1/tag3", "value2"); + g_hash_table_insert(data.tags, "vendor2/tag4", ""); + msg = "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= " + ":irc.example.com COMMAND param1 param2 :param3 param3"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_everything_but_tags(void) { + TestPurpleIRCv3ParserData data = { + .source = "irc.example.com", + .params = {"param1", "param2", "param3 param3"}, + const gchar *msg = NULL; + msg = ":irc.example.com COMMAND param1 param2 :param3 param3"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_everything_but_source(void) { + TestPurpleIRCv3ParserData data = { + .params = {"param1", "param2", "param3 param3"}, + const gchar *msg = NULL; + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "value1"); + g_hash_table_insert(data.tags, "tag2", ""); + g_hash_table_insert(data.tags, "vendor1/tag3", "value2"); + g_hash_table_insert(data.tags, "vendor2/tag4", ""); + msg = "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 " + "COMMAND param1 param2 :param3 param3"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_command_only(void) { + TestPurpleIRCv3ParserData data = { + test_purple_ircv3_parser("COMMAND", &data); +test_purple_ircv3_slashes_are_fun(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "foo", "\\\\;\\s \r\n"); + test_purple_ircv3_parser("@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND", &data); +test_purple_ircv3_unreal_broken_1(void) { + TestPurpleIRCv3ParserData data = { + .source = "gravel.mozilla.org", + .params = {"#momo", "Erroneous Nickname: Illegal characters"}, + const gchar *msg = NULL; + msg = ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal " + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_unreal_broken_2(void) { + TestPurpleIRCv3ParserData data = { + .source = "gravel.mozilla.org", + .params = {"#tckk", "+n"}, + test_purple_ircv3_parser(":gravel.mozilla.org MODE #tckk +n ", &data); +test_purple_ircv3_unreal_broken_3(void) { + TestPurpleIRCv3ParserData data = { + .source = "services.esper.net", + .params = {"#foo-bar", "+o", "foobar"}, + test_purple_ircv3_parser(":services.esper.net MODE #foo-bar +o foobar ", +test_purple_ircv3_tag_escape_char_at_a_time(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "value\\ntest"); + test_purple_ircv3_parser("@tag1=value\\\\ntest COMMAND", &data); +test_purple_ircv3_tag_drop_unnecessary_escapes(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "value1"); + test_purple_ircv3_parser("@tag1=value\\1 COMMAND", &data); +test_purple_ircv3_tag_drop_trailing_slash(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "value1"); + test_purple_ircv3_parser("@tag1=value1\\ COMMAND", &data); +test_purple_ircv3_duplicate_tags(void) { + TestPurpleIRCv3ParserData data = { + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "5"); + g_hash_table_insert(data.tags, "tag2", "3"); + g_hash_table_insert(data.tags, "tag3", "4"); + test_purple_ircv3_parser("@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND", &data); +test_purple_ircv3_vendor_tags_are_namespaced(void) { + TestPurpleIRCv3ParserData data = { + const gchar *msg = NULL; + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "tag1", "5"); + g_hash_table_insert(data.tags, "tag2", "3"); + g_hash_table_insert(data.tags, "tag3", "4"); + g_hash_table_insert(data.tags, "vendor/tag2", "8"); + msg = "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_special_mode_1(void) { + TestPurpleIRCv3ParserData data = { + .params = {"#channel", "+i"}, + test_purple_ircv3_parser(":SomeOp MODE #channel :+i", &data); +test_purple_ircv3_special_mode_2(void) { + TestPurpleIRCv3ParserData data = { + .params = {"#channel", "+oo", "SomeUser", "AnotherUser"}, + test_purple_ircv3_parser(":SomeOp MODE #channel +oo SomeUser :AnotherUser", +/****************************************************************************** + * Message tags examples + *****************************************************************************/ +test_purple_ircv3_parser_message_tags_none(void) { + TestPurpleIRCv3ParserData data = { + .source = "nick!ident@host.com", + .command = PURPLE_IRCV3_MSG_PRIVMSG, + .params = {"me", "Hello"}, + const char *msg = NULL; + msg = ":nick!ident@host.com PRIVMSG me :Hello"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_parser_message_tags_3_tags(void) { + TestPurpleIRCv3ParserData data = { + .source = "nick!ident@host.com", + .command = PURPLE_IRCV3_MSG_PRIVMSG, + .params = {"me", "Hello"}, + const char *msg = NULL; + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "aaa", "bbb"); + g_hash_table_insert(data.tags, "ccc", ""); + g_hash_table_insert(data.tags, "example.com/ddd", "eee"); + msg = "@aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me " + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_parser_message_tags_client_only(void) { + TestPurpleIRCv3ParserData data = { + .source = "url_bot!bot@example.com", + .command = PURPLE_IRCV3_MSG_PRIVMSG, + .params = {"#channel", "Example.com: A News Story"}, + const char *msg = NULL; + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "+icon", "https://example.com/favicon.png"); + msg = "@+icon=https://example.com/favicon.png :url_bot!bot@example.com " + "PRIVMSG #channel :Example.com: A News Story"; + test_purple_ircv3_parser(msg, &data); +test_purple_ircv3_parser_message_tags_labeled_response(void) { + TestPurpleIRCv3ParserData data = { + .source = "nick!user@example.com", + .params = {"#channel"}, + const char *msg = NULL; + data.tags = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(data.tags, "label", "123"); + g_hash_table_insert(data.tags, "msgid", "abc"); + g_hash_table_insert(data.tags, "+example-client-tag", "example-value"); + msg = "@label=123;msgid=abc;+example-client-tag=example-value " + ":nick!user@example.com TAGMSG #channel"; + test_purple_ircv3_parser(msg, &data); +/****************************************************************************** + *****************************************************************************/ +main(gint argc, gchar *argv[]) { + g_test_init(&argc, &argv, NULL); + /* Make sure an error in the handler is propagated back up to the calling + g_test_add_func("/ircv3/parser/propagates-errors", + test_purple_ircv3_parser_propagates_errors); + /* These tests are based on the msg-split tests from + * https://github.com/ircdocs/parser-tests/blob/master/tests/msg-split.yaml + g_test_add_func("/ircv3/parser/simple", + test_purple_ircv3_parser_simple); + g_test_add_func("/ircv3/parser/with-source", + test_purple_ircv3_parser_with_source); + g_test_add_func("/ircv3/parser/with-trailing", + test_purple_ircv3_parser_with_trailing); + g_test_add_func("/ircv3/parser/with-empty-trailing", + test_purple_ircv3_parser_with_empty_trailing); + g_test_add_func("/ircv3/parser/with-trailing-starting-colon", + test_purple_ircv3_parser_with_trailing_starting_colon); + g_test_add_func("/ircv3/parser/with-source-and-trailing", + test_purple_ircv3_parser_with_source_and_trailing); + g_test_add_func("/ircv3/parser/with-source-and-trailing-whitespace", + test_purple_ircv3_parser_with_source_and_trailing_whitespace); + g_test_add_func("/ircv3/parser/with-source-and-trailing-colon", + test_purple_ircv3_parser_with_source_and_trailing_colon); + g_test_add_func("/ircv3/parser/with-source-and-empty-trailing", + test_purple_ircv3_parser_with_source_and_empty_trailing); + g_test_add_func("/ircv3/parser/with-source-and-trailing-only-whitespace", + test_purple_ircv3_parser_with_source_and_trailing_only_whitespace); + g_test_add_func("/ircv3/parser/with-tags", + test_purple_ircv3_parser_with_tags); + g_test_add_func("/ircv3/parser/with-escaped-tags", + test_purple_ircv3_parser_with_escaped_tags); + g_test_add_func("/ircv3/parser/with-tags-and-source", + test_purple_ircv3_with_tags_and_source); + g_test_add_func("/ircv3/parser/last-param-no-colon", + test_purple_ircv3_last_param_no_colon); + g_test_add_func("/ircv3/parser/last-param-with-colon", + test_purple_ircv3_last_param_with_colon); + g_test_add_func("/ircv3/parser/without-last-param", + test_purple_ircv3_without_last_param); + g_test_add_func("/ircv3/parser/with-last-parsm", + test_purple_ircv3_with_last_param); + g_test_add_func("/ircv3/parser/tab-is-not-space", + test_purple_ircv3_tab_is_not_space); + g_test_add_func("/ircv3/parser/source_control_characters_1", + test_purple_ircv3_source_control_characters_1); + g_test_add_func("/ircv3/parser/source_control_characters_2", + test_purple_ircv3_source_control_characters_2); + g_test_add_func("/ircv3/parser/everything", + test_purple_ircv3_everything); + g_test_add_func("/ircv3/parser/everything-but-tags", + test_purple_ircv3_everything_but_tags); + g_test_add_func("/ircv3/parser/everything-but-source", + test_purple_ircv3_everything_but_source); + g_test_add_func("/ircv3/parser/command-only", + test_purple_ircv3_command_only); + g_test_add_func("/ircv3/parser/slashes-are-fun", + test_purple_ircv3_slashes_are_fun); + g_test_add_func("/ircv3/parser/unreal-broken-1", + test_purple_ircv3_unreal_broken_1); + g_test_add_func("/ircv3/parser/unreal-broken-2", + test_purple_ircv3_unreal_broken_2); + g_test_add_func("/ircv3/parser/unreal-broken-3", + test_purple_ircv3_unreal_broken_3); + g_test_add_func("/ircv3/parser/tag-escape-char-at-a-time", + test_purple_ircv3_tag_escape_char_at_a_time); + g_test_add_func("/ircv3/parser/tag-drop-unnecessary-escapes", + test_purple_ircv3_tag_drop_unnecessary_escapes); + g_test_add_func("/ircv3/parser/tag-drop-trailing-slash", + test_purple_ircv3_tag_drop_trailing_slash); + g_test_add_func("/ircv3/parser/duplicate-tags", + test_purple_ircv3_duplicate_tags); + g_test_add_func("/ircv3/parser/vendor-tags-are-namespaced", + test_purple_ircv3_vendor_tags_are_namespaced); + g_test_add_func("/ircv3/parser/special-mode-1", + test_purple_ircv3_special_mode_1); + g_test_add_func("/ircv3/parser/special-mode-2", + test_purple_ircv3_special_mode_2); + /* These tests are the examples from the message-tags specification, + * https://ircv3.net/specs/extensions/message-tags.html. + g_test_add_func("/ircv3/parser/message-tags/none", + test_purple_ircv3_parser_message_tags_none); + g_test_add_func("/ircv3/parser/message-tags/3-tags", + test_purple_ircv3_parser_message_tags_3_tags); + g_test_add_func("/ircv3/parser/message-tags/client-only", + test_purple_ircv3_parser_message_tags_client_only); + g_test_add_func("/ircv3/parser/message-tags/labeled-message", + test_purple_ircv3_parser_message_tags_labeled_response); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/protocols/ircv3/tests/test_ircv3_source.c Mon Mar 25 21:43:28 2024 -0500
@@ -0,0 +1,158 @@
+ * 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 "../purpleircv3source.h" +/****************************************************************************** + *****************************************************************************/ +test_ircv3_source_parse_return_address_required(void) { + if(g_test_subprocess()) { + purple_ircv3_source_parse("pidgy", NULL, NULL, NULL); + g_test_trap_subprocess(NULL, 0, G_TEST_SUBPROCESS_DEFAULT); + g_test_trap_assert_stderr("*nick != NULL || user != NULL || host != NULL' failed*"); +test_ircv3_source_parse_nick(void) { + /* Once to make sure that user and host are null. */ + purple_ircv3_source_parse("pidgy", &nick, &user, &host); + g_assert_cmpstr(nick, ==, "pidgy"); + g_clear_pointer(&nick, g_free); + /* Again to make sure it works without user and host. */ + purple_ircv3_source_parse("pidgy", &nick, NULL, NULL); + g_assert_cmpstr(nick, ==, "pidgy"); + g_clear_pointer(&nick, g_free); +test_ircv3_source_parse_user(void) { + /* Once to make sure host is null. */ + purple_ircv3_source_parse("pidgy!~u", &nick, &user, &host); + g_assert_cmpstr(nick, ==, "pidgy"); + g_clear_pointer(&nick, g_free); + g_assert_cmpstr(user, ==, "~u"); + g_clear_pointer(&user, g_free); + /* Again to make sure nick and host aren't required. */ + purple_ircv3_source_parse("pidgy!~u", NULL, &user, NULL); + g_assert_cmpstr(user, ==, "~u"); + g_clear_pointer(&user, g_free); +test_ircv3_source_parse_host(void) { + /* Once to make sure everything works. */ + purple_ircv3_source_parse("pidgy!~u@53unc8n42i868.irc", &nick, &user, + g_assert_cmpstr(nick, ==, "pidgy"); + g_clear_pointer(&nick, g_free); + g_assert_cmpstr(user, ==, "~u"); + g_clear_pointer(&user, g_free); + g_assert_cmpstr(host, ==, "53unc8n42i868.irc"); + g_clear_pointer(&host, g_free); + /* Again to make sure nick and host aren't required. */ + purple_ircv3_source_parse("pidgy!~u@53unc8n42i868.irc", NULL, NULL, &host); + g_assert_cmpstr(host, ==, "53unc8n42i868.irc"); + g_clear_pointer(&host, g_free); +test_ircv3_source_parse_baddies(void) { + purple_ircv3_source_parse("\n", &nick, NULL, NULL); + purple_ircv3_source_parse("a\nb", &nick, NULL, NULL); + purple_ircv3_source_parse("\r", &nick, NULL, NULL); + purple_ircv3_source_parse("c\rd", &nick, NULL, NULL); + purple_ircv3_source_parse(" ", &nick, NULL, NULL); + purple_ircv3_source_parse("e f", &nick, NULL, NULL); + purple_ircv3_source_parse("nick@foo!user@server", &nick, &user, &server); + g_assert_cmpstr(nick, ==, "nick@foo"); + g_clear_pointer(&nick, g_free); + g_assert_cmpstr(user, ==, "user"); + g_clear_pointer(&user, g_free); + g_assert_cmpstr(server, ==, "server"); + g_clear_pointer(&server, g_free); + purple_ircv3_source_parse("nick!user@server!foo", &nick, &user, &server); + g_assert_cmpstr(nick, ==, "nick!user@server!foo"); +/****************************************************************************** + *****************************************************************************/ +main(int argc, char *argv[]) { + g_test_init(&argc, &argv, NULL); + g_test_add_func("/ircv3/source/parse/return-address-required", + test_ircv3_source_parse_return_address_required); + g_test_add_func("/ircv3/source/parse/nick", test_ircv3_source_parse_nick); + g_test_add_func("/ircv3/source/parse/user", test_ircv3_source_parse_user); + g_test_add_func("/ircv3/source/parse/host", test_ircv3_source_parse_host); + g_test_add_func("/ircv3/source/parse/baddies", + test_ircv3_source_parse_baddies); \ No newline at end of file
--- a/protocols/meson.build Thu Mar 21 22:20:50 2024 -0500
+++ b/protocols/meson.build Mon Mar 25 21:43:28 2024 -0500
@@ -1,2 +1,4 @@