init
This commit is contained in:
65
libs/browser-fingerprint/canvas.go
Normal file
65
libs/browser-fingerprint/canvas.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package browserfingerprint
|
||||
|
||||
import "fmt"
|
||||
|
||||
func GetChangeCanvasJavaScript(rgba [4]int) string {
|
||||
return fmt.Sprintf(`
|
||||
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
|
||||
|
||||
const noisify = (canvas, context) => {
|
||||
if (context) {
|
||||
const shift = {
|
||||
r: %d,
|
||||
g: %d,
|
||||
b: %d,
|
||||
a: %d,
|
||||
};
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
if (width && height) {
|
||||
const imageData = getImageData.apply(context, [0, 0, width, height]);
|
||||
|
||||
for (let i = 0; i < height; i++)
|
||||
for (let j = 0; j < width; j++) {
|
||||
const n = i * (width * 4) + j * 4;
|
||||
imageData.data[n + 0] = imageData.data[n + 0] + shift.r;
|
||||
imageData.data[n + 1] = imageData.data[n + 1] + shift.g;
|
||||
imageData.data[n + 2] = imageData.data[n + 2] + shift.b;
|
||||
imageData.data[n + 3] = imageData.data[n + 3] + shift.a;
|
||||
}
|
||||
|
||||
|
||||
context.putImageData(imageData, 0, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
|
||||
apply(target, self, args) {
|
||||
noisify(self, self.getContext('2d'));
|
||||
|
||||
return Reflect.apply(target, self, args);
|
||||
},
|
||||
});
|
||||
|
||||
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
|
||||
apply(target, self, args) {
|
||||
noisify(self, self.getContext('2d'));
|
||||
|
||||
return Reflect.apply(target, self, args);
|
||||
},
|
||||
});
|
||||
|
||||
CanvasRenderingContext2D.prototype.getImageData = new Proxy(
|
||||
CanvasRenderingContext2D.prototype.getImageData,
|
||||
{
|
||||
apply(target, self, args) {
|
||||
noisify(self.canvas, self);
|
||||
|
||||
return Reflect.apply(target, self, args);
|
||||
},
|
||||
}
|
||||
);
|
||||
`, rgba[0], rgba[1], rgba[2], rgba[3])
|
||||
}
|
||||
42
libs/browser-fingerprint/index.go
Normal file
42
libs/browser-fingerprint/index.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package browserfingerprint
|
||||
|
||||
import (
|
||||
paramsTypes "go-site-clone/types"
|
||||
"math/rand"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
)
|
||||
|
||||
type BrowserFingerprint struct {
|
||||
}
|
||||
|
||||
func (b *BrowserFingerprint) SetBrowserFingerprint(page *rod.Page, params *paramsTypes.BrowserFingerprintParams) *rod.Page {
|
||||
var p *rod.Page = page
|
||||
if params.Canvas {
|
||||
p = setCanvas(p)
|
||||
}
|
||||
if params.TimeZone != "" {
|
||||
p = setTimezoneAndLangAndGeo(p, params)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func setCanvas(page *rod.Page) *rod.Page {
|
||||
// 注入噪声脚本
|
||||
// 生成随机 RGBA 偏移值 (Go 端控制)
|
||||
rgba := [4]int{
|
||||
rand.Intn(10) - 5, // R: -5 ~ +4
|
||||
rand.Intn(10) - 5, // G
|
||||
rand.Intn(10) - 5, // B
|
||||
rand.Intn(10) - 5, // A
|
||||
}
|
||||
// 使用更隐蔽的 Canvas 指纹修改方法
|
||||
page.MustEvalOnNewDocument(GetChangeCanvasJavaScript(rgba))
|
||||
return page
|
||||
}
|
||||
|
||||
func setTimezoneAndLangAndGeo(page *rod.Page, params *paramsTypes.BrowserFingerprintParams) *rod.Page {
|
||||
page.MustEvalOnNewDocument(GetChangeTimezoneJavaScript(params.TimeZone, params.Language, params.GeoLocation))
|
||||
return page
|
||||
}
|
||||
98
libs/browser-fingerprint/timezone.go
Normal file
98
libs/browser-fingerprint/timezone.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package browserfingerprint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
func GetChangeTimezoneJavaScript(timezone string, lang string, geo string) string {
|
||||
return fmt.Sprintf(`
|
||||
(() => {
|
||||
// 设置全局时区配置
|
||||
window.__rodTZConfig = {
|
||||
timeZone: '%s',
|
||||
language: '%s',
|
||||
geo: '%s'
|
||||
};
|
||||
|
||||
// 覆盖 Date 对象
|
||||
const OriginalDate = window.Date;
|
||||
window.Date = function(...args) {
|
||||
if (new.target) {
|
||||
// 作为构造函数
|
||||
if (args.length === 0) {
|
||||
// 无参数调用时应用时区偏移
|
||||
const now = new OriginalDate();
|
||||
const targetOffset = -480; // %s 的 UTC 偏移(分钟)
|
||||
return new OriginalDate(now.getTime() + (targetOffset - now.getTimezoneOffset()) * 60000);
|
||||
}
|
||||
return new OriginalDate(...args);
|
||||
}
|
||||
// 作为函数调用
|
||||
return OriginalDate();
|
||||
};
|
||||
|
||||
// 复制静态方法和属性
|
||||
Object.defineProperties(Date, Object.getOwnPropertyDescriptors(OriginalDate));
|
||||
|
||||
// 覆盖 Date.now()
|
||||
const origDateNow = Date.now;
|
||||
Date.now = function() {
|
||||
return origDateNow() + (%d * 60000); // 添加时区偏移
|
||||
};
|
||||
|
||||
// 覆盖 getTimezoneOffset
|
||||
const origGetTimezoneOffset = Date.prototype.getTimezoneOffset;
|
||||
Date.prototype.getTimezoneOffset = function() {
|
||||
return %d; // %s 的 UTC 偏移(分钟)
|
||||
};
|
||||
|
||||
// 覆盖 Intl.DateTimeFormat
|
||||
const origDateTimeFormat = Intl.DateTimeFormat;
|
||||
Intl.DateTimeFormat = function(locales, options) {
|
||||
options = options || {};
|
||||
options.timeZone = options.timeZone || window.__rodTZConfig.timeZone;
|
||||
return new origDateTimeFormat(locales, options);
|
||||
};
|
||||
|
||||
// 覆盖 navigator 属性
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
get: () => window.__rodTZConfig.language,
|
||||
configurable: false
|
||||
});
|
||||
|
||||
Object.defineProperty(navigator, 'languages', {
|
||||
get: () => [window.__rodTZConfig.language],
|
||||
configurable: false
|
||||
});
|
||||
|
||||
// 覆盖控制台时区
|
||||
if (console._timeZone) {
|
||||
console._timeZone = window.__rodTZConfig.timeZone;
|
||||
}
|
||||
})();
|
||||
`,
|
||||
timezone,
|
||||
lang,
|
||||
geo,
|
||||
timezone, // 用于注释
|
||||
getUTCOffsetMinutes(timezone), // 时区偏移分钟数
|
||||
getUTCOffsetMinutes(timezone), // 时区偏移分钟数
|
||||
timezone)
|
||||
}
|
||||
|
||||
// getUTCOffsetMinutes 获取时区的UTC偏移分钟数
|
||||
func getUTCOffsetMinutes(timeZone string) int {
|
||||
// 使用时区名称获取实际偏移
|
||||
loc, err := time.LoadLocation(timeZone)
|
||||
if err != nil {
|
||||
log.Printf("无法加载时区 %s: %v, 使用默认值 -480", timeZone, err)
|
||||
return -480 // 默认太平洋时间
|
||||
}
|
||||
|
||||
// 获取当前时间的偏移
|
||||
_, offset := time.Now().In(loc).Zone()
|
||||
return -offset / 60 // 转换为分钟并取反
|
||||
}
|
||||
83
libs/browser.go
Normal file
83
libs/browser.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
)
|
||||
|
||||
type Browser struct{}
|
||||
|
||||
type BrowserInfo struct {
|
||||
Id int
|
||||
Browser *rod.Browser
|
||||
Url string
|
||||
Page *rod.Page
|
||||
}
|
||||
|
||||
// 浏览器指纹数据
|
||||
type Fingerprint struct {
|
||||
Viewport []struct {
|
||||
Height int
|
||||
Width int
|
||||
}
|
||||
Proxy string
|
||||
Lang string
|
||||
}
|
||||
|
||||
var (
|
||||
BrowserPool = make(map[int]*BrowserInfo) // 初始化map
|
||||
poolMutex sync.RWMutex // 添加读写锁保证并发安全
|
||||
)
|
||||
|
||||
var ChromeExample Chrome
|
||||
|
||||
func (*Browser) GetBrowser(id int, params *Fingerprint) *BrowserInfo {
|
||||
poolMutex.RLock()
|
||||
value, ok := BrowserPool[id]
|
||||
poolMutex.RUnlock()
|
||||
|
||||
if ok {
|
||||
return value
|
||||
}
|
||||
|
||||
// 未找到时获取写锁(互斥)
|
||||
poolMutex.Lock()
|
||||
defer poolMutex.Unlock()
|
||||
|
||||
// 双检查:避免在等待锁期间其他goroutine已创建
|
||||
if value, ok := BrowserPool[id]; ok {
|
||||
return value
|
||||
}
|
||||
|
||||
// 创建新实例(补充CancelFunc)
|
||||
var ChromeExample Chrome
|
||||
browser, url, page := ChromeExample.Create(id, params)
|
||||
browserInfo := &BrowserInfo{
|
||||
Id: id,
|
||||
Browser: browser,
|
||||
Url: url,
|
||||
Page: page,
|
||||
}
|
||||
BrowserPool[id] = browserInfo
|
||||
return browserInfo
|
||||
}
|
||||
|
||||
func (*Browser) GetAllBrowser() map[int]*BrowserInfo {
|
||||
return BrowserPool
|
||||
}
|
||||
|
||||
func (*Browser) CancelAllBrowser() {
|
||||
for _, v := range BrowserPool {
|
||||
v.Browser.MustClose()
|
||||
}
|
||||
}
|
||||
|
||||
func (*Browser) CancelBrowser(id int) {
|
||||
browserInfo := BrowserPool[id]
|
||||
if browserInfo == nil {
|
||||
return
|
||||
}
|
||||
browserInfo.Browser.MustClose()
|
||||
delete(BrowserPool, id)
|
||||
}
|
||||
109
libs/chrome.go
Normal file
109
libs/chrome.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package libs
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/launcher"
|
||||
"github.com/go-rod/rod/lib/proto"
|
||||
"github.com/go-rod/stealth"
|
||||
)
|
||||
|
||||
type Chrome struct{}
|
||||
|
||||
func (c *Chrome) Create(accountId int, params *Fingerprint) (*rod.Browser, string, *rod.Page) {
|
||||
var accountIdStr = strconv.Itoa(accountId)
|
||||
userDataDir := "user-data/" + accountIdStr
|
||||
absPath, _ := filepath.Abs(userDataDir)
|
||||
log.Printf("用户数据目录: %s", absPath)
|
||||
path, _ := launcher.LookPath() // 获取当前系统的浏览器目录
|
||||
// 判断是否传递代理
|
||||
// var proxyHandle utils.ProxyHandle
|
||||
// protocol, username, password, ip, port, errProxy := proxyHandle.ParseProxy(params.Proxy)
|
||||
|
||||
// ipInfo := external.GetIpInfo(strings.TrimSpace(string(params.Proxy)))
|
||||
|
||||
l := launcher.New().
|
||||
Bin(path).
|
||||
// Proxy("210.51.27.121:50003"). // 直接传入完整认证URL
|
||||
Delete("use-mock-keychain"). // delete flag "--use-mock-keychain"
|
||||
Set("disable-blink-features", "AutomationControlled"). // 绕过自动化检测
|
||||
// Set("incognito"). // 无痕模式
|
||||
Set("user-data-dir", absPath). // 数据持久化目录
|
||||
Set("window-size", "1920,1480"). // 窗口尺寸
|
||||
Set("disable-infobars", "true"). // 隐藏自动化提示栏
|
||||
Set("no-sandbox", "true").
|
||||
Set("excludeSwitches", "enable-automation").
|
||||
Set("enable-gpu"). // 启用 GPU 加速
|
||||
Set("ignore-certificate-errors"). // 忽略证书错误
|
||||
Set("use-fake-ui-for-media-stream"). // 允许媒体流
|
||||
Set("autoplay-policy", "no-user-gesture-required"). // 自动播放
|
||||
Set("ignore-certificate-errors").
|
||||
Set("disable-application-cache").
|
||||
Set("disable-dev-shm-usage").
|
||||
// 禁用webrtc
|
||||
Set("disable-webrtc", "true"). // 核心禁用参数
|
||||
Set("disable-features", "WebRtcHideLocalIpsWithMdns"). // 隐藏本地IP
|
||||
Set("webrtc-ip-handling-policy", "disable_non_proxied_udp"). // 禁用非代理UDP
|
||||
Set("force-webrtc-ip-handling-policy").
|
||||
Headless(false) // 关闭无头模式
|
||||
|
||||
// 修改语言
|
||||
l.Set("lang", "en-US")
|
||||
l.Set("accept-lang", "en-US")
|
||||
|
||||
// // 修改时区
|
||||
// l.Set("timezone", ipInfo.Timezone)
|
||||
// l.Set("disable-geolocation", "true") // 启用地理定位API
|
||||
// l.Set("disable-web-security", "true") // 允许跨域(某些网站需要)
|
||||
// l.Set("disable-notifications", "true")
|
||||
|
||||
// // 设置环境变量 - 核心时区设置
|
||||
// l.Env(append(os.Environ(), "TZ="+ipInfo.Timezone)...)
|
||||
// l.
|
||||
// Set("timezone", ipInfo.Timezone).
|
||||
// Set("geolocation", ipInfo.Lat+","+ipInfo.Lon)
|
||||
|
||||
// if errProxy == nil {
|
||||
// if strings.Contains(protocol, "http") {
|
||||
// log.Println(protocol + "://" + ip + ":" + port)
|
||||
// l = l.Proxy(ip + ":" + port)
|
||||
// }
|
||||
// }
|
||||
|
||||
uri := l.MustLaunch()
|
||||
|
||||
browser := rod.New().
|
||||
ControlURL(uri).
|
||||
MustConnect().NoDefaultDevice()
|
||||
browser.MustIgnoreCertErrors(true)
|
||||
// if errProxy == nil {
|
||||
// if strings.Contains(protocol, "http") {
|
||||
// log.Println(username, password)
|
||||
// go browser.MustHandleAuth(username, password)()
|
||||
// }
|
||||
// }
|
||||
page := stealth.MustPage(browser)
|
||||
// page := browser.MustPage()
|
||||
|
||||
go browser.EachEvent(func(e *proto.TargetTargetCreated) {
|
||||
if e.TargetInfo.Type != proto.TargetTargetInfoTypePage {
|
||||
return
|
||||
}
|
||||
browser.MustPageFromTargetID(e.TargetInfo.TargetID).MustEvalOnNewDocument(stealth.JS)
|
||||
})()
|
||||
|
||||
// // 在页面加载前注入时区覆盖代码
|
||||
// var browserFingerprint browserfingerprint.BrowserFingerprint
|
||||
// page = browserFingerprint.SetBrowserFingerprint(page, ¶msTypes.BrowserFingerprintParams{
|
||||
// Canvas: true,
|
||||
// TimeZone: ipInfo.Timezone, // 太平洋时间 (UTC-8)
|
||||
// Language: "en-US",
|
||||
// GeoLocation: ipInfo.Lat + "," + ipInfo.Lon, // 洛杉矶坐标
|
||||
// })
|
||||
page.MustSetViewport(1920, 1480, 1.0, false)
|
||||
|
||||
return browser, uri, page
|
||||
}
|
||||
Reference in New Issue
Block a user