Golang写的,还在测试。我先用几天,没问题的话,到时候搞个release和教程。
// Command tieba-auto-sign is a simple Tieba auto-sign tool.// Usage://// ./tieba-auto-sign -i 5 "BDUSS=...;" "BDUSS=...;"// ./tieba-auto-sign -c /path/to/config.jsonpackage mainimport ( "context" "encoding/json" "flag" "fmt" "io" "log" "net/http" "net/http/cookiejar" "net/url" "os" "path/filepath" "strings" "time")const ( baseURL = "https://tieba.baidu.com" pathInfo = "/mo/q/newmoindex" pathSignAdd = "/sign/add" pathOneKeySign = "/tbmall/onekeySignin1")// Config 配置文件结构type Config struct { Accounts []string `json:"accounts"` // BDUSS列表 Interval int `json:"interval"` // 签到间隔(秒) LogFile string `json:"log_file"` // 日志文件路径}type apiResp struct { No int `json:"no"` Error string `json:"error"` Data json.RawMessage `json:"data"`}type infoData struct { LikeForum []struct { ForumName string `json:"forum_name"` IsSign int `json:"is_sign"` } `json:"like_forum"` ItbTbs string `json:"itb_tbs"`}type TiebaClient struct { cli *http.Client bduss string}func NewTiebaClient(bduss string) *TiebaClient { jar, _ := cookiejar.New(nil) cli := &http.Client{Timeout: 20 * time.Second, Jar: jar} // 设置BDUSS cookie bduss = strings.TrimSpace(bduss) bduss = strings.TrimPrefix(bduss, "BDUSS=") bduss = strings.TrimSuffix(bduss, ";") u, _ := url.Parse(baseURL) jar.SetCookies(u, []*http.Cookie{ {Name: "BDUSS", Value: bduss, Path: "/", Domain: ".baidu.com"}, }) return &TiebaClient{cli: cli, bduss: bduss}}func (c *TiebaClient) AutoSign(ctx context.Context, interval time.Duration) error { // 获取贴吧信息和tbs _, tbs, err := c.getForumInfo(ctx) if err != nil { return fmt.Errorf("获取贴吧信息失败: %v", err) } // 一键签到 if err := c.oneKeySign(ctx, tbs); err != nil { log.Printf("一键签到失败: %v", err) } // 获取更新后的签到状态 forums, _, err := c.getForumInfo(ctx) if err != nil { return fmt.Errorf("获取更新状态失败: %v", err) } // 逐个签到未签的贴吧 unsigned := []string{} for _, f := range forums { if f.IsSign == 0 { unsigned = append(unsigned, f.ForumName) } } log.Printf("开始逐个签到,共 %d 个未签贴吧", len(unsigned)) for i, name := range unsigned { if err := c.signForum(ctx, name); err != nil { log.Printf("签到 %s 失败: %v", name, err) } if i < len(unsigned)-1 { time.Sleep(interval) } } return nil}func (c *TiebaClient) getForumInfo(ctx context.Context) ([]struct { ForumName string `json:"forum_name"` IsSign int `json:"is_sign"`}, string, error) { req, err := http.NewRequestWithContext(ctx, "GET", baseURL+pathInfo, nil) if err != nil { return nil, "", err } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0") resp, err := c.cli.Do(req) if err != nil { return nil, "", err } defer resp.Body.Close() var apiResp apiResp if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { return nil, "", err } if apiResp.No != 0 { return nil, "", fmt.Errorf("API错误: %s", apiResp.Error) } var data infoData if err := json.Unmarshal(apiResp.Data, &data); err != nil { return nil, "", err } log.Printf("共关注 %d 个贴吧", len(data.LikeForum)) signed, unsigned := 0, 0 for _, f := range data.LikeForum { if f.IsSign == 1 { signed++ } else { unsigned++ } } log.Printf("已签到: %d, 未签到: %d", signed, unsigned) return data.LikeForum, data.ItbTbs, nil}func (c *TiebaClient) oneKeySign(ctx context.Context, tbs string) error { form := url.Values{"ie": {"utf-8"}, "tbs": {tbs}} req, err := http.NewRequestWithContext(ctx, "POST", baseURL+pathOneKeySign, strings.NewReader(form.Encode())) if err != nil { return err } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := c.cli.Do(req) if err != nil { return err } defer resp.Body.Close() var apiResp apiResp if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { return err } if apiResp.No == 0 || apiResp.No == 2280006 { // 成功或部分成功 log.Printf("一键签到完成") return nil } return fmt.Errorf("一键签到失败: %s", apiResp.Error)}func (c *TiebaClient) signForum(ctx context.Context, forum string) error { form := url.Values{"ie": {"utf-8"}, "kw": {forum}} req, err := http.NewRequestWithContext(ctx, "POST", baseURL+pathSignAdd, strings.NewReader(form.Encode())) if err != nil { return err } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := c.cli.Do(req) if err != nil { return err } defer resp.Body.Close() var apiResp apiResp if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { return err } if apiResp.No == 0 { log.Printf("%s吧 签到成功", forum) return nil } return fmt.Errorf("签到失败: %s", apiResp.Error)}// loadConfig 从文件加载配置func loadConfig(configPath string) (*Config, error) { data, err := os.ReadFile(configPath) if err != nil { return nil, fmt.Errorf("读取配置文件失败: %v", err) } var config Config if err := json.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("解析配置文件失败: %v", err) } return &config, nil}// setupLogger 设置日志输出func setupLogger(logFile string) (*os.File, error) { if logFile == "" { return nil, nil // 使用默认输出 } // 确保日志目录存在 dir := filepath.Dir(logFile) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("创建日志目录失败: %v", err) } // 打开日志文件 file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, fmt.Errorf("打开日志文件失败: %v", err) } // 设置日志输出到文件和控制台 multiWriter := io.MultiWriter(os.Stdout, file) log.SetOutput(multiWriter) log.SetFlags(log.LstdFlags) return file, nil}func main() { var ( interval = flag.Int("i", 5, "签到间隔秒数") configPath = flag.String("c", "", "配置文件路径 (JSON格式)") ) flag.Parse() var accounts []string var logFile string var intervalSec int if *configPath != "" { // 从配置文件读取 config, err := loadConfig(*configPath) if err != nil { log.Fatalf("加载配置文件失败: %v", err) } accounts = config.Accounts logFile = config.LogFile if config.Interval > 0 { intervalSec = config.Interval } else { intervalSec = *interval } } else { // 从命令行参数读取 accounts = flag.Args() intervalSec = *interval } if len(accounts) == 0 { fmt.Fprintf(os.Stderr, "使用方法:\n") fmt.Fprintf(os.Stderr, " %s -i 5 \"BDUSS=...;\" \"BDUSS=...;\"\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s -c /path/to/config.json\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\n配置文件格式:\n") fmt.Fprintf(os.Stderr, "{\n") fmt.Fprintf(os.Stderr, " \"accounts\": [\"BDUSS=...;\", \"BDUSS=...;\"],\n") fmt.Fprintf(os.Stderr, " \"interval\": 5,\n") fmt.Fprintf(os.Stderr, " \"log_file\": \"/path/to/sign.log\"\n") fmt.Fprintf(os.Stderr, "}\n") os.Exit(1) } // 设置日志输出 logFileHandle, err := setupLogger(logFile) if err != nil { log.Fatalf("设置日志失败: %v", err) } if logFileHandle != nil { defer logFileHandle.Close() log.Printf("日志将保存到: %s", logFile) } log.Printf("开始批量签到,共 %d 个账号,间隔 %d 秒", len(accounts), intervalSec) ctx := context.Background() for idx, bduss := range accounts { log.Printf("[%d/%d] 开始处理账号...", idx+1, len(accounts)) client := NewTiebaClient(bduss) if err := client.AutoSign(ctx, time.Duration(intervalSec)*time.Second); err != nil { log.Printf("账号 %d 签到失败: %v", idx+1, err) } else { log.Printf("账号 %d 签到完成", idx+1) } } log.Printf("所有账号处理完成")}
评论 (0)