diff --git a/cdp.go b/cdp.go
new file mode 100644
index 0000000..4e04a2f
--- /dev/null
+++ b/cdp.go
@@ -0,0 +1,263 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "image"
+ "image/gif"
+ "image/jpeg"
+ "image/png"
+ "io"
+ "log"
+ "math"
+ "math/rand"
+ "net/http"
+ "time"
+
+ "github.com/chromedp/cdproto/css"
+ "github.com/chromedp/cdproto/emulation"
+ "github.com/chromedp/cdproto/input"
+ "github.com/chromedp/cdproto/page"
+ "github.com/chromedp/chromedp"
+)
+
+func chromedpStart() (context.CancelFunc, context.CancelFunc) {
+ opts := append(chromedp.DefaultExecAllocatorOptions[:],
+ chromedp.Flag("headless", *headless),
+ chromedp.Flag("hide-scrollbars", false),
+ chromedp.Flag("enable-automation", false),
+ chromedp.Flag("disable-blink-features", "AutomationControlled"),
+ )
+ if *userAgent != "" {
+ opts = append(opts, chromedp.UserAgent(*userAgent))
+ }
+ actx, acncl = chromedp.NewExecAllocator(context.Background(), opts...)
+ ctx, cncl = chromedp.NewContext(actx)
+ return cncl, acncl
+}
+
+// Determine what action to take
+func (rq *wrpReq) action() chromedp.Action {
+ // Mouse Click
+ if rq.mouseX > 0 && rq.mouseY > 0 {
+ log.Printf("%s Mouse Click %d,%d\n", rq.r.RemoteAddr, rq.mouseX, rq.mouseY)
+ return chromedp.MouseClickXY(float64(rq.mouseX)/float64(rq.zoom), float64(rq.mouseY)/float64(rq.zoom))
+ }
+ // Buttons
+ if len(rq.buttons) > 0 {
+ log.Printf("%s Button %v\n", rq.r.RemoteAddr, rq.buttons)
+ switch rq.buttons {
+ case "Bk":
+ return chromedp.NavigateBack()
+ case "St":
+ return chromedp.Stop()
+ case "Re":
+ return chromedp.Reload()
+ case "Bs":
+ return chromedp.KeyEvent("\b")
+ case "Rt":
+ return chromedp.KeyEvent("\r")
+ case "<":
+ return chromedp.KeyEvent("\u0302")
+ case "^":
+ return chromedp.KeyEvent("\u0304")
+ case "v":
+ return chromedp.KeyEvent("\u0301")
+ case ">":
+ return chromedp.KeyEvent("\u0303")
+ case "Up":
+ return chromedp.KeyEvent("\u0308")
+ case "Dn":
+ return chromedp.KeyEvent("\u0307")
+ case "All": // Select all
+ return chromedp.KeyEvent("a", chromedp.KeyModifiers(input.ModifierCtrl))
+ }
+ }
+ // Keys
+ if len(rq.keys) > 0 {
+ log.Printf("%s Sending Keys: %#v\n", rq.r.RemoteAddr, rq.keys)
+ return chromedp.KeyEvent(rq.keys)
+ }
+ // Navigate to URL
+ log.Printf("%s Processing Navigate Request for %s\n", rq.r.RemoteAddr, rq.url)
+ return chromedp.Navigate(rq.url)
+}
+
+// Navigate to the desired URL.
+func (rq *wrpReq) navigate() {
+ ctxErr(chromedp.Run(ctx, rq.action()), rq.w)
+}
+
+// Handle context errors
+func ctxErr(err error, w io.Writer) {
+ // TODO: callers should have retry logic, perhaps create another function
+ // that takes ...chromedp.Action and retries with give up
+ if err == nil {
+ return
+ }
+ log.Printf("Context error: %s", err)
+ fmt.Fprintf(w, "Context error: %s
\n", err)
+ if err.Error() != "context canceled" {
+ return
+ }
+ ctx, cncl = chromedp.NewContext(actx)
+ log.Printf("Created new context, try again")
+ fmt.Fprintln(w, "Created new context, try again")
+}
+
+// https://github.com/chromedp/chromedp/issues/979
+func chromedpCaptureScreenshot(res *[]byte, h int64) chromedp.Action {
+ if res == nil {
+ panic("res cannot be nil")
+ }
+ if h == 0 {
+ return chromedp.CaptureScreenshot(res)
+ }
+
+ return chromedp.ActionFunc(func(ctx context.Context) error {
+ var err error
+ *res, err = page.CaptureScreenshot().Do(ctx)
+ return err
+ })
+}
+
+// Capture Screenshot using CDP
+func (rq *wrpReq) captureScreenshot() {
+ var styles []*css.ComputedStyleProperty
+ var r, g, b int
+ var bgColorSet bool
+ var h int64
+ var pngCap []byte
+ chromedp.Run(ctx,
+ emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), 10, rq.zoom, false),
+ chromedp.Location(&rq.url),
+ chromedp.ComputedStyle("body", &styles, chromedp.ByQuery),
+ chromedp.ActionFunc(func(ctx context.Context) error {
+ _, _, _, _, _, s, err := page.GetLayoutMetrics().Do(ctx)
+ if err == nil {
+ h = int64(math.Ceil(s.Height))
+ }
+ return nil
+ }),
+ )
+ log.Printf("%s Landed on: %s, Height: %v\n", rq.r.RemoteAddr, rq.url, h)
+ for _, style := range styles {
+ if style.Name != "background-color" {
+ continue
+ }
+ fmt.Sscanf(style.Value, "rgb(%d,%d,%d)", &r, &g, &b)
+ bgColorSet = true
+ break
+ }
+ if !bgColorSet {
+ r = 255
+ g = 255
+ b = 255
+ }
+ height := int64(float64(rq.height) / rq.zoom)
+ if rq.height == 0 && h > 0 {
+ height = h + 30
+ }
+ chromedp.Run(
+ ctx, emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), height, rq.zoom, false),
+ chromedp.Sleep(*delay), // TODO(tenox): find a better way to determine if page is rendered
+ )
+ // Capture screenshot...
+ ctxErr(chromedp.Run(ctx, chromedpCaptureScreenshot(&pngCap, rq.height)), rq.w)
+ seq := rand.Intn(9999)
+ imgPath := fmt.Sprintf("/img/%04d.%s", seq, rq.imgType)
+ mapPath := fmt.Sprintf("/map/%04d.map", seq)
+ ismap[mapPath] = *rq
+ var sSize string
+ var iW, iH int
+ switch rq.imgType {
+ case "png":
+ pngBuf := bytes.NewBuffer(pngCap)
+ img[imgPath] = *pngBuf
+ cfg, _, _ := image.DecodeConfig(pngBuf)
+ sSize = fmt.Sprintf("%.0f KB", float32(len(pngBuf.Bytes()))/1024.0)
+ iW = cfg.Width
+ iH = cfg.Height
+ log.Printf("%s Got PNG image: %s, Size: %s, Res: %dx%d\n", rq.r.RemoteAddr, imgPath, sSize, iW, iH)
+ case "gif":
+ i, err := png.Decode(bytes.NewReader(pngCap))
+ if err != nil {
+ log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
+ fmt.Fprintf(rq.w, "
Unable to decode page PNG screenshot:
%s
\n", err)
+ return
+ }
+ st := time.Now()
+ var gifBuf bytes.Buffer
+ err = gif.Encode(&gifBuf, gifPalette(i, rq.imgOpt), &gif.Options{})
+ if err != nil {
+ log.Printf("%s Failed to encode GIF: %s\n", rq.r.RemoteAddr, err)
+ fmt.Fprintf(rq.w, "
Unable to encode GIF:
%s
\n", err)
+ return
+ }
+ img[imgPath] = gifBuf
+ sSize = fmt.Sprintf("%.0f KB", float32(len(gifBuf.Bytes()))/1024.0)
+ iW = i.Bounds().Max.X
+ iH = i.Bounds().Max.Y
+ log.Printf("%s Encoded GIF image: %s, Size: %s, Colors: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgPath, sSize, rq.imgOpt, iW, iH, time.Since(st).Milliseconds())
+ case "jpg":
+ i, err := png.Decode(bytes.NewReader(pngCap))
+ if err != nil {
+ log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
+ fmt.Fprintf(rq.w, "
Unable to decode page PNG screenshot:
%s
\n", err)
+ return
+ }
+ st := time.Now()
+ var jpgBuf bytes.Buffer
+ err = jpeg.Encode(&jpgBuf, i, &jpeg.Options{Quality: *jpgQual})
+ if err != nil {
+ log.Printf("%s Failed to encode JPG: %s\n", rq.r.RemoteAddr, err)
+ fmt.Fprintf(rq.w, "
Unable to encode JPG:
%s
\n", err)
+ return
+ }
+ img[imgPath] = jpgBuf
+ sSize = fmt.Sprintf("%.0f KB", float32(len(jpgBuf.Bytes()))/1024.0)
+ iW = i.Bounds().Max.X
+ iH = i.Bounds().Max.Y
+ log.Printf("%s Encoded JPG image: %s, Size: %s, Quality: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgPath, sSize, *jpgQual, iW, iH, time.Since(st).Milliseconds())
+ }
+ rq.printHTML(printParams{
+ bgColor: fmt.Sprintf("#%02X%02X%02X", r, g, b),
+ pageHeight: fmt.Sprintf("%d PX", h),
+ imgSize: sSize,
+ imgURL: imgPath,
+ mapURL: mapPath,
+ imgWidth: iW,
+ imgHeight: iH,
+ })
+ log.Printf("%s Done with capture for %s\n", rq.r.RemoteAddr, rq.url)
+}
+
+// Process HTTP requests to ISMAP '/map/' url
+func mapServer(w http.ResponseWriter, r *http.Request) {
+ log.Printf("%s ISMAP Request for %s [%+v]\n", r.RemoteAddr, r.URL.Path, r.URL.RawQuery)
+ rq, ok := ismap[r.URL.Path]
+ rq.r = r
+ rq.w = w
+ if !ok {
+ fmt.Fprintf(w, "Unable to find map %s\n", r.URL.Path)
+ log.Printf("Unable to find map %s\n", r.URL.Path)
+ return
+ }
+ if !*noDel {
+ defer delete(ismap, r.URL.Path)
+ }
+ n, err := fmt.Sscanf(r.URL.RawQuery, "%d,%d", &rq.mouseX, &rq.mouseY)
+ if err != nil || n != 2 {
+ fmt.Fprintf(w, "n=%d, err=%s\n", n, err)
+ log.Printf("%s ISMAP n=%d, err=%s\n", r.RemoteAddr, n, err)
+ return
+ }
+ log.Printf("%s WrpReq from ISMAP: %+v\n", r.RemoteAddr, rq)
+ if len(rq.url) < 4 {
+ rq.printHTML(printParams{bgColor: "#FFFFFF"})
+ return
+ }
+ rq.navigate() // TODO: if error from navigate do not capture
+ rq.captureScreenshot()
+}
diff --git a/txt.go b/txt.go
index 9821e69..f1791f8 100644
--- a/txt.go
+++ b/txt.go
@@ -87,7 +87,7 @@ func (i *imageStore) del(id string) {
delete(i.img, id)
}
-func fetchImage(id, url string) error {
+func fetchImage(id, url, imgType string, maxSize, imgOpt int) error {
log.Printf("Downloading IMGZ URL=%q for ID=%q", url, id)
var img []byte
var err error
@@ -115,7 +115,7 @@ func fetchImage(id, url string) error {
return fmt.Errorf("error decoding image from url embed: %q: %v", url, err)
}
}
- gif, err := smallGif(img)
+ gif, err := smallImg(img, imgType, maxSize, imgOpt)
if err != nil {
return fmt.Errorf("Error scaling down image: %v", err)
}
@@ -123,7 +123,46 @@ func fetchImage(id, url string) error {
return nil
}
-type astTransformer struct{}
+func smallImg(src []byte, imgType string, maxSize, imgOpt int) ([]byte, error) {
+ t := http.DetectContentType(src)
+ var err error
+ var img image.Image
+ switch t {
+ case "image/png":
+ img, err = png.Decode(bytes.NewReader(src))
+ case "image/gif":
+ img, err = gif.Decode(bytes.NewReader(src))
+ case "image/jpeg":
+ img, err = jpeg.Decode(bytes.NewReader(src))
+ case "image/webp":
+ img, err = webp.Decode(bytes.NewReader(src))
+ default: // TODO: also add svg
+ err = errors.New("unknown content type: " + t)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("image decode problem: %v", err)
+ }
+ img = resize.Thumbnail(uint(*defImgSize), uint(*defImgSize), img, resize.NearestNeighbor)
+ var outBuf bytes.Buffer
+ switch imgType {
+ case "png":
+ err = png.Encode(&outBuf, img)
+ case "gif":
+ err = gif.Encode(&outBuf, gifPalette(img, int64(imgOpt)), &gif.Options{})
+ case "jpg":
+ err = jpeg.Encode(&outBuf, img, &jpeg.Options{Quality: imgOpt})
+ }
+ if err != nil {
+ return nil, fmt.Errorf("gif encode problem: %v", err)
+ }
+ return outBuf.Bytes(), nil
+}
+
+type astTransformer struct {
+ imgType string
+ maxSize int
+ imgOpt int
+}
func (t *astTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -132,8 +171,8 @@ func (t *astTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
if img, ok := n.(*ast.Image); ok && entering {
// TODO: dynamic extension based on form value
- id := fmt.Sprintf("txt%05d.gif", rand.Intn(99999)) // BUG: atomic.AddInt64 or something that ever increases - time based?
- err := fetchImage(id, string(img.Destination)) // TODO: use goroutines with waitgroup
+ id := fmt.Sprintf("txt%05d.gif", rand.Intn(99999)) // BUG: atomic.AddInt64 or something that ever increases - time based?
+ err := fetchImage(id, string(img.Destination), t.imgType, t.maxSize, t.imgOpt) // TODO: use goroutines with waitgroup
if err != nil {
log.Print(err)
n.Parent().RemoveChildren(n)
@@ -145,6 +184,18 @@ func (t *astTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
})
}
+func asciify(s []byte) []byte {
+ a := make([]byte, len(s))
+ for i := 0; i < len(s); i++ {
+ if s[i] > 127 {
+ a[i] = '.'
+ continue
+ }
+ a[i] = s[i]
+ }
+ return a
+}
+
func (rq *wrpReq) captureMarkdown() {
log.Printf("Processing Markdown conversion request for %v", rq.url)
// TODO: bug - DomainFromURL always prefixes with http:// instead of https
@@ -157,7 +208,7 @@ func (rq *wrpReq) captureMarkdown() {
return
}
log.Printf("Got %v bytes md from %v", len(md), rq.url)
- t := &astTransformer{}
+ t := &astTransformer{imgType: rq.imgType, maxSize: int(rq.maxSize), imgOpt: int(rq.imgOpt)} // TODO: maxSize still doesn't work
gm := goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithParserOptions(parser.WithASTTransformers(util.Prioritized(t, 100))),
@@ -190,32 +241,3 @@ func imgServerZ(w http.ResponseWriter, r *http.Request) {
w.Write(img)
w.(http.Flusher).Flush()
}
-
-// TODO set JPG/GIF/PNG type based on form...
-func smallGif(src []byte) ([]byte, error) {
- t := http.DetectContentType(src)
- var err error
- var img image.Image
- switch t {
- case "image/png":
- img, err = png.Decode(bytes.NewReader(src))
- case "image/gif":
- img, err = gif.Decode(bytes.NewReader(src))
- case "image/jpeg":
- img, err = jpeg.Decode(bytes.NewReader(src))
- case "image/webp":
- img, err = webp.Decode(bytes.NewReader(src))
- default: // TODO: also add svg
- err = errors.New("unknown content type: " + t)
- }
- if err != nil {
- return nil, fmt.Errorf("image decode problem: %v", err)
- }
- img = resize.Thumbnail(uint(*txtImgSize), uint(*txtImgSize), img, resize.NearestNeighbor)
- var gifBuf bytes.Buffer
- err = gif.Encode(&gifBuf, gifPalette(img, 216), &gif.Options{})
- if err != nil {
- return nil, fmt.Errorf("gif encode problem: %v", err)
- }
- return gifBuf.Bytes(), nil
-}
diff --git a/wrp.go b/wrp.go
index 49850d9..c8fcc23 100644
--- a/wrp.go
+++ b/wrp.go
@@ -15,14 +15,8 @@ import (
"fmt"
"image"
"image/color/palette"
- "image/gif"
- "image/jpeg"
- "image/png"
"io"
- "io/ioutil"
"log"
- "math"
- "math/rand"
"net"
"net/http"
"net/url"
@@ -36,27 +30,26 @@ import (
"time"
"github.com/MaxHalford/halfgone"
- "github.com/chromedp/cdproto/css"
- "github.com/chromedp/cdproto/emulation"
- "github.com/chromedp/cdproto/input"
- "github.com/chromedp/cdproto/page"
- "github.com/chromedp/chromedp"
"github.com/soniakeys/quant/median"
)
-const version = "4.7.0"
+const version = "4.8.0"
+
+var (
+ addr = flag.String("l", ":8080", "Listen address:port, default :8080")
+ headless = flag.Bool("h", true, "Headless mode / hide browser window (default true)")
+ noDel = flag.Bool("n", false, "Do not free maps and images after use")
+ defType = flag.String("t", "gif", "Image type: png|gif|jpg")
+ wrpMode = flag.String("m", "ismap", "WRP Mode: ismap|html")
+ defImgSize = flag.Int64("is", 200, "html mode default image size")
+ jpgQual = flag.Int("q", 75, "Jpeg image quality, default 75%") // TODO: this should be form dropdown when jpeg is selected as image type
+ fgeom = flag.String("g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
+ htmFnam = flag.String("ui", "wrp.html", "HTML template file for the UI")
+ delay = flag.Duration("s", 2*time.Second, "Delay/sleep after page is rendered and before screenshot is taken")
+ userAgent = flag.String("ua", "", "override chrome user agent")
+)
var (
- addr = flag.String("l", ":8080", "Listen address:port, default :8080")
- headless = flag.Bool("h", true, "Headless mode / hide browser window (default true)")
- noDel = flag.Bool("n", false, "Do not free maps and images after use")
- defType = flag.String("t", "gif", "Image type: png|gif|jpg|txt")
- txtImgSize = flag.Int("ts", 200, "txt mode image size") // make it default, this should come from the from
- jpgQual = flag.Int("q", 80, "Jpeg image quality, default 80%")
- fgeom = flag.String("g", "1152x600x216", "Geometry: width x height x colors, height can be 0 for unlimited")
- htmFnam = flag.String("ui", "wrp.html", "HTML template file for the UI")
- delay = flag.Duration("s", 2*time.Second, "Delay/sleep after page is rendered and before screenshot is taken")
- userAgent = flag.String("ua", "", "override chrome user agent")
srv http.Server
actx, ctx context.Context
acncl, cncl context.CancelFunc
@@ -78,6 +71,7 @@ type geom struct {
// Data for html template
type uiData struct {
Version string
+ WrpMode string
URL string
BgColor string
NColors int64
@@ -89,6 +83,7 @@ type uiData struct {
ImgSize string
ImgWidth int
ImgHeight int
+ MaxSize int64
MapURL string
PageHeight string
TeXT string
@@ -118,6 +113,9 @@ type wrpReq struct {
keys string // keys to send
buttons string // Fn buttons
imgType string // imgtype
+ wrpMode string // mode ismap/html
+ maxSize int64 // image max size for html mode
+ imgOpt int64
w http.ResponseWriter
r *http.Request
}
@@ -125,10 +123,15 @@ type wrpReq struct {
// Parse HTML Form, Process Input Boxes, Etc.
func (rq *wrpReq) parseForm() {
rq.r.ParseForm()
+ rq.wrpMode = rq.r.FormValue("m")
+ if rq.wrpMode == "" {
+ rq.wrpMode = *wrpMode
+ }
rq.url = rq.r.FormValue("url")
if len(rq.url) > 1 && !strings.HasPrefix(rq.url, "http") {
rq.url = fmt.Sprintf("http://www.google.com/search?q=%s", url.QueryEscape(rq.url))
}
+ // TODO: implement atoiOrZero
rq.width, _ = strconv.ParseInt(rq.r.FormValue("w"), 10, 64)
rq.height, _ = strconv.ParseInt(rq.r.FormValue("h"), 10, 64)
if rq.width < 10 && rq.height < 10 {
@@ -139,20 +142,26 @@ func (rq *wrpReq) parseForm() {
if rq.zoom < 0.1 {
rq.zoom = 1.0
}
- rq.colors, _ = strconv.ParseInt(rq.r.FormValue("c"), 10, 64)
- if rq.colors < 2 || rq.colors > 256 {
+ rq.colors, _ = strconv.ParseInt(rq.r.FormValue("c"), 10, 64) // TODO: this needs to be jpeg quality as well
+ if rq.colors < 2 || rq.colors > 256 { // ... but maybe not because of this?
rq.colors = defGeom.c
}
rq.keys = rq.r.FormValue("k")
rq.buttons = rq.r.FormValue("Fn")
+ rq.maxSize, _ = strconv.ParseInt(rq.r.FormValue("s"), 10, 64)
+ if rq.maxSize == 0 {
+ rq.maxSize = *defImgSize
+ }
rq.imgType = rq.r.FormValue("t")
switch rq.imgType {
case "png":
case "gif":
+ rq.imgOpt = defGeom.c
case "jpg":
- case "txt":
+ rq.imgOpt = int64(*jpgQual)
default:
rq.imgType = *defType
+ rq.imgOpt = 80 // TODO: fixme, this needs to be different based on image type
}
log.Printf("%s WrpReq from UI Form: %+v\n", rq.r.RemoteAddr, rq)
}
@@ -168,12 +177,14 @@ func (rq *wrpReq) printHTML(p printParams) {
}
data := uiData{
Version: version,
+ WrpMode: rq.wrpMode,
URL: rq.url,
BgColor: p.bgColor,
Width: rq.width,
Height: rq.height,
- NColors: rq.colors,
+ NColors: rq.colors, // TODO: this needs to be also jpeg quality
Zoom: rq.zoom,
+ MaxSize: rq.maxSize,
ImgType: rq.imgType,
ImgSize: p.imgSize,
ImgWidth: p.imgWidth,
@@ -189,91 +200,6 @@ func (rq *wrpReq) printHTML(p printParams) {
}
}
-// Determine what action to take
-func (rq *wrpReq) action() chromedp.Action {
- // Mouse Click
- if rq.mouseX > 0 && rq.mouseY > 0 {
- log.Printf("%s Mouse Click %d,%d\n", rq.r.RemoteAddr, rq.mouseX, rq.mouseY)
- return chromedp.MouseClickXY(float64(rq.mouseX)/float64(rq.zoom), float64(rq.mouseY)/float64(rq.zoom))
- }
- // Buttons
- if len(rq.buttons) > 0 {
- log.Printf("%s Button %v\n", rq.r.RemoteAddr, rq.buttons)
- switch rq.buttons {
- case "Bk":
- return chromedp.NavigateBack()
- case "St":
- return chromedp.Stop()
- case "Re":
- return chromedp.Reload()
- case "Bs":
- return chromedp.KeyEvent("\b")
- case "Rt":
- return chromedp.KeyEvent("\r")
- case "<":
- return chromedp.KeyEvent("\u0302")
- case "^":
- return chromedp.KeyEvent("\u0304")
- case "v":
- return chromedp.KeyEvent("\u0301")
- case ">":
- return chromedp.KeyEvent("\u0303")
- case "Up":
- return chromedp.KeyEvent("\u0308")
- case "Dn":
- return chromedp.KeyEvent("\u0307")
- case "All": // Select all
- return chromedp.KeyEvent("a", chromedp.KeyModifiers(input.ModifierCtrl))
- }
- }
- // Keys
- if len(rq.keys) > 0 {
- log.Printf("%s Sending Keys: %#v\n", rq.r.RemoteAddr, rq.keys)
- return chromedp.KeyEvent(rq.keys)
- }
- // Navigate to URL
- log.Printf("%s Processing Navigate Request for %s\n", rq.r.RemoteAddr, rq.url)
- return chromedp.Navigate(rq.url)
-}
-
-// Navigate to the desired URL.
-func (rq *wrpReq) navigate() {
- ctxErr(chromedp.Run(ctx, rq.action()), rq.w)
-}
-
-// Handle context errors
-func ctxErr(err error, w io.Writer) {
- // TODO: callers should have retry logic, perhaps create another function
- // that takes ...chromedp.Action and retries with give up
- if err == nil {
- return
- }
- log.Printf("Context error: %s", err)
- fmt.Fprintf(w, "Context error: %s
\n", err)
- if err.Error() != "context canceled" {
- return
- }
- ctx, cncl = chromedp.NewContext(actx)
- log.Printf("Created new context, try again")
- fmt.Fprintln(w, "Created new context, try again")
-}
-
-// https://github.com/chromedp/chromedp/issues/979
-func chromedpCaptureScreenshot(res *[]byte, h int64) chromedp.Action {
- if res == nil {
- panic("res cannot be nil")
- }
- if h == 0 {
- return chromedp.CaptureScreenshot(res)
- }
-
- return chromedp.ActionFunc(func(ctx context.Context) error {
- var err error
- *res, err = page.CaptureScreenshot().Do(ctx)
- return err
- })
-}
-
func gifPalette(i image.Image, n int64) image.Image {
switch n {
case 2:
@@ -313,128 +239,6 @@ func gifPalette(i image.Image, n int64) image.Image {
return i
}
-func (rq *wrpReq) captureImage() {
- var styles []*css.ComputedStyleProperty
- var r, g, b int
- var bgColorSet bool
- var h int64
- var pngCap []byte
- chromedp.Run(ctx,
- emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), 10, rq.zoom, false),
- chromedp.Location(&rq.url),
- chromedp.ComputedStyle("body", &styles, chromedp.ByQuery),
- chromedp.ActionFunc(func(ctx context.Context) error {
- _, _, _, _, _, s, err := page.GetLayoutMetrics().Do(ctx)
- if err == nil {
- h = int64(math.Ceil(s.Height))
- }
- return nil
- }),
- )
- log.Printf("%s Landed on: %s, Height: %v\n", rq.r.RemoteAddr, rq.url, h)
- for _, style := range styles {
- if style.Name != "background-color" {
- continue
- }
- fmt.Sscanf(style.Value, "rgb(%d,%d,%d)", &r, &g, &b)
- bgColorSet = true
- break
- }
- if !bgColorSet {
- r = 255
- g = 255
- b = 255
- }
- height := int64(float64(rq.height) / rq.zoom)
- if rq.height == 0 && h > 0 {
- height = h + 30
- }
- chromedp.Run(
- ctx, emulation.SetDeviceMetricsOverride(int64(float64(rq.width)/rq.zoom), height, rq.zoom, false),
- chromedp.Sleep(*delay), // TODO(tenox): find a better way to determine if page is rendered
- )
- // Capture screenshot...
- ctxErr(chromedp.Run(ctx, chromedpCaptureScreenshot(&pngCap, rq.height)), rq.w)
- seq := rand.Intn(9999)
- imgPath := fmt.Sprintf("/img/%04d.%s", seq, rq.imgType)
- mapPath := fmt.Sprintf("/map/%04d.map", seq)
- ismap[mapPath] = *rq
- var sSize string
- var iW, iH int
- switch rq.imgType {
- case "png":
- pngBuf := bytes.NewBuffer(pngCap)
- img[imgPath] = *pngBuf
- cfg, _, _ := image.DecodeConfig(pngBuf)
- sSize = fmt.Sprintf("%.0f KB", float32(len(pngBuf.Bytes()))/1024.0)
- iW = cfg.Width
- iH = cfg.Height
- log.Printf("%s Got PNG image: %s, Size: %s, Res: %dx%d\n", rq.r.RemoteAddr, imgPath, sSize, iW, iH)
- case "gif":
- i, err := png.Decode(bytes.NewReader(pngCap))
- if err != nil {
- log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
- fmt.Fprintf(rq.w, "
Unable to decode page PNG screenshot:
%s
\n", err)
- return
- }
- st := time.Now()
- var gifBuf bytes.Buffer
- err = gif.Encode(&gifBuf, gifPalette(i, rq.colors), &gif.Options{})
- if err != nil {
- log.Printf("%s Failed to encode GIF: %s\n", rq.r.RemoteAddr, err)
- fmt.Fprintf(rq.w, "
Unable to encode GIF:
%s
\n", err)
- return
- }
- img[imgPath] = gifBuf
- sSize = fmt.Sprintf("%.0f KB", float32(len(gifBuf.Bytes()))/1024.0)
- iW = i.Bounds().Max.X
- iH = i.Bounds().Max.Y
- log.Printf("%s Encoded GIF image: %s, Size: %s, Colors: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgPath, sSize, rq.colors, iW, iH, time.Since(st).Milliseconds())
- case "jpg":
- i, err := png.Decode(bytes.NewReader(pngCap))
- if err != nil {
- log.Printf("%s Failed to decode PNG screenshot: %s\n", rq.r.RemoteAddr, err)
- fmt.Fprintf(rq.w, "
Unable to decode page PNG screenshot:
%s
\n", err)
- return
- }
- st := time.Now()
- var jpgBuf bytes.Buffer
- err = jpeg.Encode(&jpgBuf, i, &jpeg.Options{Quality: *jpgQual})
- if err != nil {
- log.Printf("%s Failed to encode JPG: %s\n", rq.r.RemoteAddr, err)
- fmt.Fprintf(rq.w, "
Unable to encode JPG:
%s
\n", err)
- return
- }
- img[imgPath] = jpgBuf
- sSize = fmt.Sprintf("%.0f KB", float32(len(jpgBuf.Bytes()))/1024.0)
- iW = i.Bounds().Max.X
- iH = i.Bounds().Max.Y
- log.Printf("%s Encoded JPG image: %s, Size: %s, Quality: %d, Res: %dx%d, Time: %vms\n", rq.r.RemoteAddr, imgPath, sSize, *jpgQual, iW, iH, time.Since(st).Milliseconds())
- }
- rq.printHTML(printParams{
- bgColor: fmt.Sprintf("#%02X%02X%02X", r, g, b),
- pageHeight: fmt.Sprintf("%d PX", h),
- imgSize: sSize,
- imgURL: imgPath,
- mapURL: mapPath,
- imgWidth: iW,
- imgHeight: iH,
- })
- log.Printf("%s Done with capture for %s\n", rq.r.RemoteAddr, rq.url)
-}
-
-func asciify(s []byte) []byte {
- a := make([]byte, len(s))
- for i := 0; i < len(s); i++ {
- if s[i] > 127 {
- a[i] = '.'
- continue
- }
- a[i] = s[i]
- }
- return a
-}
-
// Process HTTP requests to WRP '/' url
func pageServer(w http.ResponseWriter, r *http.Request) {
log.Printf("%s Page Request for %s [%+v]\n", r.RemoteAddr, r.URL.Path, r.URL.RawQuery)
@@ -448,43 +252,15 @@ func pageServer(w http.ResponseWriter, r *http.Request) {
return
}
rq.navigate() // TODO: if error from navigate do not capture
- if rq.imgType == "txt" {
+ if rq.wrpMode == "html" {
rq.captureMarkdown()
return
}
- rq.captureImage()
-}
-
-// Process HTTP requests to ISMAP '/map/' url
-func mapServer(w http.ResponseWriter, r *http.Request) {
- log.Printf("%s ISMAP Request for %s [%+v]\n", r.RemoteAddr, r.URL.Path, r.URL.RawQuery)
- rq, ok := ismap[r.URL.Path]
- rq.r = r
- rq.w = w
- if !ok {
- fmt.Fprintf(w, "Unable to find map %s\n", r.URL.Path)
- log.Printf("Unable to find map %s\n", r.URL.Path)
- return
- }
- if !*noDel {
- defer delete(ismap, r.URL.Path)
- }
- n, err := fmt.Sscanf(r.URL.RawQuery, "%d,%d", &rq.mouseX, &rq.mouseY)
- if err != nil || n != 2 {
- fmt.Fprintf(w, "n=%d, err=%s\n", n, err)
- log.Printf("%s ISMAP n=%d, err=%s\n", r.RemoteAddr, n, err)
- return
- }
- log.Printf("%s WrpReq from ISMAP: %+v\n", r.RemoteAddr, rq)
- if len(rq.url) < 4 {
- rq.printHTML(printParams{bgColor: "#FFFFFF"})
- return
- }
- rq.navigate() // TODO: if error from navigate do not capture
- rq.captureImage()
+ rq.captureScreenshot()
}
// Process HTTP requests for images '/img/' url
+// TODO: merge this with html mode IMGZ
func imgServer(w http.ResponseWriter, r *http.Request) {
log.Printf("%s IMG Request for %s\n", r.RemoteAddr, r.URL.Path)
imgBuf, ok := img[r.URL.Path]
@@ -515,9 +291,6 @@ func imgServer(w http.ResponseWriter, r *http.Request) {
// Process HTTP requests for Shutdown via '/shutdown/' url
func haltServer(w http.ResponseWriter, r *http.Request) {
log.Printf("%s Shutdown Request for %s\n", r.RemoteAddr, r.URL.Path)
- w.Header().Set("Cache-Control", "max-age=0")
- w.Header().Set("Expires", "-1")
- w.Header().Set("Pragma", "no-cache")
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Shutting down WRP...\n")
w.(http.Flusher).Flush()
@@ -537,7 +310,7 @@ func tmpl(t string) string {
}
defer fh.Close()
- tmpl, err = ioutil.ReadAll(fh)
+ tmpl, err = io.ReadAll(fh)
if err != nil {
goto builtin
}
@@ -551,7 +324,7 @@ builtin:
}
defer fhs.Close()
- tmpl, err = ioutil.ReadAll(fhs)
+ tmpl, err = io.ReadAll(fhs)
if err != nil {
log.Fatal(err)
}
@@ -601,19 +374,9 @@ func main() {
log.Fatalf("Unable to parse -g geometry flag / %s", err)
}
- opts := append(chromedp.DefaultExecAllocatorOptions[:],
- chromedp.Flag("headless", *headless),
- chromedp.Flag("hide-scrollbars", false),
- chromedp.Flag("enable-automation", false),
- chromedp.Flag("disable-blink-features", "AutomationControlled"),
- )
- if *userAgent != "" {
- opts = append(opts, chromedp.UserAgent(*userAgent))
- }
- actx, acncl = chromedp.NewExecAllocator(context.Background(), opts...)
- defer acncl()
- ctx, cncl = chromedp.NewContext(actx)
+ cncl, acncl = chromedpStart()
defer cncl()
+ defer acncl()
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
diff --git a/wrp.html b/wrp.html
index c93c6c4..af34cc1 100644
--- a/wrp.html
+++ b/wrp.html
@@ -6,7 +6,7 @@