Last active
December 29, 2023 04:05
-
-
Save katsube/b0876c77cf91b8bbac21cbc5df41d691 to your computer and use it in GitHub Desktop.
[PHP] はてなブログに投稿するクラス
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* はてなブログに投稿するクラス | |
* | |
* @package HatenaBlogPost | |
* @version 0.1.0 | |
* @see https://developer.hatena.ne.jp/ja/documents/blog/apis/atom/ | |
* @see https://developer.hatena.ne.jp/ja/documents/auth/apis/wsse | |
* @example | |
* require_once('HatenaBlogPost.php'); | |
* | |
* // ユーザー情報 | |
* $user_id = 'your hatena id (foo)'; | |
* $api_key = 'your api key (xxxxxxxxxx)'; | |
* $blog_id = 'your blog id (xxxx.hatenablog.com)'; | |
* | |
* // 投稿する | |
* $hatena = new HatenaBlogPost($user_id, $api_key, $blog_id); | |
* $hatena->post('記事のタイトル', '記事の本文'); // 本文中のHTMLは文字参照などに変換しないと消えます | |
* | |
* // オプションを指定する場合 | |
* $haten->post('記事のタイトル', '記事の本文', [ | |
* 'category' => 'カテゴリ1,カテゴリ2', // カテゴリ | |
* 'draft' => false, // 下書きかどうか | |
* 'updated' => '2023-12-31T00:00:00Z', // 投稿日時 | |
* 'url' => 'foobar', // カスタムURL(パスの部分。先頭にスラッシュは付けない) | |
* 'escapehtml' => true, // 本文のHTMLをエスケープするかどうか | |
* ]); | |
*/ | |
class HatenaBlogPost{ | |
//--------------------------------------------- | |
// プロパティ | |
//--------------------------------------------- | |
private $user_id; // はてなブログのユーザーID | |
private $api_key; // はてなブログのAPIキー | |
private $blog_id; // はてなブログのブログID | |
// はてなブログのエンドポイント | |
private $endpoint = 'https://blog.hatena.ne.jp/{user_id}/{blog_id}/atom/entry'; | |
/** | |
* コンストラクタ | |
* | |
* @param string $user_id はてなブログのユーザーID | |
* @param string $api_key はてなブログのAPIキー | |
* @param string $blog_id はてなブログのブログID | |
*/ | |
function __construct($user_id, $api_key, $blog_id){ | |
$this->user_id = $user_id; | |
$this->api_key = $api_key; | |
$this->blog_id = $blog_id; | |
} | |
/** | |
* 投稿する | |
* | |
* @param string $title 記事のタイトル | |
* @param string $body 記事の本文 ※HTMLは文字参照などに変換しないと消えます | |
* @param array [$options=null] オプション (category, draft, updated, url, escapehtml) | |
* @return bool | |
*/ | |
function post($title, $body, $options=null){ | |
$xml = $this->createXml($title, $body, $options); | |
$response = $this->request($xml); | |
$status_code = $response['status_code']; | |
$body = $response['body']; | |
if( $status_code === 201 ){ | |
return true; | |
} | |
else{ | |
$message = sprintf('[%s]Post Error: %s', $status_code, $body); | |
throw new Exception($message); | |
} | |
} | |
/** | |
* エンドポイントを設定 | |
* | |
* @param string $endpoint | |
* @return void | |
*/ | |
function setEndpoint($endpoint){ | |
$this->endpoint = $endpoint; | |
} | |
/** | |
* リクエストを送信 | |
* | |
* @param string $xml | |
* @return array | |
* @access private | |
*/ | |
private function request($xml){ | |
$endpoint = $this->getEndpoint(); | |
$headers = [ | |
'Content-Type: application/x.atom+xml', | |
'X-WSSE: ' . $this->getWsseHeader(), | |
]; | |
$curl = curl_init(); | |
curl_setopt($curl, CURLOPT_URL, $endpoint); | |
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); | |
curl_setopt($curl, CURLOPT_POST, true); | |
curl_setopt($curl, CURLOPT_POSTFIELDS, $xml); | |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($curl, CURLOPT_HEADER, true); | |
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); | |
$response = curl_exec($curl); | |
$status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); | |
curl_close($curl); | |
$body = substr($response, strpos($response, "\r\n\r\n") + 4); | |
return [ | |
'status_code' => $status_code, | |
'body' => $body, | |
]; | |
} | |
/** | |
* XMLを生成 | |
* | |
* @param string $title | |
* @param string $body | |
* @param array [$options=null] | |
* @return string | |
* @access private | |
*/ | |
private function createXml($title, $body, $options=null){ | |
$category = isset($options['category']) ? $options['category'] : null; // カテゴリ | |
$draft = isset($options['draft']) ? $options['draft'] : false; // 下書きかどうか | |
$updated = isset($options['updated']) ? $options['updated'] : null; // 投稿日時 | |
$url = isset($options['url']) ? $options['url'] : null; // カスタムURL | |
$escape = isset($options['escapehtml']) ? $options['escapehtml'] : false; // 本文のHTMLをエスケープするかどうか | |
$xmlbase =<<<EOX | |
<?xml version="1.0" encoding="utf-8"?> | |
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"> | |
</entry> | |
EOX; | |
$xml = new SimpleXMLElement($xmlbase); | |
// タイトル | |
$xml->addChild('title', $title); | |
$xml->addChild('author')->addChild('name', $this->user_id); | |
// 本文 | |
if( $escape ){ | |
$body = htmlspecialchars($body, ENT_QUOTES, 'UTF-8'); | |
} | |
$xml->addChild('content', $body)->addAttribute('type', 'text/plain'); | |
// 更新日時 | |
if( $updated !== null ){ | |
$xml->addChild('updated', $updated); | |
} | |
// カテゴリー | |
if( $category !== null){ | |
$categories = explode(',', $category); | |
foreach( $categories as $category ){ | |
$xml->addChild('category')->addAttribute('term', $category); | |
} | |
} | |
// 下書き? | |
if( $draft ){ | |
$xml->addChild('app:control')->addChild('app:draft', 'yes', 'http://www.w3.org/2007/app'); | |
} | |
// カスタムURL | |
if( $url !== null ){ | |
$custom_url = $xml->addChild('hatenablog:custom-url', $url, 'http://www.hatena.ne.jp/info/xmlns#hatenablog'); | |
$custom_url->addAttribute('xmlns:hatenablog', 'http://www.hatena.ne.jp/info/xmlns#hatenablog'); | |
} | |
// 文字列にして返却する | |
return $xml->asXML(); | |
} | |
/** | |
* エンドポイントを取得 | |
* | |
* @return string | |
* @access private | |
*/ | |
private function getEndpoint(){ | |
$endpoint = str_replace('{user_id}', $this->user_id, $this->endpoint); | |
$endpoint = str_replace('{blog_id}', $this->blog_id, $endpoint); | |
return $endpoint; | |
} | |
/** | |
* X-WSSEヘッダーを作成 | |
* | |
* @return string | |
* @access private | |
*/ | |
private function getWsseHeader(){ | |
$nonce = sha1(time() . uniqid() . rand()); | |
$created = date('Y-m-d\TH:i:s\Z'); | |
$password_digest = base64_encode(sha1($nonce . $created . $this->api_key, true)); | |
$wsse_header = sprintf('UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"', | |
$this->user_id, | |
$password_digest, | |
$nonce, | |
$created | |
); | |
return $wsse_header; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment