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)