// gmail — 透過 Gmail API 發送 Email // 透過 host function 呼叫 Gmail API // //go:build tinygo package main import ( "encoding/json" "io" "os" "strings" "unsafe" ) //go:wasmimport u6u http_request func hostHttpRequest( urlPtr uintptr, urlLen uint32, methodPtr uintptr, methodLen uint32, headersPtr uintptr, headersLen uint32, bodyPtr uintptr, bodyLen uint32, outPtr uintptr, outLenPtr uintptr, ) uint32 type Input struct { To string `json:"to"` Subject string `json:"subject"` Body string `json:"body"` AccessToken string `json:"access_token"` } func main() { raw, err := io.ReadAll(os.Stdin) if err != nil { writeError("failed to read stdin: " + err.Error()) return } var input Input if err := json.Unmarshal(raw, &input); err != nil { writeError("invalid input JSON: " + err.Error()) return } if input.To == "" { writeError("to 必填") return } if input.Subject == "" { writeError("subject 必填") return } if input.AccessToken == "" { writeError("access_token 必填") return } // 建立 RFC 2822 格式的 email,base64url 編碼 emailLines := []string{ "To: " + input.To, "Subject: " + input.Subject, "Content-Type: text/plain; charset=UTF-8", "", input.Body, } emailRaw := strings.Join(emailLines, "\r\n") encoded := base64URLEncode([]byte(emailRaw)) bodyData, _ := json.Marshal(map[string]string{"raw": encoded}) apiURL := "https://gmail.googleapis.com/gmail/v1/users/me/messages/send" method := "POST" headers := map[string]string{ "Authorization": "Bearer " + input.AccessToken, "Content-Type": "application/json", } headersJSON, _ := json.Marshal(headers) urlBytes := []byte(apiURL) methodBytes := []byte(method) bodyBytes := bodyData outBuf := make([]byte, 65536) var outLen uint32 result := hostHttpRequest( uintptr(unsafe.Pointer(&urlBytes[0])), uint32(len(urlBytes)), uintptr(unsafe.Pointer(&methodBytes[0])), uint32(len(methodBytes)), uintptr(unsafe.Pointer(&headersJSON[0])), uint32(len(headersJSON)), uintptr(unsafe.Pointer(&bodyBytes[0])), uint32(len(bodyBytes)), uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)), ) if result != 0 { writeError("Gmail API 呼叫失敗") return } responseStr := string(outBuf[:outLen]) var responseData map[string]interface{} if err := json.Unmarshal([]byte(responseStr), &responseData); err != nil { responseData = map[string]interface{}{"raw": responseStr} } messageID, _ := responseData["id"].(string) out, _ := json.Marshal(map[string]interface{}{ "success": true, "data": map[string]interface{}{"message_id": messageID}, }) os.Stdout.Write(out) } // base64URLEncode — 不依賴 encoding/base64(TinyGo 相容) func base64URLEncode(data []byte) string { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" var sb strings.Builder for i := 0; i < len(data); i += 3 { b0 := data[i] var b1, b2 byte if i+1 < len(data) { b1 = data[i+1] } if i+2 < len(data) { b2 = data[i+2] } sb.WriteByte(chars[b0>>2]) sb.WriteByte(chars[((b0&0x3)<<4)|(b1>>4)]) if i+1 < len(data) { sb.WriteByte(chars[((b1&0xf)<<2)|(b2>>6)]) } if i+2 < len(data) { sb.WriteByte(chars[b2&0x3f]) } } return sb.String() } func writeError(msg string) { out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg}) os.Stdout.Write(out) }