diff --git a/internal/captcha/rotation.go b/internal/captcha/rotation.go new file mode 100644 index 0000000..4db8b45 --- /dev/null +++ b/internal/captcha/rotation.go @@ -0,0 +1,35 @@ +package captcha + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/lukaszraczylo/captcha-solver-mcp/internal/llm" +) + +// RotationResult is a best-effort FunCaptcha/rotation answer. +type RotationResult struct { + Choice int `json:"choice"` + Rotations int `json:"rotations"` + Confidence float64 `json:"confidence"` +} + +// SolveRotation handles FunCaptcha-style rotation/choice puzzles (best-effort). +func SolveRotation(ctx context.Context, c llm.Client, img []byte, mediaType, instruction string) (RotationResult, error) { + prompt := fmt.Sprintf( + "You are solving a rotation/choice puzzle captcha. Instruction: %q. "+ + "If asked to pick one image, return its 0-based index as choice. "+ + "If asked to rotate, return the number of clockwise steps as rotations. "+ + `Respond ONLY with compact JSON: {"choice":,"rotations":,"confidence":<0..1>}.`, + instruction) + raw, err := c.Vision(ctx, prompt, []llm.Image{{MediaType: mediaType, Data: img}}, llm.Options{}) + if err != nil { + return RotationResult{}, err + } + var res RotationResult + if err := json.Unmarshal([]byte(extractJSON(raw)), &res); err != nil { + return RotationResult{}, fmt.Errorf("model did not return parseable rotation answer") + } + return res, nil +} diff --git a/internal/captcha/rotation_test.go b/internal/captcha/rotation_test.go new file mode 100644 index 0000000..62cc8aa --- /dev/null +++ b/internal/captcha/rotation_test.go @@ -0,0 +1,19 @@ +package captcha + +import ( + "context" + "testing" + + "github.com/lukaszraczylo/captcha-solver-mcp/internal/llm" +) + +func TestSolveRotation(t *testing.T) { + f := &llm.Fake{VisionResp: `{"choice":3,"rotations":2,"confidence":0.6}`} + res, err := SolveRotation(context.Background(), f, []byte{1}, "image/png", "rotate the animal upright") + if err != nil { + t.Fatal(err) + } + if res.Choice != 3 || res.Rotations != 2 { + t.Fatalf("got %+v", res) + } +}