Compare commits
28 commits
Author | SHA1 | Date | |
---|---|---|---|
669a328c35 | |||
31a767886d | |||
8b04542f19 | |||
0d18ee4d3f | |||
98c666bea2 | |||
d294d79360 | |||
b4b097a927 | |||
|
c34288c982 | ||
|
2f847d5947 | ||
|
fef853ee5e | ||
|
1d4172a01f | ||
|
6b08f978b6 | ||
|
9234bd71dd | ||
|
f6fd454518 | ||
|
20014aab1b | ||
|
6aa88d953f | ||
|
019b34f8f2 | ||
|
80cb7ffd68 | ||
|
02532e6f4d | ||
|
ba2a083ab6 | ||
|
a4223da22a | ||
|
53e75b199a | ||
|
cccc7a97ea | ||
|
87b02acaf1 | ||
|
ba5dfca752 | ||
|
1bc08e3651 | ||
|
3915a04f79 | ||
|
b69ec48f20 |
11 changed files with 79 additions and 5142 deletions
24
.github/workflows/go.yml
vendored
24
.github/workflows/go.yml
vendored
|
@ -1,24 +0,0 @@
|
|||
name: CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Golang
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.13.1'
|
||||
- run: go version
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: run test
|
||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
- name: upload coverage
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 - 2020 Weilin Shi
|
||||
Copyright (c) 2017 - 2024 Weilin Shi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
19
README.md
19
README.md
|
@ -2,19 +2,17 @@
|
|||
|
||||
<div>
|
||||
|
||||
[](https://pkg.go.dev/steambap/captcha)
|
||||
[](https://github.com/steambap/captcha/actions?workflow=CI)
|
||||
[](https://codecov.io/gh/steambap/captcha)
|
||||
[](https://goreportcard.com/report/github.com/steambap/captcha)
|
||||
[](https://pkg.go.dev/concord.hectabit.org/HectaBit/captcha)
|
||||
[](https://goreportcard.com/report/concord.hectabit.org/HectaBit/captcha)
|
||||
|
||||
</div>
|
||||
|
||||
## Why another captcha generator?
|
||||
I want a simple and framework-independent way to generate captcha. It also should be flexible, at least allow me to pick my favorite font.
|
||||
Because I can.
|
||||
|
||||
## install
|
||||
```
|
||||
go get github.com/steambap/captcha
|
||||
go get concord.hectabit.org/HectaBit/captcha
|
||||
```
|
||||
|
||||
## usage
|
||||
|
@ -32,14 +30,19 @@ func handle(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
```
|
||||
|
||||
[documentation](https://godoc.org/github.com/steambap/captcha) |
|
||||
[example](example/basic/main.go)
|
||||
[documentation](https://pkg.go.dev/concord.hectabit.org/HectaBit/captcha) |
|
||||
[example](example/basic/main.go) |
|
||||
[font example](example/load-font/main.go)
|
||||
|
||||
## sample image
|
||||

|
||||
|
||||

|
||||
|
||||
## Compatibility
|
||||
|
||||
This package uses embed package from Go 1.16. If for some reasons you have to use pre 1.16 version of Go, reference pre 1.4 version of this module in your go.mod.
|
||||
|
||||
## Contributing
|
||||
If your found a bug, please contribute!
|
||||
see [contributing.md](contributing.md) for more detail.
|
||||
|
|
53
captcha.go
53
captcha.go
|
@ -2,7 +2,7 @@
|
|||
package captcha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed" // embed font
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
@ -22,6 +22,8 @@ import (
|
|||
|
||||
const charPreset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
//go:embed fonts/Comismsh.ttf
|
||||
var ttf []byte
|
||||
var ttfFont *truetype.Font
|
||||
|
||||
// Options manage captcha generation details.
|
||||
|
@ -60,7 +62,7 @@ func newDefaultOption(width, height int) *Options {
|
|||
return &Options{
|
||||
BackgroundColor: color.Transparent,
|
||||
CharPreset: charPreset,
|
||||
TextLength: 4,
|
||||
TextLength: 6,
|
||||
CurveNumber: 2,
|
||||
FontDPI: 72.0,
|
||||
FontScale: 1.0,
|
||||
|
@ -116,12 +118,12 @@ func LoadFont(fontData []byte) error {
|
|||
|
||||
// LoadFontFromReader load an external font from an io.Reader interface.
|
||||
func LoadFontFromReader(reader io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
if _, err := io.Copy(&buf, reader); err != nil {
|
||||
b, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return LoadFont(buf.Bytes())
|
||||
return LoadFont(b)
|
||||
}
|
||||
|
||||
// New creates a new captcha.
|
||||
|
@ -134,11 +136,7 @@ func New(width int, height int, option ...SetOption) (*Data, error) {
|
|||
|
||||
text := randomText(options)
|
||||
img := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{options.BackgroundColor}, image.ZP, draw.Src)
|
||||
drawNoise(img, options)
|
||||
drawCurves(img, options)
|
||||
err := drawText(text, img, options)
|
||||
if err != nil {
|
||||
if err := drawWithOption(text, img, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -155,21 +153,42 @@ func NewMathExpr(width int, height int, option ...SetOption) (*Data, error) {
|
|||
|
||||
text, equation := randomEquation()
|
||||
img := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{options.BackgroundColor}, image.ZP, draw.Src)
|
||||
drawNoise(img, options)
|
||||
drawCurves(img, options)
|
||||
err := drawText(equation, img, options)
|
||||
if err != nil {
|
||||
if err := drawWithOption(equation, img, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Data{Text: text, img: img}, nil
|
||||
}
|
||||
|
||||
// NewCustomGenerator creates a new captcha based on a custom text generator.
|
||||
func NewCustomGenerator(
|
||||
width int, height int, generator func() (anwser string, question string), option ...SetOption,
|
||||
) (*Data, error) {
|
||||
options := newDefaultOption(width, height)
|
||||
for _, setOption := range option {
|
||||
setOption(options)
|
||||
}
|
||||
|
||||
answer, question := generator()
|
||||
img := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
if err := drawWithOption(question, img, options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Data{Text: answer, img: img}, nil
|
||||
}
|
||||
|
||||
func drawWithOption(text string, img *image.NRGBA, options *Options) error {
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{options.BackgroundColor}, image.Point{}, draw.Src)
|
||||
drawNoise(img, options)
|
||||
drawCurves(img, options)
|
||||
return drawText(text, img, options)
|
||||
}
|
||||
|
||||
func randomText(opts *Options) (text string) {
|
||||
n := len(opts.CharPreset)
|
||||
n := len([]rune(opts.CharPreset))
|
||||
for i := 0; i < opts.TextLength; i++ {
|
||||
text += string(opts.CharPreset[rand.Intn(n)])
|
||||
text += string([]rune(opts.CharPreset)[rand.Intn(n)])
|
||||
}
|
||||
|
||||
return text
|
||||
|
|
|
@ -69,6 +69,12 @@ func TestNewCaptchaOptions(t *testing.T) {
|
|||
NewMathExpr(100, 34, func(options *Options) {
|
||||
options.BackgroundColor = color.Black
|
||||
})
|
||||
|
||||
NewCustomGenerator(100, 34, func() (anwser string, question string) {
|
||||
return "4", "2x2?"
|
||||
}, func(o *Options) {
|
||||
o.BackgroundColor = color.Black
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewMathExpr(t *testing.T) {
|
||||
|
@ -78,6 +84,15 @@ func TestNewMathExpr(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewCustomGenerator(t *testing.T) {
|
||||
_, err := NewCustomGenerator(150, 50, func() (anwser string, question string) {
|
||||
return "1", "2"
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilFontError(t *testing.T) {
|
||||
temp := ttfFont
|
||||
ttfFont = nil
|
||||
|
@ -92,6 +107,13 @@ func TestNilFontError(t *testing.T) {
|
|||
t.Fatal("Expect to get nil font error")
|
||||
}
|
||||
|
||||
_, err = NewCustomGenerator(150, 50, func() (anwser string, question string) {
|
||||
return "1", "2"
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Expect to get nil font error")
|
||||
}
|
||||
|
||||
ttfFont = temp
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
[https://contributor-covenant.org/version/1/4/](https://contributor-covenant.org/version/1/4/)
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct/](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)
|
|
@ -1,7 +1,7 @@
|
|||
module github.com/steambap/captcha/example/basic
|
||||
module concord.hectabit.org/HectaBit/captcha/example/basic
|
||||
|
||||
go 1.12
|
||||
|
||||
replace github.com/steambap/captcha => ../../
|
||||
replace concord.hectabit.org/HectaBit/captcha => ../../
|
||||
|
||||
require github.com/steambap/captcha v0.0.0-00010101000000-000000000000
|
||||
require concord.hectabit.org/HectaBit/captcha v0.0.0-00010101000000-000000000000
|
||||
|
|
40
fonts/gen.go
40
fonts/gen.go
|
@ -1,40 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// This program generates a go file for Comismsh font
|
||||
|
||||
func main() {
|
||||
src, err := ioutil.ReadFile("Comismsh.ttf")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
fmt.Fprint(buf, "// DO NOT EDIT. This file is generated.\n\n")
|
||||
fmt.Fprint(buf, "package captcha\n\n")
|
||||
fmt.Fprint(buf, "// The following is Comismsh TrueType font data.\n")
|
||||
fmt.Fprint(buf, "var ttf = []byte{")
|
||||
for i, x := range src {
|
||||
if i&15 == 0 {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
fmt.Fprintf(buf, "%#02x,", x)
|
||||
}
|
||||
fmt.Fprint(buf, "\n}\n")
|
||||
|
||||
dst, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join("../", "font.go"), dst, 0666); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
13
go.mod
13
go.mod
|
@ -1,15 +1,8 @@
|
|||
module github.com/steambap/captcha
|
||||
module concord.hectabit.org/HectaBit/captcha
|
||||
|
||||
go 1.12
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f // indirect
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519
|
||||
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 // indirect
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20191118222007-07fc4c7f2b98 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
|
||||
golang.org/x/image v0.15.0
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -1,24 +1,4 @@
|
|||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec h1:arXJwtMuk5vqI1NHX0UTnNw977rYk5Sl4jQqHj+hun4=
|
||||
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191118222007-07fc4c7f2b98/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
|
|
Reference in a new issue