Files
2026-08-09 00:36:04 +00:00

176 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""emlog REST API 客户端 — 发布文章/查询/分类/草稿/微语。
用法示例:
python3 emlog.py post --title "标题" --content "正文" --sort-id 14 --tags "a,b"
python3 emlog.py post --title "标题" --content-file /tmp/a.md --sort-id 14
python3 emlog.py sorts
python3 emlog.py list --sort-id 14 --count 10
python3 emlog.py detail --id 123
python3 emlog.py drafts
python3 emlog.py note --text "微语内容"
鉴权:
默认免签名(api_key);加 --sign 走签名鉴权 md5(req_time + api_key)
环境变量:
EMLOG_BASE_URL 默认 https://blog.apescale.com
EMLOG_API_KEY API 秘钥
"""
import argparse
import hashlib
import json
import os
import sys
import time
import urllib.parse
import urllib.request
import urllib.error
BASE_URL = os.environ.get("EMLOG_BASE_URL", "https://blog.apescale.com")
API_KEY = os.environ.get("EMLOG_API_KEY", "")
def request(rest_api, params, method="POST"):
url = f"{BASE_URL}/?rest-api={rest_api}"
data = urllib.parse.urlencode(params).encode("utf-8")
req = urllib.request.Request(url, data=data, method=method)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
body = e.read().decode("utf-8", "ignore")
try:
return json.loads(body)
except Exception:
return {"code": 1, "msg": f"HTTP {e.code}: {body}"}
except Exception as e:
return {"code": 1, "msg": str(e)}
def build_auth_params(args):
p = {}
if args.sign:
t = str(int(time.time()))
p["req_time"] = t
p["req_sign"] = hashlib.md5((t + args.api_key).encode()).hexdigest()
else:
p["api_key"] = args.api_key
return p
def cmd_post(args):
content = args.content
if args.content_file:
with open(args.content_file, "r", encoding="utf-8") as f:
content = f.read()
params = {
"title": args.title,
"content": content,
"sort_id": args.sort_id,
"tags": args.tags,
"draft": args.draft,
"excerpt": args.excerpt,
"alias": args.alias,
"top": args.top,
"allow_remark": args.allow_remark,
}
params = {k: v for k, v in params.items() if v not in (None, "")}
params.update(build_auth_params(args))
if args.id:
# 更新已有文章:emlog 使用 article_update 接口(id 必填)
params = {"id": args.id, **params}
return request("article_update", params)
return request("article_post", params)
def cmd_list(args):
params = {"count": args.count}
if args.sort_id is not None:
params["sort_id"] = args.sort_id
if args.keyword:
params["keyword"] = args.keyword
if args.tag:
params["tag"] = args.tag
return request("article_list", params, method="GET")
def cmd_sorts(args):
return request("sort_list", {}, method="GET")
def cmd_detail(args):
return request("article_detail", {"id": args.id}, method="GET")
def cmd_drafts(args):
params = {}
if args.count:
params["count"] = args.count
params.update(build_auth_params(args))
return request("draft_list", params)
def cmd_note(args):
params = {"t": args.text}
params.update(build_auth_params(args))
return request("note_post", params)
def main():
global BASE_URL, API_KEY
parser = argparse.ArgumentParser(description="emlog REST API 客户端")
parser.add_argument("--base-url", default=BASE_URL)
parser.add_argument("--api-key", default=API_KEY)
parser.add_argument("--sign", action="store_true", help="使用签名鉴权")
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("post", help="发布/更新文章")
p.add_argument("--id", default=None, help="文章 ID(更新已有文章时传入)")
p.add_argument("--title", required=True)
p.add_argument("--content", default="")
p.add_argument("--content-file", default=None)
p.add_argument("--sort-id", default=None)
p.add_argument("--tags", default="")
p.add_argument("--draft", default="n")
p.add_argument("--excerpt", default="")
p.add_argument("--alias", default="")
p.add_argument("--top", default="n")
p.add_argument("--allow-remark", default="n")
p.set_defaults(func=cmd_post)
p = sub.add_parser("list", help="文章列表")
p.add_argument("--sort-id", type=int, default=None)
p.add_argument("--count", type=int, default=10)
p.add_argument("--keyword", default="")
p.add_argument("--tag", default="")
p.set_defaults(func=cmd_list)
p = sub.add_parser("sorts", help="分类列表")
p.set_defaults(func=cmd_sorts)
p = sub.add_parser("detail", help="文章详情")
p.add_argument("--id", required=True)
p.set_defaults(func=cmd_detail)
p = sub.add_parser("drafts", help="草稿列表")
p.add_argument("--count", type=int, default=None)
p.set_defaults(func=cmd_drafts)
p = sub.add_parser("note", help="发布微语")
p.add_argument("--text", required=True)
p.set_defaults(func=cmd_note)
args = parser.parse_args()
args.base_url = args.base_url or BASE_URL
args.api_key = args.api_key or API_KEY
BASE_URL, API_KEY = args.base_url, args.api_key
result = args.func(args)
print(json.dumps(result, ensure_ascii=False, indent=2))
if result.get("code") != 0:
sys.exit(1)
if __name__ == "__main__":
main()