PHPを使ってMovableTypeにエントリを投稿したいなということで、
XML-RPCを使ってやってみた。

準備

まず、PEAR::XML_RPCをインストールします。
PEARが入っていれば、特に問題なくインストールできるかと。

PEARのインストールについては、こちらのエントリなどを参考にしてください。

PEAR::Net_UserAgent_Mobileを使う。



サーバサイドのプログラム

いろいろと拙いところもあると思いますが、晒しておきます。

<?php
/**
 * POST MovableType Entry by XML-RPC
 *
 * @version 0.1.0
 * @author is
 */
require_once "XML/RPC.php";

// 文字コード設定
$GLOBALS['XML_RPC_defencoding'] = 'UTF-8';

class PostMovableTypeEntry
{
  private static $mtHost;
  private static $mtXmlrpcPath;
  private static $mtPort;
  private static $mtUser;
  private static $mtXmlrpcPass;
  private static $mtBlogId;

  private static $client;
  private static $appkey;
  private static $username;
  private static $passwd;
  private static $blogId;
  private static $publish;
  private static $content;

  /**
   * factory
   *
   * @param string $host
   * @param string $xmlrpcPath
   * @param string $port
   * @param string $user
   * @param string|not login password $xmlrpcPass
   * @param string $blogId
   */
  public static function factory($args)
  {
    self::$mtHost       = $args['host'];
    self::$mtXmlrpcPath = $args['xmlrpcPath'];
    self::$mtPort       = $args['port'];
    self::$mtUser       = $args['user'];
    self::$mtXmlrpcPass = $args['xmlrpcPass'];
    self::$mtBlogId     = $args['blogId'];
  }

  /**
   * initialize
   */
  private static function initialize()
  {
    self::$client   = new XML_RPC_client(self::$mtXmlrpcPath, self::$mtHost, self::$mtPort);
    self::$appkey   = new XML_RPC_Value('', 'string');
    self::$username = new XML_RPC_Value(self::$mtUser, 'string');
    self::$passwd   = new XML_RPC_Value(self::$mtXmlrpcPass, 'string');
    self::$publish  = new XML_RPC_Value(1, 'boolean');
    self::$blogId   = new XML_RPC_Value(self::$mtBlogId, 'string');

  // タイムゾーン設定
    date_default_timezone_set('UTC');
  }

  /**
   * エントリ投稿
   * $post = array(
   *   'title',             // タイトル
   *   'description',       // 本文
   *   'dateCreated',       // 公開日時(ISO 8601(UTC):2009-01-01T08:35:04+00:00)
   *   'mt_allow_comments', // コメントの許可
   *   'mt_allow_pings',    // トラックバックの許可
   *   'mt_convert_breaks', // 改行設定(0:改行しない / 1:改行する)
   *   'mt_text_more',      // 追記
   *   'mt_excerpt',        // 概要
   *   'mt_keywords',       // キーワード
   *   'mt_tb_ping_urls',   // 送信したいトラックバック
   *   'mt_tags',           // タグ
   *   'publish',           // 再構築するかどうか(0:再構築する / 1:再構築しない)
   *   'categoryId',        // カテゴリID
   * );
   *
   * @param array $post
   */
  public static function post($post)
  {
  // initilize
    self::initialize();

  // デフォルト設定
    if(empty($post['mt_allow_comments']) === true) $post['mt_allow_comments'] = 0;
    if(empty($post['mt_allow_pings']) === true) $post['mt_allow_pings'] = 0;
    if(empty($post['dateCreated']) === true) $post['dateCreated'] = date(DATE_ATOM);

  // エントリ設定
    self::$content = new XML_RPC_Value(array(
      'title'             => new XML_RPC_Value($post['title'], 'string'),
      'description'       => new XML_RPC_Value($post['description'], 'string'),
      'dateCreated'       => new XML_RPC_Value($post['dateCreated'], 'string'),
      'mt_allow_comments' => new XML_RPC_Value($post['mt_allow_comments'], 'int'),
      'mt_allow_pings'    => new XML_RPC_Value($post['mt_allow_pings'], 'int'),
      'mt_convert_breaks' => new XML_RPC_Value($post['entryBody'], 'string'),
      'mt_text_more'      => new XML_RPC_Value($post['entryMore'], 'string'),
      'mt_excerpt'        => new XML_RPC_Value($post['mt_excerpt'], 'string'),
      'mt_keywords'       => new XML_RPC_Value($post['mt_keywords'], 'string'),
      'mt_tb_ping_urls'   => new XML_RPC_Value($post['mt_tb_ping_urls'], 'string'),
      'mt_tags'           => new XML_RPC_Value($post['mt_tags'], 'string'),
    ), 'struct');
    self::$publish   = new XML_RPC_Value($post['publish'], 'boolean');
    $message = new XML_RPC_Message(
      'metaWeblog.newPost',
      array(self::$blogId, self::$username, self::$passwd, self::$content, self::$publish)
    );
  // エントリ登録:登録が成功するとエントリーIDが返ってくる
    $postId = self::$client->send($message);

  // カテゴリ設定
    $categoryStruct = new XML_RPC_Value(array(
      'categoryId' => new XML_RPC_Value($post['categoryId'], 'string'),
      'isPrimary' => new XML_RPC_Value(1, 'boolean')
    ), 'struct');
    $category = new XML_RPC_Value(array($categoryStruct), 'array');
    if($postId && empty($post['categoryId']) === false):
      $message = new XML_RPC_Message(
        'mt.setPostCategories',
        array($postId->value(), self::$username, self::$passwd, $category)
      );
      $result = self::$client->send($message);
    endif;

  // 再構築
    if(self::$publish == 1):
      $message = new XML_RPC_Message(
        'mt.publishPost',
        array($postId->value(), self::$username, self::$passwd)
      );
      $result = self::$client->send($message);
    endif;
    return false;
  }
}
?>


プログラムの構成については、こちらのコードを参考にさせていただきました。

PHPを使って携帯からMTに記事を投稿する | チバのブログ


また、MovableType3.3でのXML-RPCについてはこちらを参考に。

Movable Type 3.3 マニュアル - XML-RPC API
MovableType で使える XML-RPC API


仕様上、「metaWeblog.newPost」は書かれていないようですが…
まぁ使えるのでそれを使いました。
それからこれも仕様には書かれていませんが、
XMLRPCServer.pmを読んでみた限りではタグも扱えるようなので、
そのあたりも追加しています。

lib/MT/XMLRPCServer.pm(206行目)
if (my $tags = $item->{mt_tags}) {
    require MT::Tag;
    my $tag_delim = chr($author->entry_prefs->{tag_delim});
    my @tags = MT::Tag->split($tag_delim, $tags);
    $entry->set_tags(@tags);
}



クライアントプログラム

上のプログラムを利用して投稿します。
クライアントプログラムの例はこんな感じです。

<?php
/**
 * Client
 */
require_once './PostMovableTypeEntry.class.php';
PostMovableTypeEntry::factory(array(
  'host' => 'www.hoge.com',
  'xmlrpcPath' => 'http://www.hoge.com/mt/mt-xmlrpc.cgi',
  'port' => '80',
  'user' => 'user',
  'xmlrpcPass' => 'pass',
  'blogId' => '1'
));
PostMovableTypeEntry::post(array(
  'title' => 'タイトル',
  'description' => '本文',
  'dateCreated' => '2009-01-01T08:00:00+00:00',
  'mt_allow_comments' => '0',
  'mt_allow_pings' => '0',
  'mt_convert_breaks' => '1',
  'mt_text_more' => '追記',
  'mt_excerpt' => '概要',
  'mt_keywords' => 'キーワード',
  'mt_tb_ping_urls' => '送信するトラックバック',
  'mt_tags' => 'test, sample',
  'publish' => '1',
  'categoryId' => '1',
));
?>



投稿上の注意

上のクライアントプログラムを実行するとエントリが投稿できます。
再構築しているので、ブログにも反映されます…が。
投稿日時(dateCreated)とタグ(mt_tags)には注意が必要です。


投稿日時

入力形式はISO 8601形式のUTCと決まっています。
仮にタイムゾーンを設定したとしても、UTCとして解釈されるので注意。

ちなみに現在時刻をISO 8601形式(UTC)で書き出したい場合、
次のように書くと書き出せるようです。

date_default_timezone_set('UTC');
echo date(DATE_ATOM)

また仕様上、指定日投稿というのは出来ないようです。
「mt.setNextScheduledPost」というメソッドも一応考えられてはいるようなのですが、
MT3.3では実装されていないような気がします。
(もし実装されていれば、再構築前にそれをセットするってことでしょうね)

なので、たとえ投稿日時を未来に設定したとしても、
普通に公開されてしまいます。
その辺を上手くやるには、再構築の部分を別処理にするか、
クライアントを動かすタイミングを調整するかということになるかと。


タグ

タグの区切り文字に合わせて入力する文字を変える必要があります。
ホントなら配列で与えて、取得した区切り文字で繋いで渡す方が良いんでしょうが…
面倒だったのでw
多分、デフォルトはカンマだとは思いますが。



というわけで、プログラムからMovableTypeに投稿する手段が準備できました。

さてこっからどう実装しようかな。



追記: 2009/01/10

「指定日投稿は出来ない」と書きましたが…モジュールをよく読むと、
未来の投稿に関するコードがいくつかありますね。
もしかすると、仕様書に書かれていないだけで使えるのかもしれません。

手順は次の通り。

  1. 指定したいエントリを下書きで登録する
  2. 登録したエントリを指定日投稿に設定する

また今度試してみます。

※ 上のコードですが…「下書きで投稿できない」など、いくつかバグがあるようです。
※ 検証後、差し替えます。



追記: 2011/08/05

「publish」設定について、誤って解釈していたようなので訂正しました。

0 … 再構築する / 1 … 再構築しない(投稿は常に公開になる)
0 … 投稿を未公開にする / 1 … 投稿を公開にする

詳しくはこちらに書かれていますが、

[MovableType]XML-RPCで投稿する場合の"publish"パラメータが広く誤解されている件 - DQNEO起業日記

「publish」を文字通り「publish」にするのはかなり古い仕様のようで、現在は仕様が異なっているとのこと。
もし、以前のような仕様(0で未公開)にしたい場合には、mt.cfgまたはmt-config.cgiに以下を記述する必要があります。

NoPublishMeansDraft 1

知るかwwww


なおこの「NoPublishMeansDraft」設定は、MovableType5でも有効であることを確認しています。