tohokuaikiのチラシの裏

技術的ネタとか。

MovableTypeのDataAPIを叩こうとしたら以前はXMLRPCだったのでRest対応はどうするか困ってしまった件をなんとかする。

経緯

昔、MovableTypeのデータAPIを叩いて大量のデータを投稿するとかよくやってたんだけど、そのころはXMLRPCを使ってた。

イマドキは、Rest APIでしょ!ってんで、MovableTypeのデータAPI V6ではRestが主流っぽかった。別にXMLRPCも使えるんだろうけど。

ということで、PHPのGuzzlehttpを使ってたんだけど、認証系とデータの取得系は行けるけど、データの更新系が全然うまくいかずに困ってしまってワンワンワワーン。

JavaScriptのDataAPIのサンプルがあるよ。

DataAPI使ったサンプルないかなーって公式のページを見つけたんだけど、読んでもよーわからん…というかこれNodeでやんの?えー。とか思ってたらいいページがあった。これ10年前かよ!

knowledge.sakura.ad.jp

で作ってみた

こんな感じ。JavaScriptSDKはmt-staticフォルダにあるので、それを使う。今回の mt-staticのパスは ↓のコードを参照。

https://mt.example.com/test_data_api.html を作って、https://mt.example.com/mt.cgiMovableTypeのデータAPIを叩くイメージ。MovableTypeの管理画面からAPIの許可を出しておいてくれ。

あと、Entryを投稿するには認証が必要なので、認証通ってない場合は「Login」のリンクをクリックしてID/Webサービスパスワード(管理画面へのログインPASSWORDではない!同じログイン画面を使うけどね)でログインする。

<html>
<head>
  <meta charset="UTF-8"/>
  <title>MT Data API</title>
  <script src="/mt-static/data-api/v6/js/mt-data-api.min.js"></script>
</head>
<body>
    <div id="entries"></div>
    <p><a href="#" id="login">Login</a></p>
    <p>UserName:<span id="username"></span></p>
    <button id="createEntry">Create Entry</button>
<script>
const siteId = 1;
var api = new MT.DataAPI({
  baseUrl:  "/mt-data-api.cgi",
  clientId: "your-client-id"
});

function goToAuthorizationUrl() {
  // 認証が成功した場合、現在の URL に戻ってくる
  const href = location.href;
  document.getElementById('login').href = api.getAuthorizationUrl(href);
}

if (! api.getTokenData()) {
  // トークンのデータを持っていない
  goToAuthorizationUrl();
}

api.listEntries(siteId, function(response) {
    let html = '';
    if (response.error) {
      html += "<p>データ取得エラーです。</p>";
      return;
    }

      html += "<ul>";

      for (var i = 0; i < response.items.length; i++) {
        var entry = response.items[i];
        html += "<li>" + entry.title + "</li>";
      }

      html += "</ul>";

      var entries = document.getElementById("entries");
      entries.innerHTML = html;

});

api.getUser('me', function(response) {
  if (response.error) {
    if (response.error.code === 401) {
      // 以前取得したトークンが期限切れになっているなどの理由で再認証が必要
      goToAuthorizationUrl();
      return;
    }

    // エラー処理
    return;
  }

  var user = response;

  document.getElementById('login').style.display = 'none';
  document.getElementById('username').textContent = user.displayName;
});

const entryData = {
    title : "entry from api",
    body: 'body sample'
};
document.getElementById('createEntry').addEventListener('click', ()=> {
api.createEntry(siteId, entryData, function(response) {
    console.log(response); 
});
});
</script>
</body>
</html>

POSTデータについて

で、上記のスクリプトを動かすと、POSTデータが以下のようになっている。(HTTPリクエストのボディ部分)

------WebKitFormBoundaryfgVhBpmmR6viaXNh
Content-Disposition: form-data; name="entry"

{"title":"entry from api","body":"body sample"}
------WebKitFormBoundaryfgVhBpmmR6viaXNh--

Content-Type: multipart/form-data; で送るのは知ってたけど、Content-Disposition: form-data; name="entry"これはなんぞ?そういえば、MT側でPOSTデータをチェックしている時にEntryってデータがないよってエラーが出てたんだよな…ここで指定するのか…

今までのPOSTデータ

今までGuzzlehttpで

<?php
        $response = $client->post($url, [
            'headers' => [
                'X-MT-Authorization'    => 'MTAuth accessToken=' . $access_token,
            ],
            'json' => [
                'entry' => [
                    'title' => 'sample entry title',
                    'body' => 'sample entry BODY',
                    'status' => 'Publish',
                    'date' => '2025-01-17T12:00:00Z',
                ],
                'publish' => 1,
            ],
        ]);

とかやってたけど、こんなJSONが返ってきた。

{"error":{"code":400,"message":"A resource \"entry\" is required."}}

Content-Disposition: form-data; name="entry"が含まれてないからMT側で「Entryデータが無い」って言われてたのか…

正しいPOSTデータ

こんな感じ。Body部分はmultipartで指定するのだった。

<?php
        $response = $client->post($url, [
            'headers' => [
                'X-MT-Authorization'    => 'MTAuth accessToken=' . $access_token,
            ],
            'multipart' => [
                [
                    'name'     => 'entry',
                    'contents' => '{"title":"entry from api(PHP)","body":"body sample"}',
                    ],
                ],
        ]);