=pod =head1 NAME AutoBlogger - Blogging to MovableType using the XML-RPC i/f =head1 VERSION This file documents C version B<1.0> released 25/01/2005 =head1 AUTHOR Dean Stringer =head1 DESCRIPTION This module provides a very simple OO interface to our local instance of MovableType. Many of the connection details are concealed to make it easy to add a blogging facility to an existing or new cron job or CGI script. =head1 SYNOPSIS use AutoBlogger; my $blogPost = AutoBlogger->new( blogID => '1', bloguser => 'fred', # must have write access to this blog blogpass => '123', ); $blogPost->title('this is a test'); $blogPost->body('this is a test body'); # set the one or more post categories by passing in an anonymous list # can use IDs or names for these, e.g. $blogPost->categories( '2', 'General Web News', '9'); # finally render the post $blogPost->publish(); # might wanna check for errors print "\nResult was: " . $blogPost->{result}; print "\nError was: " . $blogPost->{error} if $blogPost->{error}; =head1 NOTES This module conceals the proxy end-point and doesnt provide a method to be able to set this to some other MT instance. Might add one in future but at the moment we only need to post to one copy of MT. MovableType Weirdness to Note.... you can't submit a post and assign its category at the same time. instead we have to submit the post using the API 'newPost' method then call 'setPostCategories' to assign its category, and finally call 'publishPost' to render it to the blog website. All of this is handled by the publish() method exposed in this AutoBlogger module another performance gotcha is, we only really want to build the web content once, so on the original call to 'newPost' we actually set the 'publish' boolean to false as the later call to 'publishPost' is gonna do the render anyway. =cut package AutoBlogger; use strict; use XMLRPC::Lite; sub new { my $invocant = shift; my $class = ref($invocant) || $invocant; my $self = { blogID => '', bloguser => 'username', blogpass => 'password', host => 'http://mysite.com', proxy => '/cgi-bin/mt-xmlrpc.cgi', title => 'a blank title', body => 'a blank body', buildAfterPublish => 0, postID => '', result => '', error => '', @_ # everything else passed in }; # see note in header if ($ENV{HOSTNAME} =~ /mysite/) { $self->{host} = 'http://www.mysite.com'; } $self->{rpcCaller} = XMLRPC::Lite->proxy($self->{host} . $self->{proxy}); bless ($self, $class); $self->_getCategories(); # fetch blog cats for later use return $self; } =pod =head1 PUBLIC FUNCTIONS =cut # ------------------------------------------------------------------------- sub body { # ------------------------------------------------------------------------- =pod =head2 body() sets or returns the post body =cut my $self = shift; if (@_) { $self->{body} = shift; } else { return $self->{body}; } } # ------------------------------------------------------------------------- sub title { # ------------------------------------------------------------------------- =pod =head2 title() sets or returns the post title =cut my $self = shift; if (@_) { $self->{title} = shift; } else { return $self->{title}; } } # ------------------------------------------------------------------------- sub categories { # ------------------------------------------------------------------------- =pod =head2 categories() accepts an anonymous list of category id's or names which we'll use later when we actually post the entry to the blog in the publish() sub. =cut my $self = shift; if (@_) { my @catArray = @_; $self->{categories} = [ @catArray ]; } } # ------------------------------------------------------------------------- sub publish { # ------------------------------------------------------------------------- =pod =head2 publish() this is the method thats called to initiate publishing of the newly assembled post. It makes an XMLRPC call to newPost, then optionally calls the local sub _setCategories if the user has specified 1 or more categories, and finally calls the internal sub _build which actually calls the XMLRPC method to render the post out to the file system. =cut my $self = shift; my $method = 'metaWeblog.newPost'; my $rpcCaller = $self->{rpcCaller}; my $rpcObj = $rpcCaller->call($method, $self->{blogID}, $self->{bloguser}, $self->{blogpass}, { 'title' => $self->{title}, 'description' => $self->{body}, }, $self->{buildAfterPublish} ) || ''; my $result = $rpcObj->result(); my $message = ''; unless (defined $result) { $message .= "\n\tfault=" . $rpcObj->faultstring; $message .= "\tcode=" . $rpcObj->faultcode; $self->{error} = $message; } unless ($rpcObj->fault) { $self->{postID} = $rpcObj->result(); $message .= "\nPost Created: ID=$self->{postID}"; $self->{result} = $message; } $self->_setCategory() if @{$self->{categories}} > 0; $self->_build(); } =pod =head1 INTERNAL FUNCTIONS =cut # ------------------------------------------------------------------------- sub _build { # ------------------------------------------------------------------------- =pod =head2 _build() Internal sub called by publish() to finally publish and render out the post we've assembled. Stores any reported XML-RPC errors in $self->{error} =cut my $self = shift; my $rpcCaller = $self->{rpcCaller}; my $method = "metaWeblog.publishPost"; my $rpcObj = $rpcCaller->call( $method, $self->{postID}, $self->{bloguser}, $self->{blogpass}, ); my $result = $rpcObj->result(); unless (defined $result) { my $error .= "Error: " . $rpcObj->faultstring; $error .= "Error: " . $rpcObj->faultcode; $self->{error} .= $error; } unless ($rpcObj->fault) { my $postid = $rpcObj->result(); my $message .= "Post Published: ID=$self->{postID}\n"; $self->{result} .= $message; } } # ------------------------------------------------------------------------- sub _getCategories { # ------------------------------------------------------------------------- =pod =head2 _getCategories() Internal sub that's called in the new() constructor to fetch the list of available categories for this blog from the back-end. We use this list in the setCategories() sub so we can ignore categories that dont actually exist =cut my $self = shift; my $method = 'metaWeblog.getCategoryList'; my $rpcCaller = $self->{rpcCaller}; my $rpcObj = $rpcCaller->call($method, $self->{blogID}, $self->{bloguser}, $self->{blogpass} ); my $result = $rpcObj->result(); my $message = ''; unless (defined $result) { $message .= "\n\tfault=" . $rpcObj->faultstring; $message .= "\tcode=" . $rpcObj->faultcode; $self->{error} = $message; } unless ($rpcObj->fault) { $self->{categoriesAvailable} = $result; } } # ------------------------------------------------------------------------- sub _setCategory { # ------------------------------------------------------------------------- =pod =head2 _setCategory() internal function that gets called by publish() to assemble a hash of categoryIDs and caegoryNames to be used in a metaWeblog.setPostCategories XMLRPC call to the MovableType back-end. We only get called by publish() if there are more than '0' categories specified by the user script. =cut my $self = shift; my @categories = @{$self->{categories}}; my $rpcCaller = $self->{rpcCaller}; my $categories = []; # categories needs to be reference to a struct array foreach my $categoryID (@categories) { # check against the available categories we loaded from MT earlier # and only add this category if its available, ignore id's or names # we dont know about, ie that arent available foreach (@{$self->{categoriesAvailable}}) { my $catDetails = $_; # reference my $thisAvailCatID = $catDetails->{categoryId}; my $thisAvailCatName = $catDetails->{categoryName}; if ($categoryID eq $thisAvailCatID) { push @$categories, { categoryId => $thisAvailCatID }; } if ($categoryID eq $thisAvailCatName) { push @$categories, { # must use name AND ID when if using a categoryName categoryName => $thisAvailCatName, categoryId => $thisAvailCatID }; } } } my $message = ""; my $method = 'metaWeblog.setPostCategories'; my $rpcObj = $rpcCaller->call( $method, $self->{postID}, $self->{bloguser}, $self->{blogpass}, $categories, ); my $result = $rpcObj->result(); unless (defined $result) { my $error .= "Error: " . $rpcObj->faultstring; $error .= "Error: " . $rpcObj->faultcode; $self->{error} .= $error; } my $catCount = 0; unless ($rpcObj->fault) { my $postid = $rpcObj->result(); $message .= "\nCategories Set: "; foreach my $category (@$categories) { $message .= $$category{categoryId} . ","; $catCount++ } $message .= "\n"; $self->{result} .= $message; } } =head1 REFERENCES http://www.movabletype.org/docs/mtmanual_programmatic.html http://www.jayallen.org/journey/2003/10/tip_xmlrpc_and_movable_type http://erikbenson.com/code/mt-moblog http://www.soapware.org/bdg =cut 1