一个发布文章到博客园的 Python 脚本

copy from https://github.com/DeppWang/cnblogs-post

配置

博客园 -> 管理 -> 设置 -> 允许 MetaWeblog 博客客户端访问
在 cnblogs-post.py 中配置:

config = {
    'user_unique_name': 'deppwang',                                     # 你的用户名,用于拼接文章 url
    'url': 'https://rpc.cnblogs.com/metaweblog/deppwang',               # 你的 MetaWeblog 访问地址
    'username': 'DeppWangXQ',                                           # 你的登录用户名,可能跟上面的用户名不一致
    'password': '12345678'                                              # 你的登录密码
    'local_post_path': '/Users/yanjie/GitHub/HexoBlog/source/_posts/'   # 你的本地博文路径
}

在文章开头,添加文章信息块,至少需要包括 title 和 tags,格式如下:

---
title: 一个可编辑与新增博客园文章的 Python 脚本
english_title: a-python-script-to-edit-and-add-cnblogs-posts
date: 2020-06-20 20:48:37
tags: 博客园
categories: Tools
---
正文开始 ...

安装 markdown-it-py,用于将 Markdown 转换为 HTML

pip install markdown-it-py

运行脚本发布或更新

python3 cnblogs-post.py [count]   # macOS/Linux
python cnblogs-post.py [count]    # Windows

默认只操作最近修改文章,但也可以指定文章数量(count)
脚本根据文章名称来判断是否已经发布,如果已经发布,更新,否则新增。

运行脚本删除

python3 cnblogs-post.py delete    # macOS/Linux
python cnblogs-post.py delete     # Windows

默认删除最近发布文章

import xmlrpc.client
import ssl
import os
import sys
import logging

# logging.basicConfig(level=logging.INFO)

# 设置 ssl 很重要,否则将报 ssl 错
ssl._create_default_https_context = ssl._create_unverified_context

# dict
config = {
    'user_unique_name': '',                                    # 你的用户名,用于拼接文章 url
    'url': 'https://rpc.cnblogs.com/metaweblog/',              # 你的 MetaWeblog 访问地址
    'username': '',                                          # 你的登录用户名,可能跟上面的不一致
    'password': '',                                            # 你的登录密码
    'local_post_path': ''  # 你的本地博文路径
}


class MetaWeblog:
    def __init__(self, url, username, password):
        self.url, self.username, self.password = url, username, password
        self.proxy = xmlrpc.client.ServerProxy(self.url)

    def getRecentPosts(self, count):
        return self.proxy.metaWeblog.getRecentPosts('', self.username, self.password, count)

    def deletePost(self, post_id):
        return self.proxy.blogger.deletePost('', post_id, self.username, self.password, True)

    def newPost(self, article):
        return self.proxy.metaWeblog.newPost('', self.username, self.password,
                                             dict(title=article['title'], description=article['content'],
                                                  mt_keywords=article['tags']),
                                             True)

    def editPost(self, post_id, article):
        return self.proxy.metaWeblog.editPost(post_id, self.username, self.password,
                                              dict(title=article['title'], description=article['content'],
                                                   mt_keywords=article['tags']),
                                              True)


def set_article(article_path: str) -> dict:
    """ 根据文章路径设置文章(标题、标签和内容)"""

    import re
    from markdown_it import MarkdownIt
    from markdown_it.extensions.front_matter import front_matter_plugin
    from markdown_it.extensions.footnote import footnote_plugin

    with open(article_path, 'rb') as f:
        content = f.read().decode('utf-8')

    # 使用分组
    reg = r'(---(.|[\n])*?---)'

    p = re.compile(reg)
    headers = p.findall(content)

    logging.info('headers: %s', headers)
    if len(headers) < 1:
        raise ValueError('文章 %s 不存在 header 信息块「--- ** ---」,请检查!' % article_path)

    # headers[0] 为 tuple
    header_str = headers[0][0]

    lines = header_str.split('\n')

    title, tags_categories = '', []
    for line in lines:
        # 不使用':', 再替换所有空格为 null,因为可能存在中英文空格
        line_ele = line.split(': ')
        if line_ele[0] == 'title':
            title = line_ele[1].rstrip()

        elif line_ele[0] == 'tags':
            tags = line_ele[1].rstrip()
            tags = tags.replace('[', '')
            tags = tags.replace(']', '')
            tags_categories.append(tags)

        # 将分类也作为标签
        elif line_ele[0] == 'categories':
            categories = line_ele[1].rstrip()
            tags_categories.insert(0, categories)

    separator = ', '
    tags = separator.join(tags_categories)
    logging.info("tags: %s", tags)

    # 只替换第一个
    content = content.replace(header_str, '', 1)

    md = (
        MarkdownIt()
            .use(front_matter_plugin)
            .use(footnote_plugin)
            .enable('image')
            .enable('table')
    )

    # markdown 转换为 HTML
    content = md.render(content)

    # cnblogs-markdown 属性用于代码块样式
    content = '<div class=\"cnblogs-markdown\">%s</div>' % content

    if title == '' or content == '' or tags == '':
        raise ValueError('文章 title、content、tags 均不能为空;\': \'后有空格。请检查!')

    article = {'title': title, 'content': content, 'tags': tags}
    return article


def get_local_modified_file(file_count: int):
    """ 获取指定数量的最近修改文章 """

    import stat
    import datetime as dt

    modified = []

    for root, sub_folders, files in os.walk(config['local_post_path']):
        for file in files:
            try:
                unix_modified_time = os.stat(os.path.join(root, file))[stat.ST_MTIME]
                human_modified_time = dt.datetime.fromtimestamp(unix_modified_time).strftime('%Y%m%dT%H:%M:%S')
                filename = os.path.join(root, file)
                if os.path.splitext(filename)[1] == '.md':
                    modified.append((human_modified_time, filename))
            except:
                pass

    modified.sort(key=lambda a: a[0], reverse=True)
    return modified[0: file_count]


def judge_str_equal(str1, str2):
    """ 判断两个字符串是否相等 """

    str1 = str1.replace(' ', '')
    str2 = str2.replace(' ', '')
    return str1 == str2


def edit_or_new(article_path: str) -> None:
    """ 编辑或新增 """

    blog = MetaWeblog(config['url'], config['username'], config['password'])
    posts = blog.getRecentPosts(100)
    article = set_article(article_path)
    title = article['title']

    # 如果存在,更新,否则新增。接口不提供最近修改时间,所以不能跳过
    exist_flag = 0

    for post in posts:
        # 判断是否含有相同的字符,不直接用等号
        if judge_str_equal(post['title'], title):
            status = blog.editPost(post['postid'], article)
            if status is True:
                print("更新文章「%s」成功,文章地址 https://www.cnblogs.com/%s/p/%s.html" % (title, config['user_unique_name'], post['postid']))
            exist_flag = 1
            break

    if exist_flag == 0:
        id = blog.newPost(article)
        if id is not None:
            print("发布文章「%s」成功,文章地址 https://www.cnblogs.com/%s/p/%s.html" % (title, config['user_unique_name'], id))


def post(count: int) -> None:
    """ 发布指定数量的最新修改文章 """

    modified_files = get_local_modified_file(count)
    for modified_file in modified_files:
        edit_or_new(modified_file[1])


def delete() -> None:
    """ 默认删除最新文章 """

    metaWeblog = MetaWeblog(config['url'], config['username'], config['password'])
    posts = metaWeblog.getRecentPosts(100)
    postid = posts[0]['postid']
    title = posts[0]['title']
    status = metaWeblog.deletePost(postid)
    if status is True:
        print('删除「%s」成功!' % title)
    else:
        print('删除「%s」失败,文章不存在。文章 id 为:%s' % (title, postid))


def main():

    try:

        if len(sys.argv) > 1 and sys.argv[1] == 'delete':
            delete()

        elif len(sys.argv) > 1 and isinstance(int(sys.argv[1]), int):
            print('正在发布 ...')
            logging.info("sys.argv[1]: %s", sys.argv[1])
            post(int(sys.argv[1]))

        else:
            post(1)
    except Exception as err:
        print("错误提示:%s", format(err))
        print('发布失败')
        sys.exit(-1)


if __name__ == '__main__':
    main()
posted @ 2020-10-24 10:09  hyskr  阅读(116)  评论(1)    收藏  举报