1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ビットスターAdvent Calendar 2022

Day 13

情弱がMovable TypeのAPIをさわってみたよ

Last updated at Posted at 2022-12-12

先日Movable Typeが提供しているRest APIに弄ばれてみたので、その内容を書いていこうと思います。

書いた人のスペック

  • 2021年ごろまでwebディレクターっぽいことをしていた
  • html・cssがほんのり書ける
  • phpはwordPressでさわったことがある程度(雑魚)
  • プログラミングの教育は受けていない

Movable TypeのAPIって??

Movable Type 6 から搭載されている Web API です。さまざまなプログラム言語から REST/JSON方式で Movable Type にアクセスし、データの取得や更新ができます。

Data API を使えば、CMSで管理しているデータを、サイト上で自由に呼び出したり、また、独自の管理画面やアプリの開発や、他のプラットフォームとの連携などを容易に行うことができます。

なるほど~~?
テンプレートの再構築をしなくても記事のデータとかをいろんなところに呼び出したりできるやつ、と思うことにしました。
MTはMTタグ(<MT:なんとやら>とか)をたくさん使うと再構築が遅くなるらしいので、規模の大きいwebサイトではAPIから直接読み込んだほうが早い説がありそうですね。
公式はめちゃくちゃあっさりした説明しか記載がありませんでしたが、リファレンスに大体のことが書いていそうな予感です。

v●-reference.htmlのバージョンを変えるとほかのバージョンも見れるみたいです。

やったこと

MT6.1のサイトでphpを使ってやってみました。
MT7以降はコンテンツタイプが実装されているのでレスポンスがちょっと変わる気がしています。
MT6.1はdata-apiのv2に対応しているので、そちらのリファレンスと後述するさくナレを見ながら作りました。
MT6.2(APIv3)以降は/versionでAPIのバージョンを確認できるようになっているようなので、作業前にバージョンを確認するのがおすすめです。

  1. アクセストークンを取得してみる
  2. 認証無しで取得できる記事のデータを取ってみる
  3. APIを使って記事を投稿してみる

1. アクセストークンを取得してみる

phpを使って取ります。さくナレを参考に書いてみました。

// APIアクセスURL
$siteURL = '取得したいサイトのURL';
$version = 'APIのバージョン(今回だとv2)';
$apiPath = $siteURL . '/mt-data-api.cgiまでのパス/mt-data-api.cgi' . $version;

// basic認証
$header = array(
  'Content-Type: application/x-www-form-urlencoded',
  'Authorization: Basic '.base64_encode('ベーシック認証のユーザ名:ベーシック認証のPW'),
);

// ユーザ情報
$username = 'MT管理画面ログインID';
$password = 'MT管理画面ログインPW';

// ログイン・アクセストークン取得
function authentication($username, $password, $header, $apiPath) {

  $api = $apiPath . '/authentication';

  $postdata = array(
    'username' => $username,
    'password' => $password,
    'clientId' => 'getToken',  // なんでもOK
  );

  $options = array('http' => 
    array(
      'method' => 'POST',
      'header' => implode("\r\n", $header),
      'content' => http_build_query($postdata),
    )
  );

  $response = file_get_contents($api, false, stream_context_create($options));
  $response = json_decode($response);
  return $response;
}

$tokens = authentication($username, $password, $header, $apiPath);

こういうjsonが返ってくるので、

{
  "accessToken": "EowKdyeBcEUNbiFEXlp0bdQz5RJgdkJYLbBDRJ4m",
  "sessionId": "8VtaTLTLp8V9OR5Dz40hO7by8wf0wbCsCkBue0Xv",
  "expiresIn": 3600,
  "remember": true
}

アクセストークンだけ取っておきます。

// アクセストークン
$accessToken = $tokens->accessToken;

v3以降はwebサービスのPWを使うみたいですが、v2までは管理画面ログイン時に使うPWを使うようです。
ここでめちゃくちゃはまりました。かなしい・・・。

2. 認証無しで取得できる記事のデータを取ってみる

誰でも認証無しで見れる記事の情報を取ってみました。

$siteID = 71;
function entriesList($username, $password, $apiPath, $siteID, $header) {
  $api = $apiPath . '/sites/' . $siteID . '/entries';

  $postdata = array(
      'username' => $username,
      'password' => $password,
  );

  $options = array('http' => 
  array(
    'method' => 'GET',
    'header' => implode("\r\n", $header),
    'content' => http_build_query($postdata),
    )
  );

  $response = file_get_contents($api, false, stream_context_create($options));
  $response = json_decode($response, 'UTF-16');
  return $response;
}
$entries = entriesList($username, $password, $apiPath, $siteID, $header);

これでいけるやろ!と思うじゃないですか。
1回目に確認したときは認証もOKでjsonも返ってきていたのですが、
5分後ぐらいに再度ファイルを更新するとエラーが返ってくるんですよねえ・・・・。

Warning: file_get_contents(テスト用のMTのURLとか/mt-data-api.cgi/v2/sites/ブログid/entries): failed to open stream: HTTP request failed! HTTP/1.1 401 Unauthorized in /var/www/html/apitest.php on line 121

ほんとにちゃんと返ってきてたんですよ、ほんとなんですよ・・・。
そしてなぜかアクセストークンは何回ファイルを更新してもfile_get_contents()で取れていました。なんで・・・。

気を取り直してfile_get_contents()がだめならcurlうんにゃらシリーズで取ってこようと思います。

$curl = curl_init();
$siteID = 71;
function entriesList($username, $password, $apiPath, $siteID, $header, $curl) {

  $api = $apiPath . '/sites/' . $siteID . '/entries';

  $postdata = array(
      'username' => $username,
      'password' => $password,
  );

  curl_setopt_array($curl, array(
      CURLOPT_URL => $api,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_ENCODING => '',
      CURLOPT_MAXREDIRS => 10,
      CURLOPT_TIMEOUT => 0,
      CURLOPT_FOLLOWLOCATION => true,
      CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
      CURLOPT_CUSTOMREQUEST => 'GET',
      CURLOPT_HTTPHEADER => array(
        implode("\r\n", $header),
        'Authorization: ' . http_build_query($postdata)
      ),
    )
  );

  $response = curl_exec($curl);
  $response = json_decode($response, 'UTF-16');
  return $response;
}
$entries = entriesList($username, $password, $apiPath, $siteID, $header, $curl);
curl_close($curl);

返ってきたjsonはUnicodeになっているので、json_decode()します。(絵文字が含まれるかもと思ったのでUTF-16にしています)
情弱なのでUnicodeになることを知らずなんで・・・・。となってここでも時間がかかりました。
文字コードを直すとこんなかんじで返ってきます。

{
  "totalResults": 記事の件数,
  "items": [
    {
      "excerpt": "記事の抜粋",
      "status": "Publish",  // 公開ステータス
      "date": "2022-12-06T09:59:58+09:00",
      "updatable": false,
      "author": {
        "userpicUrl": null,
        "displayName": "ユーザ名"
      },
      "allowComments": false,
      "comments": [],
      "permalink": "記事のパーマリンク",
      "body": "記事の本文",
      "keywords": "記事のKW",
      "allowTrackbacks": false,
      "id": 記事のid,
      "trackbacks": [],
      "modifiedDate": "2022-12-06T18:58:05+09:00",
      "trackbackCount": "0",
      "categories": [記事が属するカテゴリ],
      "blog": {
        "id": "ブログid"
      },
      "commentCount": "0",
      "tags": [記事のタグ],
      "basename": "記事のベースネーム",
      "assets": [記事画像など],
      "pingsSentUrl": [],
      "title": "記事タイトル",
      "class": "entry",
      "createdDate": "2022-12-06T18:58:05+09:00",
      "more": "",
      "customFields": [
        {
          "value": "カスタムフィールドの値",
          "basename": "カスタムフィールドのベースネーム"
        }
      ]
    }
  ]
}

テンプレートやロール、ログ、非公開にしているもの以外なら結構いろいろな情報がとれるみたいです。

3. APIを使って記事を投稿してみる

基本2と同じ流れやろ!!できるできる!!!と思いながらノリで書いてみました。

$curl = curl_init();
$siteId = 71;
$tokenAuth = 'X-MT-Authorization: MTAuth accessToken=' . $accessToken;
$entry = '{"excerpt": "記事の抜粋","status": "投稿状態","date": "2022-12-06T09:59:58\u002b09:00(投稿日時)","updatable": false,"author": {"userpicUrl": null,"displayName": "投稿者名"},"allowComments": false,"permalink": "記事のパーマリンク、なくてもいいかも","body": "記事の本文","keywords": "記事のKW","allowTrackbacks": false,"id": 記事のid,"modifiedDate": "2022-12-06T09:59:58\u002b09:00(投稿日時、なくてもいいかも)","trackbackCount": "0","blog": {"id": "投稿先のブログid"},"commentCount": "0","basename": "記事のベースネーム","title": "記事タイトル","class": "entry","createdDate": "2022-12-06T10:03:36\u002b09:00","more": "","customFields": [{"basename": "カスタムフィールドのベースネーム","value": "カスタムフィールドの値"},{"basename": "カスタムフィールドのベースネーム","value": "カスタムフィールドの値"}]}';

function createEntries($username, $password, $apiPath, $siteId, $curl, $header, $tokenAuth, $entry)
{

  $api = $apiPath . '/sites/' . $siteId . '/entries';

  $postdata = array(
    'username' => $username,
    'password' => $password,
  );

  curl_setopt_array($curl, array(
    CURLOPT_URL => $api,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '',
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => array(
      implode("\r\n", $header),
      $tokenAuth,
      'Authorization: ' . http_build_query($postdata)
    ),
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => 'entry=' . $entry,
  ));

  $response = curl_exec($curl);
  return $response;
}
$createEntries = createEntries($username, $password, $apiPath, $siteId, $curl, $header, $tokenAuth, $entry);
curl_close($curl);

$entryの中身

見づらいので成形して見てみます。

{
  "excerpt": "記事の抜粋",
  "status": "投稿状態",
  "date": "2022-12-06T09:59:58\u002b09:00(投稿日時)",
  "updatable": false,
  "author": {
    "userpicUrl": null,
    "displayName": "投稿者名"
  },
  "allowComments": false,
  "permalink": "記事のパーマリンク、なくてもいいかも",
  "body": "記事の本文",
  "keywords": "記事のKW",
  "allowTrackbacks": false,
  "id": 記事のid,
  "modifiedDate": "2022-12-06T09:59:58\u002b09:00(変更日時、なくてもいいかも)",
  "trackbackCount": "0",
  "blog": {
    "id": "投稿先のブログid"
  },
  "commentCount": "0",
  "basename": "記事のベースネーム",
  "title": "記事タイトル",
  "class": "entry",
  "createdDate": "2022-12-06T10:03:36\u002b09:00(作成日時、なくてもいいかも)",
  "more": "",
  "customFields": [
    {
      "basename": "カスタムフィールドのベースネーム",
      "value": "カスタムフィールドの値"
    },
    {
      "basename": "カスタムフィールドのベースネーム",
      "value": "カスタムフィールドの値"
    }
  ]
}

2で取得した記事データとほぼほぼ同じ内容です。
相違点は下記が無いあたりです。

  • categories
  • tags
  • assets

これらは今回投稿した記事では使用しないため削除しています。
空のままPOSTすると書くんだったら中身も入れてくれと怒られます。
なくてもいいかも、と書いているものは何か指定をしていても記事が挿入されるタイミングでMT側で自動的に設定されていたやつらです。

投稿できたか確認

管理画面には新しい記事が追加されているはずです。
単純にGETするよりも時間がかかるので辛抱強く待ちましょう。
名称未設定-1.jpg
やったあ!できたぞ!!

なんでfile_get_contents()できなかったの?

後日情強に聞いてみたところ、file_get_contents()は細かいことができないからじゃないの??と教えてもらい、
エラー文にUnauthorizedとあるのでベーシック認証に失敗してAPIにアクセスできなかったのかな???と思いました。
が、また別のタイミングで同じことをしてみるとデータの取得ができたのですよね・・・。よくわからん・・・・。
普段の再構築中にタイムアウトしたりログアウトしたりする程度には重たいサイトなので、タイミングの問題なのかもしれません。。

これを使ってやりたいこと

対応可能なものは絞られますが、弊社はサイトのリニューアルや移設をご依頼いただくことが多いので、
その時に応用できたらいいなあと思っています。
サイトAから取得したデータをそのまま新設のサイトBにうつしたいよ、でもmysqldumpとかとかできないよ、
というようなときにしゅしゅっと作業できそうな予感がしています。

さいごに

MTはドキュメントが少なかったり古かったりでつらさがありますよね。
この記事がひとりでも多くのMTをさわる人の役に立つといいなあと思っています。
では、よいクリスマスを!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?