#!/usr/bin/perl # # Blosxom XML-RPC Server # Author: Allen Hutchison # Version: $Id: blosxom-xmlrpc.cgi,v 1.2 2004/08/29 06:46:20 allen Exp $ # Home/Docs/Licensing: http://www.hutchison.org/allen/source/bxr/ # BXR Mailing List: http://groups.yahoo.com/groups/bxr/ # package BXR; # --- Configurable Variables ---- # What's this blog's title? $blogName = "BLOG NAME"; # Where are this blog's entries kept? $datadir = "/path/to/blosxom/data/dir/"; # What's my preferred base URL for this blog? $url = "http://www/path/to/blosxom/"; # What file extension signifies a blosxom entry? $file_extension = "blos"; # What username will you use with your client? $username = "USERNAME"; # What password will you use with your client? $password = "PASSWORD"; # What is your first name? $firstName = "FIRSTNAME"; # What is your last name? $lastName = "LASTNAME"; # What is your email address? (single quotes are important) $email = 'you@domain.org'; # What is your nickname? (required for blogger api) $nickname = "nick"; #---------------------------------------------------- our ( $datadir, $url, $file_extension, $username, $password, $blogName, $firstName, $lastName, $email, $nickname ); use strict; use warnings; use File::Find; use File::stat; use POSIX qw(strftime); use XMLRPC::Transport::HTTP; # Remove trailing / from url and datadir $datadir =~ s!/$!!, $url =~ s!/$!!; sub auth { my $user = shift @_; my $pass = shift @_; unless ( ( $user eq $username ) and ( $pass eq $password ) ) { die SOAP::Fault->faultcode('BXR.authError') ->faultstring('Incorrect Username or Password'); } } sub getPostIdFromFilename { my $filename = shift @_; $filename =~ s!$datadir/!!; return $filename; } sub getFilenameFromPostId { my $postid = shift @_; unless ( $postid =~ /$datadir/ ) { $postid = "$datadir/$postid"; } return $postid; } sub getCategoryFromFilename { my $filename = shift @_; if ( $filename =~ /$file_extension$/ ) { $filename =~ s!$datadir/(.*)/.*$file_extension$!$1!; } else { $filename =~ s!$datadir/!!; } return $filename; } sub getIsoTime { my $time = shift @_ || time; my $isoTime = strftime( "%Y-%m-%dT%T", localtime($time) ); return $isoTime; } XMLRPC::Transport::HTTP::CGI->dispatch_to( 'metaWeblog', 'blogger' )->handle; ######################################################## package blosxom; ######################################################## sub getCategories { my $start = shift @_ || $BXR::datadir; my $extension = shift @_ || $BXR::file_extension; my %cats; File::Find::find( sub { $File::Find::name =~ /$extension$/ ? $cats{$File::Find::dir}++ : 0; }, $start ); return ( \%cats ); } sub getRecentPosts { my $postsWanted = shift @_; my $start = shift @_ || $BXR::datadir; my $extension = shift @_ || $BXR::file_extension; my %posts; File::Find::find( sub { $File::Find::name =~ /$extension$/ ? $posts{$File::Find::name} = File::stat::stat($File::Find::name)->mtime : 0; }, $start ); my @postList = sort { $posts{$b} <=> $posts{$a} } keys %posts; @postList = @postList[ 0 .. ( $postsWanted - 1 ) ]; return ( \@postList ); } sub getPost { my $filename = shift @_; my $start = shift @_ || $BXR::datadir; my $extension = shift @_ || $BXR::file_extension; if ( -e $filename ) { open POST, "$filename"; my @post = ; close POST; my %struct; $struct{'postid'} = $filename; $struct{'dateCreated'} = File::stat::stat($filename)->mtime; $struct{'title'} = shift @post; foreach (@post) { $struct{'description'} .= $_; } my @cats; $filename =~ s!$start/(.*)/.*$extension$!$1!; push @cats, $filename; $struct{'categories'} = \@cats; return \%struct; } else { return 0; } } sub newPost { my $struct = shift @_; my $start = shift @_ || $BXR::datadir; my $extension = shift @_ || $BXR::file_extension; my $filename = lc( $struct->{'title'} ); $filename =~ s/\W+/_/g; $filename =~ s/_+$//; if ( $struct->{'categories'} ) { $filename = "$start/$struct->{'categories'}[0]/$filename.$extension"; } else { $filename = "$start/$filename.$extension"; } unless ( -e $filename ) { open POST, ">$filename"; print POST "$struct->{'title'}\n\n"; print POST "$struct->{'description'}\n"; close POST; } return $filename; } sub editPost { my $struct = shift @_; my $start = shift @_ || $BXR::datadir; my $extension = shift @_ || $BXR::file_extension; my $filename = lc( $struct->{'title'} ); $filename =~ s/\W+/_/g; $filename =~ s/_+$//; $filename = "$start/$struct->{'categories'}[0]/$filename.$extension"; if ( -e $filename ) { unlink $filename; } return ( newPost($struct) ); } sub deletePost { my $filename = shift @_; if ( -e $filename ) { unlink $filename; return 1; } else { return 0; } } ######################################################## package metaWeblog; ######################################################## sub getPost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $postid = shift; my $username = shift; my $password = shift; BXR::auth( $username, $password ); $postid = BXR::getFilenameFromPostId($postid); my $post = blosxom::getPost($postid); my $date = BXR::getIsoTime( $post->{'dateCreated'} ); $post->{'dateCreated'} = SOAP::Data->type( dateTime => "$date" ); $post->{'postid'} = BXR::getPostIdFromFilename( $post->{'postid'} ); return $post; } sub getRecentPosts { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $blogid = shift; my $username = shift; my $password = shift; my $numberOfPosts = shift; BXR::auth( $username, $password ); my $postIds = blosxom::getRecentPosts($numberOfPosts); my @retList; foreach ( @{$postIds} ) { my $post = getPost( $_, $username, $password ); push @retList, $post; } return \@retList; } sub getCategories { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $blogid = shift; my $username = shift; my $password = shift; BXR::auth( $username, $password ); my $cats = blosxom::getCategories(); my %retList; foreach ( keys %{$cats} ) { $_ = BXR::getCategoryFromFilename($_); $retList{'struct'}{$_}{'description'} = "No Description"; $retList{'struct'}{$_}{'rssUrl'} = "$BXR::url/$_/index.rss"; $retList{'struct'}{$_}{'htmlUrl'} = "$BXR::url/$_"; } return %retList; } sub newPost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $blogid = shift @_; my $username = shift @_; my $password = shift @_; my $struct = shift @_; my $publish = shift @_; BXR::auth( $username, $password ); my $postid = blosxom::newPost($struct); $postid =~ s!$BXR::datadir/!!; return $postid; } sub editPost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $blogid = shift @_; my $username = shift @_; my $password = shift @_; my $struct = shift @_; my $publish = shift @_; BXR::auth( $username, $password ); my $rc = blosxom::editPost($struct); return $rc; } ######################################################## package blogger; ######################################################## sub getUsersBlogs { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $username = shift @_; my $password = shift @_; BXR::auth( $username, $password ); my %struct; $struct{'url'} = $BXR::url; $struct{'blogid'} = 1; $struct{'blogName'} = $BXR::blogName; my @return; push @return, \%struct; return \@return; } sub getUserInfo { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $username = shift @_; my $password = shift @_; BXR::auth( $username, $password ); my %struct = ( userid => 1, firstname => $BXR::firstName, lastname => $BXR::lastName, nickname => $BXR::nickname, email => $BXR::email, url => $BXR::url ); return \%struct; } sub getRecentPosts { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $blogid = shift @_; my $username = shift @_; my $password = shift @_; my $numberOfPosts = shift @_; BXR::auth( $username, $password ); my $postIds = blosxom::getRecentPosts($numberOfPosts); my @retList; foreach ( @{$postIds} ) { my $post = getPost( $appkey, $_, $username, $password ); push @retList, $post; } return \@retList; } sub getPost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $postid = shift @_; my $username = shift @_; my $password = shift @_; BXR::auth( $username, $password ); $postid = BXR::getFilenameFromPostId($postid); my $post = blosxom::getPost($postid); my $date = BXR::getIsoTime( $post->{'dateCreated'} ); $post->{'dateCreated'} = SOAP::Data->type( dateTime => "$date" ); $post->{'postid'} = BXR::getPostIdFromFilename( $post->{'postid'} ); $post->{'content'} = $post->{'description'}; delete $post->{'description'}; $post->{'userId'} = 1; return $post; } sub editPost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $postid = shift @_; my $username = shift @_; my $password = shift @_; my $content = shift @_; my $publish = shift @_; BXR::auth( $username, $password ); my $filename = BXR::getFilenameFromPostId($postid); my $post = blosxom::getPost($filename); $post->{'description'} = $content; my $rc = blosxom::editPost($post); return $rc; } sub newPost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $blogid = shift @_; my $username = shift @_; my $password = shift @_; my $content = shift @_; my $publish = shift @_; BXR::auth( $username, $password ); my $title = $content; $title =~ s/((\S+\s+){4}\S+).*/$1/; my %struct; $struct{'description'} = $content; $struct{'title'} = $title; my $filename = blosxom::newPost( \%struct ); my $postid = BXR::getPostIdFromFilename($filename); return $postid; } sub deletePost { shift if UNIVERSAL::isa( $_[0] => __PACKAGE__ ); my $appkey = shift @_; my $postid = shift @_; my $username = shift @_; my $password = shift @_; BXR::auth( $username, $password ); my $filename = BXR::getFilenameFromPostId($postid); my $rc = blosxom::deletePost($filename); } =head1 NAME BXR: The Blosxom XML-RPC Interface =head1 SYNOPSIS Provides an XML-RPC interface to the Blosxom blogging system for the metaWeblog and blogger APIs. Once it's installed you will point your blogging client at the following URL as the XML-RPC access point: http://your/blosxom/path/blosxom-xmlrpc.cgi =head1 VERSION $Id: blosxom-xmlrpc.cgi,v 1.2 2004/08/29 06:46:20 allen Exp $ =head1 AUTHOR Allen Hutchison , http://www.hutchison.org/allen/ =head1 BUGS Currently there is a problem with the Ecto weblog editor. Otherwise things seem to be working. Address bug reports and comments to me or to the BXR mailing list [http://www.yahoogroups.com/groups/bxr/]. =head1 INSTALLATION =over 4 =item 1. Install the SOAP::Lite module from CPAN. =item 2. Download the script and save it in the same directory as blosxom.cgi. Make sure it's executable by the www server just like blosxom.cgi (chmod a+x blosxom-xmlrpc.cgi) =item 3. Fill out the variables at the top to match your blosxom.cgi varialbes. Make sure to set a username and password, they can be anything you like and you will be using them with your blog editor. =item 4. Use the URL of the script as the access-point for your client. =back =head1 LICENSE BXR (Blosxom XML-RPC) Copyright 2004, Allen Hutchison (This license is the same as Blosxom's) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =cut