Trac is being migrated to new services! Issues can be found in our new YouTrack instance and WIKI pages can be found on our website.

Version 7 (modified by hamiltont, 14 years ago) (diff)

added more code comments, unregistered commands at unload, cleaned up testing section

Command API How-To

The command API is a method by which you can add commands to libpurple clients. Pidgin and Finch use the / character to denote a command, much like many IRC clients. This document will explain how to make your plugin add commands. Again we will assume that you have completed the Basic C Plugin Howto. We'll also continue to assume that you're using Pidgin 2.0.2 for these How-To documents as described there.

This document is incomplete. It has been saved to the wiki to ensure the current work is not lost. It should be finished soon.

About the Command API

The full command API documentation can be found at cmds.h. The API allows commands to specify priority, to allow proper handling of chained commands (aka, to create an order of operations). The API allows for flexible command arguments, from a single argument to multiple complex arguments.

Writing the Example Plugin

/*
 * Command Example Plugin
 *
 * Copyright (C) 2009, Hamilton Turner <hamiltont@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02111-1301, USA.
 *
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

/* Include glib.h for various types */
#include <glib.h>

/* This will prevent compiler errors in some instances and is better explained in the
 * how-to documents on the wiki */
#ifndef G_GNUC_NULL_TERMINATED
# if __GNUC__ >= 4
#  define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
# else
#  define G_GNUC_NULL_TERMINATED
# endif
#endif

/* This is the required definition of PURPLE_PLUGINS as required for a plugin,
 * but we protect it with an #ifndef because config.h may define it for us
 * already and this would cause an unneeded compiler warning. */
#ifndef PURPLE_PLUGINS
# define PURPLE_PLUGINS
#endif

/* Here we're including the necessary libpurple headers for this plugin.  Note
 * that we're including them in alphabetical order.  This isn't necessary but
 * we do this throughout our source for consistency. */
#include "cmds.h"
#include "debug.h"
#include "plugin.h"
#include "notify.h"
#include "version.h"

/* It's more convenient to type PLUGIN_ID all the time than it is to type
 * "core-commandexample", so define this convenience macro. */
#define PLUGIN_ID "core-commandexample"

/* Common practice in third-party plugins is to define convenience macros for
 * many of the fields of the plugin info struct, so we'll do that for the
 * purposes of demonstration. */
#define PLUGIN_AUTHOR "Hamilton Turner <hamiltont@gmail.com>"

/**
 * Initialized in the plugin_load() method, this allows us to keep a handle to 
 * ourself. Such a handle is needed to register for commands, so that libpurple
 * has a handle to the plugin that registered for the command 
 */ 
static PurplePlugin *command_example = NULL;

/**
 * Used to hold a handle to the commands we register. Holding this handle
 * allows us to unregister the commands at a later time. 
 */
static PurpleCmdId log_command_id, notify_command_id;

/**
 * The callback function for our /log command. This function simply prints
 * whatever was entered as the argument to the debug command into the debug 
 * log. 
 *
 * @param conv The conversation that the command occurred in
 * @param cmd The exact command that was entered
 * @param args The args that were passed with the command
 * @param error ?Any errors that occurred?
 * @param data Any special user-defined data that was assigned during 
 *             cmd_register
 */
static PurpleCmdRet 
log_cb(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
{
  purple_debug_misc(PLUGIN_ID, "log_cb called\n");
  purple_debug_misc(PLUGIN_ID, "message = %s\n", args[0]);

  return PURPLE_CMD_RET_OK;

}

/**
 * The callback function for our /notify command. This function simply pops up
 * a notification with the word entered as the argument to the command 
 *
 * @param conv The conversation that the command occurred in
 * @param cmd The exact command that was entered
 * @param args The args that were passed with the command
 * @param error ?Any errors that occurred?
 * @param data Any special user-defined data that was assigned during 
 *             cmd_register
 */
static PurpleCmdRet 
notify_cb(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
{

  purple_notify_info(command_example,
                     "Notify Notification Title",
                     "Notify Notification Primary Text",
                     args[0]);   /* Secondary text is the word entered */

  return PURPLE_CMD_RET_OK;

}

/**
 * Because we added a pointer to this function in the load section of our 
 * PurplePluginInfo, this function is called when the plugin loads.  
 * Here we're using it to show off the capabilities of the
 * command API by registering a few commands. We return TRUE to tell libpurple 
 * it's safe to continue loading.
 */
static gboolean
plugin_load(PurplePlugin *plugin)
{
  // Declare all vars up front. Avoids warnings on some compilers 
  gchar *log_help = NULL;
  gchar *notify_help = NULL;
  
  // Save a handle to ourself for later
  command_example = plugin;

  // Help message for log command, in the correct format 
  log_help = "log &lt;log message here&gt;:  Prints a message to the debug log.";

  // Register a command to allow a user to enter /log some message and have
  // that message stored to the log. This command runs with default priority,
  // can only be used in a standard chat message, not while in group chat
  // mode
  log_command_id = purple_cmd_register 
    ("log",                         /* command name */ 
     "s",                           /* command argument format */
     PURPLE_CMD_P_DEFAULT,          /* command priority flags */  
     PURPLE_CMD_FLAG_IM,            /* command usage flags */
     PLUGIN_ID,                     /* Plugin ID */
     log_cb,                        /* Name of the callback function */
     log_help,                      /* Help message */
     NULL                           /* Any special user-defined data */
     );
        

  // Help message for notify command, in the correct format
  notify_help = "notify &lt;notify word here&gt;:  Pops up a notification with the word you enter.";

  // Register a command to allow a user to enter /notify some word and have
  // that word notified back to them. This command runs with high priority,
  // and can be used in both group and standard chat messages 
  notify_command_id = purple_cmd_register
    ("notify",                      /* command name */ 
     "w",                           /* command argument format */
     PURPLE_CMD_P_HIGH,             /* command priority flags */  
     PURPLE_CMD_FLAG_IM | 
       PURPLE_CMD_FLAG_CHAT,        /* command usage flags */
     PLUGIN_ID,                     /* Plugin ID */
     notify_cb,                     /* Callback function */
     notify_help,                   /* Help message */
     NULL                           /* Any special user-defined data */
     );
 

  /* Just return true to tell libpurple to finish loading. */
  return TRUE;
}

/**
 * Because we added a pointer to this function in the unload section of our 
 * PurplePluginInfo, this function is called when the plugin unloads.  
 * Here we're using it to show off the capabilities of the
 * command API by unregistering a few commands. We return TRUE to tell libpurple 
 * it's safe to continue unloading.
 */
static gboolean
plugin_unload(PurplePlugin *plugin)
{
  purple_cmd_unregister(log_command_id);
  purple_cmd_unregister(notify_command_id);

  /* Just return true to tell libpurple to finish unloading. */
  return TRUE;
}

/**
 * Struct used to let Pidgin understand our plugin
 */
static PurplePluginInfo info = {
        PURPLE_PLUGIN_MAGIC,        /* magic number */
        PURPLE_MAJOR_VERSION,       /* purple major */
        PURPLE_MINOR_VERSION,       /* purple minor */
        PURPLE_PLUGIN_STANDARD,     /* plugin type */
        NULL,                       /* UI requirement */
        0,                          /* flags */
        NULL,                       /* dependencies */
        PURPLE_PRIORITY_DEFAULT,    /* priority */

        PLUGIN_ID,                  /* id */
        "Command API Example",      /* name */
        "1.0",                      /* version */
        "Command API Example",      /* summary */
        "Command API Example",      /* description */
        PLUGIN_AUTHOR,              /* author */
        "http://pidgin.im",         /* homepage */

        plugin_load,                /* load */
        plugin_unload,              /* unload */
        NULL,                       /* destroy */

        NULL,                       /* ui info */
        NULL,                       /* extra info */
        NULL,                       /* prefs info */
        NULL,                       /* actions */
        NULL,                       /* reserved */
        NULL,                       /* reserved */
        NULL,                       /* reserved */
        NULL                        /* reserved */
};

static void
init_plugin(PurplePlugin *plugin)
{
}

PURPLE_INIT_PLUGIN(commandexample, init_plugin, info)

Compiling, Installing, and Testing the Plugin

As before, run make command_example.so (make -f Makefile.mingw command_example.dll for you Windows types) to build the plugin. Copy the resulting command_example.so to ~/.purple/plugins, or copy the resulting command_example.dll to %APPDATA%\.purple\plugins.

To test (we will assume you are using Pidgin), go to Tools->Plugins and enable the Command API Example plugin. Also, go to Help->Debug Window to show the Debug Window. Open an IM chat session, and type

/log my log message here

Your message should show up in the Debug Window. Then type

/notify Hello!

You should receive a notification box saying Hello''' Also, be sure to try

/notify Hello World!

Note that the command fails, because you input multiple arguments (2) when it was only expecting a single value. It prints Syntax Error: You typed the wrong number of arguments to that command.

Lastly, if you disable the Command API Example plugin, and then type a command, you will send an IM. Try it!

/log why is this not working now

Beyond the Command API

The command API provides a basic command framework for plugins, which can be an extremely valuable resource to plugin authors. The other areas we didn't cover here include much of the true power of the command API, but we will leave those as an area for your exploration. This document is done, but there are still others in the C Plugin How-To for you to explore!

All information, including names and email addresses, entered onto this website or sent to mailing lists affiliated with this website will be public. Do not post confidential information, especially passwords!