mtsend.py – A Command Line Tool for Movable Type

I’ve spent a bit of time coding and testing during the Christmas break, and here it is – mtsend.py, a command line tool for Movable Type (SY: Works fine in WordPress as well) written in Python. It uses XML-RPC exported by Movable Type to retrieve posts, edit posts, make new post entry, fix up categories and list recent posts, etc. It is a command line tool, i.e. no graphical user interface, but it is relatively easy to use. It is written in a scripting language called Python, and it currently has only been tested under Python 2.1 on Windows 2000, and Python 2.2/2.3/2.4 on Linux (Mandrake 8.2/9Gentoo), Windows 2000/XP and Mac OS X 10.2/10.3.

You can download the source code here:

  • Download mtsend.py 1.1 (Released on 19 Nov 2005)

What is it?

mtsend.py is a command line tool that utilise Movable Type’s XML-RPC interface. It allows its users to edit/post/view/list post entries on a Movable Type site. It takes input from the standard input, and sends output to the stdout, just like all the other well-behaved command line applications. It uses a file format similar to Movable Type’s import export format.

Updated: It appears that mtsend.py also works with WordPress, as WordPress has implemented a compatible XML-RPC interface. In fact I am using this same script everyday since I switched from MT to WP.

Updated 2: There seems to be some timezone issue regarding how WP handles dateCreated attribute, tested in WP 1.5.

Why command line?

Real man works on text-consoles.

No. It is not true. But I found myself spending most of my day in the front of text console/command prompt. My typical Windows desktop consists 2 of VIM windows and 2 SSH session to other Linux boxes, and half the website I browse everyday is in the excellent text-mode browser w3m. I don’t like to fire up Mozilla and open up the Movable Type site just to post an blog entry. Moreover, I have very bad spelling problem, and posting through the web interface does not allow me to check my spellings. Of course I can edit my post in a separate editor, run that external file through a spell checker, and then paste the content into the Movable Type’s edit entry page. However, since Movable Type has already exported its interface via XML-RPC, why don’t I just write a simple script to post that text file after I’ve run it through the spell checker? The result becomes mtsend.py.

With a command line tool that can work with pipes and standard IO, it makes posting much easier for me. I can now write up a blog entry in Vi, save the text file, run it through aspell check, and then direct it to mtsend.py to have it posted to my blog site. All under your favourite Unix shell!

Documentation

These are taken directly from the source code – so just download the latest source and you will have the lot.

Configuration File

  Configuration file for mtsend.py is in the style of Windows INI files, which
  consist of sections and key/value pairs. There are 3 main sections - global,
  site and blog.

  Global Section:
    There is only one key/value in this section, and it is used to note the
    default blog alias to use if it is not provided on the command line.
    For example:

      [global]
      default=example

    It shows the default blog alias will be 'example'

  Site Section:
    You can have multiple site sections for each Movable Type installation
    you have access to. The section name will be [site-"site name"]. For
    example:

      [site-test]
      url=http://testdomain.com/mtinstall/mt-xmlrpc.cgi
      username=foo
      password=bar
      encoding=UTF-8

    It defines site "test" with the URL to the MovableType's XML-RPC CGI
    script, and the username/password used to access that site. "encoding" is
    optional, and defaults to UTF-8.

  Blog Section:
    You can have multiple blog sections for each Movable Type blogs you have
    on the sites you have access to. Blogs are distinguished by their 'alias',
    which you can select in the command line using -a. The section name for
    this blog will be [blog-"blog alias"]. For example,

      [blog-example]
      site=test
      blogid=3

    Each blog section must have "site", which indicates the site this blog
    belongs to, so that mtsend would be able to locate site-related
    information from the configuration file. It also needs the blog ID on that
    site. To find out all the blog IDs, you can use -B "site name" to print
    out the list.

Post Format

  When editing or posting via mtsend, the post needs to be in a specific
  format. The format is very close to Movable Type's import/export format,
  which is documented here:

http://www.movabletype.org/docs/mtmanual_importing.html

  It consists of a header and body. For example:

    [header1]: [value1]
    [header2]: [value2]
    [header3]: [value3]
    -----
    BODY:
    ....
    -----
    EXTENDED BODY:
    ....
    -----
    EXCERPT:
    ....

  Extended body and excerpt are optional in a post. Most header elements are
  optional when you are creating a new post. If they do not provide a value,
  then the default value configured by Movable Type will be used.

  These are the header keys/values:

    TITLE:
      The title of this post.

    ALLOW COMMENTS: 0/1
      Whether this post allows comments.

    ALLOW PINGS: 0/1
      Whether this post allows trackback pings.

    CATEGORY:
      The category associated with this post entry. You can have multiple
      CATEGORY in the header. The first CATEGORY automatically becomes the
      primary category, if PRIMARY CATEGORY is not specified.

    CONVERT BREAKS: 0/1/customised text filter name.
      Whether the line break will be automatically converted into <br/> and
      <p/> when posted. It can also be the name of an installed text filer. To
      get the list of installed text filter, use mtsend.py -T

    DATE: dd/mm/yyyy HH:MM:SS [AM|PM]
      The post date. It might not work if you are creating a new post.

    KEYWORDS:
      The keywords of your post.

    PING:
      The URL to be pinged during posting. You can have multiple PING in the
      header.

Command Line Argument

  Usage:
      mtsend.py [action] [options]

  Actions:
      -B site     List all the blogs you can access in [site]. Site has to be in
                  the configuration file.
      -C          Print out a list of existing categories.
      -E postid   Edit an old post. It will read the post entry from the
                  standard input, in Movable Type's import/export format, and
                  then save it back to the server. If the value is '-', then it
                  will try to detect the postid from the input message itself.
      -G postid   Retrieve/get post from the blog. If the value is '-', it will
                  then try to get the most recent blog entry. Retrieved entry
                  will be printed to the standard output.
      -L num      List the most recent [num] posts.
      -N          Posting a new blog. The entry, in the Movable Type
                  import/export format, is read from the standard input.
      -P postid   List out trackback pings to this post.
      -R postid   Rebuild all the static files related to this entry.
      -T          List out the text filters installed on the server.
      -U filename Upload a file, reading from standard input, to the blog site,
                  with destination filename provided.
      -V          Show version information.

  Options:
      -a alias    Use "alias" as the blog alias. This script will locate
                  relavent site URL/username/password information using this
                  alias.
      -c config   Load "config" as configuration file, instead of $HOME/.mtsendrc
      -h          Display this help message.
      -q          Decrease verbose level.
      -v          Increase verbose level. Message goes to standard error.

History

  • 1.1 – 19 Nov 2005
    • Add SSL support over proxy.
  • 1.0 – 26 May 2005
    • time module related fix for Python 2.4.
    • Ensure all cells passed to printTable() function are in string-type.
  • 0.6.1 – 6 Apr 2004
    • Properly handles mt_allow_comments for MT2.6 servers.
  • 0.6 – 1 Apr 2004
    • Add build-in support for HTTP proxy server, which is detected via environment variable HTTP_PROXY.
    • Alternative encoding for XML-RPC packets.
  • 0.5 – 14 Oct 2003
    • Remove the support of MT2.5. Use the older version of mtsend.py if you need these supports.
    • Support KEYWORDS and PING into the header.
    • Add new functionalities provided by MT2.6’s backend.
      • List out trackback pings of a post.
      • List out text filters installed.
    • Documentation in the source code.
  • 0.4 – 10 Mar 2003
    • Support the new metaWeblog.newMediaObject() function via mtsend.py -U filename, i.e. you can now upload text/binary files to your MovableType site via mtsend.py!
    • Use mt.getRecentPostTitles() function in MT2.6 to save bandwidth.
    • Some bug fixes due to some inconsistency between MT2.6 and MT2.5.
  • 0.3 – 3 Jan 2003
    • Make it to work on Python 2.1. “xmlrpclib” needs to be downloaded separately. It is tested on Python 2.1.2 for Windows.
  • 0.2 – 30 Dec 2002
    • Fixed a bug in saving the post entry back, where new line characters are stripped.
  • 0.1 – 30 Dec 2002
    • Initial public version

External Resources

Category: General | Mon, 30 December 2002 11:53 am

Links to This Article

  1. Thu, 30 January 2003 6:46 am
    Pudding Time!
  2. Thu, 27 February 2003 12:13 pm
    InsultConsult
  3. Thu, 27 March 2003 2:23 am
    From The Orient
  4. Sun, 1 February 2004 9:28 am
    quicklinks
  5. Tue, 13 April 2004 6:01 pm
    esprit libre
  6. Wed, 14 April 2004 12:25 am
    The Maelstr
  7. Wed, 14 April 2004 7:37 pm
    Raw
  8. Sat, 24 April 2004 6:40 am
    Joe Grossberg
  9. Mon, 24 May 2004 1:25 am
    blog-o-fobik
  10. Wed, 15 June 2005 3:16 am
    zosome :: Another try at stir-fry… :: June :: 2005
  11. Sat, 27 August 2005 2:58 pm
    Thought Bottles :: Testing mtsend.py :: August :: 2005
  12. Sat, 8 October 2005 1:58 pm
    henman.ca » Blog Archive » Testing out blog clients
  13. Thu, 17 November 2005 10:07 am
    Lewis Collard
  14. Mon, 2 January 2006 7:13 am
    La domo de karotoj » Mi aktualigita mian blogon
  15. Sun, 15 January 2006 7:45 pm
    hairy ‘ » Blog Archive » Importing of The Old Important Stuff Into WordPress.com from Old Weblog: mtsend.py (part I)
  16. Mon, 13 February 2006 2:45 pm
    sleepy jenkins :: blog by vim :: February :: 2006
  17. Mon, 6 March 2006 11:14 am
    le blog d’Alexandre FROUART » Blog Archive » mtsend.py
  18. Tue, 7 March 2006 7:50 am
    le blog d’Alexandre FROUART » Blog Archive » Poster des images sur son blog en les uploadant automatiquement
  19. Tue, 7 March 2006 8:01 am
    le blog d’Alexandre FROUART » Blog Archive » envoi des formules mathématiques sur son blog
  20. Fri, 17 March 2006 1:50 am
    Emil Sit » Migration
  21. Fri, 17 March 2006 1:51 am
    Emil Sit » Colophon, Part 2
  22. Tue, 10 October 2006 1:58 am
    madame, a move is in order « diskographik
  23. Tue, 19 December 2006 6:16 am
    Mysterya. Наша жизнь. » Архив блога » mtsend.py+vim
  24. Thu, 1 February 2007 6:29 pm
    Misc Random
  25. Thu, 10 May 2007 9:24 am
    CLICK
  26. Wed, 5 September 2007 5:50 am
    La domo de karotoj » XML-RPC and Ubuntu
  27. Sun, 10 February 2008 7:47 am
    Finally: Blogging from the CLI with mtsend.py » oscillate it
  28. Sat, 5 April 2008 11:33 pm
    Millenniumdark » Blog Archive » mtsend命令行下的blog客户端
  29. Sun, 4 May 2008 8:35 am
    House blogject/tweetject experiments « Notes from a small field
  30. Thu, 30 April 2009 7:52 pm
    DJ’s Weblog » Blog Archive » Blosxom entries into MT

Comments

1.
Avatar for Elaine
Posted by Elaine on Fri, 16 January 2004 12:05 am

Well done. mtsend.py looks like exactly what I wanted for a command line client, and seems to be working well under Fedora Core.


2.
Avatar for mph
Posted by mph on Sat, 28 February 2004 7:29 am

Funny… I’m having some problems with it from OS X/Panther:

ornithopter:~ mph$ cat .blog.tmp|mtsend.py -N -vv
Parsing post entry from standard input…
Traceback (most recent call last):
File “/Users/mph/bin/mtsend.py”, line 771, in ?
main(sys.argv[1:])
File “/Users/mph/bin/mtsend.py”, line 763, in main
mt.execute()
File “/Users/mph/bin/mtsend.py”, line 219, in execute
handler()
File “/Users/mph/bin/mtsend.py”, line 313, in execute_n
post, cts, publish = parsePost()
File “/Users/mph/bin/mtsend.py”, line 579, in parsePost
raise Exception, ‘Invalid field key: %s’ % key
Exception: Invalid field key: BODY


3.
Avatar for f.
Posted by f. on Fri, 5 March 2004 10:25 am

mph, this happened to me under Panther (btw. other unixes too) when i failed to insert the “” line before BODY: (and before EXTENDED BODY: too ).
I now just take care that the input looks very much the same like the output of a “./mtsend.py -G – ” and had no problems since then.

This was exactly the only “problem” I had.
( forgive me, I surely rtfm, but I found the line not really stated as a must :)
This great one-file-software saved my day. Thank you very much.

My search done before leaded me deep into the heart of XML-specs and
RPC-definitions, MT inner working stuff, it made sort of … you know :)

and this one just worked….


4.
Avatar for Brad
Posted by Brad on Tue, 6 April 2004 5:49 am

Howdy… thanks for writing this script, it’s exactly what I was looking for. I guess great minds think alike.

Just one nitpick: I do believe the “ALLOW COMMENTS” header is not a boolean, but a string (integer, really, but whatever). A value of 2 means comments should be published if they exist, but no further comments can be posted. This can come in handy if, say, one wants to use your script in an occasional cron job to disable comments to old posts, as I in fact was attempting and failing miserably to do until examining the code. Looks like just a couple of lines in obvious places need adjustment.

Thanks again!


5.
Avatar for scotty
Posted by scotty on Tue, 6 April 2004 6:38 am

Brad,

You are right. Some code is still stuck at the stage of Movable Type 2.5x. I’ll try to fix that. Thanks.


6.
Avatar for Suchi Garg
Posted by Suchi Garg on Thu, 28 October 2004 3:06 am

Thanks for this wonderful script. Please tell me one thing. I’m using ExtraFields plugin, and have 2 extra fields. Is it possible for me to post those entries as well using mtsend.py.
Any ideas will be appreciated, thanks.


7.
Avatar for scotty
Posted by scotty on Thu, 28 October 2004 6:45 am

No. Don’t think ExtraFields would be supported as it is not supported in MT’s XML-RPC interface.


8.
Avatar for Suchi Garg
Posted by Suchi Garg on Tue, 2 November 2004 3:09 pm

Thanx for your response. I have a new problem. In order to solve the issue I asked u in my last comment, i wrote a php script, which
1. created a new MT entry,
2. filled up the required table for Extra fields
3. then i rebuild the entry taking the entry id from 1 above (using -R option of mtsend)

My problem is that this rebuild is not working. When i was testing it on my test server, it worked fine, but in the production server, it just does not rebuild. Can u pls help?? here is part of my code:


$blog = "bnaarticles";
$conf_file = "mtsend.conf";
$command_str = " cat $txtfilename | /var/www/mt/admin/mtsend.py -c $conf_file -N -a $blog";
$entry_id_added = `$command_str`;

# now make the entry to the extra plugin table
$query = "insert into mt_bna_author values ($entry_id_added, '$user_name', '$user_email')";
$result = mysql_query($query) or die("Query failed : " . mysql_error());
unlink($txtfilename);

# now rebuild the entry. This is needed becoz the template does not
# know abt the bna_author etc. before the execution of the
# SQL above, and hence the earlier built was not useful

$command_str = " /var/www/mt/admin/mtsend.py -c $conf_file -R $entry_id_added -a $blog ";
$command_executed = exec($command_str);

When i echo out the commands, they seem to be ok. Can u please help me??


9.
Avatar for suchi
Posted by suchi on Thu, 3 February 2005 4:13 pm

Hi again. I have a new problem now. Last week i installed XML::Parser and XML::Simple on my machine as i needed to get some plugin installed. After that, my mtsend.py is giving me the following error:
Error:
What could be the problem here?? Any ideas?


Avatar for suchi
Posted by suchi on Thu, 3 February 2005 4:15 pm

Sorry, the error didnt turn up in the previous comment

Error: <Fault Client: ‘Application failed during request deserialization: \nnot well-formed (invalid token) at line 50, column 62, byte 6788 at /usr/lib/perl5/XML/Parser.pm line 187\n’$gt;


Avatar for matt
Posted by matt on Sun, 10 April 2005 10:52 pm

Seems weird that you would get a Perl error from a PHP and python script


Avatar for joshbzin
Posted by joshbzin on Thu, 16 March 2006 11:55 am

MT’s docs on the importexport format have moved to http://www.sixapart.com/movabletype/docs/mtimport


Avatar for Phil
Posted by Phil on Wed, 18 October 2006 9:21 am

Is there any chance to use mtsend.py with emacs to have a functionality equaly to TextMate on OS X:
writting in Emacs and publishing via mtsend.py to your wordpress-blog?

I can’t imagine, that I can’t use Emacs as a blogeditor for wordpress.
:-o


Avatar for scotty
Posted by scotty on Wed, 18 October 2006 9:49 am

Phil,

As you can see that mtsend.py has not been actively developed for a while (anyone would like to take over?) However I don’t see why you can’t invoke it from Emacs.

Too bad that I am not an Emacs user, because Vi rules :)


Avatar for Jasper
Posted by Jasper on Wed, 28 February 2007 3:43 am

I’m not having much luck, I keep getting Error: ‘url’ although I’ve verified that the url is correct:

cfg reads:

[global]
default=domainname

[site-domainname]
url=http://www.domainname.com/xmlrpc.php
username=xxx
password=xxx


Avatar for trecall
Posted by trecall on Fri, 2 March 2007 9:06 pm

Dear Scott,
v1.1 is working like a charm on OS X Tiger. I am able to post and then retrieve.

However, when I try to retrieve an old post entered through the web interface, I get:

Error: 'ascii' codec can't encode character you'\u2014' in
position 72: ordinal not in range(128)

BTW, the AUTHOR field is not allowed. We have to remove it.

Thanks for the wonderful tool.


Avatar for Miles
Posted by Miles on Sat, 10 March 2007 5:03 am

I am having the same problem as Jasper. Everything is giving me an error ‘url’. I am trying to access a wordpress blog.


Avatar for Mark Blakeney
Posted by Mark Blakeney on Sat, 31 March 2007 8:43 am

Just thought I’d add a comment here that this great utility also works fine with Drupal.


Avatar for Lloyd Budd
Posted by Lloyd Budd on Fri, 14 September 2007 12:58 am

In case anyone else thought to use this to export content from TypePad, it breaks in enough ways that I wouldn’t feel comfortable using it without thoroughly testing it.

The two issues I encountered before moving on to try other solutions are:
1st. xmlrpclib.DateTime(val) is not in ISO8601, it is like “2007-09-11T01:24:40Z”
2nd. cat in cts are a pair not a tuple, no ‘isPrimary’


Avatar for Justin Mason
Posted by Justin Mason on Fri, 7 March 2008 10:16 pm

hi — great script! could do with an update, though ;)

Recently, I’ve started getting this error with my WP 2.3.3 blog:

blogclient -v -L 2
Traceback (most recent call last):
File “/home/jm/bin/blogclient”, line 906, in
main(sys.argv[1:])
File “/home/jm/bin/blogclient”, line 897, in main
mtsend.execute()
File “/home/jm/bin/blogclient”, line 231, in execute
handler()
File “/home/jm/bin/blogclient”, line 314, in execute_l
self.get_password(), num)
File “/usr/local/putplace/Python-2.5.1/lib/python2.5/xmlrpclib.py”, line 1147, in __call__
return self.__send(self.__name, args)
File “/usr/local/putplace/Python-2.5.1/lib/python2.5/xmlrpclib.py”, line 1437, in __request
verbose=self.__verbose
File “/usr/local/putplace/Python-2.5.1/lib/python2.5/xmlrpclib.py”, line 1201, in request
return self._parse_response(h.getfile(), sock)
File “/usr/local/putplace/Python-2.5.1/lib/python2.5/xmlrpclib.py”, line 1335, in _parse_response
p.feed(response)
File “/usr/local/putplace/Python-2.5.1/lib/python2.5/xmlrpclib.py”, line 547, in feed
self._parser.Parse(data, 0)
xml.parsers.expat.ExpatError: XML or text declaration not at start of entity: line 2, column 0

it appears the server is sending back an additional newline at the start of the XML-RPC response, I’m not sure why, or if this is valid.

I hacked around it by adding the re.sub() line below to xmlrpclib.py:

def _parse_response(self, file, sock):
# read response from input file/socket, and parse it

p, u = self.getparser()

while 1:
if sock:
response = sock.recv(1024)
else:
response = file.read(1024)
if not response:
break
if self.verbose:
print “body:”, repr(response)
response = re.sub(r’(?s)^\n’, ”, response) # jm fix
p.feed(response)

file.close()
p.close()

return u.close()


Avatar for ClintJCL
Posted by ClintJCL on Sun, 9 March 2008 10:58 am

Hi,

I am interested in using this tool to post to LiveJournal as well. I am having very little success! Does anyone have any information on this?

I have tried both directly with LJ’s XMLRCP interace (no results), and indirectly using Delicious Glue, a script for forwarding del.icio.us’s daily blogposts to LJ via XMLRCP request. When I use delicious glue, I can get the body through, but not the subject and tags.

Any clue what I’m doing wrong?


Avatar for r benegal
Posted by r benegal on Sat, 23 August 2008 4:27 pm

Dear Scott,
Just a note to tell you that I still use mtsend.py both from Vim and the command line on Mac OS X Leopard (10.5.4), so it’s still chugging along great.

It inspired me to write a ruby blogger for Blogger (blogspot). I use mtsend.py for wordpress blogs.

I just wrote a little script so it decides on the alias based on the folder I am in (from vim, it’s a problem changing aliases)

if [ $# -eq 0 ]
then
echo "I got no filename"
exit 1
fi
alias=`basename $PWD`
echo "Using alias: $alias"
mtsend.py -N -a $alias < $*


rahul


Avatar for Marco
Posted by Marco on Sat, 28 February 2009 9:51 am

Hi,

I’m trying mtsend.py on a drupal site, and I, too, can’t get past the “Error:url’ message. What is the reason? Did anybody find it out?

Another question: can mtsend.py today set or read fields in posts which have custom fields?

TIA,
Marco


Avatar for ClintJCL
Posted by ClintJCL on Wed, 4 March 2009 9:27 am

I wanted to use this to create future-dated wordpress posts.. but.. it’s hard.

I have to manually offset for GMT. Fine. It’s not relaly 6:26PM, it’s 11:26PM. I know I’m GMT-5. That part is easy.

Now it successfully posts to wordpress — scheduled and everything! Except when the time comes, it doesn’t post. ARGH.

I literally will need to find another complete solution for blogging if that doesn’t work. It sucks, because I have been using mtsend for over a year! ):


Avatar for benegal r
Posted by benegal r on Wed, 4 March 2009 1:52 pm

I don’t know what you mean exactly, but I do use a shell script I wrote to pick up posts from a certain folder and post them using mtsend.py. The file names contain the date to be posted.
A cron job runs 2 times a day and checks.

mtsend.py follows the usual unix philosophy of one program doing one job.


Avatar for ClintJCL
Posted by ClintJCL on Fri, 6 March 2009 7:03 am

okay… the timezone issue is due to not using date_created_gmt … but anyway… i’m just trying to do a normal post like the last 200+ posts i’ve used mtsend.py for, and all of a sudden i’m getting an error:

Parsing post entry from standard input…
Saving new post entry…
Error:

Of course, how can i know what isn’t well formed when it doesn’t tell me what it is? Frustrated.


Avatar for ClintJCL
Posted by ClintJCL on Fri, 6 March 2009 7:03 am

Sorry, the error message didn’t come through. It was:
Fault -32700: ‘parse error. not well formed’


Avatar for benegal
Posted by benegal on Thu, 29 October 2009 5:47 pm

@clintJCL
I’ve had parse errors recently too. Seems like a server issue. I tried posting the next day and all was fine.
–rb


Add a comment

Gravatar is used. Email address is required but will not be displayed. Please keep your comment on topic. No spamming and/or bad language. First time poster will be moderated. Scott reserves the right to delete/edit your comments.