Initial v1.1.8 Commits
This commit is contained in:
@@ -0,0 +1,637 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
_ "image/jpeg"
|
||||
"log"
|
||||
"math"
|
||||
mathrand "math/rand"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type sliderPuzzleV2 struct {
|
||||
Image image.Image
|
||||
Size int
|
||||
Swaps []int
|
||||
Attempts int
|
||||
}
|
||||
|
||||
type sliderGuessV2 struct {
|
||||
Index int
|
||||
Swaps []int
|
||||
Score int64
|
||||
ScoreRGB int64
|
||||
ScoreLuma int64
|
||||
ScoreText float64
|
||||
ConsensusRank int
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) solveSliderCaptcha(
|
||||
sessionToken string,
|
||||
browserFP string,
|
||||
hash string,
|
||||
settings string,
|
||||
debugInfo string,
|
||||
) (string, error) {
|
||||
values := [][2]string{
|
||||
{"session_token", sessionToken},
|
||||
{"domain", "vk.com"},
|
||||
{"adFp", ""},
|
||||
{"access_token", ""},
|
||||
{"captcha_settings", settings},
|
||||
}
|
||||
|
||||
resp, err := s.captchaRequest("captchaNotRobot.getContent", values)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("slider getContent failed: %w", err)
|
||||
}
|
||||
puzzle, err := parseSliderPuzzleV2(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("[КАПЧА] v2 slider puzzle decoded: grid=%d attempts=%d swaps=%d", puzzle.Size, puzzle.Attempts, len(puzzle.Swaps))
|
||||
|
||||
guesses, err := rankSliderGuessesV2(puzzle.Image, puzzle.Size, puzzle.Swaps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
limit := puzzle.Attempts
|
||||
if limit > len(guesses) {
|
||||
limit = len(guesses)
|
||||
}
|
||||
if limit <= 0 {
|
||||
return "", errors.New("slider has no attempts available")
|
||||
}
|
||||
log.Printf("[КАПЧА] v2 slider guesses ranked: total=%d limit=%d", len(guesses), limit)
|
||||
|
||||
deviceJSON := captchaV2DeviceInfo
|
||||
if s.savedProfile != nil && strings.TrimSpace(s.savedProfile.DeviceJSON) != "" {
|
||||
deviceJSON = s.savedProfile.DeviceJSON
|
||||
}
|
||||
if _, err := s.captchaRequest("captchaNotRobot.componentDone", [][2]string{
|
||||
{"session_token", sessionToken},
|
||||
{"domain", "vk.com"},
|
||||
{"adFp", ""},
|
||||
{"access_token", ""},
|
||||
{"browser_fp", browserFP},
|
||||
{"device", deviceJSON},
|
||||
}); err != nil {
|
||||
return "", fmt.Errorf("captcha componentDone failed: %w", err)
|
||||
}
|
||||
|
||||
for i := 0; i < limit; i++ {
|
||||
log.Printf("[КАПЧА] v2 slider attempt %d/%d (guess #%d)", i+1, limit, guesses[i].Index)
|
||||
answerData, err := json.Marshal(struct {
|
||||
Value []int `json:"value"`
|
||||
}{Value: guesses[i].Swaps})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
check, err := s.performCaptchaCheck(
|
||||
sessionToken,
|
||||
browserFP,
|
||||
hash,
|
||||
string(answerData),
|
||||
buildSliderCursorV2(guesses[i].Index, len(guesses)),
|
||||
debugInfo,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.EqualFold(check.Status, "ok") {
|
||||
if check.SuccessToken == "" {
|
||||
return "", errors.New("captcha success token not found")
|
||||
}
|
||||
log.Printf("[КАПЧА] v2 slider accepted on attempt %d", i+1)
|
||||
return check.SuccessToken, nil
|
||||
}
|
||||
if strings.EqualFold(check.Status, "error_limit") {
|
||||
return "", errCaptchaV2RateLimit
|
||||
}
|
||||
}
|
||||
return "", errors.New("slider guesses exhausted")
|
||||
}
|
||||
|
||||
func parseSliderPuzzleV2(raw map[string]any) (*sliderPuzzleV2, error) {
|
||||
resp, ok := raw["response"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid slider content response: %v", raw)
|
||||
}
|
||||
status := captchaV2StringifyAny(resp["status"])
|
||||
if !strings.EqualFold(status, "ok") {
|
||||
return nil, fmt.Errorf("slider getContent status: %s", status)
|
||||
}
|
||||
rawImage := captchaV2StringifyAny(resp["image"])
|
||||
if rawImage == "" {
|
||||
return nil, errors.New("slider image missing")
|
||||
}
|
||||
rawSteps, ok := resp["steps"].([]any)
|
||||
if !ok {
|
||||
return nil, errors.New("slider steps missing")
|
||||
}
|
||||
steps := make([]int, 0, len(rawSteps))
|
||||
for _, item := range rawSteps {
|
||||
switch v := item.(type) {
|
||||
case float64:
|
||||
steps = append(steps, int(v))
|
||||
case int:
|
||||
steps = append(steps, v)
|
||||
case string:
|
||||
n, err := strconv.Atoi(strings.TrimSpace(v))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid numeric value: %v", item)
|
||||
}
|
||||
steps = append(steps, n)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid numeric value: %v", item)
|
||||
}
|
||||
}
|
||||
size, swaps, attempts, err := splitSliderStepsV2(steps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(rawImage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode slider image: %w", err)
|
||||
}
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode slider image: %w", err)
|
||||
}
|
||||
return &sliderPuzzleV2{Image: img, Size: size, Swaps: swaps, Attempts: attempts}, nil
|
||||
}
|
||||
|
||||
func splitSliderStepsV2(steps []int) (int, []int, int, error) {
|
||||
if len(steps) < 3 {
|
||||
return 0, nil, 0, errors.New("slider steps payload too short")
|
||||
}
|
||||
size := steps[0]
|
||||
if size <= 0 {
|
||||
return 0, nil, 0, fmt.Errorf("invalid slider size: %d", size)
|
||||
}
|
||||
tail := append([]int(nil), steps[1:]...)
|
||||
attempts := 4
|
||||
if len(tail)%2 != 0 {
|
||||
attempts = tail[len(tail)-1]
|
||||
tail = tail[:len(tail)-1]
|
||||
log.Printf("[КАПЧА] v2 slider payload had odd-length tail; fallback attempts=%d", attempts)
|
||||
}
|
||||
if attempts <= 0 {
|
||||
attempts = 4
|
||||
}
|
||||
if len(tail) == 0 || len(tail)%2 != 0 {
|
||||
return 0, nil, 0, errors.New("invalid slider swap payload")
|
||||
}
|
||||
return size, tail, attempts, nil
|
||||
}
|
||||
|
||||
func rankSliderGuessesV2(img image.Image, gridSize int, swaps []int) ([]sliderGuessV2, error) {
|
||||
candidateCount := len(swaps) / 2
|
||||
if candidateCount == 0 {
|
||||
return nil, errors.New("slider has no candidates")
|
||||
}
|
||||
|
||||
guesses := make([]sliderGuessV2, candidateCount)
|
||||
for idx := 1; idx <= candidateCount; idx++ {
|
||||
active := activeSwapsForIndexV2(swaps, idx)
|
||||
mapping, err := applySliderSwapsV2(gridSize, active)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
guesses[idx-1] = sliderGuessV2{Index: idx, Swaps: active}
|
||||
guesses[idx-1].ScoreLuma = seamScoreLumaV2(img, gridSize, mapping)
|
||||
}
|
||||
|
||||
lumaOrder := append([]sliderGuessV2(nil), guesses...)
|
||||
sort.SliceStable(lumaOrder, func(i, j int) bool {
|
||||
if lumaOrder[i].ScoreLuma == lumaOrder[j].ScoreLuma {
|
||||
return lumaOrder[i].Index < lumaOrder[j].Index
|
||||
}
|
||||
return lumaOrder[i].ScoreLuma < lumaOrder[j].ScoreLuma
|
||||
})
|
||||
lumaRank := make(map[int]int, candidateCount)
|
||||
for rank, g := range lumaOrder {
|
||||
lumaRank[g.Index] = rank
|
||||
}
|
||||
|
||||
stage2Count := candidateCount
|
||||
if stage2Count > 12 {
|
||||
stage2Count = 12
|
||||
}
|
||||
stage2Set := make(map[int]struct{}, stage2Count)
|
||||
for i := 0; i < stage2Count; i++ {
|
||||
stage2Set[lumaOrder[i].Index] = struct{}{}
|
||||
}
|
||||
|
||||
type stage2Result struct {
|
||||
index int
|
||||
rgb int64
|
||||
text float64
|
||||
err error
|
||||
}
|
||||
jobs := make([]int, 0, stage2Count)
|
||||
for idx := range stage2Set {
|
||||
jobs = append(jobs, idx)
|
||||
}
|
||||
jobCh := make(chan int, len(jobs))
|
||||
resCh := make(chan stage2Result, len(jobs))
|
||||
|
||||
workers := runtime.NumCPU()
|
||||
if workers < 1 {
|
||||
workers = 1
|
||||
}
|
||||
if workers > len(jobs) {
|
||||
workers = len(jobs)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for w := 0; w < workers; w++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for index := range jobCh {
|
||||
mapping, err := applySliderSwapsV2(gridSize, guesses[index-1].Swaps)
|
||||
if err != nil {
|
||||
resCh <- stage2Result{index: index, err: err}
|
||||
continue
|
||||
}
|
||||
rgb, text := seamScoreRGBTextV2(img, gridSize, mapping)
|
||||
resCh <- stage2Result{index: index, rgb: rgb, text: text}
|
||||
}
|
||||
}()
|
||||
}
|
||||
for _, idx := range jobs {
|
||||
jobCh <- idx
|
||||
}
|
||||
close(jobCh)
|
||||
wg.Wait()
|
||||
close(resCh)
|
||||
for r := range resCh {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
g := &guesses[r.index-1]
|
||||
g.ScoreRGB = r.rgb
|
||||
g.ScoreText = r.text
|
||||
}
|
||||
|
||||
stage2 := make([]sliderGuessV2, 0, stage2Count)
|
||||
for _, g := range guesses {
|
||||
if _, ok := stage2Set[g.Index]; ok {
|
||||
stage2 = append(stage2, g)
|
||||
}
|
||||
}
|
||||
|
||||
rgbOrder := append([]sliderGuessV2(nil), stage2...)
|
||||
sort.SliceStable(rgbOrder, func(i, j int) bool {
|
||||
if rgbOrder[i].ScoreRGB == rgbOrder[j].ScoreRGB {
|
||||
return rgbOrder[i].Index < rgbOrder[j].Index
|
||||
}
|
||||
return rgbOrder[i].ScoreRGB < rgbOrder[j].ScoreRGB
|
||||
})
|
||||
rgbRank := make(map[int]int, len(rgbOrder))
|
||||
for rank, g := range rgbOrder {
|
||||
rgbRank[g.Index] = rank
|
||||
}
|
||||
|
||||
textOrder := append([]sliderGuessV2(nil), stage2...)
|
||||
sort.SliceStable(textOrder, func(i, j int) bool {
|
||||
if textOrder[i].ScoreText == textOrder[j].ScoreText {
|
||||
return textOrder[i].Index < textOrder[j].Index
|
||||
}
|
||||
return textOrder[i].ScoreText < textOrder[j].ScoreText
|
||||
})
|
||||
textRank := make(map[int]int, len(textOrder))
|
||||
for rank, g := range textOrder {
|
||||
textRank[g.Index] = rank
|
||||
}
|
||||
|
||||
for i := range guesses {
|
||||
g := &guesses[i]
|
||||
g.ConsensusRank = lumaRank[g.Index]
|
||||
if _, ok := stage2Set[g.Index]; ok {
|
||||
g.ConsensusRank += rgbRank[g.Index] + textRank[g.Index]
|
||||
} else {
|
||||
g.ConsensusRank += candidateCount
|
||||
}
|
||||
g.Score = int64(g.ConsensusRank)
|
||||
}
|
||||
|
||||
sort.SliceStable(guesses, func(i, j int) bool {
|
||||
if guesses[i].ConsensusRank == guesses[j].ConsensusRank {
|
||||
if guesses[i].ScoreLuma == guesses[j].ScoreLuma {
|
||||
return guesses[i].Index < guesses[j].Index
|
||||
}
|
||||
return guesses[i].ScoreLuma < guesses[j].ScoreLuma
|
||||
}
|
||||
return guesses[i].ConsensusRank < guesses[j].ConsensusRank
|
||||
})
|
||||
return guesses, nil
|
||||
}
|
||||
|
||||
func activeSwapsForIndexV2(swaps []int, index int) []int {
|
||||
if index <= 0 {
|
||||
return []int{}
|
||||
}
|
||||
end := index * 2
|
||||
if end > len(swaps) {
|
||||
end = len(swaps)
|
||||
}
|
||||
return append([]int(nil), swaps[:end]...)
|
||||
}
|
||||
|
||||
func applySliderSwapsV2(gridSize int, swaps []int) ([]int, error) {
|
||||
tileCount := gridSize * gridSize
|
||||
if tileCount <= 0 {
|
||||
return nil, fmt.Errorf("invalid slider tile count: %d", tileCount)
|
||||
}
|
||||
if len(swaps)%2 != 0 {
|
||||
return nil, fmt.Errorf("invalid slider swaps length: %d", len(swaps))
|
||||
}
|
||||
mapping := make([]int, tileCount)
|
||||
for i := range mapping {
|
||||
mapping[i] = i
|
||||
}
|
||||
for i := 0; i < len(swaps); i += 2 {
|
||||
left := swaps[i]
|
||||
right := swaps[i+1]
|
||||
if left < 0 || right < 0 || left >= tileCount || right >= tileCount {
|
||||
return nil, fmt.Errorf("slider step out of range: %d,%d", left, right)
|
||||
}
|
||||
mapping[left], mapping[right] = mapping[right], mapping[left]
|
||||
}
|
||||
return mapping, nil
|
||||
}
|
||||
|
||||
func sliderTileRect(bounds image.Rectangle, gridSize int, index int) image.Rectangle {
|
||||
w := bounds.Dx() / gridSize
|
||||
h := bounds.Dy() / gridSize
|
||||
col := index % gridSize
|
||||
row := index / gridSize
|
||||
return image.Rect(
|
||||
bounds.Min.X+col*w,
|
||||
bounds.Min.Y+row*h,
|
||||
bounds.Min.X+(col+1)*w,
|
||||
bounds.Min.Y+(row+1)*h,
|
||||
)
|
||||
}
|
||||
|
||||
func pixelDiff(a, b color.Color) int64 {
|
||||
ar, ag, ab, _ := a.RGBA()
|
||||
br, bg, bb, _ := b.RGBA()
|
||||
dr := int64(ar>>8) - int64(br>>8)
|
||||
dg := int64(ag>>8) - int64(bg>>8)
|
||||
db := int64(ab>>8) - int64(bb>>8)
|
||||
if dr < 0 {
|
||||
dr = -dr
|
||||
}
|
||||
if dg < 0 {
|
||||
dg = -dg
|
||||
}
|
||||
if db < 0 {
|
||||
db = -db
|
||||
}
|
||||
return dr + dg + db
|
||||
}
|
||||
|
||||
func seamScoreLumaV2(img image.Image, gridSize int, mapping []int) int64 {
|
||||
bounds := img.Bounds()
|
||||
var score int64
|
||||
for row := 0; row < gridSize; row++ {
|
||||
for col := 0; col < gridSize-1; col++ {
|
||||
leftIdx := row*gridSize + col
|
||||
rightIdx := leftIdx + 1
|
||||
leftDst := sliderTileRect(bounds, gridSize, leftIdx)
|
||||
rightDst := sliderTileRect(bounds, gridSize, rightIdx)
|
||||
leftSrc := sliderTileRect(bounds, gridSize, mapping[leftIdx])
|
||||
rightSrc := sliderTileRect(bounds, gridSize, mapping[rightIdx])
|
||||
h := leftDst.Dy()
|
||||
if rightDst.Dy() < h {
|
||||
h = rightDst.Dy()
|
||||
}
|
||||
for y := 0; y < h; y++ {
|
||||
yy := leftDst.Min.Y + y
|
||||
a := sampleLumaMappedV2(img, leftDst, leftSrc, leftDst.Max.X-1, yy)
|
||||
b := sampleLumaMappedV2(img, rightDst, rightSrc, rightDst.Min.X, yy)
|
||||
score += int64(absIntV2(int(a) - int(b)))
|
||||
}
|
||||
}
|
||||
}
|
||||
for row := 0; row < gridSize-1; row++ {
|
||||
for col := 0; col < gridSize; col++ {
|
||||
topIdx := row*gridSize + col
|
||||
bottomIdx := (row+1)*gridSize + col
|
||||
topDst := sliderTileRect(bounds, gridSize, topIdx)
|
||||
bottomDst := sliderTileRect(bounds, gridSize, bottomIdx)
|
||||
topSrc := sliderTileRect(bounds, gridSize, mapping[topIdx])
|
||||
bottomSrc := sliderTileRect(bounds, gridSize, mapping[bottomIdx])
|
||||
w := topDst.Dx()
|
||||
if bottomDst.Dx() < w {
|
||||
w = bottomDst.Dx()
|
||||
}
|
||||
for x := 0; x < w; x++ {
|
||||
xx := topDst.Min.X + x
|
||||
a := sampleLumaMappedV2(img, topDst, topSrc, xx, topDst.Max.Y-1)
|
||||
b := sampleLumaMappedV2(img, bottomDst, bottomSrc, xx, bottomDst.Min.Y)
|
||||
score += int64(absIntV2(int(a) - int(b)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func seamScoreRGBTextV2(img image.Image, gridSize int, mapping []int) (int64, float64) {
|
||||
bounds := img.Bounds()
|
||||
height := float64(bounds.Dy())
|
||||
textCenters := []float64{
|
||||
float64(bounds.Min.Y) + 0.2*height,
|
||||
float64(bounds.Min.Y) + 0.5*height,
|
||||
float64(bounds.Min.Y) + 0.8*height,
|
||||
}
|
||||
sigma := height * 0.14
|
||||
if sigma < 1.0 {
|
||||
sigma = 1.0
|
||||
}
|
||||
weight := func(y int) float64 {
|
||||
yf := float64(y)
|
||||
best := absFloatV2(yf - textCenters[0])
|
||||
for i := 1; i < len(textCenters); i++ {
|
||||
d := absFloatV2(yf - textCenters[i])
|
||||
if d < best {
|
||||
best = d
|
||||
}
|
||||
}
|
||||
return 1 + 3*math.Exp(-(best*best)/(2*sigma*sigma))
|
||||
}
|
||||
|
||||
var rgbScore int64
|
||||
var textScore float64
|
||||
for row := 0; row < gridSize; row++ {
|
||||
for col := 0; col < gridSize-1; col++ {
|
||||
leftIdx := row*gridSize + col
|
||||
rightIdx := leftIdx + 1
|
||||
leftDst := sliderTileRect(bounds, gridSize, leftIdx)
|
||||
rightDst := sliderTileRect(bounds, gridSize, rightIdx)
|
||||
leftSrc := sliderTileRect(bounds, gridSize, mapping[leftIdx])
|
||||
rightSrc := sliderTileRect(bounds, gridSize, mapping[rightIdx])
|
||||
h := leftDst.Dy()
|
||||
if rightDst.Dy() < h {
|
||||
h = rightDst.Dy()
|
||||
}
|
||||
for y := 0; y < h; y++ {
|
||||
yy := leftDst.Min.Y + y
|
||||
l := sampleColorMappedV2(img, leftDst, leftSrc, leftDst.Max.X-1, yy)
|
||||
r := sampleColorMappedV2(img, rightDst, rightSrc, rightDst.Min.X, yy)
|
||||
rgbScore += pixelDiff(l, r)
|
||||
_, _, lb, _ := l.RGBA()
|
||||
_, _, rb, _ := r.RGBA()
|
||||
textScore += weight(yy) * float64(absIntV2(int(lb>>8)-int(rb>>8)))
|
||||
}
|
||||
}
|
||||
}
|
||||
for row := 0; row < gridSize-1; row++ {
|
||||
for col := 0; col < gridSize; col++ {
|
||||
topIdx := row*gridSize + col
|
||||
bottomIdx := (row+1)*gridSize + col
|
||||
topDst := sliderTileRect(bounds, gridSize, topIdx)
|
||||
bottomDst := sliderTileRect(bounds, gridSize, bottomIdx)
|
||||
topSrc := sliderTileRect(bounds, gridSize, mapping[topIdx])
|
||||
bottomSrc := sliderTileRect(bounds, gridSize, mapping[bottomIdx])
|
||||
w := topDst.Dx()
|
||||
if bottomDst.Dx() < w {
|
||||
w = bottomDst.Dx()
|
||||
}
|
||||
for x := 0; x < w; x++ {
|
||||
xx := topDst.Min.X + x
|
||||
t := sampleColorMappedV2(img, topDst, topSrc, xx, topDst.Max.Y-1)
|
||||
b := sampleColorMappedV2(img, bottomDst, bottomSrc, xx, bottomDst.Min.Y)
|
||||
rgbScore += pixelDiff(t, b)
|
||||
_, _, tb, _ := t.RGBA()
|
||||
_, _, bb, _ := b.RGBA()
|
||||
textScore += 0.65 * float64(absIntV2(int(tb>>8)-int(bb>>8)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return rgbScore, textScore
|
||||
}
|
||||
|
||||
func sampleColorMappedV2(img image.Image, dstRect image.Rectangle, srcRect image.Rectangle, dstX int, dstY int) color.Color {
|
||||
dx := dstRect.Dx()
|
||||
if dx < 1 {
|
||||
dx = 1
|
||||
}
|
||||
dy := dstRect.Dy()
|
||||
if dy < 1 {
|
||||
dy = 1
|
||||
}
|
||||
sx := srcRect.Min.X + (dstX-dstRect.Min.X)*srcRect.Dx()/dx
|
||||
sy := srcRect.Min.Y + (dstY-dstRect.Min.Y)*srcRect.Dy()/dy
|
||||
return img.At(sx, sy)
|
||||
}
|
||||
|
||||
func sampleLumaMappedV2(img image.Image, dstRect image.Rectangle, srcRect image.Rectangle, dstX int, dstY int) uint8 {
|
||||
c := sampleColorMappedV2(img, dstRect, srcRect, dstX, dstY)
|
||||
r, g, b, _ := c.RGBA()
|
||||
y := (299*(r>>8) + 587*(g>>8) + 114*(b>>8)) / 1000
|
||||
return uint8(y)
|
||||
}
|
||||
|
||||
func absFloatV2(v float64) float64 {
|
||||
if v < 0 {
|
||||
return -v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func absIntV2(v int) int {
|
||||
if v < 0 {
|
||||
return -v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func buildSliderCursorV2(candidateIndex int, candidateCount int) string {
|
||||
if candidateCount <= 0 {
|
||||
return "[]"
|
||||
}
|
||||
if candidateIndex < 1 {
|
||||
candidateIndex = 1
|
||||
}
|
||||
if candidateIndex > candidateCount {
|
||||
candidateIndex = candidateCount
|
||||
}
|
||||
|
||||
type cursorPoint struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
}
|
||||
|
||||
startX := 570 + mathrand.Intn(40)
|
||||
startY := 875 + mathrand.Intn(30)
|
||||
|
||||
denom := candidateCount - 1
|
||||
if denom < 1 {
|
||||
denom = 1
|
||||
}
|
||||
baseTargetX := 734 + (937-734)*(candidateIndex-1)/denom
|
||||
targetX := baseTargetX + mathrand.Intn(10) - 5
|
||||
targetY := 655 + mathrand.Intn(14)
|
||||
|
||||
points := make([]cursorPoint, 0, 28)
|
||||
|
||||
for i := 0; i < 1+mathrand.Intn(3); i++ {
|
||||
points = append(points, cursorPoint{
|
||||
X: startX + mathrand.Intn(5) - 2,
|
||||
Y: startY + mathrand.Intn(5) - 2,
|
||||
})
|
||||
}
|
||||
|
||||
transitSteps := 2 + mathrand.Intn(3)
|
||||
arcOffX := mathrand.Intn(60) - 30
|
||||
arcOffY := -(mathrand.Intn(30) + 10)
|
||||
for i := 1; i <= transitSteps; i++ {
|
||||
t := float64(i) / float64(transitSteps+1)
|
||||
cx := float64(startX+targetX)/2 + float64(arcOffX)
|
||||
cy := float64(startY+targetY)/2 + float64(arcOffY)
|
||||
bx := (1-t)*(1-t)*float64(startX) + 2*t*(1-t)*cx + t*t*float64(targetX)
|
||||
by := (1-t)*(1-t)*float64(startY) + 2*t*(1-t)*cy + t*t*float64(targetY)
|
||||
jitter := int((1-t)*8) + 2
|
||||
points = append(points, cursorPoint{
|
||||
X: int(math.Round(bx)) + mathrand.Intn(jitter*2+1) - jitter,
|
||||
Y: int(math.Round(by)) + mathrand.Intn(jitter*2+1) - jitter,
|
||||
})
|
||||
}
|
||||
|
||||
approachSteps := 4 + mathrand.Intn(4)
|
||||
prev := points[len(points)-1]
|
||||
for i := 1; i <= approachSteps; i++ {
|
||||
t := float64(i) / float64(approachSteps)
|
||||
ax := prev.X + int(math.Round(t*float64(targetX-prev.X))) + mathrand.Intn(5) - 2
|
||||
ay := prev.Y + int(math.Round(t*float64(targetY-prev.Y))) + mathrand.Intn(5) - 2
|
||||
points = append(points, cursorPoint{X: ax, Y: ay})
|
||||
}
|
||||
|
||||
settleCount := 3 + mathrand.Intn(5)
|
||||
for i := 0; i < settleCount; i++ {
|
||||
points = append(points, cursorPoint{
|
||||
X: targetX + mathrand.Intn(7) - 3,
|
||||
Y: targetY + mathrand.Intn(7) - 3,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := json.Marshal(points)
|
||||
if err != nil {
|
||||
return "[]"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
Reference in New Issue
Block a user