自動上傳
import tkinter as tk
from tkinter import messagebox, simpledialog, ttk
import os
import re
import datetime
import json
import subprocess
import sys
# --- 自動檢查並安裝必要的套件 ---
def install_dependencies():
# 額外增加 requests 套件用於 API 上傳
dependencies = ["beautifulsoup4", "requests"]
for lib in dependencies:
try:
__import__(lib if lib != "beautifulsoup4" else "bs4")
except ImportError:
print(f"正在安裝缺失的套件: {lib}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", lib])
install_dependencies()
from bs4 import BeautifulSoup
import requests
# --- 設定區 ---
API_KEY = ' '
POSTS_FOLDER = "posts"
CSS_PATH = "../style.css"
SEARCH_INDEX_FILE = "search_index.json"
# UI 配色
BG_MAIN = "#1e1e1e"
BG_SECOND = "#252526"
FG_MAIN = "#d4d4d4"
FG_ACCENT = "#6fb1fc"
BG_BTN_DEFAULT = "#333333"
# 乾淨的 HTML 模板
HTML_TEMPLATE = """
{title}
{content}
"""
class BlogEditor:
def __init__(self, root):
self.root = root
self.root.title("Neocities 專業文章編輯器 v2.7 (自動上傳版)")
self.root.geometry("800x900")
self.root.configure(bg=BG_MAIN)
# 1. 頂部標題輸入區
header_frame = tk.Frame(root, bg=BG_MAIN)
header_frame.pack(fill='x', padx=20, pady=(15, 0))
tk.Label(header_frame, text="文章標題:", font=("Microsoft JhengHei", 10, "bold"), bg=BG_MAIN, fg=FG_ACCENT).pack(anchor='w')
self.entry_title = tk.Entry(header_frame, bg=BG_SECOND, fg="white", insertbackground="white", borderwidth=0, font=("Microsoft JhengHei", 11))
self.entry_title.pack(fill='x', pady=5, ipady=5)
# 2. 工具按鈕區 (排版)
toolbar = tk.Frame(root, bg=BG_MAIN)
toolbar.pack(pady=5)
btn_style = {"bg": BG_BTN_DEFAULT, "fg": "white", "borderwidth": 0, "activebackground": "#444444", "cursor": "hand2"}
tk.Button(toolbar, text="粗體 B", command=lambda: self.wrap_tags("strong"), width=10, **btn_style).grid(row=0, column=0, padx=3)
tk.Button(toolbar, text="斜體 I", command=lambda: self.wrap_tags("em"), width=10, **btn_style).grid(row=0, column=1, padx=3)
tk.Button(toolbar, text="底線 U", command=lambda: self.wrap_tags("u"), width=10, **btn_style).grid(row=0, column=2, padx=3)
# 3. 媒體功能列 (圖片、下載、網頁)
func_bar = tk.Frame(root, bg=BG_MAIN)
func_bar.pack(pady=5)
tk.Button(func_bar, text="🖼️ 插入圖片", command=self.insert_image, bg="#c0392b", fg="white", borderwidth=0, width=14).grid(row=0, column=0, padx=5)
tk.Button(func_bar, text="💾 下載按鈕", command=self.insert_download_btn, bg="#27ae60", fg="white", borderwidth=0, width=14).grid(row=0, column=1, padx=5)
tk.Button(func_bar, text="🌐 內嵌網頁", command=self.insert_iframe, bg="#8e44ad", fg="white", borderwidth=0, width=14).grid(row=0, column=2, padx=5)
# 4. 中間編輯區
edit_frame = tk.Frame(root, bg=BG_MAIN)
edit_frame.pack(expand=True, fill='both', padx=20, pady=10)
self.text_content = tk.Text(edit_frame, font=("Consolas", 12), bg="#1a1a1a", fg="#d4d4d4", undo=True, insertbackground="white", padx=15, pady=15, borderwidth=0, height=10)
scrollbar = tk.Scrollbar(edit_frame, command=self.text_content.yview)
self.text_content.configure(yscrollcommand=scrollbar.set)
self.text_content.pack(side='left', expand=True, fill='both')
scrollbar.pack(side='right', fill='y')
# 5. 底部固定區
bottom_frame = tk.Frame(root, bg=BG_SECOND, pady=15)
bottom_frame.pack(fill='x', side='bottom')
tag_frame = tk.Frame(bottom_frame, bg=BG_SECOND)
tag_frame.pack()
tk.Label(tag_frame, text="標籤:", bg=BG_SECOND, fg=FG_MAIN).grid(row=0, column=0, padx=5)
self.entry_tags = tk.Entry(tag_frame, width=45, bg="#1e1e1e", fg="white", insertbackground="white", borderwidth=0)
self.entry_tags.grid(row=0, column=1, ipady=3)
self.btn_save = tk.Button(bottom_frame, text="🚀 生成文章並自動上傳至 Neocities", command=self.save_post, bg="#007acc", fg="white", font=("Microsoft JhengHei", 12, "bold"), pady=8, width=40, borderwidth=0)
self.btn_save.pack(pady=(15, 0))
# --- 功能函式 ---
def wrap_tags(self, tag):
try:
start = self.text_content.index("sel.first")
end = self.text_content.index("sel.last")
txt = self.text_content.get(start, end)
self.text_content.delete(start, end)
self.text_content.insert(start, f"<{tag}>{txt}{tag}>")
except tk.TclError:
messagebox.showwarning("提示", "請先選取文字!")
def insert_image(self):
url = simpledialog.askstring("插入圖片", "請輸入圖片連結 (URL):")
if url:
self.text_content.insert(tk.INSERT, f'\n
def insert_download_btn(self):
url = simpledialog.askstring("下載連結", "請輸入檔案下載網址:")
txt = simpledialog.askstring("按鈕文字", "顯示文字:", initialvalue="立即下載檔案")
if url:
self.text_content.insert(tk.INSERT, f'\n
\n')def insert_iframe(self):
url = simpledialog.askstring("內嵌網頁", "請輸入嵌入連結 (YouTube/GoogleMap 等):")
if url:
self.text_content.insert(tk.INSERT, f'\n
\n')def update_search_index(self):
index_data = []
if not os.path.exists(POSTS_FOLDER): return
files = [f for f in os.listdir(POSTS_FOLDER) if f.startswith('post') and f.endswith('.html')]
files.sort(key=lambda x: int(re.search(r'\d+', x).group()), reverse=True)
for filename in files:
try:
with open(os.path.join(POSTS_FOLDER, filename), 'r', encoding='utf-8') as f:
soup = BeautifulSoup(f, 'html.parser')
post_id = re.search(r'\d+', filename).group()
content_div = soup.find('div', class_='content')
index_data.append({
"id": int(post_id),
"url": f"posts/{filename}",
"title": soup.title.string or f"文章 #{post_id}",
"content": content_div.get_text(strip=True)[:500] if content_div else ""
})
except: pass
with open(SEARCH_INDEX_FILE, 'w', encoding='utf-8') as f:
json.dump(index_data, f, ensure_ascii=False, indent=2)
def upload_to_neocities(self, local_path, remote_path):
"""執行 API 上傳動作"""
url = "https://neocities.org/api/upload"
headers = {"Authorization": f"Bearer {API_KEY}"}
try:
with open(local_path, "rb") as f:
# 檔案字典格式: { "遠端路徑": 檔案物件 }
files = {remote_path: f}
response = requests.post(url, headers=headers, files=files)
return response.json()
except Exception as e:
return {"result": "error", "message": str(e)}
def save_post(self):
title = self.entry_title.get().strip()
content = self.text_content.get("1.0", tk.END).strip()
if not title or not content:
messagebox.showwarning("錯誤", "請輸入標題與內容")
return
# 1. 處理段落
processed_html = "".join([f"
{l.strip()}
\n" if not l.strip().startswith('<') else f"{l.strip()}\n" for l in content.split('\n') if l.strip()])# 2. 決定編號與路徑
if not os.path.exists(POSTS_FOLDER): os.makedirs(POSTS_FOLDER)
files = os.listdir(POSTS_FOLDER)
nums = [int(re.search(r'post(\d+)\.html', f).group(1)) for f in files if re.match(r'post\d+\.html', f)]
post_num = max(nums) + 1 if nums else 1
filename = f"post{post_num}.html"
local_post_path = os.path.join(POSTS_FOLDER, filename)
# 3. 生成 HTML 並寫入本地
final = HTML_TEMPLATE.format(title=title, post_num=post_num, date=datetime.date.today().strftime("%Y-%m-%d"),
tags=self.entry_tags.get() or "隨筆", content=processed_html, css_path=CSS_PATH)
with open(local_post_path, "w", encoding="utf-8") as f:
f.write(final)
# 4. 更新搜尋索引
self.update_search_index()
# 5. 上傳至 Neocities
# 同時上傳新文章與更新後的索引檔
res1 = self.upload_to_neocities(local_post_path, f"posts/{filename}")
res2 = self.upload_to_neocities(SEARCH_INDEX_FILE, SEARCH_INDEX_FILE)
if res1.get("result") == "success" and res2.get("result") == "success":
messagebox.showinfo("成功", f"✅ 文章 {filename} 已生成\n✅ 搜尋索引已更新\n✅ 檔案已同步上傳至 Neocities!")
else:
err_msg = res1.get("message") if res1.get("result") != "success" else res2.get("message")
messagebox.showwarning("上傳提示", f"本地檔案已生成,但上傳失敗:\n{err_msg}")
if __name__ == "__main__":
root = tk.Tk()
app = BlogEditor(root)
# 啟動時預先更新一次索引(可選)
app.update_search_index()
root.mainloop()