From 390b9ccc92fe5b5672f195ce5a3b69e3a0d5462f Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Tue, 18 Mar 2025 15:53:40 +0800 Subject: [PATCH] first commit --- .gitignore | 1 + .idea/.gitignore | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/transfer_md.iml | 8 + .idea/vcs.xml | 6 + README.md | 8 + requirements.txt | 6 + transfer_md/transfer.py | 237 ++++++++++++++++++ transfer_md/upload_img.py | 50 ++++ typecho_markdown_upload/config.py.example | 21 ++ typecho_markdown_upload/cos_pic_uploader.py | 17 ++ typecho_markdown_upload/main.py | 73 ++++++ .../markdown_file_searcher.py | 27 ++ .../markdown_img_searcher.py | 30 +++ .../typecho_direct_mysql_publisher.py | 87 +++++++ .../typecho_xmlrpc_publisher.py | 11 + 18 files changed, 608 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/transfer_md.iml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 requirements.txt create mode 100644 transfer_md/transfer.py create mode 100644 transfer_md/upload_img.py create mode 100644 typecho_markdown_upload/config.py.example create mode 100644 typecho_markdown_upload/cos_pic_uploader.py create mode 100644 typecho_markdown_upload/main.py create mode 100644 typecho_markdown_upload/markdown_file_searcher.py create mode 100644 typecho_markdown_upload/markdown_img_searcher.py create mode 100644 typecho_markdown_upload/typecho_direct_mysql_publisher.py create mode 100644 typecho_markdown_upload/typecho_xmlrpc_publisher.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f85c6b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.py \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ea20432 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..908cce0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/transfer_md.iml b/.idea/transfer_md.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/transfer_md.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e00169 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +将本地文件夹下的markdown文件发布到typecho的站点中 + +### TODO +- [x] 将markdown发布到typecho +- [x] 发布前将markdown的图片资源上传到TencentCloud的COS中, 并替换markdown中的图片链接 +- [x] 将md所在的文件夹名称作为post的category(mysql发布可以插入category, xmlrpc接口暂时不支持category操作) +- [ ] category的层级 +- [ ] 发布前先获取所有post信息, 不发布已经发布过的post \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..66054f9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +cos_python_sdk_v5==1.9.15 +panflute==2.1.3 +pypandoc==1.8 +pytypecho==2.1.0 +qcloud_cos==3.3.6 +pymysql==1.0.2 \ No newline at end of file diff --git a/transfer_md/transfer.py b/transfer_md/transfer.py new file mode 100644 index 0000000..75f94b7 --- /dev/null +++ b/transfer_md/transfer.py @@ -0,0 +1,237 @@ +import os +import re +import shutil +import uuid +import requests +from urllib.parse import urlparse +from upload_img import upload_image + + +def download_image(url, output_path): + """ + 从网络下载图片并保存到指定路径 + """ + try: + response = requests.get(url, stream=True) + if response.status_code == 200: + # 获取图片扩展名 + parsed_url = urlparse(url) + ext = os.path.splitext(parsed_url.path)[1] + if not ext: + ext = '.png' # 默认使用 .png 扩展名 + + # 生成新的文件名 + new_filename = f"{uuid.uuid4()}{ext}" + dest_path = os.path.join(output_path, new_filename) + + # 保存图片 + with open(dest_path, 'wb') as f: + response.raw.decode_content = True + shutil.copyfileobj(response.raw, f) + print(f"已下载: {url} → {dest_path}") + return new_filename + else: + print(f"警告: 无法下载图片 {url},状态码: {response.status_code}") + except Exception as e: + print(f"错误: 下载图片 {url} 时出错: {e}") + return None + +def extract_image_paths(content): + """ + 从 Markdown 内容中提取所有图片路径(支持 Markdown 和 HTML 格式) + """ + pattern_md = re.compile(r'!\[.*?\]\((.*?)\)') + pattern_html = re.compile(r']*src\s*=\s*"(.*?)"') + return set(pattern_md.findall(content) + pattern_html.findall(content)) + +def process_local_image_copy(abs_img_path, dest_folder): + """ + 复制本地图片到目标文件夹,并返回新文件名(使用 UUID 命名,保留扩展名) + """ + ext = os.path.splitext(abs_img_path)[1] + new_filename = f"{uuid.uuid4()}{ext}" + dest_path = os.path.join(dest_folder, new_filename) + shutil.copy2(abs_img_path, dest_path) + return new_filename + +def process_md_file_local(md_file, output_path): + """ + 处理一个 Markdown 文件: + - 提取 Markdown 和 HTML 格式的图片路径 + - 复制本地图片到 output_path,并修改 md 文件中的图片引用路径 + - 下载网络图片到 output_path,并修改 md 文件中的图片引用路径 + - 图片复制时使用 UUID 作为文件名(保留扩展名) + - 更新后的图片路径为绝对路径 + """ + with open(md_file, 'r', encoding='utf-8') as f: + content = f.read() + + # 使用抽离的函数提取图片路径 + img_paths = extract_image_paths(content) + + # 获取当前 md 文件所在目录 + md_dir = os.path.dirname(md_file) + + for img_path in img_paths: + # 判断图片路径是本地路径还是网络 URL + if img_path.startswith(('http://', 'https://')): + # 处理网络图片 + new_filename = download_image(img_path, output_path) + if new_filename: + # 使用绝对路径替换 + new_ref = os.path.join(output_path, new_filename).replace('\\', '/') + content = content.replace(img_path, new_ref) + else: + # 处理本地图片 + if os.path.isabs(img_path): + abs_img_path = img_path + else: + abs_img_path = os.path.normpath(os.path.join(md_dir, img_path)) + + if os.path.exists(abs_img_path): + if os.path.isfile(abs_img_path): # 确保是文件而不是文件夹 + # 使用抽离的复制函数处理图片 + new_filename = process_local_image_copy(abs_img_path, output_path) + dest_path = os.path.join(output_path, new_filename) + print(f"已复制: {abs_img_path} → {dest_path}") + # 使用绝对路径替换 + new_ref = dest_path.replace('\\', '/') + content = content.replace(img_path, new_ref) + else: + print(f"警告: 跳过文件夹 {abs_img_path}") + else: + print(f"警告: 图片文件不存在 {abs_img_path}") + + # 写回修改后的内容 + with open(md_file, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新: {md_file}") + +def process_md_file_with_assets(md_file, output_base_path): + """ + 处理单个 Markdown 文件,将其拷贝到 output_base_path// 下, + 并在该文件夹中建立 assets 文件夹保存相关图片。 + 同时更新 md 文件中图片的引用路径为相对路径 assets/ + """ + # 创建对应的输出文件夹及 assets 子文件夹 + md_filename = os.path.basename(md_file) + md_name, _ = os.path.splitext(md_filename) + target_folder = os.path.join(output_base_path, md_name) + assets_folder = os.path.join(target_folder, "assets") + os.makedirs(assets_folder, exist_ok=True) + + # 读取 Markdown 文件内容 + with open(md_file, 'r', encoding='utf-8') as f: + content = f.read() + + # 使用抽离的函数提取图片路径 + img_paths = extract_image_paths(content) + + # 获取 md 文件所在目录(用于处理相对路径的本地图片) + md_dir = os.path.dirname(md_file) + + # 遍历所有图片路径 + for img_path in img_paths: + new_filename = None + if img_path.startswith(('http://', 'https://')): + # 处理网络图片:下载图片到 assets_folder + try: + # 处理网络图片:下载图片到 assets_folder + new_filename = download_image(img_path, assets_folder) + except Exception as e: + print(f"错误: 下载图片 {img_path} 时出错: {e}") + else: + # 处理本地图片 + if os.path.isabs(img_path): + abs_img_path = img_path + else: + abs_img_path = os.path.normpath(os.path.join(md_dir, img_path)) + if os.path.exists(abs_img_path) and os.path.isfile(abs_img_path): + try: + # 使用抽离的复制函数处理图片 + new_filename = process_local_image_copy(abs_img_path, assets_folder) + print(f"已复制: {abs_img_path} → {os.path.join(assets_folder, new_filename)}") + except PermissionError as e: + print(f"错误: 无法复制文件 {abs_img_path},权限被拒绝: {e}") + else: + print(f"警告: 图片文件不存在或不是文件 {abs_img_path}") + + # 如果成功处理图片,则替换 md 文件中的引用路径 + if new_filename: + new_ref = f"assets/{new_filename}" + content = content.replace(img_path, new_ref) + + # 将更新后的 md 内容写入目标文件夹中的 md 文件 + target_md_path = os.path.join(target_folder, md_filename) + with open(target_md_path, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新: {target_md_path}") + +def process_md_file_remote(md_file): + """ + 处理一个 Markdown 文件: + - 提取 Markdown 和 HTML 格式的图片路径 + - 对于本地图片,调用 upload_image 上传到 easyimage 图床, + 并替换 md 文件中的图片引用路径为返回的公网地址 + - 对于网络图片,保持不变 + """ + with open(md_file, 'r', encoding='utf-8') as f: + content = f.read() + + # 使用抽离的函数提取图片路径 + img_paths = extract_image_paths(content) + + # 获取当前 md 文件所在目录 + md_dir = os.path.dirname(md_file) + + for img_path in img_paths: + # 判断是否为本地图片(非网络 URL) + if not img_path.startswith(('http://', 'https://')): + if os.path.isabs(img_path): + abs_img_path = img_path + else: + abs_img_path = os.path.normpath(os.path.join(md_dir, img_path)) + + if os.path.exists(abs_img_path) and os.path.isfile(abs_img_path): + try: + public_url = upload_image(abs_img_path) + print(f"图片已上传: {abs_img_path} → {public_url}") + content = content.replace(img_path, public_url) + except Exception as e: + print(f"错误: 图片上传失败 {abs_img_path}: {e}") + else: + print(f"警告: 图片文件不存在 {abs_img_path}") + else: + print(f"跳过网络图片: {img_path}") + + with open(md_file, 'w', encoding='utf-8') as f: + f.write(content) + print(f"已更新: {md_file}") + + +def process_md_files(input_path,output_path,type): + # 创建输出目录(如果不存在) + os.makedirs(output_path, exist_ok=True) + + # 遍历处理所有 Markdown 文件 + for root, _, files in os.walk(input_path): + for file in files: + if file.lower().endswith('.md'): + md_file = os.path.join(root, file) + if type==1: + process_md_file_local(md_file, output_path) + elif type==2: + process_md_file_with_assets(md_file,output_path) + elif type==3: + process_md_file_remote(md_file) + else: + pass + + print("处理完成!所有图片已保存至:", os.path.abspath(output_path)) + + +if __name__ == "__main__": + type=1 + input_path = r'D:\folder\test\tt' + output_path = r'D:\folder\test\output2' + process_md_files(input_path,output_path,type) \ No newline at end of file diff --git a/transfer_md/upload_img.py b/transfer_md/upload_img.py new file mode 100644 index 0000000..9c5ea88 --- /dev/null +++ b/transfer_md/upload_img.py @@ -0,0 +1,50 @@ +import requests + + +def upload_image(img_path: str) -> str: + """ + 上传本地图片到 easyimage 图床,并返回图片的公网地址。 + + 参数: + img_path: 本地图片路径 + + 返回: + 图片在图床上的公网地址 + + API 参数说明: + - API 地址: http://124.71.159.195:1000/api/index.php + - 图片文件对应的 POST 参数名: image + - 自定义 body 参数: {"token": "1a61048560d9a63430816f98ba5a4fb0"} + - 响应 JSON 中的图片地址字段路径: url + """ + url = "https://pic.bitday.top/api/index.php" + token = "3b54c300cba118d185a4f9d2da9af513" + + try: + with open(img_path, "rb") as f: + files = {"image": f} + data = {"token": token} + response = requests.post(url, files=files, data=data) + + # 检查响应状态码是否为 200 OK + if response.status_code == 200: + result = response.json() + public_url = result.get("url") + if public_url: + return public_url + else: + raise ValueError("响应中未找到图片地址") + else: + raise Exception(f"上传失败,状态码: {response.status_code}, 响应内容: {response.text}") + except Exception as e: + raise Exception(f"上传过程中发生错误: {e}") + + +# 示例调用 +if __name__ == "__main__": + img_path = r"C:\Users\zhangsan\Pictures\社会实践\1.png" # 替换为实际图片路径 + try: + public_address = upload_image(img_path) + print("图片上传成功,公网地址:", public_address) + except Exception as err: + print("图片上传失败:", err) diff --git a/typecho_markdown_upload/config.py.example b/typecho_markdown_upload/config.py.example new file mode 100644 index 0000000..2c3c7db --- /dev/null +++ b/typecho_markdown_upload/config.py.example @@ -0,0 +1,21 @@ +base_folder = 'D:/Notes/' +exclude_folders = ['工作笔记'] + +# cos config +secret_id = 'xxx' # 替换为用户的 SecretId,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi +secret_key = 'xxx' # 替换为用户的 SecretKey,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi +region = 'ap-shanghai' +bucket = 'xxx' + +# typecho config +website_xmlrpc_url = '' # https://www.abc.com/index.php/action/xmlrpc +website_username = 'xxx' +website_password = 'xxx' + +# mysql config +mysql_host = 'localhost' +mysql_port = 3306 +mysql_username = 'xxx' +mysql_password = 'xxx' +mysql_typecho_database = 'typecho' +mysql_typecho_table_prefix = 'typecho_' \ No newline at end of file diff --git a/typecho_markdown_upload/cos_pic_uploader.py b/typecho_markdown_upload/cos_pic_uploader.py new file mode 100644 index 0000000..bf29d52 --- /dev/null +++ b/typecho_markdown_upload/cos_pic_uploader.py @@ -0,0 +1,17 @@ +import os.path + +from qcloud_cos import CosConfig, CosS3Client + + +class CosPicUploader: + def __init__(self, secret_id, secret_key, region, bucket): + self.__bucket = bucket + self.__config = CosConfig(Region=region, Secret_id=secret_id, Secret_key=secret_key) + self.__client = CosS3Client(self.__config) + + def upload_file(self, key, file_path): + file_path = file_path.replace('\\', '/') + with open(file_path, 'rb') as f: + self.__client.put_object(Bucket=self.__bucket, Body=f, Key=key) + res = self.__client.get_object_url(Bucket=self.__bucket, Key=key) + return res diff --git a/typecho_markdown_upload/main.py b/typecho_markdown_upload/main.py new file mode 100644 index 0000000..057578e --- /dev/null +++ b/typecho_markdown_upload/main.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +import logging +import os.path + +from markdown_file_searcher import scan_files +from markdown_img_searcher import scan_imgs +from cos_pic_uploader import CosPicUploader +import config +from typecho_xmlrpc_publisher import TypechoXmlRpcPublisher +from typecho_direct_mysql_publisher import TypechoDirectMysqlPublisher + +uploader = CosPicUploader( + config.secret_id, + secret_key=config.secret_key, + region=config.region, + bucket=config.bucket +) + +typecho_publisher = TypechoXmlRpcPublisher( + config.website_xmlrpc_url, + config.website_username, + config.website_password +) + +mysql_publisher = TypechoDirectMysqlPublisher( + config.mysql_host, + config.mysql_port, + config.mysql_username, + config.mysql_password, + config.mysql_typecho_database, + config.mysql_typecho_table_prefix +) + +def execute_flow_with_typecho_xmlrpc(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + file_base_path = os.path.dirname(file_path) + file_base_name = os.path.splitext(os.path.basename(file_path))[0] #无后缀文件名 + md_source_text = file.read() + md_img_urls = scan_imgs(file_path) + if len(md_img_urls) > 0: + for md_img_url in md_img_urls: + img_file = os.path.join(file_base_path, md_img_url) + img_file_name = os.path.basename(img_file) + oss_url = uploader.upload_file(key=file_base_name+'-'+img_file_name, file_path=img_file) + md_source_text = md_source_text.replace('](' + md_img_url + ')', '](' + oss_url + ')') + post_id = typecho_publisher.publish_post(file_base_name, md_source_text) + print('发布成功 --> ' + file_base_name + ' - ' + str(post_id)) + + +def execute_flow_with_typecho_mysql(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + file_base_path = os.path.dirname(file_path) + file_base_name = os.path.splitext(os.path.basename(file_path))[0] #无后缀文件名 + category_name = os.path.basename(file_base_path) + md_source_text = file.read() + md_img_urls = scan_imgs(file_path) + if len(md_img_urls) > 0: + for md_img_url in md_img_urls: + img_file = os.path.join(file_base_path, md_img_url) + img_file_name = os.path.basename(img_file) + oss_url = uploader.upload_file(key=file_base_name+'-'+img_file_name, file_path=img_file) + md_source_text = md_source_text.replace('](' + md_img_url + ')', '](' + oss_url + ')') + post_id = mysql_publisher.publish_post(file_base_name, md_source_text, category_name) + print('发布成功 --> ' + file_base_name + ' - ' + str(post_id)) + + +if __name__ == '__main__': + logging.basicConfig(level='ERROR') + files = scan_files(config.base_folder, config.exclude_folders) + for md_file in files: + # execute_flow_with_typecho_xmlrpc(md_file) + execute_flow_with_typecho_mysql(md_file) diff --git a/typecho_markdown_upload/markdown_file_searcher.py b/typecho_markdown_upload/markdown_file_searcher.py new file mode 100644 index 0000000..96a0988 --- /dev/null +++ b/typecho_markdown_upload/markdown_file_searcher.py @@ -0,0 +1,27 @@ +import os +from os import path + + +# md文件扫描 +def __scaner_files(results, file_path, exclude_folders=[]): + file = os.listdir(file_path) + for f in file: + real_path = path.join(file_path, f) + if path.isfile(real_path): + if path.basename(real_path).endswith('.md'): + results.append(path.abspath(real_path)) + # 如果是文件,则保存绝对路径 + elif path.isdir(real_path): + # 如果是目录,则是递归 + if path.basename(real_path) in exclude_folders: + continue + else: + __scaner_files(results, real_path, exclude_folders) + else: + print("error") + + +def scan_files(file_path, exclude_folders): + results = [] + __scaner_files(results, file_path, exclude_folders) + return results diff --git a/typecho_markdown_upload/markdown_img_searcher.py b/typecho_markdown_upload/markdown_img_searcher.py new file mode 100644 index 0000000..ca6c485 --- /dev/null +++ b/typecho_markdown_upload/markdown_img_searcher.py @@ -0,0 +1,30 @@ +import io +import os.path + +import panflute +import pypandoc + + +# 读取md图片地址 +def __prepare(doc): + doc.images = [] + doc.links = [] + + +def __action(elem, doc): + if isinstance(elem, panflute.Image): + doc.images.append(elem) + elif isinstance(elem, panflute.Link): + doc.links.append(elem) + + +def scan_imgs(file_path): + data = pypandoc.convert_file(file_path, 'json') + doc = panflute.load(io.StringIO(data)) + doc.images = [] + doc.links = [] + doc = panflute.run_filter(__action, prepare=__prepare, doc=doc) + results = [] + for image in doc.images: + results.append(image.url) + return results diff --git a/typecho_markdown_upload/typecho_direct_mysql_publisher.py b/typecho_markdown_upload/typecho_direct_mysql_publisher.py new file mode 100644 index 0000000..9e9973f --- /dev/null +++ b/typecho_markdown_upload/typecho_direct_mysql_publisher.py @@ -0,0 +1,87 @@ +import pymysql +import time + +from pymysql.converters import escape_string + + +class TypechoDirectMysqlPublisher: + def __init__(self, host, port, user, password, database, table_prefix): + self.__table_prefix = table_prefix + self.__categories_table_name = table_prefix + 'metas' + self.__relationships_table_name = table_prefix + 'relationships' + self.__contents_table_name = table_prefix + 'contents' + self.__db = pymysql.connect( + host=host, + port=port, + user=user, + password=password, + database=database, + charset='utf8mb4' + ) + self.__init_categories() + + def __init_categories(self): + cursor = self.__db.cursor() + sql = "select mid,name from %s where type='%s'" % (self.__categories_table_name, 'category') + cursor.execute(sql) + results = cursor.fetchall() + self.__exist_categories = [] + for item in results: + self.__exist_categories.append({ + 'mid': item[0], + 'name': item[1] + }) + + def __get_category_id(self, category_name): + if len(self.__exist_categories) > 0: + for item in self.__exist_categories: + if item['name'] == category_name: + return item['mid'] + return -1 + + def __add_category(self, category_name): + cursor = self.__db.cursor() + sql = "INSERT INTO %s " \ + "(`name`, `slug`, `type`, `description`, `count`, `order`, `parent`) " \ + "VALUES " \ + "('%s', '%s', 'category', '', 0, 1, 0)" % (self.__categories_table_name, category_name, category_name) + cursor.execute(sql) + mid = cursor.lastrowid + self.__db.commit() + self.__init_categories() + return mid + + def __insert_relationship(self,cursor, cid, mid): + insert_relationship_sql = "INSERT INTO %s" \ + "(`cid`, `mid`) " \ + "VALUES " \ + "(%d, %d)" % (self.__relationships_table_name, cid, mid) + cursor.execute(insert_relationship_sql) + + def __update_category_count(self, cursor, mid): + update_category_count_sql = "UPDATE %s SET `count`=`count`+1 WHERE mid=%d" % (self.__categories_table_name, mid) + cursor.execute(update_category_count_sql) + + def publish_post(self, title, content, category): + content = '' + content + mid = self.__get_category_id(category) + if mid < 0: + mid = self.__add_category(category) + + now_time_int = int(time.time()) + cursor = self.__db.cursor() + sql = "INSERT INTO %s " \ + "(`title`, `slug`, `created`, `modified`, `text`, `order`, `authorId`, `template`, `type`, `status`, `password`, `commentsNum`, `allowComment`, `allowPing`, `allowFeed`, `parent`) " \ + "VALUES " \ + "('%s', NULL , %d, %d, '%s', 0, 1, NULL, 'post', 'publish', NULL, 0, '1', '1', '1', 0)" \ + "" % (self.__contents_table_name, escape_string(title), now_time_int, now_time_int, escape_string(content)) + cursor.execute(sql) + cid = cursor.lastrowid + update_slug_sql = "UPDATE %s SET slug=%d WHERE cid=%d" % (self.__contents_table_name, cid, cid) + cursor.execute(update_slug_sql) + + self.__insert_relationship(cursor, cid=cid, mid=mid) + self.__update_category_count(cursor, mid) + + self.__db.commit() + return cid diff --git a/typecho_markdown_upload/typecho_xmlrpc_publisher.py b/typecho_markdown_upload/typecho_xmlrpc_publisher.py new file mode 100644 index 0000000..28f6c5a --- /dev/null +++ b/typecho_markdown_upload/typecho_xmlrpc_publisher.py @@ -0,0 +1,11 @@ +# typecho api调用 +from pytypecho import Post, Typecho + + +class TypechoXmlRpcPublisher: + def __init__(self, xmlrpc_url, username, password): + self.__typecho = Typecho(xmlrpc_url, username=username, password=password) + + def publish_post(self, title, content): + post = Post(title=title, description=content) + return self.__typecho.new_post(post, publish=True)