Skip to content

Commit

Permalink
1.2
Browse files Browse the repository at this point in the history
## 更新日志

### BUG FIX
- 重写了文章上传逻辑,现在是基于本地文件.slug_cid_mapping.yml来实现自动识别更新文章或者上传新文章的逻辑,请不要删除这个文件
- 重写了读取YAML头信息的读取方式,修复了之前有可能读取头文件不完全的BUG
- 重写了加载逻辑
- 优化了error.txt的判断点,现在代码更健壮了
- 增加了企业微信应用id的可配置变量,现在可以在配置文件中修改它
- 错别字修正

### 功能更新
- 集成一个WebDav服务端,在服务器中部署它,或者在电脑中运行他,现在可以直接使用WebDav来配合Obsidian的webdav同步插件来实现无缝上传或者更新文章了
- 支持了XMLRPC上传解析YAML头信息中标签信息,但是这里有一个已知 BUG 详见 typecho/typecho#1607
- 增加异步执行的相关代码

### 重要公告
- 如果想直接运行在本地运行.py程序,请务必安装requirements.txt中的相关依赖
  • Loading branch information
Xingsandesu committed Jul 31, 2023
1 parent 282edd5 commit 7b7106f
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 50 deletions.
243 changes: 196 additions & 47 deletions Server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,84 @@
import logging
import requests
import json
import platform
import subprocess
import threading


# 运行命令部分
def run_command_with_output(command):
webdav_logger = WebDavLogger()
try:
# 执行命令并捕获输出
process = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
)

# 实时输出标准输出和错误输出,使用临时日志记录器
for line in process.stdout:
webdav_logger.logger.info(line.rstrip())

for line in process.stderr:
webdav_logger.logger.error(line.rstrip())

# 等待子进程完成
process.wait()

except Exception as e:
logger.logger.error(f"执行命令时出错:{e}")


# Webdav 构建命令运行服务器部分
class WebDAVServer:
def __init__(self, webdav_host="0.0.0.0", webdav_port=80, webdav_root_path="/tmp", webdav_auth_type="nt"):
self.host = webdav_host
self.port = webdav_port
self.root_path = webdav_root_path

def start_server(self):
system_type = platform.system()
try:

if system_type == "Windows":
command = f"wsgidav --host={self.host} --port={self.port} --root={self.root_path} --auth=nt"
elif system_type == "Linux":
command = f"wsgidav --host={self.host} --port={self.port} --root={self.root_path} --auth=pam-login"
elif system_type == "Darwin":
command = f"wsgidav --host={self.host} --port={self.port} --root={self.root_path} --auth=pam-login"
else:
logger.logger.error("不支持的操作系统类型。")

command_to_run = command
command_thread = threading.Thread(target=run_command_with_output, args=(command_to_run,))
command_thread.start()

except:
timestamp = time.strftime('%Y%m%d%H%M%S', time.localtime())
filename = f'error_{timestamp}.txt'
with open(filename, 'w') as f:
traceback.print_exc(file=f)
logger.logger.error(f"未知错误,报错已保存到{filename}")


# 企业微信推送部分
class WeChatPush:
def __init__(self, corpid, corpsecret):
def __init__(self, corpid, corpsecret, agentid):
self.corpid = corpid
self.corpsecret = corpsecret
self.agentid = agentid
self.access_token = self.get_access_token()

def send_text_message(self, message):
send_text_url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={}".format(self.access_token)
data = {
"touser": "@all",
"msgtype": "text",
"agentid": 1000002,
"agentid": self.agentid,
"text": {
"content": message
},
Expand All @@ -51,7 +114,7 @@ def send_picture_message(self, media_id):
data = {
"touser": "@all",
"msgtype": "image",
"agentid": 1000002,
"agentid": self.agentid,
"image": {
"media_id": media_id
},
Expand Down Expand Up @@ -82,12 +145,11 @@ def __init__(self):
sh = logging.StreamHandler()

# 创建处理器:sh为控制台处理器,fh为文件处理器,log_file为日志存放的文件夹
# log_file = os.path.join(log_dir,"{}_log".format(time.strftime("%Y/%m/%d",time.localtime())))
log_file = os.path.join("print.log")
fh = logging.FileHandler(log_file, mode="a", encoding="UTF-8")

# 创建格式器,并将sh,fh设置对应的格式
formator = logging.Formatter(fmt="%(asctime)s %(levelname)s %(message)s",
formator = logging.Formatter(fmt="%(asctime)s %(levelname)s :%(message)s",
datefmt="%Y/%m/%d %X")
sh.setFormatter(formator)
fh.setFormatter(formator)
Expand All @@ -97,6 +159,31 @@ def __init__(self):
self.logger.addHandler(fh)


class WebDavLogger:
def __init__(self):
# 创建一个日志器
self.logger = logging.getLogger("webdav_logger")

# 设置日志输出的最低等级,低于当前等级则会被忽略
self.logger.setLevel(logging.INFO)

# 创建处理器:sh为控制台处理器,fh为文件处理器
sh = logging.StreamHandler()

# 创建处理器:sh为控制台处理器,fh为文件处理器,log_file为日志存放的文件夹
log_file = os.path.join("print.log")
fh = logging.FileHandler(log_file, mode="a", encoding="UTF-8")

# 创建格式器,并将sh,fh设置对应的格式
formator = logging.Formatter('')
sh.setFormatter(formator)
fh.setFormatter(formator)

# 将处理器,添加至日志器中
self.logger.addHandler(sh)
self.logger.addHandler(fh)


# 文件夹 Hook 部分
class FolderWatcher:
def __init__(self, folder_path):
Expand Down Expand Up @@ -157,38 +244,43 @@ def publish_to_typecho(markdown_file, url, username, password):
发布Markdown内容到Typecho
"""
# 读取Markdown文件内容
global metadata
with open(markdown_file, 'r', encoding='utf-8') as f:
content = f.read()

# 读取Markdown
# 读取Markdown文件名
title = os.path.splitext(os.path.basename(markdown_file))[0]

# 解析YAML头信息
yaml_end = content.find('---', 3)
try:
if yaml_end != -1:
yaml_str = content[3:yaml_end]
metadata = yaml.safe_load(yaml_str)
else:
metadata = {}
except yaml.constructor.ConstructorError:
logger.logger.error("YAML头信息解析错误,请检查YAML头信息内容和格式是否正确")
wechat_push.send_text_message("YAML头信息解析错误,请检查YAML头信息内容和格式是否正确")
sys.exit()
yaml_lines = []
content_lines = content.splitlines()
yaml_start = None
for idx, line in enumerate(content_lines):
if line.strip() == '---':
if yaml_start is not None:
yaml_end = idx
break
else:
yaml_start = idx + 1
elif yaml_start is not None:
yaml_lines.append(line)

# 连接到Typecho的XML-RPC接口
server = xmlrpc.client.ServerProxy(url)
if yaml_start is not None:
yaml_str = "\n".join(yaml_lines)
try:
metadata = yaml.safe_load(yaml_str)
except yaml.constructor.ConstructorError:
print("YAML头信息解析错误,请检查YAML头信息内容和格式是否正确")
return
else:
metadata = {}

# 登录并获取会话ID
session_id = server.metaWeblog.getUsersBlogs('', username, password)[0]['blogid']

# 获取分类

categories = metadata.get('categories', [])
# 连接到Typecho的XML-RPC接口
server = xmlrpc.client.ServerProxy(url)

# 解析去除 YAML 字段 正文部分Markdown文档
md_content = content[yaml_end + 3:]
md_content = "\n".join(content_lines[yaml_end + 1:]) # 连接YAML头之后的内容行
# 创建 Mistune 实例,解析 Markdown 正文文档,并添加TOC Table Strikethrough删除线
markdown = mistune.create_markdown(
plugins=[
Expand All @@ -200,22 +292,56 @@ def publish_to_typecho(markdown_file, url, username, password):
)
html_content = markdown(md_content)

# 文章参数
post = {
'title': title, # 文章标题
'description': html_content, # 文章内容
'categories': categories, # 文章分类
}

# 发布文章
post_id = server.metaWeblog.newPost(session_id, username, password, post, True)

if post_id:
logger.logger.info(f"文章发布成功!文章ID为:{post_id}")
wechat_push.send_text_message(f"文章发布成功!文章ID为:{post_id}")
# 从 .slug_cid_mapping.yml 中加载已保存的文章信息
try:
with open(".slug_cid_mapping.yml", "r") as file:
slug_cid_mapping = yaml.safe_load(file)
except FileNotFoundError:
slug_cid_mapping = {}

# 检查是否有对应标题的文章已经发布
if title in slug_cid_mapping:
post_id = slug_cid_mapping[title]['post_id']
# 使用已有的文章ID进行更新
post = {
'title': title, # 文章标题
'description': html_content, # 文章内容
'categories': metadata.get('categories', []), # 文章分类
'mt_keywords': metadata.get('mt_keywords', []) # 文章Tag
}
server.metaWeblog.editPost(post_id, username, password, post, True)
logger.logger.info(f"标题:{title},id:{post_id}文章更新成功!")
wechat_push.send_text_message(f"标题:{title},id:{post_id}文章更新成功!")
else:
logger.logger.error("文章发布失败!")
wechat_push.send_text_message("文章发布失败!")
# 如果文章是新的,使用 XML-RPC API 进行发布
# 登录并获取会话ID
session_id = server.metaWeblog.getUsersBlogs('', username, password)[0]['blogid']
# 文章参数
post = {
'title': title, # 文章标题
'description': html_content, # 文章内容
'categories': metadata.get('categories', []), # 文章分类
'mt_keywords': metadata.get('mt_keywords', []) # 文章Tag
}
# 发布文章
post_id = server.metaWeblog.newPost(session_id, username, password, post, True)
if post_id:
logger.logger.info(f"标题:{title},id:{post_id}文章发布成功!")
wechat_push.send_text_message(f"标题:{title},id:{post_id}文章发布成功!")
# 将新文章的信息保存到 .slug_cid_mapping 中
slug_cid_mapping[title] = {
'post_id': post_id,
'categories': metadata.get('categories', []),
'mt_keywords': metadata.get('mt_keywords', [])
}
# 保存更新后的 .slug_cid_mapping.yml 文件,使用追加模式 'a'
with open(".slug_cid_mapping.yml", "a") as file:
file.write(f"\n{title}:\n")
file.write(f" post_id: {post_id}\n")
file.write(f" categories: {metadata.get('categories', [])}\n")
file.write(f" mt_keywords: {metadata.get('mt_keywords', [])}\n")
else:
print("文章发布失败!")


# 配置文件部分
Expand All @@ -234,10 +360,14 @@ def get_default_config():
'QingQ': {
'Corp_id': 'Wechat Corp_id',
'Corp_secret': 'Wechat Corp_secret',
'File_dir': 'The absolute path of the hook folder',
'Agentid': 'Wechat App Agentid',
'File_dir': 'Hook File PS: C:\...\...',
'Url': 'https://Your_Server_Url/action/xmlrpc',
'Username': 'Your_User',
'Password': 'Your_password'
'Username': 'Your_Typecho_User',
'Password': 'Your_Typecho_Password',
'Webdav_host': '0.0.0.0',
'Webdav_port': '8080',
'Webdav_root_path': 'Webdav Server Root File PS: /.../... '
}
}

Expand Down Expand Up @@ -304,7 +434,7 @@ def save_config(cls, config_data):


def ver():
ver = '1.1 - Server'
ver = '1.2 - Server'
logger.logger.info('--------------------')
logger.logger.info('Copyright (C) 2023 kodesu, Inc. All Rights Reserved ')
logger.logger.info('Date : 2023-7')
Expand All @@ -316,7 +446,6 @@ def ver():
logger.logger.info('--------------------')



if __name__ == "__main__":
# 版本显示
try:
Expand Down Expand Up @@ -354,7 +483,11 @@ def ver():
password = config['QingQ']['Password']
file = config['QingQ']['File_dir']
corpid = config['QingQ']['Corp_id']
agentid = config['QingQ']['Agentid']
corpsecret = config['QingQ']['Corp_secret']
Webdav_host = config['QingQ']['Webdav_host']
Webdav_port = config['QingQ']['Webdav_port']
Webdav_root_path = config['QingQ']['Webdav_root_path']
except NameError:
logger.logger.error("请检查配置文件,确认相关信息是否正确,服务器是否能够正常访问,XML-PRC接口是否开启")

Expand All @@ -365,18 +498,27 @@ def ver():
logger.logger.info(f"密码:{password}")
logger.logger.info(f"监视文件夹路径:{file}")
logger.logger.info(f"企业微信企业id值:{corpid}")
logger.logger.info(f"应用secret值:{corpsecret}")
logger.logger.info(f"企业微信应用secret值:{corpsecret}")
logger.logger.info(f"企业微信应用Agentid值:{agentid}")
logger.logger.info(f"WebDav服务器监听IP:{Webdav_host}")
logger.logger.info(f"WebDav服务器监听端口:{Webdav_port}")
logger.logger.info(f"WebDav服务器工作目录:{Webdav_root_path}")
except NameError:
logger.logger.error("无法加载配置文件")

# 加载微信推送 Class
try:
wechat_push = WeChatPush(corpid, corpsecret)
wechat_push = WeChatPush(corpid, corpsecret, agentid)
except KeyError:
logger.logger.error("企业微信配置错误,请检查企业微信相关配置")
except NameError:
logger.logger.error("企业微信配置错误,请检查企业微信相关配置")

# 加载Webdav Server
webdav_server = WebDAVServer(webdav_host=Webdav_host, webdav_port=Webdav_port,
webdav_root_path=Webdav_root_path)
webdav_server.start_server()

# 加载 文件Hook Class
try:
watcher = FolderWatcher(file)
Expand All @@ -385,6 +527,13 @@ def ver():
logger.logger.error("监视文件路径配置错误,请检查监视文件路径是否正确")
except NameError:
logger.logger.error("监视文件路径配置错误,请检查监视文件路径是否正确")
except OSError:
logger.logger.error("监视文件路径配置错误,请检查监视文件路径是否正确")





except Exception:
# 捕获所有异常,并将Traceback信息写入到文件中
try:
Expand Down
10 changes: 7 additions & 3 deletions Template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
日期: {{date: YYYY年 MM 日 DD 日}}
时间: {{time: HH 点 mm 分}}
Tags:
- 折腾
- Obsidian标签

categories:
- 折腾
- Typecho分类

mt_keywords: # 此处有一个已知bug 详见 https://github.com/typecho/typecho/issues/1607
-标签1

---

Expand All @@ -17,4 +21,4 @@ categories:

## 二级标题

内容
内容

0 comments on commit 7b7106f

Please sign in to comment.