2 Commits

Author SHA1 Message Date
0e0ef72778 Merge pull request #185 from aykhans/dependabot/go_modules/golang.org/x/net-0.53.0
Bump golang.org/x/net from 0.52.0 to 0.53.0
2026-04-10 11:35:40 +04:00
dependabot[bot]
8d10198f02 Bump golang.org/x/net from 0.52.0 to 0.53.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.52.0 to 0.53.0.
- [Commits](https://github.com/golang/net/compare/v0.52.0...v0.53.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.53.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 00:16:33 +00:00
6 changed files with 14 additions and 402 deletions

8
go.mod
View File

@@ -15,7 +15,7 @@ require (
github.com/yuin/gopher-lua v1.1.2
go.aykhans.me/utils v1.0.7
go.yaml.in/yaml/v4 v4.0.0-rc.4
golang.org/x/net v0.52.0
golang.org/x/net v0.53.0
)
require (
@@ -52,7 +52,7 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.8.2 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
)

16
go.sum
View File

@@ -109,16 +109,16 @@ go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -1,271 +0,0 @@
package sarin
import (
"bytes"
"context"
"encoding/json"
"net/http"
"strings"
"time"
"go.aykhans.me/sarin/internal/types"
)
const (
captchaPollInterval = 5 * time.Second
captchaTimeout = 120 * time.Second
)
var captchaHTTPClient = &http.Client{Timeout: captchaTimeout}
// solveCaptcha creates a task and polls for the result.
// baseURL is the service API base (e.g. "https://api.2captcha.com").
// taskIDIsString controls whether taskId is sent back as a string or number.
// solutionKey is the field name in the solution object that holds the token.
func solveCaptcha(baseURL, apiKey string, task map[string]any, solutionKey string, taskIDIsString bool) (string, error) {
if apiKey == "" {
return "", types.ErrCaptchaKeyEmpty
}
taskID, err := captchaCreateTask(baseURL, apiKey, task)
if err != nil {
return "", err
}
return captchaPollResult(baseURL, apiKey, taskID, solutionKey, taskIDIsString)
}
func captchaCreateTask(baseURL, apiKey string, task map[string]any) (string, error) {
body := map[string]any{
"clientKey": apiKey,
"task": task,
}
data, err := json.Marshal(body)
if err != nil {
return "", types.NewCaptchaRequestError("createTask", err)
}
resp, err := captchaHTTPClient.Post(
baseURL+"/createTask",
"application/json",
bytes.NewReader(data),
)
if err != nil {
return "", types.NewCaptchaRequestError("createTask", err)
}
defer resp.Body.Close() //nolint:errcheck
var result struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
TaskID json.RawMessage `json:"taskId"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", types.NewCaptchaRequestError("createTask", err)
}
if result.ErrorID != 0 {
return "", types.NewCaptchaAPIError("createTask", result.ErrorCode, result.ErrorDescription)
}
// taskId may be a JSON number (2captcha, anti-captcha) or a quoted string (capsolver UUIDs).
// Strip surrounding quotes if present so we always work with the underlying value.
return strings.Trim(string(result.TaskID), `"`), nil
}
func captchaPollResult(baseURL, apiKey, taskID, solutionKey string, taskIDIsString bool) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), captchaTimeout)
defer cancel()
ticker := time.NewTicker(captchaPollInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return "", types.NewCaptchaTimeoutError(taskID)
case <-ticker.C:
token, done, err := captchaGetTaskResult(baseURL, apiKey, taskID, solutionKey, taskIDIsString)
if err != nil {
return "", err
}
if done {
return token, nil
}
}
}
}
func captchaGetTaskResult(baseURL, apiKey, taskID, solutionKey string, taskIDIsString bool) (string, bool, error) {
var bodyMap map[string]any
if taskIDIsString {
bodyMap = map[string]any{"clientKey": apiKey, "taskId": taskID}
} else {
bodyMap = map[string]any{"clientKey": apiKey, "taskId": json.Number(taskID)}
}
data, err := json.Marshal(bodyMap)
if err != nil {
return "", false, types.NewCaptchaRequestError("getTaskResult", err)
}
resp, err := captchaHTTPClient.Post(
baseURL+"/getTaskResult",
"application/json",
bytes.NewReader(data),
)
if err != nil {
return "", false, types.NewCaptchaRequestError("getTaskResult", err)
}
defer resp.Body.Close() //nolint:errcheck
var result struct {
ErrorID int `json:"errorId"`
ErrorCode string `json:"errorCode"`
ErrorDescription string `json:"errorDescription"`
Status string `json:"status"`
Solution map[string]any `json:"solution"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", false, types.NewCaptchaRequestError("getTaskResult", err)
}
if result.ErrorID != 0 {
return "", false, types.NewCaptchaAPIError("getTaskResult", result.ErrorCode, result.ErrorDescription)
}
if result.Status == "processing" || result.Status == "idle" {
return "", false, nil
}
token, ok := result.Solution[solutionKey]
if !ok {
return "", false, types.NewCaptchaSolutionKeyError(solutionKey)
}
tokenStr, ok := token.(string)
if !ok {
return "", false, types.NewCaptchaSolutionKeyError(solutionKey)
}
return tokenStr, true, nil
}
// ======================================== 2Captcha ========================================
const twoCaptchaBaseURL = "https://api.2captcha.com"
func twoCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey string) (string, error) {
return solveCaptcha(twoCaptchaBaseURL, apiKey, map[string]any{
"type": "RecaptchaV2TaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}, "gRecaptchaResponse", false)
}
func twoCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction string) (string, error) {
task := map[string]any{
"type": "RecaptchaV3TaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}
if pageAction != "" {
task["pageAction"] = pageAction
}
return solveCaptcha(twoCaptchaBaseURL, apiKey, task, "gRecaptchaResponse", false)
}
func twoCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, cData string) (string, error) {
task := map[string]any{
"type": "TurnstileTaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}
if cData != "" {
task["data"] = cData
}
return solveCaptcha(twoCaptchaBaseURL, apiKey, task, "token", false)
}
// ======================================== Anti-Captcha ========================================
const antiCaptchaBaseURL = "https://api.anti-captcha.com"
func antiCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey string) (string, error) {
return solveCaptcha(antiCaptchaBaseURL, apiKey, map[string]any{
"type": "RecaptchaV2TaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}, "gRecaptchaResponse", false)
}
func antiCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction string) (string, error) {
// Anti-Captcha requires minScore for reCAPTCHA v3. 0.3 is the loosest threshold.
task := map[string]any{
"type": "RecaptchaV3TaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
"minScore": 0.3,
}
if pageAction != "" {
task["pageAction"] = pageAction
}
return solveCaptcha(antiCaptchaBaseURL, apiKey, task, "gRecaptchaResponse", false)
}
func antiCaptchaSolveHCaptcha(apiKey, websiteURL, websiteKey string) (string, error) {
// Anti-Captcha returns hCaptcha tokens under "gRecaptchaResponse" (not "token").
return solveCaptcha(antiCaptchaBaseURL, apiKey, map[string]any{
"type": "HCaptchaTaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}, "gRecaptchaResponse", false)
}
func antiCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, cData string) (string, error) {
task := map[string]any{
"type": "TurnstileTaskProxyless",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}
if cData != "" {
task["cData"] = cData
}
return solveCaptcha(antiCaptchaBaseURL, apiKey, task, "token", false)
}
// ======================================== CapSolver ========================================
const capSolverBaseURL = "https://api.capsolver.com"
func capSolverSolveRecaptchaV2(apiKey, websiteURL, websiteKey string) (string, error) {
return solveCaptcha(capSolverBaseURL, apiKey, map[string]any{
"type": "ReCaptchaV2TaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}, "gRecaptchaResponse", true)
}
func capSolverSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction string) (string, error) {
task := map[string]any{
"type": "ReCaptchaV3TaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}
if pageAction != "" {
task["pageAction"] = pageAction
}
return solveCaptcha(capSolverBaseURL, apiKey, task, "gRecaptchaResponse", true)
}
func capSolverSolveTurnstile(apiKey, websiteURL, websiteKey, cData string) (string, error) {
task := map[string]any{
"type": "AntiTurnstileTaskProxyLess",
"websiteURL": websiteURL,
"websiteKey": websiteKey,
}
if cData != "" {
task["metadata"] = map[string]any{"cdata": cData}
}
return solveCaptcha(capSolverBaseURL, apiKey, task, "token", true)
}

View File

@@ -12,10 +12,3 @@ func NewDefaultRandSource() rand.Source {
uint64(now>>32),
)
}
func firstOrEmpty(values []string) string {
if len(values) == 0 {
return ""
}
return values[0]
}

View File

@@ -574,7 +574,8 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
"fakeit_ErrorHTTP": func() string { return fakeit.ErrorHTTP().Error() },
"fakeit_ErrorHTTPClient": func() string { return fakeit.ErrorHTTPClient().Error() },
"fakeit_ErrorHTTPServer": func() string { return fakeit.ErrorHTTPServer().Error() },
"fakeit_ErrorRuntime": func() string { return fakeit.ErrorRuntime().Error() },
// "fakeit_ErrorInput": func() string { return fakeit.ErrorInput().Error() },
"fakeit_ErrorRuntime": func() string { return fakeit.ErrorRuntime().Error() },
// Fakeit / School
"fakeit_School": fakeit.School,
@@ -584,55 +585,6 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
"fakeit_SongName": fakeit.SongName,
"fakeit_SongArtist": fakeit.SongArtist,
"fakeit_SongGenre": fakeit.SongGenre,
// Captcha / 2Captcha
// Usage: {{ twocaptcha_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com" }}
"twocaptcha_RecaptchaV2": func(apiKey, websiteKey, websiteURL string) (string, error) {
return twoCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey)
},
// Usage: {{ twocaptcha_RecaptchaV3 "API_KEY" "SITE_KEY" "https://example.com" "action" }}
"twocaptcha_RecaptchaV3": func(apiKey, websiteKey, websiteURL, pageAction string) (string, error) {
return twoCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction)
},
// Usage: {{ twocaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" }}
// {{ twocaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" "cdata" }}
"twocaptcha_Turnstile": func(apiKey, websiteKey, websiteURL string, cData ...string) (string, error) {
return twoCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, firstOrEmpty(cData))
},
// Captcha / Anti-Captcha
// Usage: {{ anticaptcha_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com" }}
"anticaptcha_RecaptchaV2": func(apiKey, websiteKey, websiteURL string) (string, error) {
return antiCaptchaSolveRecaptchaV2(apiKey, websiteURL, websiteKey)
},
// Usage: {{ anticaptcha_RecaptchaV3 "API_KEY" "SITE_KEY" "https://example.com" "action" }}
"anticaptcha_RecaptchaV3": func(apiKey, websiteKey, websiteURL, pageAction string) (string, error) {
return antiCaptchaSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction)
},
// Usage: {{ anticaptcha_HCaptcha "API_KEY" "SITE_KEY" "https://example.com" }}
"anticaptcha_HCaptcha": func(apiKey, websiteKey, websiteURL string) (string, error) {
return antiCaptchaSolveHCaptcha(apiKey, websiteURL, websiteKey)
},
// Usage: {{ anticaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" }}
// {{ anticaptcha_Turnstile "API_KEY" "SITE_KEY" "https://example.com" "cdata" }}
"anticaptcha_Turnstile": func(apiKey, websiteKey, websiteURL string, cData ...string) (string, error) {
return antiCaptchaSolveTurnstile(apiKey, websiteURL, websiteKey, firstOrEmpty(cData))
},
// Captcha / CapSolver
// Usage: {{ capsolver_RecaptchaV2 "API_KEY" "SITE_KEY" "https://example.com" }}
"capsolver_RecaptchaV2": func(apiKey, websiteKey, websiteURL string) (string, error) {
return capSolverSolveRecaptchaV2(apiKey, websiteURL, websiteKey)
},
// Usage: {{ capsolver_RecaptchaV3 "API_KEY" "SITE_KEY" "https://example.com" "action" }}
"capsolver_RecaptchaV3": func(apiKey, websiteKey, websiteURL, pageAction string) (string, error) {
return capSolverSolveRecaptchaV3(apiKey, websiteURL, websiteKey, pageAction)
},
// Usage: {{ capsolver_Turnstile "API_KEY" "SITE_KEY" "https://example.com" }}
// {{ capsolver_Turnstile "API_KEY" "SITE_KEY" "https://example.com" "cdata" }}
"capsolver_Turnstile": func(apiKey, websiteKey, websiteURL string, cData ...string) (string, error) {
return capSolverSolveTurnstile(apiKey, websiteURL, websiteKey, firstOrEmpty(cData))
},
}
}

View File

@@ -442,65 +442,3 @@ func NewScriptUnknownEngineError(engineType string) ScriptUnknownEngineError {
func (e ScriptUnknownEngineError) Error() string {
return "unknown engine type: " + e.EngineType
}
// ======================================== Captcha ========================================
var ErrCaptchaKeyEmpty = errors.New("captcha API key cannot be empty")
type CaptchaAPIError struct {
Endpoint string
Code string
Description string
}
func NewCaptchaAPIError(endpoint, code, description string) CaptchaAPIError {
return CaptchaAPIError{Endpoint: endpoint, Code: code, Description: description}
}
func (e CaptchaAPIError) Error() string {
return fmt.Sprintf("captcha %s error: %s (%s)", e.Endpoint, e.Code, e.Description)
}
type CaptchaRequestError struct {
Endpoint string
Err error
}
func NewCaptchaRequestError(endpoint string, err error) CaptchaRequestError {
if err == nil {
err = errNoError
}
return CaptchaRequestError{Endpoint: endpoint, Err: err}
}
func (e CaptchaRequestError) Error() string {
return fmt.Sprintf("captcha %s request failed: %v", e.Endpoint, e.Err)
}
func (e CaptchaRequestError) Unwrap() error {
return e.Err
}
type CaptchaTimeoutError struct {
TaskID string
}
func NewCaptchaTimeoutError(taskID string) CaptchaTimeoutError {
return CaptchaTimeoutError{TaskID: taskID}
}
func (e CaptchaTimeoutError) Error() string {
return fmt.Sprintf("captcha solving timed out (taskId: %s)", e.TaskID)
}
type CaptchaSolutionKeyError struct {
Key string
}
func NewCaptchaSolutionKeyError(key string) CaptchaSolutionKeyError {
return CaptchaSolutionKeyError{Key: key}
}
func (e CaptchaSolutionKeyError) Error() string {
return fmt.Sprintf("captcha solution missing expected key %q", e.Key)
}