/** @ page perl - howto Perl Scripting HOWTO libpurple Perl Plugins are set up very similarly to their C counterparts . Most of the API calls are implemented and are divided into packages . There are some significant differences between the Perl and C API . Much like the C API , the best place to seek guidance is the source , which is located in the *. xs files in the [ libpurple | pidgin ] / plugins / perl / common directories . The tutorial that follows will be example based and attempt to touch on some of the notable features of the embedded perl interpreter . It is also important to note that some of the C API is missing in libpurple 's perl API. It is possible to get Gtk2 - Perl to work with libpurple 's Perl API, however you must not load the module with @c use, but rather with @c require. Keep this in mind if you would like to use other perl modules that are dynamically loaded. You can avoid the problem by always using @c require instead of @c use. @ section first - script Writing your first script Let us start with a simple example of a perl plugin . The following code sample is a complete plugin that can be copied and used as - is . name => "Perl Test Plugin" , summary => "Test plugin for the Perl interpreter." , description => "Your description here" , author => "John H. Kelm <johnhkelm\@gmail.com" , url => "http://pidgin.im" , unload => "plugin_unload" Purple :: Debug :: info ( "testplugin" , "plugin_load() - Test Plugin Loaded. \n " ); Purple :: Debug :: info ( "testplugin" , "plugin_unload() - Test Plugin Unloaded. \n " ); It is necessary to load the libpurple perl package with the @ code use Purple ; @ endcode line ; this makes all the libpurple perl API available to the script . The @ c \
% PLUGIN_INFO hash contains all the information that will be displayed in the Plugin frame of the Preferences dialog , it also contains information about how the plugin is to be handled . The @ c load and @ c unload keys specify subroutines to be called when the plugin is loaded and unloaded . There are other key values that may be present in the @ c \
% PLUGIN_INFO hash ; some of those will be covered in the following sections . The Perl subroutine @ c plugin_init is executed when the plugin is probed by the plugin subsystem . What this means is that as soon as Pidgin or Finch is started , this subroutine is run once , regardless of whether or not the plugin is actually loaded . The other two subroutines present are the @ c load and @ c unload routines specified in the @ c \
% PLUGIN_INFO hash and will receive the plugin handle as an argument . When the plugin is loaded and subsequently unloaded , it will print a message to the debug window using the @ c Purple :: Debug :: info () API call . In order to use this simple plugin , save the script text in a file with a @ c . pl extension in your ~/. purple / plugins directory . After restarting Pidgin , you should see the plugin ( "Perl Test Plugin" ) listed in the plugin list under "Tools -> Plugins" . To view the debug output , make sure you run Pidgin from the console with the '-d' flag or open the Debug Window which is available in the "Help" menu . When you check the checkbox next the plugin , you should see a message appear in the Debug Window ( or console ); similarly , when you uncheck the checkbox you should see another message appear . You have now created a framework that will allow you to create almost any kind of libpurple plugin you can imagine . @ section account - api Account and Account Option Functions The Account API is in the @ c Purple :: Account :: and @ c Purple :: Accounts :: packages ; both are nearly identical to their C counterparts , @ c purple_account_ and @ c purple_accounts_ . The Account Option API is in the @ c Purple :: Account :: Option package and is identical to the C implementation , @ c purple_account_option . The Account * APIs allow scripts to create , remove , and edit accounts . An account will have the Perl type of "PurpleAccount" . ( Note : Purple types have no real meaning in perl scripts , other than that the types passed to perl subroutines need to be correct . ) This section will not go into detail about the @ c Purple :: Account :: Option package since its use is mainly in building protocol plugins , which are outside the scope of this document . However , the API for the @ c Purple :: Account :: Option package should function as expected , should you need to To reduce redundant code , the following code examples will use the simple plugin from the previous section as a template . To highlight some of the more useful features of the Account API , we will be replacing the @ c plugin_load perl subroutine . For testing purposes , we will display output on the command line by using perl @ c print commands . # Testing was done using AIM, but this should work regardless of the protocol chosen my $ protocol = "prpl-aim" ; my $ account_name = "test" ; print "Testing: Purple::Account::new()... " ; $ account = Purple :: Account -> new ( $ account_name , $ protocol ); if ( $ account ) { print "ok. \n " ; } else { print "fail. \n " ; } print "Testing: Purple::Account::add()..." ; Purple :: Accounts :: add ( $ account ); print "pending find... \n " ; # Find the account we just added to verify its existence print "Testing: Purple::Accounts::find()..." ; $ account = Purple :: Accounts :: find ( $ account_name , $ protocol ); if ( $ account ) { print "ok. \n " ; } else { print "fail. \n " ; } print "Testing: Purple::Account::get_username()... " ; $ user_name = $ account -> get_username (); print "Success: $user_name. \n " ; # Verify if the user is connected print "Testing: Purple::Account::is_connected()" ; if ( $ account -> is_connected ()) { print " Disconnected. \n " ; # The status mechanism is how users are Connected, set Away, # Disconnected (status set to Offline), etc # $status is now a Purple::Status perl type. print "Testing: Purple::Accounts::get_active_status()... " ; if ( $ account -> get_active_status ()) { # It follows that to connect a user you must set the account status to # "available" similarly we can disconnect a user by setting the account print "Testing: Purple::Accounts::connect()...pending... \n " ; $ account -> set_status ( "available" , TRUE ); $ account -> set_enabled ( Purple :: Core :: get_ui (), TRUE ); The above code is mostly explained in the comments , however there are a few notable points to make . The variables above are all specialized Perl types that contain pointers to the actual Purple types . They can be reassigned at will , just like any other variable in Perl . The only way to edit the internal values of a Purple type from within perl is to use the accessor methods , e . g . @ c Purple :: Account :: get_username () . If you would like to assign a C @ c NULL value , @ section buddylist - api Buddylist , Group and Chat API The BuddyList , Group and Chat APIs are very similar and whatever is shown for the @ c Purple :: BuddyList API should carry over to @ c Purple :: BuddyList :: Chat and @ c Purple :: BuddyList :: Group . Note that a @ c Purple :: Find package was created to keep the naming consistent with the C API . my $ protocol = "prpl-aim" ; my $ account_name = "test" ; # This is how we get an account to use in the following tests. You should replace the username $ account = Purple :: Accounts :: find ( $ account_name , $ protocol ); # Testing a find function: Note Purple::Find not Purple::Buddy:find! # Furthermore, this should work the same for chats and groups Purple :: Debug :: info ( "testplugin" , "Testing: Purple::Find::buddy()..." ); $ buddy = Purple :: Find :: buddy ( $ account , "BUDDYNAME" ); Purple :: Debug :: info ( "" , ( $ buddy ? "ok." : "fail." ) . " \n " ); # If you should need the handle for some reason, here is how you do it Purple :: Debug :: info ( "testplugin" , "Testing: Purple::BuddyList::get_handle()..." ); $ handle = Purple :: BuddyList :: get_handle (); Purple :: Debug :: info ( "" , ( $ handle ? "ok." : "fail." ) . " \n " ); # This gets the Purple::BuddyList and references it by $blist Purple :: Debug :: info ( "testplugin" , "Testing: Purple::get_blist()..." ); $ blist = Purple :: get_blist (); Purple :: Debug :: info ( "" , ( $ blist ? "ok." : "fail." ) . " \n " ); # This is how you would add a buddy named "NEWNAME" with the alias "ALIAS" Purple :: Debug :: info ( "testplugin" , "Testing: Purple::BuddyList::Buddy::new..." ); $ buddy = Purple :: BuddyList :: Buddy :: new ( $ account , "NEWNAME" , "ALIAS" ); Purple :: Debug :: info ( "" , ( $ buddy ? "ok." : "fail." ) . " \n " ); # Here we add the new buddy '$buddy' to the group "GROUP" # so first we must find the group Purple :: Debug :: info ( "testplugin" , "Testing: Purple::Find::group..." ); $ group = Purple :: Find :: group ( "GROUP" ); Purple :: Debug :: info ( "" , ( $ group ? "ok." : "fail." ) . " \n " ); # To add the buddy we need to have the buddy, contact, group and node for insertion. # For this example we can let contact be undef and set the insertion node as the group Purple :: Debug :: info ( "testplugin" , "Testing: Purple::BuddyList::add_buddy... \n " ); Purple :: BuddyList :: add_buddy ( $ buddy , undef , $ group , $ group ); # The example that follows gives an indication of how an API call that returns a list is handled. # In this case the buddies of the account found earlier are retrieved and put in an array '@buddy_array' # Further down an accessor method is used, 'get_name()' -- see source for details on the full set of methods Purple :: Debug :: info ( "testplugin" , "Testing: Purple::Find::buddies... \n " ); @ buddy_array = Purple :: Find :: buddies ( $ account , undef ); Purple :: Debug :: info ( "testplugin" , "Buddies in list (" . @ buddy_array . "): \n " ); foreach $ bud ( @ buddy_array ) { Purple :: Debug :: info ( "testplugin" , Purple :: BuddyList :: Buddy :: get_name ( $ bud ) . " \n " ); The BuddyList API allows plugins to edit buddies in the list , find the buddies for a given account , assign aliases , and further manipulate the structure as needed . It is also contains methods for accessing @ c Purple :: BuddyList :: Group and @ c Purple :: BuddyList :: Chat types . @ section conn - api Connection API The @ c Purple :: Connection API is one of the many packages that will not be covered in - depth in this tutorial . It is most useful to protocol plugin developers . However , the entire @ c purple_connection_ API has corresponding , functioning perl subroutines . @ section conv - api Conversation API The libpurple perl APIs for @ c purple_conversation_ and @ c pidgin_conv_window_ allow plugins to interact with existing conversations , create new conversations , and modify conversations at will . In the example script , a new window is created , displayed and a new conversation instant message is created . The following example again replaces the @ c plugin_load subroutine in the simple plugin template . The @ c Purple :: Conversation :: Chat package handles the @ c purple_conv_chat_ portion of the API very similarly to how the Purple :: Conversation :: IM package is used in the examples that follow . Notice that the interaction with the conversation window is in the @ c Pidgin package as it is UI - specific code interacting with Pidgin and not libpurple . To use any of the Pidgin :: functionality , you will need to add the following to the top of your script : @ code use Pidgin ; @ endcode my $ protocol = "prpl-aim" ; my $ account_name = "test" ; $ account = Purple :: Accounts :: find ( $ account_name , $ protocol ); # First we create two new conversations. print "Testing Purple::Conversation->new()..." ; $ conv1 = Purple :: Conversation -> new ( 1 , $ account , "Test Conversation 1" ); if ( $ conv1 ) { print "ok. \n " ; } else { print "fail. \n " ; } print "Testing Purple::Conversation->new()..." ; $ conv2 = Purple :: Conversation -> new ( 1 , $ account , "Test Conversation 2" ); if ( $ conv2 ) { print "ok. \n " ; } else { print "fail. \n " ; } # Second we create a window to display the conversations in. # Note that the package here is Pidgin::Conversation::Window print "Testing Pidgin::Conversation::Window->new()... \n " ; $ win = Pidgin :: Conversation :: Window -> new (); # The third thing to do is to move second the conversation to a new window. # The subroutine add_gtkconv() returns the number of conversations print "Testing Pidgin::Conversation::Window::add_conversation()..." ; $ gtkconv2 = Pidgin :: Conversation :: get_gtkconv ( $ conv2 ); $ gtkconv2 -> get_window () -> remove_gtkconv ( $ gtkconv2 ); $ conv_count = $ win -> add_gtkconv ( $ gtkconv2 ); print "ok..." . $ conv_count . " conversations... \n " ; # Now the window is displayed to the user. print "Testing Pidgin::Conversation::Window::show()... \n " ; # Use get_im_data() to get a handle for the conversation print "Testing Purple::Conversation::get_im_data()... \n " ; $ im = $ conv1 -> get_im_data (); if ( $ im ) { print "ok. \n " ; } else { print "fail. \n " ; } # Here we send messages to the conversation print "Testing Purple::Conversation::IM::send()... \n " ; $ im -> send ( "Message Test." ); print "Testing Purple::Conversation::IM::write()... \n " ; $ conv2 -> get_im_data () -> write ( "SENDER" , "<b>Message</b> Test." , 0 , 0 ); The next block of code shows how a script can close a known conversation window print "Testing Pidgin::Conversation::Window::get_gtkconv_count()... \n " ; $ conv_count = $ win -> get_gtkconv_count (); print "...and it returned $conv_count. \n " ; print "Testing Pidgin::Conversation::Window::destroy()... \n " ; @ section plugin - pref - api Plugin Preference and Gtk Preference API The plugin preference API allows plugins to display options for manipulating the plugin 's behavior in a popup window. The method used for creating the pane in native C plugins does not allow a direct mapping into perl . Therefore , perl plugin writers must be aware that there will be significant differences in how they create plugin preference panes . To first create a standard plugin preference tab , we need specify the @ c prefs_info key / value pair in the @ c \
% PLUGIN_INFO hash . This specifies the name of the perl subroutine that will build and return a @ c Purple :: Pref :: Frame . prefs_info => "prefs_info_cb" The perl subroutine @ c prefs_info_cb will be called to create the preferences popup for the perl plugin . The following example will demonstrate creating a preference frame . It is necessary to first initialize the preferences to be used in the @ c plugin_load subroutine , as follows . # This is necessary to create each level in the preferences tree. Purple :: Prefs :: add_none ( "/plugins/core/perl_test" ); # Here we are adding a set of preferences # The second argument is the default value for the preference. Purple :: Prefs :: add_bool ( "/plugins/core/perl_test/bool" , 1 ); Purple :: Prefs :: add_string ( "/plugins/core/perl_test/choice_str" , "ch1" ); Purple :: Prefs :: add_int ( "/plugins/core/perl_test/choice_int" , 1 ); Purple :: Prefs :: add_string ( "/plugins/core/perl_test/text" , "Foobar" ); Now we can provide an UI for manipulating these preferences in our @ c prefs_info # The first step is to initialize the Purple::Pref::Frame that will be returned $ frame = Purple :: PluginPref :: Frame -> new (); # Create a new boolean option with a label "Boolean Label" and then add $ ppref = Purple :: PluginPref -> new_with_label ( "Boolean Label" ); $ ppref = Purple :: PluginPref -> new_with_name_and_label ( "/plugins/core/perl_test/bool" , "Boolean Preference" ); # Create a set of choices. To do so, we must set the type to 1 which is # the numerical equivalent of the PurplePrefType for choice. $ ppref = Purple :: PluginPref -> new_with_name_and_label ( "/plugins/core/perl_test/choice_str" , "Choice Preference" ); $ ppref -> add_choice ( "ch0" , "ch0" ); # The following will be the default value as set from plugin_load $ ppref -> add_choice ( "ch1" , "ch1" ); # Create a set of choices. To do so we must set the type to 1 which is # the numerical equivalent of the PurplePrefType for choice. $ ppref = Purple :: PluginPref -> new_with_name_and_label ( "/plugins/core/perl_test/choice_int" , "Choice Preference 2" ); $ ppref -> add_choice ( "zero" , 0 ); # The following will be the default value as set from plugin_load $ ppref -> add_choice ( "one" , 1 ); # Create a text box. The default value will be "Foobar" as set by $ ppref = Purple :: PluginPref -> new_with_name_and_label ( "/plugins/core/perl_test/text" , "Text Box Preference" ); $ ppref -> set_max_length ( 16 ); Using the Gtk2 - Perl module for Perl it is possible to create tailored @ c GtkFrame elements and display them in a preference window . Note that Gtk2 - Perl must be loaded with @ c require and not @ c use . The first step is to create the proper key / value pairs in the @ c \
% PLUGIN_INFO hash noting that the @ c prefs_info key is no longer valid . Instead the keys @ c GTK_UI and @ c gtk_prefs_info must be set as follows . # Used to differentiate between a regular and a Gtk preference frame gtk_prefs_info => "gtk_prefs_info_cb" , To finish this example @ c gtk_prefs_info_cb needs to be defined . To introduce some of the flexibility of using Gtk2 - Perl the example also includes a button and a callback for the button . Explaining Gtk2 - Perl is beyond the scope of this tutorial and more info can be found at the project 's website <a href="http://gtk2-perl.sourceforge.net/">http://gtk2-perl.sourceforge.net/</a>. # A simple call back that prints out whatever value it is given as an argument. print "Clicked button with message: " . $ data . " \n " ; # Create a button that prints a message to the console and places it in the frame. $ frame = Gtk2 :: Frame -> new ( \
'Gtk Test Frame \' ); $ button = Gtk2 :: Button -> new ( \
'Print Message \' ); $ frame -> set_border_width ( 10 ); $ button -> set_border_width ( 150 ); $ button -> signal_connect ( "clicked" => \
& button_cb , "Message Text" ); @ section request - api Request Dialog Box API The @ c Purple :: Request package allows plugins to have interactive dialog boxes without the need to directly interact with the UI ( e . g . GTK + ) . This allows core ( libpurple ) plugins to create a UI that can be displayed on whichever UI frontend is being used . The portion of the Request API available to perl scripts is listed below , followed by an example illustrating its use . These arguments are the same for each of the three request types : @ arg @ em handle - The plugin handle . @ arg @ em title - String title for the dialog . @ arg @ em primary - The first sub - heading . @ arg @ em secondary - The second sub - heading . @ arg @ em ok_text - The Text for the OK button . @ arg @ em ok_cb - The string name of the perl subroutine to call when the OK button is clicked . @ arg @ em cancel_text - The text for the Cancel button . @ arg @ em cancel_cb - The string name of the perl subroutine to call when the Cancel button is clicked . @ arg @ em default_value - Default text string to display in the input box . @ arg @ em multiline - Boolean where true indicates multiple line input boxes are allowed . @ arg @ em masked - Boolean indicating if the user can edit the text . @ arg @ em hint - See source for more information - can be left blank . @ arg @ em filename - String default file name value . @ arg @ em savedialog - Boolean where true indicates use as a save file dialog and false indicates an open file dialog . # Create a simple text input box Purple :: Request :: input ( handle , title , primary , secondary , default_value , multiline , masked , hint , ok_text , ok_cb , cancel_text , cancel_cb ); # Prompt user to select a file Purple :: Request :: file ( handle , title , filename , savedialog , ok_cb , cancel_cb ); # Create a unique input dialog as shown in the following example Purple :: Request :: fields ( handle , title , primary , secondary , fields , ok_text , ok_cb , cancel_text , cancel_cb ); The following is an example of a @ c Purple :: Request :: fields () dialog box with a callback for the OK button and another for the Cancel Button . # The $fields is passed to the callback function when the button is clicked. # To access a specific field, it must be extracted from $fields by name. $ account = Purple :: Request :: Fields :: get_account ( $ fields , "acct_test" ); $ int = Purple :: Request :: Fields :: get_integer ( $ fields , "int_test" ); $ choice = Purple :: Request :: Fields :: get_choice ( $ fields , "ch_test" ); # Cancel does nothing but is equivalent to the ok_cb_test # Create a group to pool together multiple fields. $ group = Purple :: Request :: Field :: Group :: new ( "Group Name" ); # Each field is created with Purple::Request::*_new(), is made viewable with Purple::Request::field_*_set_show_all() # and is then added to the group with Purple::Request::field_group_add_field() # Add an account combobox containing all active accounts $ field = Purple :: Request :: Field :: account_new ( "acct_test" , "Account Text" , undef ); Purple :: Request :: Field :: account_set_show_all ( $ field , 0 ); Purple :: Request :: Field :: Group :: add_field ( $ group , $ field ); # Add an integer input box $ field = Purple :: Request :: Field :: int_new ( "int_test" , "Integer Text" , 33 ); Purple :: Request :: Field :: Group :: add_field ( $ group , $ field ); $ field = Purple :: Request :: Field :: choice_new ( "ch_test" , "Choice Text" , 1 ); Purple :: Request :: Field :: choice_add ( $ field , "Choice 0" ); Purple :: Request :: Field :: choice_add ( $ field , "Choice 1" ); Purple :: Request :: Field :: choice_add ( $ field , "Choice 2" ); Purple :: Request :: Field :: Group :: add_field ( $ group , $ field ); # Create a Purple::Request and add the group that was just created. $ request = Purple :: Request :: Fields :: new (); Purple :: Request :: Fields :: add_group ( $ request , $ group ); # Display the dialog box with the input fields added earlier with the appropriate titles. "Cancel Text" , "cancel_cb_test" ); @ section timeout - cb Misc : Plugin Actions , Timeouts and Callbacks This section of the manual covers some of the important features that can be added to libpurple perl Plugins . Plugin actions are callback functions that are accessible to the user from the Buddy List window under "Tools -> [Plugin Name]" . Timeouts allow a plugin to execute a perl subroutine after a given period of time . Note that timeouts only occur once , so if the timeout must occur periodically , the timeout needs to be re - added at the end of the timeout callback function . Callbacks are functions that are called when an event occurs ( such as a buddy signing - on , or a message being received ) . The following three examples will demonstrate the usage of these features . Plugin actions require the @ c \
% PLUGIN_INFO hash to have a key / value pair added , specifying a perl subroutine which will list the available actions ; each action listed must have a definition , including a reference to a callback subroutine , added to the @ c \
% plugin_actions hash . plugin_action_sub => "plugin_actions_cb" , my @ actions = ( "Action 1" , "Action 2" ); # The callback subroutine that is called when "Tools -> Plugin -> Action 1" is selected "Action 1" => \
& action1_cb , # The callback subroutine that is called when "Tools -> Plugin -> Action 2" is selected "Action 2" => \
& action2_cb Purple :: Debug :: info ( "Test Plugin" , "Action 1 activated \n " ); Purple :: Debug :: info ( "Test Plugin" , "Action 2 activated \n " ); Timeouts allow a perl subroutine to be executed after a specified time . They only occur once , so , as stated earlier , the timeout must be re - registered after every time it is called . Purple :: Debug :: info ( "testplugin" , "Timeout occurred. \n " ); Purple :: timeout_add ( $ plugin , 10 , \
& timeout_cb , $ plugin ); # Schedule a timeout for ten seconds from now Purple :: timeout_add ( $ plugin , 10 , \
& timeout_cb , $ plugin ); Callbacks are handled by creating a perl subroutine to serve as the callback and then attaching the callback to a signal . # The signal data and the user data come in as arguments my ( $ account , $ data ) = @ _ ; Purple :: Debug :: info ( "testplugin" , "Account \" " . $ account -> get_username () . " \" just connected. \n " ); # User data to be given as an argument to the callback perl subroutine. # A pointer to the handle to which the signal belongs needed by the callback function $ accounts_handle = Purple :: Accounts :: get_handle (); # Connect the perl subroutine 'signal_cb' to the event 'account-connecting' Purple :: Signal :: connect ( $ accounts_handle , "account-connecting" , $ plugin , \
& signal_cb , $ data );