init
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"huya-go/internal/huya"
|
||||
"huya-go/internal/sniper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
cmd = flag.String("cmd", "list", "命令:list/score/records/exchange/monitor")
|
||||
cookie = flag.String("cookie", "", "虎牙 Cookie")
|
||||
cookieFile = flag.String("cookie-file", "", "Cookie 文件,一行一个账号")
|
||||
sid = flag.Int("sid", 0, "活动 SID")
|
||||
pid = flag.Int("pid", 0, "奖品 PID")
|
||||
name = flag.String("name", "", "按奖品名包含匹配")
|
||||
loops = flag.Int("loops", 1, "monitor 循环次数")
|
||||
delayMS = flag.Int("delay", 150, "monitor 每轮间隔毫秒")
|
||||
concurrency = flag.Int("concurrency", 5, "monitor/exchange 并发账号数")
|
||||
stopAfterSuccess = flag.Bool("stop-after-success", true, "monitor 成功后停止")
|
||||
at = flag.String("at", "", "定时开始时间,支持 RFC3339 或 2006-01-02 15:04:05")
|
||||
timeoutSec = flag.Int("timeout", 15, "单次请求超时秒数")
|
||||
transport = flag.String("transport", "http", "传输层:http/wss")
|
||||
wssHost = flag.String("wss-host", huya.DefaultWSSHost, "WSS host,来自 launch.queryHttpDns 或抓包")
|
||||
verbose = flag.Bool("v", false, "输出调试日志")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
if *sid <= 0 {
|
||||
exitErr(errors.New("必须提供有效 -sid"))
|
||||
}
|
||||
|
||||
cookies, err := loadCookies(*cookie, *cookieFile)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
accounts := make([]sniper.Account, 0, len(cookies))
|
||||
for _, ck := range cookies {
|
||||
uid := huya.ResolveUID(ck)
|
||||
if uid <= 0 {
|
||||
exitErr(errors.New("无法从 Cookie 解析 yyuid/udb_uid"))
|
||||
}
|
||||
accounts = append(accounts, sniper.Account{UID: uid, Cookie: ck})
|
||||
}
|
||||
|
||||
logger := func(string) {}
|
||||
if *verbose {
|
||||
logger = func(msg string) { fmt.Fprintln(os.Stderr, msg) }
|
||||
}
|
||||
client, closeClient, err := huya.NewActivityClient(*transport, *wssHost, time.Duration(*timeoutSec)*time.Second, logger)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
defer closeClient()
|
||||
ctx := context.Background()
|
||||
|
||||
if *at != "" {
|
||||
when, err := parseTime(*at)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
waitUntil(ctx, when)
|
||||
}
|
||||
|
||||
switch *cmd {
|
||||
case "list":
|
||||
resp, err := client.GetActPrizeList(ctx, accounts[0].UID, accounts[0].Cookie, int32(*sid))
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
printJSON(resp)
|
||||
case "score":
|
||||
results := make([]map[string]any, 0, len(accounts))
|
||||
for _, account := range accounts {
|
||||
resp, err := client.QueryUserScore(ctx, account.UID, account.Cookie, int32(*sid))
|
||||
results = append(results, map[string]any{
|
||||
"uid": account.UID,
|
||||
"resp": resp,
|
||||
"score": availableScore(resp),
|
||||
"error": errString(err),
|
||||
})
|
||||
}
|
||||
printJSON(results)
|
||||
case "records":
|
||||
results := make([]map[string]any, 0, len(accounts))
|
||||
for _, account := range accounts {
|
||||
resp, err := client.GetUserPrizeRecords(ctx, account.UID, account.Cookie, int32(*sid))
|
||||
results = append(results, map[string]any{
|
||||
"uid": account.UID,
|
||||
"resp": resp,
|
||||
"error": errString(err),
|
||||
})
|
||||
}
|
||||
printJSON(results)
|
||||
case "exchange":
|
||||
if *pid <= 0 {
|
||||
exitErr(errors.New("exchange 必须提供 -pid"))
|
||||
}
|
||||
runExchange(ctx, client, accounts, int32(*sid), int32(*pid), *concurrency)
|
||||
case "monitor":
|
||||
if *pid <= 0 && strings.TrimSpace(*name) == "" {
|
||||
exitErr(errors.New("monitor 必须提供 -pid 或 -name"))
|
||||
}
|
||||
runner := sniper.Runner{Client: client}
|
||||
var outMu sync.Mutex
|
||||
err := runner.Run(ctx, accounts, sniper.Target{PID: int32(*pid), Name: strings.TrimSpace(*name)}, sniper.Config{
|
||||
SID: int32(*sid),
|
||||
Loops: *loops,
|
||||
Delay: time.Duration(*delayMS) * time.Millisecond,
|
||||
Concurrency: *concurrency,
|
||||
StopAfterSuccess: *stopAfterSuccess,
|
||||
}, func(result sniper.Result) {
|
||||
outMu.Lock()
|
||||
defer outMu.Unlock()
|
||||
_ = json.NewEncoder(os.Stdout).Encode(result)
|
||||
})
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
exitErr(err)
|
||||
}
|
||||
default:
|
||||
exitErr(fmt.Errorf("未知 -cmd: %s", *cmd))
|
||||
}
|
||||
}
|
||||
|
||||
func runExchange(ctx context.Context, client huya.ActivityClient, accounts []sniper.Account, sid, pid int32, concurrency int) {
|
||||
if concurrency <= 0 {
|
||||
concurrency = 1
|
||||
}
|
||||
type item struct {
|
||||
UID int64 `json:"uid"`
|
||||
Resp *huya.ScoreExchangePrizeResp `json:"resp,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
results := make([]item, len(accounts))
|
||||
sem := make(chan struct{}, concurrency)
|
||||
var wg sync.WaitGroup
|
||||
for i, account := range accounts {
|
||||
i, account := i, account
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
resp, err := client.ScoreExchangePrize(ctx, account.UID, account.Cookie, sid, pid)
|
||||
results[i] = item{UID: account.UID, Resp: resp, Error: errString(err)}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
printJSON(results)
|
||||
}
|
||||
|
||||
func loadCookies(cookie, file string) ([]string, error) {
|
||||
var out []string
|
||||
if strings.TrimSpace(cookie) != "" {
|
||||
out = append(out, strings.TrimSpace(cookie))
|
||||
}
|
||||
if file != "" {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil, errors.New("必须提供 -cookie 或 -cookie-file")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseTime(text string) (time.Time, error) {
|
||||
text = strings.TrimSpace(text)
|
||||
if text == "" {
|
||||
return time.Time{}, errors.New("时间为空")
|
||||
}
|
||||
if ts, err := strconv.ParseInt(text, 10, 64); err == nil {
|
||||
return time.Unix(ts, 0), nil
|
||||
}
|
||||
for _, layout := range []string{time.RFC3339, "2006-01-02 15:04:05", "2006-01-02 15:04"} {
|
||||
if t, err := time.ParseInLocation(layout, text, time.Local); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("无法解析 -at 时间: %s", text)
|
||||
}
|
||||
|
||||
func waitUntil(ctx context.Context, when time.Time) {
|
||||
for {
|
||||
remaining := time.Until(when)
|
||||
if remaining <= 0 {
|
||||
return
|
||||
}
|
||||
timer := time.NewTimer(minDuration(remaining, 200*time.Millisecond))
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printJSON(value any) {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(value); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func errString(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
func availableScore(resp *huya.GetUserScoreResp) int64 {
|
||||
if resp == nil {
|
||||
return 0
|
||||
}
|
||||
return resp.AvailableScore()
|
||||
}
|
||||
|
||||
func exitErr(err error) {
|
||||
fmt.Fprintln(os.Stderr, "错误:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func minDuration(a, b time.Duration) time.Duration {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user