背景
要件
- 画像生成までは完了させている
- 固定の画像名を投稿させる
- タイトルは毎回異なるので、タイトルは毎回渡したい
試してみた
事前にexportしておく
export HATENA_ID="XXXXX" export HATENA_API_KEY="XXXXXX" export HATENA_BLOG_ID="XXXXX.hateblo.jp"
import os
import base64
import hashlib
import requests
import argparse
from datetime import datetime, timezone
from email.utils import format_datetime
from xml.etree import ElementTree as ET
HATENA_ID = os.environ["HATENA_ID"]
HATENA_API_KEY = os.environ["HATENA_API_KEY"]
HATENA_BLOG_ID = os.environ["HATENA_BLOG_ID"]
FIXED_IMAGE_PATH = "/path/to/fixed_image.png"
def build_wsse(username: str, api_key: str) -> str:
created = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
nonce = os.urandom(16)
digest = hashlib.sha1(nonce + created.encode("utf-8") + api_key.encode("utf-8")).digest()
b64nonce = base64.b64encode(nonce).decode("ascii")
b64digest = base64.b64encode(digest).decode("ascii")
return (
f'UsernameToken Username="{username}", '
f'PasswordDigest="{b64digest}", '
f'Nonce="{b64nonce}", '
f'Created="{created}"'
)
def upload_to_fotolife(image_path: str, title: str) -> str:
with open(image_path, "rb") as f:
data_b64 = base64.b64encode(f.read()).decode("ascii")
# base64埋め込み形式のエントリXML[web:29]
entry_xml = f"""\
<entry xmlns="http://purl.org/atom/ns#">
<title>{title}</title>
<content mode="base64" type="image/png">
{data_b64}
</content>
<generator>python-script</generator>
</entry>
"""
headers = {
"X-WSSE": build_wsse(HATENA_ID, HATENA_API_KEY),
"Content-Type": "application/xml",
"Authorization": 'WSSE profile="UsernameToken"',
}
url = "https://f.hatena.ne.jp/atom/post"
resp = requests.post(url, data=entry_xml.encode("utf-8"), headers=headers)
resp.raise_for_status()
ns = {
"atom": "http://purl.org/atom/ns#",
"hatena": "http://www.hatena.ne.jp/info/xmlns#",
}
root = ET.fromstring(resp.content)
syntax = root.find("hatena:syntax", ns)
if syntax is None or not syntax.text:
raise RuntimeError("hatena:syntax が取得できませんでした")
return syntax.text.strip()
def post_hatena_entry(title: str, body: str, draft: bool = False) -> str:
collection_url = f"https://blog.hatena.ne.jp/{HATENA_ID}/{HATENA_BLOG_ID}/atom/entry" # [web:33]
now = datetime.now(timezone.utc)
updated_str = format_datetime(now) # RFC形式の日時文字列[web:33]
draft_value = "yes" if draft else "no"
entry_xml = f"""\
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:app="http://www.w3.org/2007/app">
<title>{title}</title>
<updated>{updated_str}</updated>
<author><name>{HATENA_ID}</name></author>
<content type="text/plain">
{body}
</content>
<app:control>
<app:draft>{draft_value}</app:draft>
</app:control>
</entry>
"""
resp = requests.post(
collection_url,
data=entry_xml.encode("utf-8"),
auth=(HATENA_ID, HATENA_API_KEY), # Basic認証[web:33]
headers={"Content-Type": "application/xml"},
)
resp.raise_for_status()
# レスポンスから <link rel="alternate"> の href = 記事URL を取り出す[web:33]
root = ET.fromstring(resp.content)
ns = {"atom": "http://www.w3.org/2005/Atom"}
for link in root.findall("atom:link", ns):
if link.get("rel") == "alternate":
return link.get("href")
return ""
def post_with_title_and_image(post_title: str) -> str:
image_syntax = upload_to_fotolife(FIXED_IMAGE_PATH, post_title)
body = f"{post_title}\n\n{image_syntax}\n"
entry_url = post_hatena_entry(post_title, body, draft=False)
return entry_url
def main():
parser = argparse.ArgumentParser(description="はてなブログに固定画像+タイトルで投稿するスクリプト")
# タイトルを位置引数で受け取る[web:48][web:50]
parser.add_argument("title", help="記事タイトル")
args = parser.parse_args()
url = post_with_title_and_image(args.title)
print("posted:", url)
if __name__ == "__main__":
main()
使い方
python3 hatena.py "テスト"
ちゃんと投稿された!!