mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
dd82794255
* init * fix lint * fix go test * update docs * incorporate pr review * Update pkg/channels/weixin/weixin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat(weixin): add media sync and typing support * test(weixin): cover media and sync helpers --------- Co-authored-by: zhangmikoto <i@electromaster.me> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Hoshina <hoshina@evaz.org>
112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
package weixin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/mdp/qrterminal/v3"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/logger"
|
|
)
|
|
|
|
// AuthFlowOpts configures the interactive QR login flow.
|
|
type AuthFlowOpts struct {
|
|
BaseURL string
|
|
BotType string
|
|
Timeout time.Duration
|
|
Proxy string
|
|
}
|
|
|
|
// PerformLoginInteractive starts the Weixin QR login flow and blocks until login is successful or times out.
|
|
// It prints a QR code to the terminal for the user to scan.
|
|
// Returns the BotToken, UserID, AccountID, and BaseUrl on success.
|
|
func PerformLoginInteractive(
|
|
ctx context.Context,
|
|
opts AuthFlowOpts,
|
|
) (botToken, userID, accountID, baseUrl string, err error) {
|
|
if opts.BaseURL == "" {
|
|
opts.BaseURL = "https://ilinkai.weixin.qq.com/"
|
|
}
|
|
if opts.BotType == "" {
|
|
opts.BotType = "3" // Default iLink Bot Type
|
|
}
|
|
if opts.Timeout == 0 {
|
|
opts.Timeout = 5 * time.Minute
|
|
}
|
|
|
|
api, err := NewApiClient(opts.BaseURL, "", opts.Proxy)
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("failed to create api client: %w", err)
|
|
}
|
|
|
|
logger.InfoC("weixin", "Requesting Weixin QR code...")
|
|
qrResp, err := api.GetQRCode(ctx, opts.BotType)
|
|
if err != nil {
|
|
return "", "", "", "", fmt.Errorf("failed to get qrcode: %w", err)
|
|
}
|
|
|
|
fmt.Println("\n=======================================================")
|
|
fmt.Println("Please scan the following QR code with WeChat to login:")
|
|
fmt.Println("=======================================================")
|
|
fmt.Println()
|
|
|
|
// Create Small QR
|
|
qrconfig := qrterminal.Config{
|
|
Level: qrterminal.L,
|
|
Writer: os.Stdout,
|
|
HalfBlocks: true,
|
|
}
|
|
qrterminal.GenerateWithConfig(qrResp.QrcodeImgContent, qrconfig)
|
|
|
|
fmt.Printf("\nQR Code Link: %s\n\n", qrResp.QrcodeImgContent)
|
|
fmt.Println("Waiting for scan...")
|
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, opts.Timeout)
|
|
defer cancel()
|
|
|
|
pollTicker := time.NewTicker(2 * time.Second)
|
|
defer pollTicker.Stop()
|
|
|
|
scannedPrinted := false
|
|
|
|
for {
|
|
select {
|
|
case <-timeoutCtx.Done():
|
|
return "", "", "", "", fmt.Errorf("login timeout")
|
|
case <-pollTicker.C:
|
|
statusResp, err := api.GetQRCodeStatus(timeoutCtx, qrResp.Qrcode)
|
|
if err != nil {
|
|
// Long poll timeout or temporary error
|
|
continue
|
|
}
|
|
|
|
switch statusResp.Status {
|
|
case "wait":
|
|
// still waiting
|
|
case "scaned":
|
|
if !scannedPrinted {
|
|
fmt.Println("👀 QR Code scanned! Please confirm login on your WeChat app...")
|
|
scannedPrinted = true
|
|
}
|
|
case "confirmed":
|
|
if statusResp.BotToken == "" || statusResp.IlinkBotID == "" {
|
|
return "", "", "", "", fmt.Errorf("login confirmed but missing bot_token or ilink_bot_id")
|
|
}
|
|
logger.InfoCF("weixin", "Login successful", map[string]any{
|
|
"account_id": statusResp.IlinkBotID,
|
|
})
|
|
|
|
return statusResp.BotToken, statusResp.IlinkUserID, statusResp.IlinkBotID, statusResp.Baseurl, nil
|
|
case "expired":
|
|
return "", "", "", "", fmt.Errorf("qrcode expired, please try again")
|
|
default:
|
|
logger.WarnCF("weixin", "Unknown QR code status", map[string]any{
|
|
"status": statusResp.Status,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|