iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0

api 完整

好欸要完賽了,功能也介紹得差不多了,今天跟明天就是把全部的程式碼統整放出來。今天先放api的

from fastapi import FastAPI, File, UploadFile, HTTPException
from pydantic import BaseModel
import subprocess
import logging
import win32com.client as win32
import pythoncom
import os
from pathlib import Path
import docx
import PyPDF2
import json


# 設定日誌記錄
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 建立 FastAPI 應用程式實例
app = FastAPI(
    title="我的桌面控制 API",
    description="這個 API 提供控制本機桌面應用程式的功能。",
    version="1.0.0"
)

# 定義接收的資料模型
class WordRequest(BaseModel):
    content: str = "這是預設的文字。"

class WordSaveRequest(BaseModel):
    filename: str

class FileUploadResponse(BaseModel):
    status: str
    message: str
    content: str = ""
    filename: str = ""

# 文件讀取相關函數

# 檔案讀取輔助函式
def read_text_file(file_path: str) -> str:
    """讀取文字檔案"""
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
    except UnicodeDecodeError:
        # 嘗試其他編碼
        try:
            with open(file_path, 'r', encoding='big5') as file:
                return file.read()
        except:
            with open(file_path, 'r', encoding='cp1252') as file:
                return file.read()

def read_docx_file(file_path: str) -> str:
    """讀取 Word 檔案"""
    doc = docx.Document(file_path)
    full_text = []
    for paragraph in doc.paragraphs:
        full_text.append(paragraph.text)
    return '\n'.join(full_text)

def read_pdf_file(file_path: str) -> str:
    """讀取 PDF 檔案"""
    text = ""
    with open(file_path, 'rb') as file:
        pdf_reader = PyPDF2.PdfReader(file)
        for page in pdf_reader.pages:
            text += page.extract_text() + "\n"
    return text

def get_file_content(file_path: str, filename: str) -> str:
    """根據檔案類型讀取內容"""
    file_extension = Path(filename).suffix.lower()
    
    if file_extension in ['.txt', '.py', '.js', '.html', '.css', '.json', '.xml', '.csv']:
        return read_text_file(file_path)
    elif file_extension in ['.docx', '.doc']:
        return read_docx_file(file_path)
    elif file_extension == '.pdf':
        return read_pdf_file(file_path)
    else:
        raise ValueError(f"不支援的檔案格式: {file_extension}")

# 定義開啟記事本的 API 端點
@app.post("/open-notepad")
def open_notepad():
    """
    開啟 Windows 記事本應用程式。
    """
    try:
        subprocess.Popen(["notepad.exe"])
        logger.info("已成功開啟記事本。")
        return {"status": "success", "message": "已成功開啟記事本。"}
    except FileNotFoundError:
        logger.error("錯誤:找不到記事本應用程式。")
        return {"status": "error", "message": "錯誤:找不到記事本應用程式。"}
    except Exception as e:
        logger.error(f"開啟記事本時發生錯誤: {e}")
        return {"status": "error", "message": f"開啟記事本時發生錯誤: {e}"}

# 定義開啟小畫家的 API 端點
@app.post("/open-paint")
def open_paint():
    """
    開啟 Windows 小畫家應用程式。
    """
    try:
        subprocess.Popen(["mspaint.exe"])
        logger.info("已成功開啟小畫家。")
        return {"status": "success", "message": "已成功開啟小畫家。"}
    except FileNotFoundError:
        logger.error("錯誤:找不到小畫家應用程式。")
        return {"status": "error", "message": "錯誤:找不到小畫家應用程式。"}
    except Exception as e:
        logger.error(f"開啟小畫家時發生錯誤: {e}")
        return {"status": "error", "message": f"開啟小畫家時發生錯誤: {e}"}



@app.post("/open-blank-word")
def open_blank_word():
    """
    純粹開啟一個空白的 Word 應用程式,不寫入任何內容。
    """
    print(f"[DEBUG] 收到 open-blank-word 請求")
    logger.info("收到 open-blank-word 請求")
    
    word_app = None
    try:
        # 在多執行緒環境中,必須先初始化 COM
        pythoncom.CoInitialize()
        
        # 嘗試取得現有的Word實例,如果沒有則創建新的
        try:
            word_app = win32.GetObject(Class="Word.Application")
            if word_app.Documents.Count == 0:
                # 沒有開啟的文件,創建新文件
                doc = word_app.Documents.Add()
            else:
                # 已有Word開啟,直接顯示不創建新文件
                word_app.Visible = True
                logger.info("Word 已經在運行,直接顯示現有實例。")
                return {"status": "success", "message": "Word 已經在運行,已顯示現有實例。"}
        except:
            # 如果取得現有實例失敗,創建新的Word應用程式
            word_app = win32.Dispatch("Word.Application")
            word_app.Visible = True
            doc = word_app.Documents.Add()
        
        # 確保Word是可見的
        word_app.Visible = True
        
        logger.info("已成功開啟空白的 Word 應用程式。")
        return {"status": "success", "message": "已成功開啟空白的 Word 應用程式。"}
        
    except Exception as e:
        error_msg = str(e)
        logger.error(f"開啟空白 Word 時發生錯誤: {error_msg}")
        
        if "(-2147352567" in error_msg:
            return {"status": "error", "message": "Word 啟動失敗。請確保 Microsoft Word 已正確安裝。"}
        else:
            return {"status": "error", "message": f"開啟空白 Word 時發生錯誤: {error_msg}"}
            
    finally:
        try:
            pythoncom.CoUninitialize()
        except:
            pass

@app.post("/open-word")
def open_word_and_type(request: WordRequest):
    """
    開啟 Word 應用程式並將指定的文字寫入。
    """
    print(f"[DEBUG] 收到 open-word 請求: {request.content}")
    logger.info(f"收到 open-word 請求,內容: {request.content}")
    
    word_app = None
    try:
        # 在多執行緒環境中,必須先初始化 COM
        pythoncom.CoInitialize()
        
        # 嘗試取得現有的Word實例,如果沒有則創建新的
        try:
            word_app = win32.GetObject(Class="Word.Application")
            if word_app.Documents.Count == 0:
                # 沒有開啟的文件,創建新文件
                doc = word_app.Documents.Add()
            else:
                # 有開啟的文件,使用第一個活動文件
                doc = word_app.ActiveDocument
                # 清空現有內容(如果需要的話)
                doc.Range().Text = ""
        except:
            # 如果取得現有實例失敗,創建新的Word應用程式
            word_app = win32.Dispatch("Word.Application")
            word_app.Visible = True
            doc = word_app.Documents.Add()
        
        # 確保Word是可見的
        word_app.Visible = True
        
        # 寫入內容
        content = request.content if request.content.strip() else "這是一個新的Word文件。"
        
        # 使用更安全的方式寫入內容
        try:
            # 方法1:使用Range
            doc.Range().Text = content
        except:
            try:
                # 方法2:使用Selection
                word_app.Selection.TypeText(content)
            except:
                # 方法3:使用Paragraphs
                doc.Paragraphs(1).Range.Text = content
        
        logger.info("已成功開啟 Word 並寫入內容。")
        return {"status": "success", "message": f"已開啟 Word 並寫入:'{content}'"}
        
    except Exception as e:
        error_msg = str(e)
        logger.error(f"開啟 Word 時發生錯誤: {error_msg}")
        
        if "(-2147352567" in error_msg:
            return {"status": "error", "message": "Word 啟動失敗。請確保 Microsoft Word 已正確安裝。"}
        else:
            return {"status": "error", "message": f"開啟 Word 時發生錯誤: {error_msg}"}
            
    finally:
        try:
            pythoncom.CoUninitialize()
        except:
            pass

# 修改:更新已開啟 Word 文件的 API 端點
@app.post("/modify-word")
def modify_word_document(request: WordRequest):
    """
    修改已開啟的 Word 應用程式,用新內容取代舊內容。
    """
    word_app = None
    try:
        # 在多執行緒環境中,必須先初始化 COM
        pythoncom.CoInitialize()
        
        # 嘗試取得正在運行的 Word 應用程式實例
        try:
            word_app = win32.GetObject(Class="Word.Application")
        except:
            return {"status": "error", "message": "錯誤:找不到已開啟的 Word 應用程式。請先開啟 Word。"}
        
        # 確保有開啟的文件
        if word_app.Documents.Count == 0:
            return {"status": "error", "message": "錯誤:沒有已開啟的 Word 文件。"}
        
        doc = word_app.ActiveDocument
        
        # 使用更安全的方式清除和添加內容
        try:
            # 方法1:選擇全部內容並替換
            word_app.Selection.WholeStory()
            word_app.Selection.Delete()
            word_app.Selection.TypeText(request.content)
        except:
            try:
                # 方法2:使用Range替換
                doc.Range().Text = request.content
            except:
                # 方法3:清除所有段落並添加新內容
                doc.Content.Delete()
                doc.Content.InsertAfter(request.content)
        
        logger.info("已成功修改已開啟的 Word 文件。")
        return {"status": "success", "message": f"已成功修改 Word 文件,新增:'{request.content}'"}
        
    except Exception as e:
        error_msg = str(e)
        logger.error(f"修改 Word 時發生錯誤: {error_msg}")
        
        if "(-2147352567" in error_msg:
            return {"status": "error", "message": "Word 操作失敗。請確保文件未被保護且 Word 正常運行。"}
        else:
            return {"status": "error", "message": f"修改 Word 時發生錯誤: {error_msg}"}
            
    finally:
        try:
            pythoncom.CoUninitialize()
        except:
            pass

@app.post("/save-word")
def save_word_document(request: WordSaveRequest):
    """
    儲存已開啟的 Word 文件到指定的檔名。
    """
    word_app = None
    try:
        # 在多執行緒環境中,必須先初始化 COM
        pythoncom.CoInitialize()
        
        # 嘗試取得正在運行的 Word 應用程式實例
        try:
            word_app = win32.GetObject(Class="Word.Application")
        except:
            # 如果無法取得現有的Word實例,嘗試創建新的
            word_app = win32.Dispatch("Word.Application")
            word_app.Visible = True
        
        # 確保有開啟的文件
        if word_app.Documents.Count == 0:
            return {"status": "error", "message": "錯誤:沒有已開啟的 Word 文件。"}
        
        doc = word_app.ActiveDocument
        
        # 建構完整的檔案路徑
        filename = request.filename.strip()
        if not filename:
            return {"status": "error", "message": "錯誤:檔案名稱不能為空。"}
            
        # 確保檔案名稱有正確的副檔名
        if not filename.lower().endswith(('.docx', '.doc')):
            filename += '.docx'
        
        # 取得桌面路徑作為預設儲存位置
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        if not os.path.exists(desktop_path):
            os.makedirs(desktop_path, exist_ok=True)
            
        full_path = os.path.join(desktop_path, filename)
        
        # 檢查檔案是否已存在,如果存在則加上數字後綴
        counter = 1
        original_path = full_path
        while os.path.exists(full_path):
            name_without_ext = os.path.splitext(original_path)[0]
            ext = os.path.splitext(original_path)[1]
            full_path = f"{name_without_ext}_{counter}{ext}"
            counter += 1
        
        # 使用更安全的儲存方法
        try:
            # 先嘗試使用SaveAs2
            doc.SaveAs2(full_path, FileFormat=16)  # 16 = wdFormatXMLDocument (.docx)
        except:
            try:
                # 如果SaveAs2失敗,嘗試使用SaveAs
                doc.SaveAs(full_path)
            except:
                # 最後嘗試Save當前文件
                doc.Save()
                full_path = doc.FullName
        
        logger.info(f"已成功儲存 Word 文件到: {full_path}")
        return {"status": "success", "message": f"已成功儲存 Word 文件到: {full_path}"}
        
    except Exception as e:
        error_msg = str(e)
        logger.error(f"儲存 Word 時發生錯誤: {error_msg}")
        
        # 提供更詳細的錯誤訊息
        if "(-2147352567" in error_msg:
            return {"status": "error", "message": "Word 操作失敗。請確保 Word 應用程式正常運行且文件未被鎖定。"}
        elif "(-2146824090" in error_msg:
            return {"status": "error", "message": "Word 命令執行失敗。請檢查檔案權限或嘗試不同的檔案名稱。"}
        else:
            return {"status": "error", "message": f"儲存 Word 時發生錯誤: {error_msg}"}
            
    finally:
        try:
            pythoncom.CoUninitialize()
        except:
            pass

@app.post("/upload-file")
async def upload_file(file: UploadFile = File(...)):
    """
    上傳檔案並讀取內容
    """
    try:
        # 檢查檔案大小 (限制為 10MB)
        if file.size and file.size > 10 * 1024 * 1024:
            raise HTTPException(status_code=400, detail="檔案大小超過 10MB 限制")
        
        # 建立暫存目錄
        temp_dir = Path("temp_uploads")
        temp_dir.mkdir(exist_ok=True)
        
        # 儲存上傳的檔案到暫存目錄
        temp_file_path = temp_dir / file.filename
        
        with open(temp_file_path, "wb") as buffer:
            content = await file.read()
            buffer.write(content)
        
        # 讀取檔案內容
        try:
            file_content = get_file_content(str(temp_file_path), file.filename)
            
            # 清理暫存檔案
            if temp_file_path.exists():
                temp_file_path.unlink()
            
            logger.info(f"成功讀取檔案: {file.filename}")
            return {
                "status": "success", 
                "message": f"成功上傳並讀取檔案: {file.filename}",
                "content": file_content,
                "filename": file.filename
            }
            
        except ValueError as e:
            # 清理暫存檔案
            if temp_file_path.exists():
                temp_file_path.unlink()
            raise HTTPException(status_code=400, detail=str(e))
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"上傳檔案時發生錯誤: {e}")
        # 確保清理暫存檔案
        if 'temp_file_path' in locals() and temp_file_path.exists():
            temp_file_path.unlink()
        raise HTTPException(status_code=500, detail=f"上傳檔案時發生錯誤: {e}")

@app.post("/check-word-status")
def check_word_status():
    """
    檢查Word應用程式的狀態
    """
    try:
        pythoncom.CoInitialize()
        
        try:
            word_app = win32.GetObject(Class="Word.Application")
            doc_count = word_app.Documents.Count
            is_visible = word_app.Visible
            
            if doc_count > 0:
                active_doc = word_app.ActiveDocument
                doc_name = active_doc.Name
                doc_saved = active_doc.Saved
                
                return {
                    "status": "success", 
                    "message": f"Word正在運行,有{doc_count}個文件開啟",
                    "details": {
                        "documents_count": doc_count,
                        "is_visible": is_visible,
                        "active_document": doc_name,
                        "is_saved": doc_saved
                    }
                }
            else:
                return {
                    "status": "success", 
                    "message": "Word正在運行但沒有開啟的文件",
                    "details": {
                        "documents_count": 0,
                        "is_visible": is_visible
                    }
                }
                
        except:
            return {
                "status": "info", 
                "message": "Word應用程式未運行"
            }
            
    except Exception as e:
        return {
            "status": "error", 
            "message": f"檢查Word狀態時發生錯誤: {e}"
        }
    finally:
        try:
            pythoncom.CoUninitialize()
        except:
            pass


# 啟動伺服器
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

上一篇
DAY 27
系列文
我的 AI 助手開發28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言