238 lines
4.6 KiB
Go
238 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/playwright-community/playwright-go"
|
|
)
|
|
|
|
type rawImageFile struct {
|
|
url string
|
|
blocks []map[string]int
|
|
width *int
|
|
height *int
|
|
}
|
|
|
|
type file struct {
|
|
contentType string
|
|
data []byte
|
|
}
|
|
|
|
type browser struct {
|
|
db *database
|
|
pw *playwright.Playwright
|
|
browser *playwright.Browser
|
|
}
|
|
|
|
func installBrowser() error {
|
|
opts := playwright.RunOptions{
|
|
Browsers: []string{"chromium"},
|
|
Verbose: true,
|
|
}
|
|
|
|
return playwright.Install(&opts)
|
|
}
|
|
|
|
type runOptions struct {
|
|
db *database
|
|
headless bool
|
|
}
|
|
|
|
func runBrowser(options runOptions) *browser {
|
|
pw, err := playwright.Run()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
launchOptions := playwright.BrowserTypeLaunchOptions{
|
|
Headless: playwright.Bool(options.headless),
|
|
}
|
|
|
|
b, err := pw.Chromium.Launch(launchOptions)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
browser := browser{
|
|
db: options.db,
|
|
pw: pw,
|
|
browser: &b,
|
|
}
|
|
|
|
return &browser
|
|
}
|
|
|
|
func (b browser) stop() error {
|
|
if b.browser != nil {
|
|
(*b.browser).Close()
|
|
}
|
|
|
|
return b.pw.Stop()
|
|
}
|
|
|
|
func (b browser) newContext() (playwright.BrowserContext, error) {
|
|
browser := *b.browser
|
|
|
|
return browser.NewContext()
|
|
}
|
|
|
|
func (b browser) loadBrowserContext(platform string) (playwright.BrowserContext, error) {
|
|
var secrets string
|
|
row := b.db.QueryRow("select secrets from platforms where name = $1", platform)
|
|
if err := row.Scan(&secrets); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var storageState playwright.OptionalStorageState
|
|
if err := json.Unmarshal([]byte(secrets), &storageState); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
browserNewContextOptions := playwright.BrowserNewContextOptions{
|
|
StorageState: &storageState,
|
|
}
|
|
|
|
ctx, err := (*b.browser).NewContext(browserNewContextOptions)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ctx, nil
|
|
}
|
|
|
|
func (b browser) saveBrowserContext(platform string, ctx playwright.BrowserContext) error {
|
|
storageState, err := ctx.StorageState()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
secrets, err := json.Marshal(storageState)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := b.db.Exec("update platforms set secrets = $1 where name = $2", secrets, platform); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseDataURL(dataURL string) (*file, error) {
|
|
if !strings.HasPrefix(dataURL, "data:") {
|
|
return nil, errors.New("not data scheme")
|
|
}
|
|
parts := strings.SplitN(dataURL, ",", 2)
|
|
if len(parts) != 2 {
|
|
return nil, errors.New("invalid data URL")
|
|
}
|
|
raw, err := url.PathUnescape(parts[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var data []byte
|
|
|
|
if strings.HasSuffix(parts[0], ";base64") {
|
|
data, err = base64.StdEncoding.DecodeString(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
data = []byte(raw)
|
|
}
|
|
|
|
file := file{
|
|
contentType: http.DetectContentType(data),
|
|
data: data,
|
|
}
|
|
|
|
return &file, nil
|
|
}
|
|
|
|
var draw = `
|
|
async function drawImage(imageFile) {
|
|
const canvas = Object.assign(document.createElement("canvas"), {
|
|
width: imageFile.width,
|
|
height: imageFile.height,
|
|
});
|
|
const image = (await new Promise((resolve) => {
|
|
Object.assign(new Image(), {
|
|
crossOrigin: "use-credentials",
|
|
src: imageFile.url,
|
|
onload() {
|
|
resolve(this);
|
|
},
|
|
});
|
|
}));
|
|
const ctx = canvas.getContext("2d");
|
|
for (const q of imageFile.blocks) {
|
|
ctx.drawImage(image, q.destX, q.destY, q.width, q.height, q.srcX, q.srcY, q.width, q.height);
|
|
}
|
|
const dataUrl = canvas.toDataURL();
|
|
return dataUrl;
|
|
}
|
|
`
|
|
|
|
var fetch = `
|
|
async function fetchImage(imageFile) {
|
|
const res = await fetch(imageFile.url);
|
|
const blob = await res.blob();
|
|
const dataUrl = await new Promise((resolve, reject) => {
|
|
const fileReader = Object.assign(new FileReader(), {
|
|
onload() {
|
|
resolve(this.result);
|
|
},
|
|
onerror(e) {
|
|
const error = new Error(` + "`${e.type}: ${e.message}`" + `);
|
|
reject(error);
|
|
},
|
|
});
|
|
fileReader.readAsDataURL(blob);
|
|
});
|
|
return dataUrl;
|
|
}
|
|
`
|
|
|
|
func (b browser) drawImage(page playwright.Page, imageFile rawImageFile) (*file, error) {
|
|
if len(imageFile.blocks) > 0 {
|
|
dataURL, err := page.Evaluate(draw, imageFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return parseDataURL(dataURL.(string))
|
|
}
|
|
|
|
if strings.HasPrefix(imageFile.url, "blob:") {
|
|
dataURL, err := page.Evaluate(fetch, imageFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return parseDataURL(dataURL.(string))
|
|
}
|
|
|
|
res, err := page.Context().Request().Get(imageFile.url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := res.Body()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
file := file{
|
|
contentType: http.DetectContentType(data),
|
|
data: data,
|
|
}
|
|
|
|
return &file, nil
|
|
}
|