// kbdb_get — 從 KBDB 讀 block(GET /blocks?page_name=... 或 GET /blocks/:id) // thin wrapper:透過 host function http_request 呼叫 KBDB API // //go:build tinygo package main import ( "encoding/json" "io" "os" "strconv" "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 { KBDBUrl string `json:"kbdb_url"` // optional APIKey string `json:"api_key"` // 必填 BlockID string `json:"block_id"` // 與 page_name 二擇一 PageName string `json:"page_name"` // 與 block_id 二擇一 Limit int `json:"limit"` // optional, default 50 } var dummy [1]byte func safePtr(b []byte) (uintptr, uint32) { if len(b) == 0 { return uintptr(unsafe.Pointer(&dummy[0])), 0 } return uintptr(unsafe.Pointer(&b[0])), uint32(len(b)) } func writeError(msg string) { out, _ := json.Marshal(map[string]interface{}{"success": false, "error": msg}) os.Stdout.Write(out) } // urlEncode:簡易 query string encoder(只處理 KBDB 會用到的字元) // 避免引入 net/url(白名單外) func urlEncode(s string) string { var out []byte for i := 0; i < len(s); i++ { c := s[i] if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' { out = append(out, c) } else { const hex = "0123456789ABCDEF" out = append(out, '%', hex[c>>4], hex[c&0x0f]) } } return string(out) } 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.APIKey == "" { writeError("api_key 必填") return } if input.BlockID == "" && input.PageName == "" { writeError("block_id 或 page_name 必填其一") return } kbdbURL := input.KBDBUrl if kbdbURL == "" { kbdbURL = "https://kbdb.finally.click" } limit := input.Limit if limit <= 0 { limit = 50 } // 構造 URL:block_id 模式走 /blocks/:id(單一),page_name 模式走 /blocks?page_name=...&limit=N var url string if input.BlockID != "" { url = kbdbURL + "/blocks/" + urlEncode(input.BlockID) } else { url = kbdbURL + "/blocks?page_name=" + urlEncode(input.PageName) + "&limit=" + strconv.Itoa(limit) } headers := map[string]string{ "Authorization": "Bearer " + input.APIKey, } headersBytes, _ := json.Marshal(headers) method := "GET" urlBytes := []byte(url) methodBytes := []byte(method) outBuf := make([]byte, 1<<20) // 1MB(list 可能很大) var outLen uint32 urlPtr, urlLen := safePtr(urlBytes) methodPtr, methodLen := safePtr(methodBytes) headersPtr, headersLenU := safePtr(headersBytes) bodyPtr, bodyLenU := safePtr(nil) result := hostHttpRequest( urlPtr, urlLen, methodPtr, methodLen, headersPtr, headersLenU, bodyPtr, bodyLenU, uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)), ) if result != 0 { writeError("KBDB GET request failed (host_http_request returned non-zero)") return } respStr := string(outBuf[:outLen]) // 解析回應 if input.BlockID != "" { // 單一 block:KBDB 直接回 block 物件,包成 array 給下游 foreach var block map[string]interface{} if err := json.Unmarshal([]byte(respStr), &block); err != nil { writeError("KBDB returned non-JSON: " + respStr) return } if _, hasErr := block["error"]; hasErr { out, _ := json.Marshal(map[string]interface{}{ "success": false, "error": block["error"], }) os.Stdout.Write(out) return } out, _ := json.Marshal(map[string]interface{}{ "success": true, "blocks": []map[string]interface{}{block}, "count": 1, }) os.Stdout.Write(out) return } // page_name 列表模式:KBDB 回 {"blocks": [...], "count": N} var listResp struct { Blocks []map[string]interface{} `json:"blocks"` Count int `json:"count"` Error interface{} `json:"error"` } if err := json.Unmarshal([]byte(respStr), &listResp); err != nil { writeError("KBDB returned non-JSON: " + respStr) return } if listResp.Error != nil { out, _ := json.Marshal(map[string]interface{}{ "success": false, "error": listResp.Error, }) os.Stdout.Write(out) return } out, _ := json.Marshal(map[string]interface{}{ "success": true, "blocks": listResp.Blocks, "count": listResp.Count, }) os.Stdout.Write(out) }