Dec 30 2002

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

60 Comments

  1. Elaine on 16 Jan 2004 at 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. mph on 28 Feb 2004 at 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. f. on 5 Mar 2004 at 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. Brad on 6 Apr 2004 at 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. scotty on 6 Apr 2004 at 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. Pingback: Pudding Time!

  7. Pingback: InsultConsult

  8. Pingback: From The Orient

  9. Pingback: quicklinks

  10. Pingback: esprit libre

  11. Pingback: The Maelstr

  12. Pingback: Raw

  13. Pingback: Joe Grossberg

  14. Pingback: blog-o-fobik

  15. Suchi Garg on 28 Oct 2004 at 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.

  16. scotty on 28 Oct 2004 at 6:45 am #

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

  17. Suchi Garg on 2 Nov 2004 at 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??

  18. suchi on 3 Feb 2005 at 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?

  19. suchi on 3 Feb 2005 at 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;

  20. matt on 10 Apr 2005 at 10:52 pm #

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

  21. Pingback: zosome :: Another try at stir-fry… :: June :: 2005

  22. Pingback: Thought Bottles :: Testing mtsend.py :: August :: 2005

  23. Pingback: henman.ca » Blog Archive » Testing out blog clients

  24. Pingback: Lewis Collard

  25. Pingback: La domo de karotoj » Mi aktualigita mian blogon

  26. Pingback: hairy ‘ » Blog Archive » Importing of The Old Important Stuff Into WordPress.com from Old Weblog: mtsend.py (part I)

  27. Pingback: sleepy jenkins :: blog by vim :: February :: 2006

  28. Pingback: le blog d’Alexandre FROUART » Blog Archive » mtsend.py

  29. Pingback: le blog d’Alexandre FROUART » Blog Archive » Poster des images sur son blog en les uploadant automatiquement

  30. Pingback: le blog d’Alexandre FROUART » Blog Archive » envoi des formules mathématiques sur son blog

  31. joshbzin on 16 Mar 2006 at 11:55 am #

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

  32. Pingback: Emil Sit » Migration

  33. Pingback: Emil Sit » Colophon, Part 2

  34. Pingback: madame, a move is in order « diskographik

  35. Phil on 18 Oct 2006 at 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

  36. scotty on 18 Oct 2006 at 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 :)

  37. Pingback: Mysterya. Наша жизнь. » Архив блога » mtsend.py+vim

  38. Pingback: Misc Random

  39. Jasper on 28 Feb 2007 at 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

  40. trecall on 2 Mar 2007 at 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.

  41. Miles on 10 Mar 2007 at 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.

  42. Mark Blakeney on 31 Mar 2007 at 8:43 am #

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

  43. Pingback: CLICK

  44. Pingback: La domo de karotoj » XML-RPC and Ubuntu

  45. Lloyd Budd on 14 Sep 2007 at 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’

  46. Pingback: Finally: Blogging from the CLI with mtsend.py » oscillate it

  47. Justin Mason on 7 Mar 2008 at 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()

  48. ClintJCL on 9 Mar 2008 at 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?

  49. Pingback: Millenniumdark » Blog Archive » mtsend命令行下的blog客户端

  50. Pingback: House blogject/tweetject experiments « Notes from a small field

  51. r benegal on 23 Aug 2008 at 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

  52. Marco on 28 Feb 2009 at 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

  53. ClintJCL on 4 Mar 2009 at 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! ):

  54. benegal r on 4 Mar 2009 at 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.

  55. ClintJCL on 6 Mar 2009 at 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.

  56. ClintJCL on 6 Mar 2009 at 7:03 am #

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

  57. Pingback: DJ’s Weblog » Blog Archive » Blosxom entries into MT

  58. benegal on 29 Oct 2009 at 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

  59. Phillip Smith on 18 Apr 2011 at 8:46 am #

    Any chance of you throwing this on Github? I’d like to add support for post status, tags, and so on, and it would be great to be able to contribute it back.

  60. Vash on 2 Jan 2012 at 7:40 pm #

    I’m having a bit of a problem:

    I’m hosting my own wordpress site on my own server. I want to use this python script on that server, but I can’t figure out how to get the URL correct. I’ve tried localhost/xmlrpc.php, but I just get a 301 Moved Permanently error. I’ve also tried my actual dns/IP address, but it just times out. I also also tried giving it the absolute path to the xmlrpc.php file, but no such luck.

    I’m running apache2 on an ubuntu server. I know everything is set up fine because I can use this script with the same config and post from a different network (using my dns/IP). Unfortunately, I need to be able to use this script on the same machine that the wordpress site is being hosted on. What’s the best way to go about doing this?

    Thanks!
    Vash

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>