MarkdownをはてなブログにポストするPythonスクリプト メモ
はてなブログのTipsです。
CTFのWriteupなど長い記事をポストするときは、MarkdownエディタのTyporaを使っていったんローカルで作成して、ほぼ完成したらはてなにアップしています。
画像が多くなるとアップロードも大変なので、スクリプトを作成しました。エラーハンドリングやバグチェックなど細かく行えていないので、使用される際は自己責任でお願いします。(バグのご指摘は大歓迎です。)
スクリプト
import datetime import random import hashlib import base64 import requests import time import mimetypes import os import xml.etree.ElementTree as ET import re import sys END_POINT = '' username = '' api_key = '' def create_wsse(username, password): created = datetime.datetime.now().isoformat() + "Z" b_nonce = hashlib.sha1(str(random.random()).encode()).digest() b_digest = hashlib.sha1(b_nonce + created.encode() + password.encode()).digest() s = 'UsernameToken Username="{0}", PasswordDigest="{1}", Nonce="{2}", Created="{3}"' return s.format(username, base64.b64encode(b_digest).decode(), base64.b64encode(b_nonce).decode(), created) def post_photo(username, api_key, filepath): with open(filepath, "rb") as f: b_photo_data = f.read() b64_photo_data = base64.b64encode(b_photo_data).decode() content_type = mimetypes.guess_type(filepath)[0] post_data = '''<entry xmlns="http://purl.org/atom/ns#"> <title></title> <content mode="base64" type="''' + content_type + '''">''' + b64_photo_data + '''</content> <generator>Hatena Blog</generator> </entry>''' wsse = create_wsse(username, api_key) r = requests.post("https://f.hatena.ne.jp/atom/post", data=post_data, headers={'X-WSSE': wsse}) if r.status_code == 201: r_xml = ET.fromstring(r.text) photo_id = r_xml.find('{http://www.hatena.ne.jp/info/xmlns#}syntax').text.split(':')[3] return photo_id else: print("写真の投稿に失敗しました" + filepath) return "" def del_photos(username, api_key, photo_ids): wsse = create_wsse(username, api_key) print("--- Delete Photos ---") for id in photo_ids: r = requests.delete("https://f.hatena.ne.jp/atom/edit/" + id[:-1], headers={'X-WSSE': wsse}) if r.status_code == 200: print(id + " : Deleted") else: print(id + " : Delete Failed") def get_entry_data(title, content): TEMPLATE = """<?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"> <title>{title}</title> <author><name>{name}</name></author> <content type="text/plain"><![CDATA[{content}]]></content> <updated></updated> <app:control> <app:draft>yes</app:draft> </app:control> </entry> """ #テンプレにtitle, name, contentを挿入 entry = TEMPLATE.format( title = title, name = username, content = content, draft = "yes" ) return entry def post_entry_hatena(END_POINT, username, api_key, entry): print("--- Post Blog Entry ---") wsse = create_wsse(username, api_key) r = requests.post(END_POINT + "/entry", data=entry.encode('utf-8'), headers={'X-WSSE': wsse}) if r.status_code == 201: print(u"投稿しました") else: print(u"投稿に失敗しました") print(r.text) if(__name__ == "__main__"): if(len(sys.argv) == 1): exit() md_path = sys.argv[1] print('Input Markdown file : ' + md_path) print('Press enter key to continue') input() photo_ids = [] md_path_dir = os.path.dirname(md_path) + '\\' md_data = open(md_path, "rb").read().decode() print("--- Post Photos ---") cnt = 1 md_ppaths = re.findall(r'!\[.*\]\(.*\)', md_data) for md_ppath in md_ppaths: ppath = md_path_dir + re.findall(r'!\[.*\]\((.*)\)', md_ppath)[0].replace('/','\\') if(os.path.exists(ppath)): print(str(cnt) + "/" + str(len(md_ppaths)) + " : " + ppath) cnt += 1 res = post_photo(username, api_key, ppath) if(res != ""): md_data = md_data.replace(md_ppath, '[f:id:' + username + ':' + res + ':plain]') photo_ids.append(res) else: del_photos(username, api_key, photo_ids) print('Press enter key to continue') input() exit() post_entry_hatena(END_POINT, username, api_key, get_entry_data(os.path.splitext(os.path.basename(md_path))[0], md_data)) open(md_path + '_output.txt', "wb").write(md_data.encode('utf-8')) print('Press enter key to continue') input() exit()
補足
Markownファイルをそのままブログにポストするため、はてなブログの編集モードはMarkdownモードになっている必要があります。
エンドポイント、APIキーは、ブログ設定→詳細設定から確認できます。
動作の補足です。
- コマンドラインオプションで指定したMDファイルを処理します。
- ブログをポストする前に、画像をフォトライフにアップロードします。画像のアップロードが1つでも失敗した場合、すべての処理は中断され、それまでアップロードした写真はすべて削除します。
- ブログは下書き(draft)にアップロードされます。
- ポストしたmarkdownは入力したMDファイルと同じフォルダに、「(MDファイルのファイル名)_output.txt」というファイル名で保存されます。
参考
pythonでwsse認証を用いて、はてなブログにエントリーを投稿する - Qiita