feat: ratelimit, ghcr package support, media path sensitization, LID to CN migration (#6)

* feat: ratelimit, ghcr package and proper project refactor from askarzh

* fix(bridge): validate media path

* update: dependencies

* fix: added LID to contact number migration
This commit is contained in:
Atul Singh
2026-05-13 12:54:31 +02:00
committed by GitHub
parent 680ef32b31
commit 193e0d3c77
20 changed files with 1245 additions and 173 deletions
+66
View File
@@ -82,6 +82,16 @@ func InitMcpTool() {
Description: "Download media from a WhatsApp message and return local file path.",
}, downloadMediaHandler)
mcp.AddTool[getLoginStatusInput, any](server, &mcp.Tool{
Name: "get_login_status",
Description: "Check whether the WhatsApp bridge is connected and logged in. Returns {connected, logged_in, pairing_required}.",
}, getLoginStatusHandler)
mcp.AddTool[getPairingQrInput, any](server, &mcp.Tool{
Name: "get_pairing_qr",
Description: "Fetch the WhatsApp pairing QR as a PNG image. Returns image content when pairing is required; returns a text message when the bridge is already logged in or pairing has not started.",
}, getPairingQrHandler)
isHttp := strings.ToLower(ReadEnv("IS_HTTP", "false")) == "true" ||
strings.ToLower(ReadEnv("IS_HTTP", "0")) == "1"
@@ -175,6 +185,10 @@ type downloadMediaInput struct {
ChatJid string `json:"chat_jid"`
}
type getLoginStatusInput struct{}
type getPairingQrInput struct{}
func callAPI(method, path string, body any) ([]byte, error) {
token, err := GetOrRefreshJwtToken()
if err != nil {
@@ -556,3 +570,55 @@ func sendAudioMessageHandler(ctx context.Context,
return &mcp.CallToolResult{IsError: !success}, resultData, nil
}
func getLoginStatusHandler(
ctx context.Context,
req *mcp.CallToolRequest,
_ getLoginStatusInput,
) (*mcp.CallToolResult, any, error) {
data, err := callAPI(http.MethodGet, "/auth/status", nil)
if err != nil {
return ErrResult(fmt.Sprintf("failed to fetch login status: %v", err)), nil, nil
}
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: string(data)}},
}, nil, nil
}
func getPairingQrHandler(
ctx context.Context,
req *mcp.CallToolRequest,
_ getPairingQrInput,
) (*mcp.CallToolResult, any, error) {
token, err := GetOrRefreshJwtToken()
if err != nil {
return ErrResult(fmt.Sprintf("authentication failed: %v", err)), nil, nil
}
httpReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/auth/pairing-qr", apiBaseURL), nil)
if err != nil {
return ErrResult(fmt.Sprintf("failed to build request: %v", err)), nil, nil
}
httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
client := &http.Client{Timeout: apiTimeout}
resp, err := client.Do(httpReq)
if err != nil {
return ErrResult(fmt.Sprintf("request failed: %v", err)), nil, nil
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
switch resp.StatusCode {
case http.StatusOK:
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.ImageContent{Data: body, MIMEType: "image/png"}},
}, nil, nil
case http.StatusGone:
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: "No pairing QR available. The bridge is either already logged in or has not started the pairing flow yet."}},
}, nil, nil
default:
return ErrResult(fmt.Sprintf("unexpected status %d: %s", resp.StatusCode, string(body))), nil, nil
}
}