package auth import ( "context" "fmt" "strings" "time" "github.com/chromedp/cdproto/network" "github.com/chromedp/chromedp" ) // CompleteWithChromedp 使用 chromedp 完成 S2A OAuth 授权 func CompleteWithChromedp(authURL, email, password, teamID string, headless bool, proxy string) (string, error) { opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", headless), chromedp.Flag("disable-gpu", true), chromedp.Flag("no-sandbox", true), chromedp.Flag("disable-dev-shm-usage", true), chromedp.Flag("disable-blink-features", "AutomationControlled"), chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"), ) if proxy != "" { opts = append(opts, chromedp.ProxyServer(proxy)) } allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() ctx, cancel := chromedp.NewContext(allocCtx) defer cancel() ctx, cancel = context.WithTimeout(ctx, 120*time.Second) defer cancel() var callbackURL string chromedp.ListenTarget(ctx, func(ev interface{}) { if req, ok := ev.(*network.EventRequestWillBeSent); ok { url := req.Request.URL if strings.Contains(url, "localhost") && strings.Contains(url, "code=") { callbackURL = url } } }) err := chromedp.Run(ctx, network.Enable(), chromedp.Navigate(authURL), chromedp.WaitReady("body"), ) if err != nil { return "", fmt.Errorf("访问失败: %v", err) } time.Sleep(2 * time.Second) if callbackURL != "" { return ExtractCodeFromCallbackURL(callbackURL), nil } var currentURL string _ = chromedp.Run(ctx, chromedp.Location(¤tURL)) if strings.Contains(currentURL, "code=") { return ExtractCodeFromCallbackURL(currentURL), nil } time.Sleep(1 * time.Second) emailSelectors := []string{ `input[name="email"]`, `input[type="email"]`, `input[name="username"]`, } var emailFilled bool for _, sel := range emailSelectors { err = chromedp.Run(ctx, chromedp.WaitVisible(sel, chromedp.ByQuery)) if err == nil { err = chromedp.Run(ctx, chromedp.Clear(sel, chromedp.ByQuery), chromedp.SendKeys(sel, email, chromedp.ByQuery), ) if err == nil { emailFilled = true break } } } if !emailFilled { return "", fmt.Errorf("未找到邮箱输入框") } time.Sleep(300 * time.Millisecond) buttonSelectors := []string{ `button[type="submit"]`, `button[data-testid="login-button"]`, `button.continue-btn`, `input[type="submit"]`, } for _, sel := range buttonSelectors { err = chromedp.Run(ctx, chromedp.Click(sel, chromedp.ByQuery)) if err == nil { break } } time.Sleep(1500 * time.Millisecond) if callbackURL != "" { return ExtractCodeFromCallbackURL(callbackURL), nil } _ = chromedp.Run(ctx, chromedp.Location(¤tURL)) if strings.Contains(currentURL, "code=") { return ExtractCodeFromCallbackURL(currentURL), nil } passwordSelectors := []string{ `input[name="current-password"]`, `input[name="password"]`, `input[type="password"]`, } var passwordFilled bool for _, sel := range passwordSelectors { err = chromedp.Run(ctx, chromedp.WaitVisible(sel, chromedp.ByQuery)) if err == nil { err = chromedp.Run(ctx, chromedp.Clear(sel, chromedp.ByQuery), chromedp.SendKeys(sel, password, chromedp.ByQuery), ) if err == nil { passwordFilled = true break } } } if !passwordFilled { return "", fmt.Errorf("未找到密码输入框") } time.Sleep(300 * time.Millisecond) for _, sel := range buttonSelectors { err = chromedp.Run(ctx, chromedp.Click(sel, chromedp.ByQuery)) if err == nil { break } } for i := 0; i < 30; i++ { time.Sleep(500 * time.Millisecond) if callbackURL != "" { return ExtractCodeFromCallbackURL(callbackURL), nil } var url string if err := chromedp.Run(ctx, chromedp.Location(&url)); err == nil { if strings.Contains(url, "code=") { return ExtractCodeFromCallbackURL(url), nil } if strings.Contains(url, "consent") { for _, sel := range buttonSelectors { err = chromedp.Run(ctx, chromedp.Click(sel, chromedp.ByQuery)) if err == nil { break } } time.Sleep(1 * time.Second) } if strings.Contains(url, "authorize") && teamID != "" { err = chromedp.Run(ctx, chromedp.Click(fmt.Sprintf(`[data-workspace-id="%s"], [data-account-id="%s"]`, teamID, teamID), chromedp.ByQuery), ) } } } if callbackURL != "" { return ExtractCodeFromCallbackURL(callbackURL), nil } return "", fmt.Errorf("授权超时") }