Skip to content
67 changes: 67 additions & 0 deletions classify_rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"_comment": "新闻类型分类规则 - 按优先级从高到低排列,首个匹配的规则生效",
"_doc": {
"pattern": "正则表达式(不区分大小写)",
"exclude": "排除模式列表 - 标题命中任一排除模式则跳过该规则",
"require_chinese": "是否也检测中文关键词",
"chinese_keywords": "中文关键词列表(任一命中即匹配)"
},
"rules": [
{
"type": "java_snapshot",
"pattern": "\\b\\d{2}w\\d{2}[a-z]\\b|\\b(?:minecraft|java\\s*edition)[\\s\\S]*?snapshot\\b",
"exclude": [],
"require_chinese": true,
"chinese_keywords": ["快照"]
},
{
"type": "java_prerelease",
"pattern": "\\bpre[-\\s]?release\\b",
"exclude": [],
"require_chinese": true,
"chinese_keywords": ["预发布", "预发布版"]
},
{
"type": "java_rc",
"pattern": "\\brelease[-\\s]+candidate\\b",
"exclude": [],
"require_chinese": true,
"chinese_keywords": ["候选", "候选版本"]
},
{
"type": "bedrock_beta",
"pattern": "\\b(?:beta|preview)\\b[\\s\\S]*?\\d{2,}",
"exclude": [],
"require_chinese": true,
"chinese_keywords": ["预览", "测试版"]
},
{
"type": "bedrock_release",
"pattern": "bedrock[\\s\\S]*?(?:edition|changelog)|\\b基岩版\\b",
"exclude": ["\\bcaption", "\\bsubtitle", "\\blocali[sz]", "\\btranslation\\b", "\\baccessibility\\b"],
"require_chinese": true,
"chinese_keywords": ["基岩版"]
},
{
"type": "java_release",
"pattern": "java[-\\s]*edition[\\s\\S]*?\\d{2,}|\\d{2,}[\\s\\S]*?java[-\\s]*edition",
"exclude": ["\\bsnapshot\\b", "\\bpre[-\\s]?release\\b", "\\brelease[-\\s]+candidate\\b"],
"require_chinese": true,
"chinese_keywords": ["Java版"]
},
{
"type": "host",
"pattern": "\\b(?:xbox|nintendo[-\\s]*switch|playstation|ps[45]\\b|switch\\s*版)",
"exclude": [],
"require_chinese": false,
"chinese_keywords": ["主机"]
},
{
"type": "flash",
"pattern": "\\bflash\\b|块讯|速报",
"exclude": [],
"require_chinese": true,
"chinese_keywords": ["块讯", "速报"]
}
]
}
4 changes: 3 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@
"bedrock_beta": 20,
"bedrock_release": 20,
"commentary": 16,
"normal": 16
"normal": 16,
"host": 16,
"flash": 16
},
"attach_json": true
}
Expand Down
12 changes: 12 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ def run_post(processed: list, config: dict, no_image: bool = False, no_json: boo
success = 0
failed = 0

# 处理上次运行中进入审核的待高亮帖子
try:
poster.process_pending_highlights(save_dir)
except Exception as e:
print(f"[主] 处理待高亮帖子时出错: {e}")

for stem, txt_path, json_path in processed:
try:
print(f"\n[主] 发布: {stem}")
Expand Down Expand Up @@ -365,6 +371,12 @@ def run_post_only(config: dict):
print(f"\n[主] MCBBS 登录失败: {e}")
return

# 处理待高亮帖子
try:
poster.process_pending_highlights(save_dir)
except Exception as e:
print(f"[主] 处理待高亮帖子时出错: {e}")

for stem, txt_path, json_path in pending:
try:
print(f"\n[主] 发布: {stem}")
Expand Down
237 changes: 236 additions & 1 deletion poster.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@
'bedrock_release': '基岩版资讯',
'commentary': '块讯',
'normal': '块讯',
'host': '主机资讯',
'flash': '快讯',
}

# 高亮颜色映射(news_type → Discuz highlight_color 值)
# 对应 MCBBS 管理操作中的高亮设定
HIGHLIGHT_COLOR_MAP = {
'java_release': 1, # 正式版 #EE1B2E
'bedrock_release': 1, # 正式版 #EE1B2E
'java_snapshot': 4, # 快照/预览版 #3C9D40
'java_prerelease': 4, # 预发布版 #3C9D40
'java_rc': 4, # 候选版本 #3C9D40
'bedrock_beta': 4, # 测试版 #3C9D40
'flash': 8, # 块讯 #EC1282
'host': 2, # 主机资讯 #EE5023
'peripheral': 6, # 周边消息 #2B65B7
'normal': 8, # 块讯(兜底)#EC1282
}

UA = (
Expand Down Expand Up @@ -562,6 +579,192 @@ def post_thread(self, title: str, message: str,

raise RuntimeError(f"发帖结果不明: {r.url}")

def _apply_highlight(self, thread_url: str, highlight_color: int) -> bool:
"""对已发帖子应用高亮(Discuz topicadmin 管理操作)

需要版主/管理员权限。通过 topicadmin moderate 端点提交高亮请求。
"""
if not self.session:
return False
if highlight_color <= 0:
return False

tid_match = re.search(r'thread-(\d+)-', thread_url)
if not tid_match:
print(" ⚠ 无法提取帖子 ID,跳过高亮")
return False
tid = tid_match.group(1)

try:
# 确保 formhash 有效
if not self.formhash:
r = self.session.get(f"{self.base_url}/forum.php")
r.raise_for_status()
self.formhash = extract_formhash(r.text)

# 通过 topicadmin 获取高亮表单
moderate_url = (
f"{self.base_url}/forum.php?mod=topicadmin&action=moderate"
f"&fid={self.forum_fid}&tid={tid}&optgroup=1&infloat=yes&inajax=1"
)
referer = f"{self.base_url}/thread-{tid}-1-1.html"
self.session.headers.update({"Referer": referer, "X-Requested-With": "XMLHttpRequest"})

r_get = self.session.get(moderate_url)
self.session.headers.pop("X-Requested-With", None)

# 检查是否有权限
if "没有权限" in r_get.text or "alert_error" in r_get.text:
err_m = re.search(r'class="alert_error">([^<]+)', r_get.text)
err_msg = err_m.group(1).strip() if err_m else "无管理权限"
print(f" ⚠ 高亮失败: {err_msg}")
self.session.headers.pop("Referer", None)
return False

# 从返回的表单中提取 formhash
form_fh = re.search(r'name="formhash"\s+value="([a-f0-9]+)"', r_get.text)
if not form_fh:
print(" ⚠ 无法提取高亮表单 formhash")
self.session.headers.pop("Referer", None)
return False

# 提交高亮(Discuz topicadmin 标准参数)
action_url = (
f"{self.base_url}/forum.php?mod=topicadmin&action=moderate"
f"&optgroup=1&modsubmit=yes&infloat=yes"
)
post_data = {
"formhash": form_fh.group(1),
"fid": str(self.forum_fid),
"redirect": f"{self.base_url}/thread-{tid}-1-1.html",
"handlekey": "mods",
"moderate[]": tid,
"operations[]": "highlight",
"highlight_color": str(highlight_color),
"highlight_style[1]": "0",
"highlight_style[2]": "0",
"highlight_style[3]": "0",
"highlight_bgcolor": "",
"expirationhighlight": "",
"sendreasonpm": "on",
}

r_post = self.session.post(action_url, data=post_data)
self.session.headers.pop("Referer", None)

# 检查结果
resp = r_post.text
if "succeedhandle" in resp or "成功" in resp:
return True
if "没有权限" in resp:
print(" ⚠ 高亮失败: 账号无管理权限")
return False
if "errorhandle" in resp:
err_m = re.search(r"errorhandle[^']*'([^']+)'", resp)
print(f" ⚠ 高亮失败: {err_m.group(1) if err_m else '未知错误'}")
return False
return False
except Exception as e:
print(f" ⚠ 高亮失败: {e}")
return False

def _save_pending_highlight(self, post_url: str, original_title: str,
translated_title: str, news_dir: str):
"""保存待高亮的帖子信息到 JSON 文件,等待审核通过后处理"""
from utils import classify_article_type
news_type = None
if original_title:
news_type = classify_article_type(original_title, chinese=False, fallback=None)
if not news_type:
news_type = classify_article_type(translated_title, chinese=True, fallback=None)
if not news_type:
news_type = "normal"
highlight_color = HIGHLIGHT_COLOR_MAP.get(news_type, 0)

pending_file = os.path.join(news_dir, ".pending_highlights.json")
pending = []
if os.path.exists(pending_file):
try:
with open(pending_file, encoding="utf-8") as f:
pending = json.load(f)
except (OSError, json.JSONDecodeError):
pass

# 提取 thread ID
tid_match = re.search(r'thread-(\d+)-', post_url)
tid = tid_match.group(1) if tid_match else ""

pending.append({
"url": post_url,
"tid": tid,
"title": original_title,
"translated_title": translated_title,
"news_type": news_type,
"highlight_color": highlight_color,
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
})

try:
with open(pending_file, "w", encoding="utf-8") as f:
json.dump(pending, f, ensure_ascii=False, indent=2)
print(f" 📝 已保存待高亮信息(共{len(pending)}条待处理)")
except OSError as e:
print(f" ⚠ 保存待高亮信息失败: {e}")

def process_pending_highlights(self, news_dir: str) -> int:
"""处理待高亮的帖子(审核通过后调用)。返回成功数。"""
pending_file = os.path.join(news_dir, ".pending_highlights.json")
if not os.path.exists(pending_file):
return 0

try:
with open(pending_file, encoding="utf-8") as f:
pending = json.load(f)
except (OSError, json.JSONDecodeError):
return 0

if not pending:
return 0

print(f"\n[*] 处理 {len(pending)} 条待高亮帖子...")
remaining = []
success = 0
color_names = {
1: "红色(正式版)", 2: "橙色(主机)",
4: "绿色(快照/测试版)", 6: "蓝色(周边)",
8: "粉色(块讯)",
}

for item in pending:
url = item.get("url", "")
color = item.get("highlight_color", 0)
title = item.get("translated_title") or item.get("title", "")

if not url.startswith("http") or not color:
continue

print(f" 尝试高亮: {title[:50]}...")
if self._apply_highlight(url, color):
print(f" ✓ 高亮成功: {color_names.get(color, str(color))}")
success += 1
else:
# 可能仍未审核通过,保留待处理
remaining.append(item)

# 更新待处理文件
try:
if remaining:
with open(pending_file, "w", encoding="utf-8") as f:
json.dump(remaining, f, ensure_ascii=False, indent=2)
print(f" 剩余 {len(remaining)} 条待处理")
else:
os.remove(pending_file)
print(" 所有待高亮帖子已处理完毕")
except OSError:
pass

return success

def post_news_file(self, stem: str, txt_path: str, json_path: str,
news_dir: str, no_image: bool = False,
attach_json: bool = True) -> str:
Expand Down Expand Up @@ -627,7 +830,39 @@ def post_news_file(self, stem: str, txt_path: str, json_path: str,
else:
print(f" ⚠ 警告: 未找到分类 {module_type} 的 sortid 配置")

return self.post_thread(title, message, attachment_ids=attachment_ids, sortid=sortid)
post_url = self.post_thread(title, message, attachment_ids=attachment_ids, sortid=sortid)

# 发帖后自动高亮(审核中的帖子跳过,记录待处理)
need_moderation = "(需审核)" in (post_url or "")
if post_url and post_url.startswith("http") and not need_moderation:
# 用原标题检测新闻类型用于高亮(英文优先,中文补充)
from utils import classify_article_type
news_type = None
if original_title:
news_type = classify_article_type(original_title, chinese=False, fallback=None)
if not news_type:
news_type = classify_article_type(title, chinese=True, fallback=None)
# 兜底:无法识别的新闻默认归为“块讯”,确保所有帖子都有高亮
if not news_type:
news_type = "normal"
highlight_color = HIGHLIGHT_COLOR_MAP.get(news_type, 0)
if highlight_color:
color_names = {
1: "红色(正式版)", 2: "橙色(主机)",
4: "绿色(快照/测试版)", 6: "蓝色(周边)",
8: "粉色(块讯)",
}
print(f" 高亮: {color_names.get(highlight_color, str(highlight_color))}")
if self._apply_highlight(post_url, highlight_color):
print(" ✓ 高亮设置成功!")
else:
print(" ⚠ 高亮设置失败(不影响发帖)")
elif need_moderation:
# 审核中的帖子:记录待高亮信息,下次运行时补上
print(" ⏳ 帖子进入审核,高亮待审核通过后处理")
self._save_pending_highlight(post_url, original_title, title, news_dir)

return post_url


# ── 状态管理 ─────────────────────────────────────────
Expand Down
Loading
Loading