Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
fit folders into a parent folder for organization.
  • Loading branch information
suffz authored Jun 6, 2023
1 parent c490056 commit 3c6e217
Show file tree
Hide file tree
Showing 25 changed files with 4,078 additions and 0 deletions.
162 changes: 162 additions & 0 deletions packages/Chrome/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package chromedpundetected

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"time"

"github.com/Davincible/chromedp-undetected/util/easyjson"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/emulation"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/chromedp"
)

// Cookie is used to set browser cookies.
type Cookie struct {
Name string `json:"name" yaml:"name"`
Value string `json:"value" yaml:"value"`
Domain string `json:"domain" yaml:"domain"`
Path string `json:"path" yaml:"path"`
Expires float64 `json:"expires" yaml:"expires"`
HTTPOnly bool `json:"httpOnly" yaml:"httpOnly"`
Secure bool `json:"secure" yaml:"secure"`
}

// UserAgentOverride overwrites the Chrome user agent.
//
// It's better to use this method than emulation.UserAgentOverride.
func UserAgentOverride(userAgent string) chromedp.ActionFunc {
return func(ctx context.Context) error {
return cdp.Execute(ctx, "Network.setUserAgentOverride",
emulation.SetUserAgentOverride(userAgent), nil)
}
}

// LoadCookiesFromFile takes a file path to a json file containing cookies, and
// loads in the cookies into the browser.
func LoadCookiesFromFile(path string) chromedp.ActionFunc {
return chromedp.ActionFunc(func(ctx context.Context) error {
f, err := os.Open(path) //nolint:gosec
if err != nil {
return fmt.Errorf("failed to open file '%s': %w", path, err)
}

data, err := io.ReadAll(f)
if err != nil {
return err
}

if err := f.Close(); err != nil {
return err
}

var cookies []Cookie
if err := json.Unmarshal(data, &cookies); err != nil {
return fmt.Errorf("unmarshal cookies from json: %w", err)
}

return LoadCookies(cookies)(ctx)
})
}

// LoadCookies will load a set of cookies into the browser.
func LoadCookies(cookies []Cookie) chromedp.ActionFunc {
return chromedp.ActionFunc(func(ctx context.Context) error {
for _, cookie := range cookies {
expiry := cdp.TimeSinceEpoch(time.Unix(int64(cookie.Expires), 0))
if err := network.SetCookie(cookie.Name, cookie.Value).
WithHTTPOnly(cookie.HTTPOnly).
WithSecure(cookie.Secure).
WithDomain(cookie.Domain).
WithPath(cookie.Path).
WithExpires(&expiry).
Do(ctx); err != nil {
return err
}
}
return nil
})
}

// SaveCookies extracts the cookies from the current URL and appends them to
// provided array.
func SaveCookies(cookies *[]Cookie) chromedp.ActionFunc {
return chromedp.ActionFunc(func(ctx context.Context) error {
c, err := network.GetCookies().Do(ctx)
if err != nil {
return err
}

for _, cookie := range c {
*cookies = append(*cookies, Cookie{
Name: cookie.Name,
Value: cookie.Value,
Domain: cookie.Domain,
Path: cookie.Path,
Expires: cookie.Expires,
HTTPOnly: cookie.HTTPOnly,
Secure: cookie.HTTPOnly,
})
}

return nil
})
}

// SaveCookiesTo extracts the cookies from the current page and saves them
// as JSON to the provided path.
func SaveCookiesTo(path string) chromedp.ActionFunc {
return chromedp.ActionFunc(func(ctx context.Context) error {
var c []Cookie

if err := SaveCookies(&c).Do(ctx); err != nil {
return err
}

b, err := json.Marshal(c)
if err != nil {
return err
}

if err := os.WriteFile(path, b, 0644); err != nil { //nolint:gosec
return err
}

return nil
})
}

// RunCommandWithRes runs any Chrome Dev Tools command, with any params and
// sets the result to the res parameter. Make sure it is a pointer.
//
// In contrast to the native method of chromedp, with this method you can directly
// pass in a map with the data passed to the command.
func RunCommandWithRes(method string, params, res any) chromedp.ActionFunc {
return chromedp.ActionFunc(func(ctx context.Context) error {
i := easyjson.New(params)
o := easyjson.New(res)

return cdp.Execute(ctx, method, i, o)
})
}

// RunCommand runs any Chrome Dev Tools command, with any params.
//
// In contrast to the native method of chromedp, with this method you can directly
// pass in a map with the data passed to the command.
func RunCommand(method string, params any) chromedp.ActionFunc {
return chromedp.ActionFunc(func(ctx context.Context) error {
i := easyjson.New(params)

return cdp.Execute(ctx, method, i, nil)
})
}

// BlockURLs blocks a set of URLs in Chrome.
func BlockURLs(url ...string) chromedp.ActionFunc {
return RunCommand("Network.setBlockedURLs", map[string][]string{"urls": url})
}
177 changes: 177 additions & 0 deletions packages/Chrome/chrome_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//go:build unix

// Package chromedpundetected provides a chromedp context with an undetected
// Chrome browser.
package chromedpundetected

import (
"context"
"net"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"

"github.com/Xuanwo/go-locale"
"github.com/chromedp/chromedp"
"github.com/google/uuid"
"golang.org/x/exp/slog"
)

var (
// DefaultUserDirPrefix Defaults.
DefaultUserDirPrefix = "chromedp-undetected-"
)

// New creates a context with an undetected Chrome executor.
func New(config Config) (context.Context, context.CancelFunc, error) {
var opts []chromedp.ExecAllocatorOption

userDataDir := path.Join(os.TempDir(), DefaultUserDirPrefix+uuid.NewString())
if len(config.ChromePath) > 0 {
userDataDir = config.ChromePath
}

headlessOpts, closeFrameBuffer, err := headlessFlag(config)
if err != nil {
return nil, func() {}, err
}

opts = append(opts, localeFlag())
opts = append(opts, supressWelcomeFlag()...)
opts = append(opts, logLevelFlag(config))
opts = append(opts, debuggerAddrFlag(config)...)
opts = append(opts, noSandboxFlag(config)...)
opts = append(opts, chromedp.UserDataDir(userDataDir))
opts = append(opts, headlessOpts...)
opts = append(opts, config.ChromeFlags...)

ctx := context.Background()
if config.Ctx != nil {
ctx = config.Ctx
}

cancelT := func() {}
if config.Timeout > 0 {
ctx, cancelT = context.WithTimeout(ctx, config.Timeout)
}

ctx, cancelA := chromedp.NewExecAllocator(ctx, opts...)
ctx, cancelC := chromedp.NewContext(ctx, config.ContextOptions...)

cancel := func() {
cancelT()
cancelA()
cancelC()

if err := closeFrameBuffer(); err != nil {
slog.Error("failed to close Xvfb", err)
}

if len(config.ChromePath) == 0 {
_ = os.RemoveAll(userDataDir) //nolint:errcheck
}
}

return ctx, cancel, nil
}

func supressWelcomeFlag() []chromedp.ExecAllocatorOption {
return []chromedp.ExecAllocatorOption{
chromedp.Flag("no-first-run", true),
chromedp.Flag("no-default-browser-check", true),
}
}

func debuggerAddrFlag(config Config) []chromedp.ExecAllocatorOption {
port := strconv.Itoa(config.Port)
if config.Port == 0 {
port = getRandomPort()
}

return []chromedp.ExecAllocatorOption{
chromedp.Flag("remote-debugging-host", "127.0.0.1"),
chromedp.Flag("remote-debugging-port", port),
}
}

func localeFlag() chromedp.ExecAllocatorOption {
lang := "en-US"
if tag, err := locale.Detect(); err != nil && len(tag.String()) > 0 {
lang = tag.String()
}

return chromedp.Flag("lang", lang)
}

func noSandboxFlag(config Config) []chromedp.ExecAllocatorOption {
var opts []chromedp.ExecAllocatorOption

if config.NoSandbox {
opts = append(opts,
chromedp.Flag("no-sandbox", true),
chromedp.Flag("test-type", true))
}

return opts
}

func logLevelFlag(config Config) chromedp.ExecAllocatorOption {
return chromedp.Flag("log-level", strconv.Itoa(config.LogLevel))
}

func headlessFlag(config Config) ([]chromedp.ExecAllocatorOption, func() error, error) {
var opts []chromedp.ExecAllocatorOption

cleanup := func() error { return nil }

if config.Headless {
// Create virtual display
frameBuffer, err := newFrameBuffer("1920x1080x24")
if err != nil {
return nil, nil, err
}

cleanup = frameBuffer.Stop

opts = append(opts,
// chromedp.Flag("headless", true),
chromedp.Flag("window-size", "1920,1080"),
chromedp.Flag("start-maximized", true),
chromedp.Flag("no-sandbox", true),
chromedp.ModifyCmdFunc(func(cmd *exec.Cmd) {
cmd.Env = append(cmd.Env, "DISPLAY=:"+frameBuffer.Display)
cmd.Env = append(cmd.Env, "XAUTHORITY="+frameBuffer.AuthPath)

// Default modify command per chromedp
if _, ok := os.LookupEnv("LAMBDA_TASK_ROOT"); ok {
// do nothing on AWS Lambda
return
}

if cmd.SysProcAttr == nil {
cmd.SysProcAttr = new(syscall.SysProcAttr)
}

// When the parent process dies (Go), kill the child as well.
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
}),
)
}

return opts, cleanup, nil
}

func getRandomPort() string {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err == nil {
addr := l.Addr().String()
_ = l.Close() //nolint:errcheck,gosec

return strings.Split(addr, ":")[1]
}

return "42069"
}
Loading

0 comments on commit 3c6e217

Please sign in to comment.