Post #21

自動上傳

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}
Post #{post_num}

{title}

{content}

© 2026 MOEMOE0200 空間

"""

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}")

except tk.TclError:

messagebox.showwarning("提示", "請先選取文字!")

def insert_image(self):

url = simpledialog.askstring("插入圖片", "請輸入圖片連結 (URL):")

if url:

self.text_content.insert(tk.INSERT, f'\n

image
\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()