顯示具有 君子不器 標籤的文章。 顯示所有文章
顯示具有 君子不器 標籤的文章。 顯示所有文章

2024年4月30日 星期二

C# / Excel VBA將檔案另存成PDF再使用自訂C#程式加密PDF

本篇是 VBA / Excel VBA將檔案另存成PDF再使用自訂python程式加密PDF 的番外
在爬文的過程中,可能因為關鍵字的關係「 VBA PDF 加密」
為了下載檔案而加入會員,還好檔案還在、還好不需要什麼鬼積分
依樣畫葫蘆可以得到相同結果
但是這個開源ProtectPDF.dll引起我的興趣
再次爬文之後,在這篇 VBA : encrypted / password protected PDF for MS Office 可以看到使用相同名稱的DLL
但是這篇文章的作者沒有提供ProtectPDF.dll檔案
下面有人問他 "Where is ProtectPDF.dll download site " 也沒有得到回應
所以嘗試用dnSpy看看能不能反編譯在mobile01論壇下載的DLL,看看兩篇文章的DLL是不是一樣的
根據dnSpy的編譯結果,可以確認至少功能是一樣的
這個DLL是用C#編寫的,主要是使用ITEXTSHARP.dll的功能
Rummaging through all the above to address the simple task of filling in a gap in the Word/Excel object model functionality to save an encrypted PDF and set different security options from VBA, I found that the simplest and quickest solution to make a custom COM wrapper around one of well-known .Net open source libraries. As an in house component, you shouldn’t have to worry about getting IT approval.

If you not familiar with making COM visible .NET DLL, here is an attached Visual Studio 2013 .NET v 4.0 project, written in C# and wrapping a popular library ITEXTSHARP.dll which is an .NET PDF library ported from Java.

For simplicity, ITEXTSHARP.dll is imbedded into single output resulting library ProtectPDF.dll with a single method 
GoPDF.ProtectPdfStandard(string passwordUser,  string passwordOwner, string namepathPDFIn, string namepathPDFOUT )
 
所以嘗試改成用exe來執行程式碼
基本上就是把ProtectPdfStandard()抓出來,在main()接收參數後執行
流程:

1.在visual studio新增C#解決方案

2.修改預設的Program.cs程式碼,也可以修改名稱,visual studio會自動修改相關的登錄設定

#3 引用 iTextSharp.text.pdf 
#20-23 接收命令列傳來的參數
#29 將參數傳到 自訂函數ProtectPdfStandard
#39-81 ProtectPdfStandard的內容,都是從ProtectPdfStandard()抓來的
重點是在#61-70執行iTextSharp.text.pdf的PdfReader.Encrypt方法

Program.cs程式碼如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.IO;
using iTextSharp.text.pdf;

namespace goPdf
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //Console.WriteLine($"parameter count = {args.Length}");
            /*
            for (int i = 0; i < args.Length; i++)
            {
              Console.WriteLine($"Arg[{i}] = [{args[i]}]");
            }*/
            if (args.Length > 0)
            {
                //
                string passwordUser = args[0];
                string passwordOwner = args[1];
                string inputPath = args[2];
                string outputPath = args[3];
                //Console.WriteLine(passwordUser);
                //Console.WriteLine(passwordOwner);
                //Console.WriteLine(inputPath);
                //Console.WriteLine(outputPath);
                //
                string errorText = ProtectPdfStandard(passwordUser, passwordOwner, inputPath, outputPath);
                Console.WriteLine(errorText);
                //
            }
            else
            {
                Console.WriteLine("沒有輸入參數");
            }
        }

        public static string ProtectPdfStandard(string passwordUser, string passwordOwner, string namepathPDFIn, string namepathPDFOUT)
        {
            string text = string.Empty;
            string result;
            if (string.IsNullOrWhiteSpace(passwordUser))
            {
                result = "Error: User Password not set";
            }
            else if (string.IsNullOrWhiteSpace(passwordOwner))
            {
                result = "Error: Owner Password not set";
            }
            else if (string.IsNullOrWhiteSpace(namepathPDFIn))
            {
                result = "Error: User Password not set";
            }
            else if (string.IsNullOrWhiteSpace(namepathPDFOUT))
            {
                result = "Error: Owner Password not set";
            }
            else
            {
                try
                {
                    using (Stream stream = new FileStream(namepathPDFIn, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        using (Stream stream2 = new FileStream(namepathPDFOUT, FileMode.Create, FileAccess.Write, FileShare.None))
                        {
                            PdfReader pdfReader = new PdfReader(stream);
                            PdfEncryptor.Encrypt(pdfReader, stream2, true, passwordUser, passwordOwner, 2564);
                        }
                    }
                }
                catch (Exception ex)
                {
                    text = ex.Message;
                }
                result = text;
            }
            return result;
        }

    }
}


3.在參考引用ITEXTSHARP.dll

備註:ITEXTSHARP.dll是從dnSpy另存出來的


4.測試或者建置,在bin資料夾的Debug資料夾內會產生exe檔跟相關的檔案,包含參考的ITEXTSHARP.dll


5.在Excel VBA內執行

這邊的流程就大同小異,不過因為這個函數接收4個參數
passwordUser 開啟密碼
passwordOwner 編輯權限密碼
inputPath 輸入檔案路徑
outputPath 輸出檔案路徑

所以程式碼稍微修改如下
#42-57 新增了一個陣列,用來存儲輸出的檔案路徑
#72-75 設定參數,密碼的部分之後可以再視情框修改
#78 執行命令列

 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
Public Sub tt3()

    Application.DisplayAlerts = False
    Application.ScreenUpdating = False
    
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ActiveWorkbook.Sheets(1).Activate
    
    '目前檔案路徑
    nPath = ActiveWorkbook.Path
    '輸出資料夾名稱
    strFolderName = "output"
    
    '完整的輸出資料夾路徑
    outputFolderPath = nPath & Application.PathSeparator & strFolderName & Application.PathSeparator

    '判斷輸出資料夾是否存在
    strFolderExists = Dir(nPath & Application.PathSeparator & strFolderName, vbDirectory)
    
    If strFolderExists = "" Then
        '不存在則新增
        fso.CreateFolder nPath & Application.PathSeparator & strFolderName
    Else
        '存在則移除 再新增
        fso.DeleteFolder nPath & Application.PathSeparator & strFolderName, True 'True 強制刪除
        fso.CreateFolder nPath & Application.PathSeparator & strFolderName
    End If
    
    '將工作表轉存為PDF檔
    For i = 2 To Sheets.Count
        nName = Sheets(i).Name
        Sheets(i).Copy
        ActiveWorkbook.ExportAsFixedFormat Type:=xlTypePDF, Filename:=outputFolderPath & nName & ".pdf"
        ActiveWorkbook.Close False
    Next
    
    '讀取output資料夾的PDF 如果存在 回傳檔案名稱.副檔名
    pdfFile = Dir(outputFolderPath & "*.pdf")
        
    Dim fileArr() As String
    Dim outArr() As String
    
    i = 0
    Do While pdfFile <> ""
        ReDim Preserve fileArr(i)
        ReDim Preserve outArr(i)
        fileArr(i) = outputFolderPath & pdfFile
        'Debug.Print outputFolderPath & pdfFile
        
        outArr(i) = outputFolderPath & Split(pdfFile, ".")(0) & "_PW.pdf"
        'Debug.Print outputFolderPath & Split(pdfFile, ".")(0) & "_PW.pdf"
        
        i = i + 1
        pdfFile = Dir()
    Loop
        
    'Debug.Print fileArr(0)
    '陣列上限(序位值)
    num = UBound(fileArr)
    
    Dim wsh As Object
    Set wsh = VBA.CreateObject("WScript.Shell")
    Dim waitOnReturn As Boolean: waitOnReturn = True
    Dim windowStyle As Integer: windowStyle = 0
    Dim errorCode As Long
    
    For i = 0 To num
            
        DoEvents
        passwordUser = "123"
        passwordOwner = "456"
        inputPath = fileArr(i)
        outputPath = outArr(i)
        
        '透過WScript.Shell 執行pdf加密程式
        s = Chr(34) & Excel.ActiveWorkbook.Path & "\C#\goPDF\" & "goPDF.exe" & Chr(34) & Chr(32) & Chr(34) & passwordUser & Chr(34) & Chr(32) & Chr(34) & passwordOwner & Chr(34) & Chr(32) & Chr(34) & inputPath & Chr(34) & Chr(32) & Chr(34) & outputPath & Chr(34)
            
'        Debug.Print s
        Shell s

        errorCode = wsh.Run(s, windowStyle, waitOnReturn)

        If errorCode = 0 Then
'            MsgBox "Done! No error to report."
            Debug.Print "輸出:" & fileArr(i) & Chr(32) & "到:" & outArr(i)
        Else
            MsgBox "Program exited with error code " & errorCode & "."
        End If
    Next
    
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
End Sub
 

參考資源

使用vba + 開源函式庫,解決excel 另存pdf 無法加密問題(vba範例)

VBA / Excel VBA將檔案另存成PDF再使用自訂python程式加密PDF 2

本篇主要修改 VBA / Excel VBA將檔案另存成PDF再使用自訂python程式加密PDF的程式碼
讓程式執行更為流暢
主要修改的地方有以下兩點

1.使用 WScript.Shell 執行命令列

因為WScript.Shell可以透過第3個參數 waitOnReturn=True,來讓程式同步執行(一個執行完畢,再執行下一個,依序執行)

2.在迴圈增加 "DoEvents" 移交控制權

這樣可以讓程式在執行會花比較久時間的流程時,讓系統還可以做其他的事情

修改後的VBA 程式碼如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
'
'透過WScript.Shell 執行pdf加密程式
'
Public Sub tt2()

    Application.DisplayAlerts = False
    Application.ScreenUpdating = False
    
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ActiveWorkbook.Sheets(1).Activate
    
    '目前檔案路徑
    nPath = ActiveWorkbook.Path
    '輸出資料夾名稱
    strFolderName = "output"
    
    '完整的輸出資料夾路徑
    outputFolderPath = nPath & Application.PathSeparator & strFolderName & Application.PathSeparator

    '判斷輸出資料夾是否存在
    strFolderExists = Dir(nPath & Application.PathSeparator & strFolderName, vbDirectory)
    
    If strFolderExists = "" Then
        '不存在則新增
        fso.CreateFolder nPath & Application.PathSeparator & strFolderName
    Else
        '存在則移除 再新增
        fso.DeleteFolder nPath & Application.PathSeparator & strFolderName, True 'True 強制刪除
        fso.CreateFolder nPath & Application.PathSeparator & strFolderName
    End If
    
    '將工作表轉存為PDF檔
    For i = 2 To Sheets.Count
        nName = Sheets(i).Name
        Sheets(i).Copy
        ActiveWorkbook.ExportAsFixedFormat Type:=xlTypePDF, Filename:=outputFolderPath & nName & ".pdf"
        ActiveWorkbook.Close False
    Next
    
    '讀取output資料夾的PDF 如果存在 回傳檔案名稱.副檔名
    pdfFile = Dir(outputFolderPath & "*.pdf")
        
    Dim fileArr() As String
    i = 0
    Do While pdfFile <> ""
        ReDim Preserve fileArr(i)
        fileArr(i) = outputFolderPath & pdfFile
        'Debug.Print outputFolderPath & pdfFile
        
        i = i + 1
        pdfFile = Dir()
    Loop
        
    'Debug.Print fileArr(0)
    '陣列上限(序位值)
    num = UBound(fileArr)
    
    Dim wsh As Object
    Set wsh = VBA.CreateObject("WScript.Shell")
    Dim waitOnReturn As Boolean: waitOnReturn = True
    Dim windowStyle As Integer: windowStyle = 0
    Dim errorCode As Long
    
    For i = 0 To num
    
        DoEvents
        sourceFilename = fileArr(i)
        setPassword = "123456"
        
        '透過WScript.Shell 執行pdf加密程式
        s = Chr(34) & Excel.ActiveWorkbook.Path & "\" & "0426_3.exe" & Chr(34) & Chr(32) & Chr(34) & sourceFilename & Chr(34) & Chr(32) & Chr(34) & setPassword & Chr(34)
            
        'Debug.Print s
        'Shell s
        
        errorCode = wsh.Run(s, windowStyle, waitOnReturn)
        
        If errorCode = 0 Then
'            MsgBox "Done! No error to report."
            Debug.Print "輸出:" & fileArr(i)
        Else
            MsgBox "Program exited with error code " & errorCode & "."
        End If
    Next
    
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
End Sub

2024年4月26日 星期五

VBA / Excel VBA將檔案另存成PDF再使用自訂python程式加密PDF

VBA能否將excel轉成加密的PDF?

這個是在Line的excel社團看到的提問

其中的管理員提供了一個網路教學

這個教學是使用 Adobe Acrobat 物件

但是前提是必須先安裝付費版的 Adobe Acrobat

我後來想起之前彰化一整天有分享某個客製程式

其中有PDF加密功能,這個功能是在VBA執行他自己寫的python程式

經過自己土炮測試之後

我的構想,整體流程可以分成三部分

第一部分是在Excel透過VBA將工作表另存成PDF

第二部分是透過python程式加密PDF

第三部分是在Excel VBA執行 python程式

1.在Excel透過VBA將工作表另存成PDF

這是透過Excel的內建功能將工作表複製到新的工作簿

然後再存成PDF

流程大致如下

透過迴圈,使用工作簿功能ExportAsFixedForma逐一將工作表轉成PDF

1
2
3
4
5
6
    For i = 2 To Sheets.Count
        nName = Sheets(i).Name
        Sheets(i).Copy
        ActiveWorkbook.ExportAsFixedFormat Type:=xlTypePDF, Filename:=outputFolderPath & nName & ".pdf"
        ActiveWorkbook.Close False
    Next

 

2.透過python程式加密PDF

這裡的知識點是如何讓python接收外部參數

最簡單的方式 透過sys.argv來接收從cmd傳入的參數

1
2
3
4
5
6
7
8
9
import sys

def getArg(ar1, ar2):
    
    print()
    print('接收參數:', ar1,ar2)
    print()

getArg(sys.argv[1], sys.argv[2])

 

將以上的程式碼存成xx.py (xx是指隨意名稱)

然後在cmd執行 python xx.py 1 2 

就會輸出

接收參數:1 2

另一個知識點是如何加密PDF

可以利用套件 PyPDF2來處理

流程如下

#6~43 自訂一個函數

#8 第一個參數為檔案路徑

#9 第二個參數為密碼

#12 取出不含副檔名的檔案名稱

os.path.basename(pdf_path) 可以取出檔案路徑中最後一層,也就是檔案名稱.副檔名

#15 設定暫存檔的完整路徑

#17 設定用來寫入的檔案路徑

os.path.dirname(pdf_path) 可以取出最後一層之外的路徑,也就是檔案所在的目錄

因為加密後的檔案無法寫回原檔案,所以寫入另一個pdf

#21-23 使用套件 shutil複製原檔案產生新檔案

#25-27 使用PyPDF2的PdfReader物件來讀取PDF

 要先用open讀取檔案,再用PdfReader讀取PDF

#29-32 加密PDF

使用PyPDF2的PdfWriter物件的append_pages_from_reader獲取從PdfReader讀取的PDF

再用encrypt來加密

#34-36 將加密後的檔案寫入到用來輸出的檔案

因為產生一個空白的PDF還需要用別的套件生成

感覺太麻煩...

所以直接複製原本的檔案

#38-39 關閉利用open開啟的檔案

#41 刪除暫存檔

#42 關閉 PyPDF2的PdfReader、PdfReader物件

#43 如果之後封裝的程式要在背景執行,就不能有任何的print寫出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import os, sys
import shutil
from PyPDF2 import PdfReader 
from PyPDF2 import PdfWriter

def addPw(ar1, ar2):
    #print(ar1,ar2)
    pdf_path =ar1
    pswd=ar2
    
    #不含副檔名的檔案名稱
    fileName = os.path.basename(pdf_path).replace(".pdf", "")
    #print(fileName)
    #暫存檔 用來加密
    tempFile =os.path.dirname(pdf_path)+"\\"+ fileName + "_temp.pdf"
    #print(tempFile)
    #最後輸出的檔案  因為檔案加密之後 不能寫回原本的檔案
    newFile = os.path.dirname(pdf_path)+"\\"+ fileName +"_pw.pdf"
    #print(newFile)
    
    #複製產生檔案
    shutil.copyfile(pdf_path, tempFile)
    shutil.copyfile(pdf_path, newFile)
    
    #讀取暫存檔案
    in_file = open(tempFile, "r+b")
    in_pdf = PdfReader(in_file)
    
    #加密檔案
    out_pdf = PdfWriter()
    out_pdf.append_pages_from_reader(in_pdf)
    out_pdf.encrypt(pswd)
    
    #讀取用來存儲的檔案
    out_file = open(newFile, "wb")
    #寫出檔案
    out_pdf.write(out_file)
    
    #關閉open()開啟的檔案
    in_file.close()
    out_file.close()
    #刪除暫存檔
    os.remove(tempFile)
    #刪除PdfReader() PdfWriter()
    del in_pdf, out_pdf
    print("檔案加密完成")
    
addPw(sys.argv[1], sys.argv[2])

#addPw(r"C:\Users\edu\Desktop\PDF 轉檔 加密\output\臺北市.pdf","123456")

之後再用pyinstaller封裝成執行檔

3.在Excel VBA執行 python程式

#6-37 將工作表轉存成PDF

#39-51 讀取輸出資料夾內的所有PDF,將完整的檔案路徑寫入陣列中

#55-66 利用陣列進行迴圈

#59 是PDF密碼,之後可以再視需求來修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Public Sub export_encrypt_PDF()

    Application.DisplayAlerts = False
    Application.ScreenUpdating = False
    
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ActiveWorkbook.Sheets(1).Activate
    
    '目前檔案路徑
    nPath = ActiveWorkbook.Path
    '輸出資料夾名稱
    strFolderName = "output"
    
    '完整的輸出資料夾路徑
    outputFolderPath = nPath & Application.PathSeparator & strFolderName & Application.PathSeparator

    '判斷輸出資料夾是否存在
    strFolderExists = Dir(nPath & Application.PathSeparator & strFolderName, vbDirectory)
    
    If strFolderExists = "" Then
        '不存在則新增
        fso.CreateFolder nPath & Application.PathSeparator & strFolderName
    Else
        '存在則移除 再新增
        fso.DeleteFolder nPath & Application.PathSeparator & strFolderName, True 'True 強制刪除
        fso.CreateFolder nPath & Application.PathSeparator & strFolderName
    End If
    
    '將工作表轉存為PDF檔
    For i = 2 To Sheets.Count
        nName = Sheets(i).Name
        Sheets(i).Copy
        ActiveWorkbook.ExportAsFixedFormat Type:=xlTypePDF, Filename:=outputFolderPath & nName & ".pdf"
        ActiveWorkbook.Close False
    Next
    
    '讀取output資料夾的PDF 如果存在 回傳檔案名稱.副檔名
    pdfFile = Dir(outputFolderPath & "*.pdf")
        
    Dim fileArr() As String
    i = 0
    Do While pdfFile <> ""
        ReDim Preserve fileArr(i)
        fileArr(i) = outputFolderPath & pdfFile
        'Debug.Print outputFolderPath & pdfFile
        
        i = i + 1
        pdfFile = Dir()
    Loop
        
    'Debug.Print fileArr(0)
    '陣列上限(序位值)
    num = UBound(fileArr)
    
    For i = 0 To num
        sourceFilename = fileArr(i)
        setPassword = "123456"
        
        '透過cmd 執行pdf加密程式
        s = Chr(34) & Excel.ActiveWorkbook.Path & "\" & "0426_3.exe" & Chr(34) & Chr(32) & Chr(34) & sourceFilename & Chr(34) & Chr(32) & Chr(34) & setPassword & Chr(34)
            
        'Debug.Print s
        Shell s
    Next
    
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
End Sub

 

參考資料

如何用pyinstaller将需要传参数的Python文件生成exe文件

Python:批次加密PDF文件

别再问我怎么Python打包成exe了!

2024年4月19日 星期五

JS / 在Blogger增加”goTop”功能

在很多網站都能夠看到

當瀏覽到頁面結尾時,會出現一個回到最上面的圖示

點擊之後,網頁就會捲到最上面 在 tricohobby也有,這是佈景主題內建的

不過在blogger就要自己土炮

參考了兩個網站的做法

網頁Top按鈕 (網頁回到頂端),純 CSS+jQuery 免圖片

參考流程設定,不過因為圖示的設定會跑掉,所以圖示的呈現方式是參考另一種方式

jQuery 筆記 - 讓網頁回到最頂點寫法

參考圖示的呈現方式,利用font-awesome載入特殊字形

而font-awesome 的詳細使用方式可以參考這個網頁

整體程式碼如下

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/css/all.min.css"
/>
<style>
/* 56px */
.toTop-arrow {
/*width: 3.5rem;
 height: 3.5rem;
 padding: 0;
 margin: 0;
 border: 0;
 border-radius: 33%;
 opacity: 0.6;
 background: #000;*/
cursor:
pointer;
position:fixed;
right:
1rem;
bottom:
1rem;
display:
none;
}

/* arrow color */
.arrow-up {
color:
#3d82ad;
}

.arrow-footer {
color:
#cb6c8f;
}

/*arrow icon */
.fa-3x {
font-size:
5em;
}

.fas {
font-family:
"Font Awesome 5 Free";
font-weight:
900;
-moz-osx-font-smoothing:
grayscale;
-webkit-font-smoothing:
antialiased;
display:
inline-block;
font-style:
normal;
font-variant: normal;
text-rendering:
auto;
line-height:
1;
}

/*initial arrow icon */
.hide {
display:none;
}

</style>
<i id="BackTop" class="toTop-arrow fas fa-3x fa-arrow-alt-circle-up
hide"></i>
<script>
$(function(){
$('#BackTop').on("click",function(){
$('html,body').animate({scrollTop:0}, 333);
});
$(window).on( 'scroll',
function() {
//console.log('$(window).height()',
$(window).height());
//console.log('$(document).height()',
$(document).height());
//console.log('$(this).scrollTop()',
$(this).scrollTop());
//console.log('$(document).height()-$(window).height()',$(document).height()-$(window).height());
if ( $(this).scrollTop() > $(document).height()*0.10 && $(this).scrollTop() <= $(document).height()-$(window).height() - 100){
//console.log("#1");
$('#BackTop').fadeIn(222);
$('#BackTop').removeClass("arrow-footer");
$('#BackTop').addClass("arrow-up");
$('#BackTop').toggleClass("hide");
} else
if($(this).scrollTop() > $(document).height()-$(window).height() - 100 ){
//console.log("#2");
$('#BackTop').fadeIn(222);
$('#BackTop').removeClass("arrow-up");
$('#BackTop').addClass("arrow-footer");
} else {
//console.log("#3");
$('#BackTop').stop().fadeOut(222);
}
});
});
</script>

  再透過HTML/JavaScript小工具來執行

2024年4月14日 星期日

Gg / 使用GoogleCharts快速製作QrCode 替代方案 QuickChart

原本在 Gg / 使用GoogleCharts快速製作QrCode 所使用的api

由於google正式關閉了 https://chart.googleapis.com

所以也就無法用來生成Qr Code

因此改用 https://quickchart.io/ 的api

基本用法

https://quickchart.io/qr?text=Hello world

詳細的參數設定可以參考官網的說明

也可以直接沿用 https://chart.googleapis.com的參數設定

例如:https://chart.googleapis.com/chart?chs=120x120&cht=qr&chld=M|3&chl=https://inservice.edu.tw/NAPP/CourseView.aspx?cid=2861727 

直接改成:https://quickchart.io/chart?chs=120x120&cht=qr&chld=M|3&chl=https://inservice.edu.tw/NAPP/CourseView.aspx?cid=2861727

此外,在 Gg / 下載在GoogleCharts製作QrCode的圖檔 的程式碼

修改如下

  1. function myQR() {
  2. //課程代碼
  3. var cId = '2861727';
  4. var cText = 'https://inservice.edu.tw/NAPP/CourseView.aspx?cid='+cId;

  5. //QuickChart api
  6. var url = 'https://quickchart.io/qr?text='+cText;

  7. //fetch連線
  8. var hData = UrlFetchApp.fetch(url);

  9. //伺服器連線情況代碼
  10. var rowRe = hData.getResponseCode();
  11. Logger.log(rowRe);
  12. //Logger.log(hData.getContent());

  13. if (rowRe == '200'){
  14. //#1
  15. var fileBlob = hData.getBlob().getAs('image/png').setName('研習代碼-' + cId + '.png');
  16. fileBlob= DriveApp.createFile(fileBlob);

  17. //#2
  18. //var fileBlob = Utilities.newBlob(hData.getContent(), 'image/png', '研習代碼-' + cId + '.png');
  19. //fileBlob= DriveApp.createFile(fileBlob);

  20. Logger.log('fetch 成功')
  21. Logger.log(fileBlob.getDownloadUrl());

  22. }else{
  23. Logger.log('fetch 失敗');
  24. }
  25. }

Py / 透過selenium-webdriver爬youtube的介紹資訊 2

在 Py / 透過selenium-webdriver爬youtube的介紹資訊(2021/10/28修改)

所引用的selenium-webdriver 在更新之後,目前selenium版本是4.19.0

可以不用設置driver.exe的路徑,selenium可以處理瀏覽器和驅動程序

抓取網頁元素的方式也有改變

更新之後的程式碼如下

 
  1. from selenium import webdriver
  2. # 2024.04.03 更新為 4.19.0 許多方法變動
  3. import time
  4. #
  5. #目前使用的selenium版本是4.19.0,可以不必设置driver.exe的路径,selenium可以自己处理浏览器和驱动程序
  6. #chromedriver.exe執行檔所存在的路徑 須配合電腦上的chrome版本
  7. #chrome_path =r"C:\Users\trico\Desktop\chromedriver\chromedriver.exe"
  8. #chrome_path =r"D:\chromedriver_win32\chromedriver.exe"
  9. options = webdriver.ChromeOptions() #透過options紀錄webdriver.ChromeOptions()的設定
  10. options.add_argument("--headless") #增加啟動參數,瀏覽器頁面不可視化(背景執行)
  11. browser = webdriver.Chrome(options)
  12.  
  13. #讀取資料
  14. f = open("url.csv","r")
  15. s = f.readlines()
  16. c = "科目,示例名稱,網址,介紹\n" #設定寫出檔案的第一行
  17. for i in range(1,len(s)):
  18. url=s[i]
  19. list=url.split(",") #分割串列
  20. # print(list[2].replace('\n','')) #第3筆是最後1筆資料會有換行符號,須去掉
  21. browser.get(list[2].replace('\n','')) #載入網址
  22. time.sleep(3) #等待3秒載入網頁
  23. #tagX = browser.find_element_by_xpath("//meta[@name='description']") # meta標籤 name屬性值為'description'的元素
  24. #新的方法
  25. tagX = browser.find_element("xpath","//meta[@name='description']")
  26. #tag = browser.find_element_by_id('description') #資訊所在的DIV id 現在不能直接抓Html
  27. #print(tagX.get_attribute("content")) #取出其中的content屬性值
  28. tag = tagX.get_attribute("content")
  29. tag2 = tag.replace('\n','').replace('\r','') #用空字串取代所有換行符號 ,不能用strip刪除
  30. #tag2=tag.text.replace('\n','').replace('\r','') #用空字串取代所有換行符號 ,不能用strip刪除
  31. # print(tag2)
  32. # print(len(tag2))
  33. # 因為沒有資訊也同樣能抓到空字串,不會出現錯誤
  34. # try:
  35. # tag = browser.find_element_by_id('description')
  36. # tag2=tag.text.replace('\n', '').replace('\r', '')
  37. # c +="{},{},{},{}".format(list[0],list[1],list[2],tag2)
  38. # print(c)
  39. # except:
  40. # c +="{},{},{},{}".format(list[0],list[1],list[2],"無說明")
  41. # continue
  42. # print(c)
  43. # 改用 if判斷 len(tag2)是否>0
  44. if i < len(s)-1: #如果不是最後一筆才加分行符號
  45. if len(tag2) > 0 :
  46. print(tag2)
  47. c +="{},{},{},{}\n".format(list[0],list[1],list[2].replace('\n',''),tag2)
  48. else:
  49. tag2="無內容資訊"
  50. c +="{},{},{},{}\n".format(list[0],list[1],list[2].replace('\n',''),tag2)
  51. print(tag2)
  52. else:
  53. if len(tag2) > 0 :
  54. print(tag2)
  55. c +="{},{},{},{}".format(list[0],list[1],list[2].replace('\n',''),tag2)
  56. else:
  57. tag2="無內容資訊"
  58. c +="{},{},{},{}".format(list[0],list[1],list[2].replace('\n',''),tag2)
  59. print(tag2)
  60. #寫出資料
  61. fw=open("c.txt","w",encoding="utf8")
  62. fw.write(c)
  63. fw.close()
  64. print(c)
  65. browser.close() #關閉 瀏覽器
  66. print("完成")

 

如果引用from selenium.webdriver.common.by import By

tagX = browser.find_element("xpath","//meta[@name='description']")

就要改成

tagX = browser.find_element(By.xpath,"//meta[@name='description']")

參考資料

爬虫学习记录(持续更新) 

selenium 4.19.0 

selenium入门超详细教程——网页自动化操作

2024年4月13日 星期六

網站 / 使用Google Translate JS將網站變成多語系網站

聽說網站如果能夠具備多語系內容 

可以更容易被搜尋到 

但是我又不會多語

還好現在AI發展的關係 

機器翻譯的品質越來越好

wordpress可以直接用外掛達成網站多語系

目前使用的外掛Translate WordPress with GTranslate是免費的 

想說為什麼可以這樣大方

原來是使用google的translate js

而這個js目前是免費使用

blogger可以用 HTML/JavaScript小工具引用translate js

其他 網站也可以用相同的方式達成多語系 

基本上就是完全按照範例 

然後將網站中不想被翻譯的部份加上class屬性- notranslate

 
  1. <div id="google_translate_element"></div>
  2. <script type="text/javascript">
  3. function googleTranslateElementInit() {
  4. new google.translate.TranslateElement({pageLanguage: 'zh-TW', includedLanguages: 'zh-TW,en,zh-CN,ja'}, 'google_translate_element');
  5. }
  6. </script>

  7. <script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>


備註1:本來想將這個js下載備份,但是測試時無法載入功能,看來還是會跟google主機互動。

備註2:嚴格來說,這種方式不是真正的多語系網站,只是單純利用機器翻譯網站內容,無法確認翻譯內容是否正確。

2024年3月23日 星期六

網站 / 改善wordpress網站的效能

之前網站因為增加了許多自訂的發文推播

導致網站速度變慢,GCP的VM觀測數據顯示發文的時候,CPU的執行率可以達到200%

所以也出現提升CPU等級的建議,但是往上升一級就多了近一倍的費用

對我來說實在吃不消,因為除了GCP的費用之外,還有youtube的月費、google 雲端硬碟的年費

一年付給google公司7~8千元跑不掉,GCP費用占了80%

當初建置網站的目的是為了紀錄一些學習程式的過程,順便紀錄自己感興趣的東西

所以想先試試看能不能透過一些方式來降低網站的執行負荷量

之前為了增加特定功能,會嘗試不同的外掛,從中挑選最適合的

但是其他的外掛,我通常只是設定成未啟用,沒有刪除

不過為了網站效能,這次刪除了所有沒有啟用、或者用不到的外掛

另外還進行了兩個改變

第一個是整併推播外掛程式

因為都是同一個觸發事件,這樣會導致網站同時執行多個程式

所以我把這些通通整併成一個程式

也就是事件觸發之後,Line、Blogger、facebook的推播都是接續處理

不再分開(卻可能同時)處理

也因為這些推播都是向外發出請求

都是非同步執行,所以每個部分都暫停5秒鐘才接續往下執行

剛好跟一般認知的概念相反,這裡是指各個程式的執行是各做各的

不會等其他程式完成,再進行下一個

第二個是不在網站的編輯器撰寫文章

後來發現在網站的編輯器撰寫文章,也會導致網站的負荷量飆高

所以我嘗試改成先在其他網頁編輯器完成內容,再將html格式的內容貼回去

之前一直使用的線上網頁編輯器,使用了CKEditor4的編輯器

出於好奇,查看了網站的程式碼,發現其實引用的方式很簡單

所以我自己也炮製了一個線上網頁編輯器

目前CKEditor除了4之外,最新的是5

不過CKEditor5是node架構了,而且好像也沒有免費授權的版本

而CKEditor4 除了LTS(Long-term support,長期支援)版本需要授權之外

其他版本都是可以免費使用,所以我引用了最後的4.22.1


目前這些方法似乎有效果,即使網站發文的時候,CPU執行率也沒有超過100%了

2024年3月18日 星期一

VBA / 使用Excel VBA批次寄信 3

之前在VBA / 使用Excel VBA批次寄信 是修改 彰化一整天所寫的excel VBA程式
而這個版本是精簡程式,原理是一樣的,都是使用 CDO.Message物件
但是省去了設定頁面,呈現核心程式
只要知道STMP的設定資料
各個郵件伺服器應該都可以使用
本文以Gmail為範例

1.取得google應用程式密碼

google為了保護帳戶安全性
之前可以用低安全性設定來使用帳密連線SMTP
不過現在這個設定應該已經無法使用了
必須要設定並開啟兩步驟驗證 然後設定應用程式密碼,取代原本的帳號密碼
這組密碼是隨機產生的16位英文字母 換言之,就是改用隨機密碼取代原始密碼
而這組隨機密碼只能用在一種googgle應用程式
例如:當密碼第一次使用是用在gmail登入,之後就只能用在登入gmail,不能一組密碼用遍所有google功能
設定流程可以參考google文件說明,不知道之後會不會又改安全性設定

2.CDO.Message的參數設定

宣告Mail變數,使用晚期繫結的方式建立CDO.Message
再用Configuration.Fields.Item 設定參數
  Dim Mail As Object
  Set Mail = CreateObject("CDO.Message")
  With Mail.Configuration.Fields
      .Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl")= True
      .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate")= 1
      .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver")= "smtp.gmail.com"
      .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport")= 465
      .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
      .Item("http://schemas.microsoft.com/cdo/configuration/sendusername")= "goohle帳號"
      .Item("http://schemas.microsoft.com/cdo/configuration/sendpassword")= "google程式密碼"
      .Update
  End With

3.郵件設定

  With Mail
    .Subject = "VBA透過gmail寄mail"
    .From = "XXX@gmail.com"
    .To = "YYY@gmail.com"
    .CC = "ZZZ@gmail.com"
    .HTMLBody = "<h1>test 測試</h1>"
    .HTMLBodyPart.Charset = "utf-8"
    .Send
  End With

基本上,這樣程式就能夠使用Gmail帳號寄出信件了
其他的可以再依據需求來細部設定的
像是郵件內容,可以是純文字,也可以是Html格式
也可以增加附件檔案
要批量寄信就可以連結工作表以迴圈方式執行

2024年3月15日 星期五

JS / 在Blogger增加”分享到臉書”功能

之前在網站 / 從wordpress發佈新文章同步發文到臉書粉絲專頁提到
臉書只能夠用分享的方式將訊息貼到個人頁面
於是,開始思考如何在Blogger達成這個效果
(雖然後來發現Blogger的佈景主題有這個功能...)

思路如下  

1.臉書分享的功能

這個可以參考臉書的官方文件-分享對話方塊

2.開啟新視窗,而不是新頁籤

想要達成的效果是點選圖檔,能夠開新視窗 如果單純用超連結,瀏覽器會開啟新頁籤
在這篇文章笨問題 - window.open() 有時在新頁籤有時在新視窗開啟提到
4.若同時指定 width/height,所有瀏覽器都會依指定寬度及高度開啟新視窗
同時為了讓開啟的新視窗緊鄰圖片,所以需要取得圖片的位置
這個參考了使用 Javascript 取得元素的座標的程式碼
程式碼如下,因為是在CodePen執行,會被臉書封鎖,不過整個流程是預期的 

See the Pen blogger fb 開新視窗 by 莊幸諺 (@trico109748007) on CodePen.

3.如何在Blogger透過圖像觸發

這裡有個知識點:如何判斷點選的是哪一個元素
(雖然不太懂觸發的原理...為什麼用立即函式可以達成這樣效果)
於是在文章的結構新增了一個span>a>img>的節點,原理跟JS / 在Blogger增加"修改"連結標籤-1.取得postID 一樣
並且在a標籤內增加了一個名稱為a2_fb的class讓程式判斷是哪一個被觸發
然後將相關的參數送到window.open()

See the Pen 判斷是哪一個DIV被觸發 by 莊幸諺 (@trico109748007) on CodePen.



  1. <script>
  2. function getPosition(element) {
  3.   var x = 0;
  4.   var y = 0;
  5.   while (element) {
  6.     x += element.offsetLeft - element.scrollLeft + element.clientLeft;
  7.     y += element.offsetTop - element.scrollLeft + element.clientTop;
  8.     element = element.offsetParent;
  9.   }
  10. return {
  11.   x: x,
  12.   y: y
  13.   };
  14. }
  15. var fb = document.getElementsByClassName('a2_fb');
  16. for (var i = 0; i < fb.length; i++) {
  17.   (function(i) {
  18.     fb[i].addEventListener('click', function(e) {
  19.       e.preventDefault();
  20.       var position = getPosition(this);
  21.       window.open(fb[i].href, '分享到臉書', config = `height=900,width=800,left= ${position.x}    ,top=${position.y} `); //引用變數的寫法 ES6 模板文字
  22.       }, false);
  23.   })(i)
  24. }
  25. </script>
 最後,程式碼還是要依靠Html/Javascipt小工具來執行

執行的畫面





2024年3月14日 星期四

JS / 移除或修改 iframe的屬性

前幾天辦公室裡其他計畫的助理
抱怨網站嵌入的google表單出現

「您的瀏覽器尚未啟用javascript...」


經詢問網站維護廠商,說因為資安掃描被列為需改善的漏洞
不過之前的表單都是正常
所以我比較了前後網頁的html內容

原來後面的表單iframe增加了sandbox標籤

我因為無聊想試試看能不能移除或修改snadbox標籤
一開始是在chrome按F12,直接編輯html
但是沒有效果,用關鍵字爬文之後
才知道已經載入的iframe是不會有任何影響

移除或修改sandbox之後必須再重新載入iframe

所以現在問題變成:如何移除或修改sandbox之後,再重新載入iframe,卻又不會重新載入原本網頁
不然原本網頁跟著重載入,又會載入原本的iframe,等於白做工
不過不是被打勾的回答,reload()會無法執行
document.getElementById('some_frame_id').contentWindow.location.reload();
而是再下一個回覆內容,重新指定iframe src的屬性值,iframe就會馬上重新載入
document.getElementById('iframeid').src =
document.getElementById('iframeid').src
接下來是如何在頁面執行javascript
我想到之前用來移除某些網站鎖右鍵功能的方式

用書籤輸入程式碼來執行javascript

將以下程式碼輸入在網址即可
javascript: (function () {  var link = document.querySelector("iframe"); link.setAttribute("sandbox","allow-same-origin allow-scripts
allow-popups allow-forms"); link.src = link.src; })();
  1. javascript: (
  2. function () {
  3. var link = document.querySelector("iframe");
  4. link.setAttribute("sandbox","allow-same-origin allow-scripts allow-popups allow-forms");
  5. link.src = link.src;
  6. }
  7. )();
  
最後,測試是可以執行的
不過後來廠商也修改sandbox的屬性值
開放可以執行表單功能~~~~

2024年3月8日 星期五

網站 / 從wordpress發佈新文章同步發文到臉書粉絲專頁

既然可以透過寄信到Blogger發文 
那有沒有可能在臉書粉絲專頁貼文?
答案是可以的!! 
接下來可能會問:有沒有可能在個人臉書貼文 
可惜答案是不可能~~,
但是可以用分享的方式

臉書已經算是商業平台了,社交只是功能之一 
粉絲頁的功能主要是為了跟成員互動,所以功能比較豐富 
可以透過Facebook 粉絲專頁 API 來貼文

臉書提供的範例剛好就是PHP語言

  1. curl -X POST "https://graph.facebook.com/v19.0/page_id/feed" \
  2.      -H "Content-Type: application/json" \
  3.      -d '{
  4.            "message":"your_message_text",
  5.             "link":"your_url",
  6.             "published":"false",
  7.             "scheduled_publish_time":"unix_time_stamp_of_a_future_date",
  8.             }'
 
臉書的說明如下: 
若要發佈貼文至粉絲專頁,請傳送 POST 要求至 /page_id/feed 端點(其中 page_id 為粉絲專頁編號),並使用下列參數:
  • message 設為貼文的文字
  • link 設為您的網址(若要張貼連結)
  • published 設為 true 以立即發佈貼文(預設),或設為 false 以稍後發佈。
    • 若設為 false,請包含 scheduled_publish_time,並使用下列任一日期格式:
      • 整數 UNIX 時間戳記(以秒為單位),例如 1530432000
      • ISO 8061 時間戳記字串(例如 2018-09-01T10:15:30+01:00)
      • 可用 PHP 語言 strtotime() 解析的字串(例如 +2 weeks、tomorrow)
可以知道要用POST方法傳遞Json格式的資料 資料內要有:message、link、published

不過如果按照這個範例程式是無法在執行的,會有2個問題

1.執行這樣的curl 指令,必須經過轉譯

但是wordpress不能轉譯,而且wordpress有自己的函式wp_remote_post()

2.資料,少了重要的應用程式API編號:access_token

這個取得流程可以參考:Facebook粉專自動發文以及留言API 
在多次嘗試以及餵狗之後,最後可以執行的程式碼如下 
整體程式碼的架構跟之前的同步寄信到Blogger基本相同,都是藉由wordpress本身的觸發函示來執行相關程式碼

程式碼

  1. //
  2. function post_fb($new_status, $old_status, $post){
  3.     $postOne = get_post($post);
  4.     $postUrl = get_permalink($post);
  5.     $postBody= $postOne->post_content;
  6.     $postExcerpt= $postOne->post_excerpt; /* 文章摘要 */
  7.     $titleOne = $postOne->post_title;
  8.     $author = $post->post_author; /* Post author ID. */
  9.     $name = get_the_author_meta( 'nickname', $author );
  10.  
  11. //判斷文章狀態
  12. if ( $new_status == "publish" && $old_status !== "publish" && $post->post_type == "post")
  13.   {
  14.     $endpoint = 'https://graph.facebook.com/v19.0/*********粉絲頁編號**********/feed';
  15.  
  16.     $body = [
  17.                'message' => $postExcerpt, /* 文章摘要 */
  18.                'link' => $postUrl,
  19.                'published' => 'true',
  20.                'access_token' =>'---取得的長期權杖---'
  21.             ];
  22.  
  23.     $body = wp_json_encode( $body );
  24.  
  25.     $options = [
  26.                 'body' => $body,
  27.                 'headers' => [
  28.                 'Content-Type' => 'application/json',
  29.               ],
  30.               'timeout' => 60,
  31.               'redirection' => 5,
  32.               'blocking' => true,
  33.               'httpversion' => '1.0',
  34.               'sslverify' => false,
  35.               'data_format' => 'body',
  36.               ];
  37.  
  38.     wp_remote_post( $endpoint, $options );
  39.   }
  40.  
  41. }
  42. add_action('transition_post_status','post_fb',10,3);
 
主要的程式碼是#14~39,執行wp_remote_post()的參數設定 
$endpoint 是要發出請求的網址 
$body是陣列,架構為 key => value,之後用 wp_json_encode()轉為Json 
$options是要帶入wp_remote_post()的參數陣列

後續編寫貼文的調整

過往沒有寫「 內容摘要」

但是為了讓分享內容時可以帶入訊息,所以要把寫在粉絲頁的訊息寫入摘要

 
程式裡的$postExcerpt就是用來接收文章的摘要 
這應該是最簡單的方式 原本想嘗試能不能跳出一個訊息框,將訊息輸入後帶回程式 
但是光想就很麻煩

備註

本篇文章發布之後,也同時貼文在粉絲頁

但是好像因為同時發文又寄信
導致網站速度變得很慢...
 

2024年3月5日 星期二

網站 / 從wordpress發佈新文章同步寄信到blogger儲存成草稿

google blogger可以接收電子郵件來發佈文章

當然這個電子郵件不是自己的gmail,而是指定的用於貼文的電子信箱

格式是google帳號名稱.自訂文字@blogger.com

因此,嘗試從wordpress發佈新文章,同步寄信到blogger設定的專用信箱來發佈草稿(因為通常會跑版,而且需要手動設定標籤)

這個可以透過add_action()勾點 transition_post_status事件來觸發郵寄文件的函式

所以可以修改 網站 / LINE Notify 推播其中的發佈新文章程式碼

程式碼的說明如下:

#3~23 自定義的發佈郵件函式 function send_email()

其中的#5是用來篩選 ‘wp_mail_content_type’ 資料,透過自訂的函式wpdocs_set_html_mail_content_type()

將預設的text/plain 轉換為’text/html

當郵件發出之後,再透過 #22刪除篩選器,回復成預設值

這個是參考WordPress Developer Resources的範例

不過後來沒有用到

因為原本的流程是將wordpress的文章,以html格式寄到blogger

但是,在blogger的編輯器會再解析一次內文,內容不是跑板就是有莫名的解析

例如: XXX.yyy,會加上a標籤變成超連結

於是改以純文字格式傳出html字串,之後再從編輯器貼到HTML編輯器

#7~12是文章的資料

#15~20 判斷文章的狀態,當文章的狀態從不是發佈狀態改變成發佈狀態才執行 wp_mail()

#17~19 是郵件的設定值,會傳入wp_mail()

#24 增加勾點 add_action()

當網站發佈新文章時,將文章資訊輸入並執行function send_email()

#26~27 function wpdocs_set_html_mail_content_type()

在#5要觸發用來修改wp_mail_content_type的函式

  1. //發佈文章的通知
  2. //
  3. function send_email($new_status, $old_status, $post){
  4. //修改內文格式為text/html
  5. //add_filter( ‘wp_mail_content_type’, ‘wpdocs_set_html_mail_content_type’ );
  6.  
  7.     $postOne = get_post($post);
  8.     $postUrl = get_permalink($post);
  9.     $postBody= $postOne->post_content;
  10.     $titleOne = $postOne->post_title;
  11.     $author = $post->post_author; /* Post author ID. */
  12.     $name = get_the_author_meta( ‘nickname’, $author );
  13.  
  14. //判斷文章狀態
  15.     if ( $new_status == “publish” && $old_status !== “publish” && $post->post_type == “post”)
  16.   {
  17.     $to = ‘trico109748007.**************@blogger.com’; $subject = ‘莊生趣味發佈-‘.$titleOne; $message = $postBody;
  18.     $headers = ‘From:莊生趣味<trico109748007@gmail.com>’;
  19.     wp_mail( $to, $subject, $message, $headers );
  20.   }
  21. //移除格式設定,回復成預設格式 text/plain
  22. //remove_filter( ‘wp_mail_content_type’, ‘wpdocs_set_html_mail_content_type’ );
  23. }
  24. add_action(‘transition_post_status’,‘send_email’,10,3);
  25.  
  26. function wpdocs_set_html_mail_content_type() {
  27.   return ‘text/html’;
  28. }
 

如果網站本來就可以正常發出郵件的話,增加以上程式碼

當發佈新文章之後,就會將文章寄到blogger接收郵件的信箱,並存成草稿

可以使用WP Mail Logging查看紀錄,不過有時候會沒有紀錄,不知道是不是文章主旨太長的緣故?