├── .gitignore ├── Makefile ├── README.md ├── example.json ├── go.mod ├── go.sum ├── main.go └── src ├── config ├── config.go └── operate.go ├── job ├── cron.go ├── option.go └── parameters.go ├── logger └── logger.go └── request ├── option.go └── requests.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/go 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=go 3 | 4 | ### Go ### 5 | # If you prefer the allow list template instead of the deny list, see community template: 6 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 7 | # 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | 24 | # Go workspace file 25 | go.work 26 | 27 | ### Go Patch ### 28 | /vendor/ 29 | /Godeps/ 30 | 31 | # End of https://www.toptal.com/developers/gitignore/api/go 32 | 33 | config.json 34 | log.txt 35 | loc 36 | .idea 37 | hostloc* -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | go build -o hostloc *.go 3 | 4 | amd64linux: 5 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o hostloc 6 | tar zcvf hostloc-check-in-linux-amd64.tar.gz hostloc example.json README.md 7 | 8 | arm64linux: 9 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags="-s -w" -o hostloc 10 | tar zcvf hostloc-check-in-linux-arm64.tar.gz hostloc example.json README.md 11 | 12 | amd64windows: 13 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o hostloc.exe 14 | tar zcvf hostloc-check-in-windows-amd64.tar.gz hostloc.exe example.json README.md 15 | 16 | amd64mac: 17 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o hostloc 18 | tar zcvf hostloc-check-in-macOS-amd64.tar.gz hostloc example.json README.md 19 | 20 | all: 21 | make amd64linux 22 | make arm64linux 23 | make amd64windows 24 | make amd64mac 25 | 26 | clean: 27 | rm hostloc* -r 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HostLoc 每日刷分脚本 2 | LOC 每日签到脚本,支持多账号、Telegram 推送, 开启 TLS 指纹伪装. 3 | 4 | ## 获取二进制文件 5 | 6 | ### (1) 从 Github Release 中下载 7 | ```shell 8 | # 以 amd64 架构的系统为例 9 | mkdir HostLoc_CheckIn 10 | cd HostLoc_CheckIn 11 | wget https://github.com/LordPenguin666/Hostloc-daily-checkin-tls/releases/download/v1.0.0/hostloc-check-in-linux-amd64.tar.gz 12 | tar xvf hostloc-check-in-linux-amd64.tar.gz 13 | ``` 14 | 15 | ### (2) 编译 (需要拥有 go 环境) 16 | ```shell 17 | git clone git@github.com:LordPenguin666/Hostloc-daily-checkin-tls.git 18 | cd Hostloc-daily-checkin-tls 19 | make default 20 | ``` 21 | 22 | ## 使用方法 23 | 24 | 1. 复制配置文件 example.json `cp example.json config.json`; 25 | 2. 修改配置文件 `vim config.json`; 26 | 3. (可选) 你也可以通过 `./hostloc -c /path/to/your/config` 指定配置文件路径; 27 | 4. 使用 `./hostloc` 运行脚本。 28 | 29 | ## 后台运行 30 | 可以使用 `tmux` 或 `screen` 等工具后台运行。 31 | 32 | ```bash 33 | tmux new -s hostloc 34 | ./hostloc 35 | ``` 36 | 37 | 你也可以使用 systemd 等工具将其作为服务运行。 38 | 39 | ```bash 40 | # /usr/lib/systemd/system/hostloc.service 41 | [Unit] 42 | Description=HostLoc CheckIn Service 43 | After=network.target 44 | Wants=network.target 45 | 46 | [Service] 47 | WorkingDirectory=/path/to/your/hostloc 48 | ExecStart=/path/to/your/hostloc/hostloc 49 | Restart=on-abnormal 50 | RestartSec=5s 51 | KillMode=mixed 52 | 53 | [Install] 54 | WantedBy=multi-user.target 55 | ``` 56 | 57 | ## 配置文件说明 58 | - 可以设置启动程序时立即开始签到 59 | - 可以通过配置文件指定多个帐号,也可以配置 Telegram 推送 60 | 61 | ### 定时任务配置 62 | ```json 63 | { 64 | "time": "0 5 * * *" // 每天 5 点执行 65 | } 66 | ``` 67 | 68 | ### 立即开始签到 69 | ```json 70 | { 71 | "startup": true 72 | } 73 | ``` 74 | 75 | ### 多帐号配置 76 | 77 | ```json 78 | { 79 | "accounts": [ 80 | {"username": "第一个帐号名", "password": "密码"}, 81 | {"username": "第二个帐号名", "password": "密码"}, 82 | {"username": "cuper", "password": "114514"} 83 | ] 84 | } 85 | ``` 86 | 87 | ### Telegram 推送 88 | 89 | ```json 90 | { 91 | "telegram": { 92 | "enable": true, // 开启推送 93 | "token": "abcd12345", //这里填写 bot token 94 | "chat_id": 114514 // 这里填写对话 id 95 | } 96 | } 97 | ``` 98 | 99 | -------------------------------------------------------------------------------- /example.json: -------------------------------------------------------------------------------- 1 | { 2 | "time": "0 5 * * *", 3 | "startup": true, 4 | "telegram": { 5 | "enable": true, 6 | "api": "", 7 | "chat_id": 0 8 | }, 9 | "accounts": [ 10 | {"username": "第一个帐号名", "password": "密码"}, 11 | {"username": "第二个帐号名", "password": "密码"}, 12 | {"username": "cuper", "password": "114514"} 13 | ] 14 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module HostLoc-Daily-CheckIn 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.3 6 | 7 | require ( 8 | github.com/go-co-op/gocron/v2 v2.14.2 9 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 10 | github.com/imroc/req/v3 v3.49.1 11 | github.com/natefinch/lumberjack v2.0.0+incompatible 12 | go.uber.org/zap v1.27.0 13 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 14 | ) 15 | 16 | require ( 17 | github.com/BurntSushi/toml v1.4.0 // indirect 18 | github.com/andybalholm/brotli v1.1.1 // indirect 19 | github.com/cloudflare/circl v1.5.0 // indirect 20 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 21 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 22 | github.com/google/uuid v1.6.0 // indirect 23 | github.com/hashicorp/errwrap v1.1.0 // indirect 24 | github.com/hashicorp/go-multierror v1.1.1 // indirect 25 | github.com/jonboulle/clockwork v0.4.0 // indirect 26 | github.com/klauspost/compress v1.17.11 // indirect 27 | github.com/onsi/ginkgo/v2 v2.22.2 // indirect 28 | github.com/quic-go/qpack v0.5.1 // indirect 29 | github.com/quic-go/quic-go v0.48.2 // indirect 30 | github.com/refraction-networking/utls v1.7.0 // indirect 31 | github.com/robfig/cron/v3 v3.0.1 // indirect 32 | go.uber.org/mock v0.5.0 // indirect 33 | go.uber.org/multierr v1.10.0 // indirect 34 | golang.org/x/crypto v0.36.0 // indirect 35 | golang.org/x/mod v0.22.0 // indirect 36 | golang.org/x/net v0.38.0 // indirect 37 | golang.org/x/sync v0.12.0 // indirect 38 | golang.org/x/sys v0.31.0 // indirect 39 | golang.org/x/text v0.23.0 // indirect 40 | golang.org/x/tools v0.29.0 // indirect 41 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 42 | gopkg.in/yaml.v2 v2.4.0 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= 2 | github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 3 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 4 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 5 | github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= 6 | github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/go-co-op/gocron/v2 v2.14.2 h1:S6CbI7MVfD3S/aPJNLoSg2YcGyEqzEMwUopDejuT4Oc= 10 | github.com/go-co-op/gocron/v2 v2.14.2/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig= 11 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 12 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 13 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 14 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 15 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= 16 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= 17 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 18 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 19 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= 20 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 21 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 22 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 24 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 25 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 26 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 27 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 28 | github.com/imroc/req/v3 v3.49.1 h1:Nvwo02riiPEzh74ozFHeEJrtjakFxnoWNR3YZYuQm9U= 29 | github.com/imroc/req/v3 v3.49.1/go.mod h1:tsOk8K7zI6cU4xu/VWCZVtq9Djw9IWm4MslKzme5woU= 30 | github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= 31 | github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= 32 | github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 33 | github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 34 | github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= 35 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 36 | github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= 37 | github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= 38 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 39 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 40 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 41 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 | github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= 43 | github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= 44 | github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= 45 | github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= 46 | github.com/refraction-networking/utls v1.7.0 h1:9JTnze/Md74uS3ZWiRAabityY0un69rOLXsBf8LGgTs= 47 | github.com/refraction-networking/utls v1.7.0/go.mod h1:lV0Gwc1/Fi+HYH8hOtgFRdHfKo4FKSn6+FdyOz9hRms= 48 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 49 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 50 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 51 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 52 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 53 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 54 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 55 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 56 | go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 57 | go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 58 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 59 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 60 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 61 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 62 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 63 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 64 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 65 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 66 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 67 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 68 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 69 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 70 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 71 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 72 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 73 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 74 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 75 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 76 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 77 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 78 | golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= 79 | golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 80 | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= 81 | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 83 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 84 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 85 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 86 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 87 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 88 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 89 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "HostLoc-Daily-CheckIn/src/config" 5 | "HostLoc-Daily-CheckIn/src/job" 6 | "HostLoc-Daily-CheckIn/src/logger" 7 | "flag" 8 | ) 9 | 10 | var fileName string 11 | 12 | func init() { 13 | flag.StringVar(&fileName, "c", "config.json", "配置文件路径, 默认当前路径下的 config.json") 14 | flag.Parse() 15 | } 16 | 17 | func main() { 18 | log := logger.New() 19 | conf := config.ReadConfig(fileName, log) 20 | 21 | j := job.NewJob( 22 | job.WithLogger(log), 23 | job.WithConfig(conf), 24 | ) 25 | 26 | j.Start() 27 | 28 | select {} 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | Time string `json:"time"` 5 | Startup bool `json:"startup"` 6 | Telegram Telegram `json:"telegram"` 7 | Accounts []Accounts `json:"accounts"` 8 | } 9 | 10 | type Telegram struct { 11 | Enable bool `json:"enable"` 12 | Token string `json:"token"` 13 | ChatID int64 `json:"chat_id"` 14 | } 15 | 16 | type Accounts struct { 17 | Username string `json:"username"` 18 | Password string `json:"password"` 19 | } 20 | -------------------------------------------------------------------------------- /src/config/operate.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "go.uber.org/zap" 6 | "os" 7 | ) 8 | 9 | func ReadConfig(fileName string, log *zap.Logger) *Config { 10 | config := &Config{} 11 | 12 | b, err := os.ReadFile(fileName) 13 | if err != nil { 14 | log.Fatal(err.Error()) 15 | } 16 | 17 | if err = json.Unmarshal(b, config); err != nil { 18 | log.Fatal(err.Error()) 19 | } 20 | 21 | return config 22 | } 23 | -------------------------------------------------------------------------------- /src/job/cron.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "HostLoc-Daily-CheckIn/src/request" 5 | "fmt" 6 | "github.com/go-co-op/gocron/v2" 7 | tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func (j *Job) Start() { 14 | if j.config.Startup { 15 | j.checkIn() 16 | } 17 | 18 | s, err := gocron.NewScheduler() 19 | if err != nil { 20 | j.logger.Fatal(err.Error()) 21 | } 22 | 23 | if _, err = s.NewJob(gocron.CronJob(j.config.Time, false), gocron.NewTask(j.checkIn)); err != nil { 24 | j.logger.Fatal(err.Error()) 25 | } 26 | 27 | s.Start() 28 | } 29 | 30 | func (j *Job) checkIn() { 31 | j.logger.Info("签到任务开始!") 32 | var messages string 33 | 34 | for n, account := range j.config.Accounts { 35 | req := request.New() 36 | j.logger.Info("正在进行第 " + strconv.Itoa(n+1) + " 个账号签到") 37 | 38 | // 获取 formhash 39 | r, err := req.Member() 40 | if err != nil { 41 | j.logger.Error("获取 [formhash] 失败! Error: " + err.Error()) 42 | continue 43 | } 44 | 45 | formhash := j.getHashForm(r) 46 | if formhash == "" { 47 | j.logger.Error("正则匹配 [formhash] 失败! ") 48 | continue 49 | } 50 | j.logger.Info("正则匹配到 [formhash] 成功, Value=" + formhash) 51 | 52 | r, err = req.MainPage() 53 | if err != nil { 54 | j.logger.Error("初始化 [cookies] 失败! Error: " + err.Error()) 55 | continue 56 | } 57 | j.logger.Info("初始化 [cookies] 成功! ") 58 | req.UpdateCookies(r.Cookies()) 59 | time.Sleep(time.Second) 60 | 61 | r, err = req.Login(&account, formhash) 62 | if err != nil { 63 | j.logger.Error(fmt.Sprintf("用户 [%v] 登陆失败\v. Error: %v", account.Username, err.Error())) 64 | continue 65 | } 66 | 67 | j.logger.Info(fmt.Sprintf("用户 [%v] 准备执行空间访问任务.", account.Username)) 68 | req.UpdateCookies(r.Cookies()) 69 | time.Sleep(time.Second) 70 | 71 | r, err = req.CheckCoin() 72 | if err != nil { 73 | j.logger.Error(fmt.Sprintf("用户 [%v] 获取金币数据失败! Error: %v", account.Username, err.Error())) 74 | continue 75 | } 76 | 77 | coins := j.getCoin(r) 78 | j.logger.Info(fmt.Sprintf("用户 [%v] 当前金钱 [%v].", account.Username, coins)) 79 | time.Sleep(time.Second) 80 | 81 | uids := j.randomUID(20, 70000) 82 | for k, uid := range uids { 83 | j.logger.Info(fmt.Sprintf("用户 [%v] 正在进行第 %v 次空间访问.", account.Username, k+1)) 84 | r, err = req.Space(uid) 85 | if err != nil { 86 | j.logger.Error(fmt.Sprintf("用户 [%v] 访问空间失败! Error: %v", account.Username, err.Error())) 87 | time.Sleep(time.Second * 10) 88 | continue 89 | } 90 | 91 | if strings.Contains(r.String(), account.Username) == true { 92 | j.logger.Info(fmt.Sprintf("用户 [%v] 空间访问成功 [Space UID: %v]", account.Username, uid)) 93 | } else { 94 | j.logger.Error(fmt.Sprintf("用户 [%v] 空间访问失败 [Space UID: %v]", account.Username, uid)) 95 | } 96 | 97 | time.Sleep(time.Second * 5) 98 | } 99 | 100 | r, err = req.CheckCoin() 101 | if err != nil { 102 | j.logger.Error(fmt.Sprintf("用户 [%v] 获取金币数据失败! Error: %v", account.Username, err.Error())) 103 | continue 104 | } 105 | 106 | newCoins := j.getCoin(r) 107 | msg := fmt.Sprintf("用户 [%v] 金钱: %v -> %v", account.Username, coins, newCoins) 108 | j.logger.Info(msg) 109 | messages += msg + "\n" 110 | 111 | time.Sleep(time.Second * 10) 112 | } 113 | 114 | if !j.config.Telegram.Enable { 115 | return 116 | } 117 | 118 | if j.config.Telegram.Token == "" || j.config.Telegram.ChatID == 0 { 119 | j.logger.Info("Telegram 配置不全, 取消推送") 120 | return 121 | } 122 | 123 | if messages == "" { 124 | messages = "没有账号进行了签到" 125 | } 126 | 127 | messages = "[LOC 签到小助手]\n\n" + messages 128 | fmt.Println(messages) 129 | 130 | bot, err := tgbotapi.NewBotAPI(j.config.Telegram.Token) 131 | if err != nil { 132 | j.logger.Error("Telegram 推送通知失败! Error: " + err.Error()) 133 | return 134 | } 135 | 136 | text := tgbotapi.NewMessage(j.config.Telegram.ChatID, messages) 137 | if _, err = bot.Send(text); err != nil { 138 | j.logger.Error("Telegram 推送通知失败! Error: " + err.Error()) 139 | } else { 140 | j.logger.Info("Telegram 推送通知成功!") 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/job/option.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "HostLoc-Daily-CheckIn/src/config" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | type Job struct { 9 | logger *zap.Logger 10 | config *config.Config 11 | } 12 | 13 | type Option func(j *Job) 14 | 15 | func (o Option) Apply(j *Job) { 16 | o(j) 17 | } 18 | 19 | func NewJob(opts ...Option) *Job { 20 | job := &Job{} 21 | 22 | for _, o := range opts { 23 | o.Apply(job) 24 | } 25 | 26 | return job 27 | } 28 | 29 | func WithLogger(logger *zap.Logger) func(j *Job) { 30 | return func(j *Job) { 31 | j.logger = logger 32 | } 33 | } 34 | 35 | func WithConfig(config *config.Config) func(j *Job) { 36 | return func(j *Job) { 37 | j.config = config 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/job/parameters.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "github.com/imroc/req/v3" 5 | "golang.org/x/exp/rand" 6 | "regexp" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | func (j *Job) getHashForm(r *req.Response) string { 12 | if strings.Contains(r.String(), "formhash") == true { 13 | rule := regexp.MustCompile(` name="formhash" value="([\da-zA-z]+)"`) 14 | result := rule.FindStringSubmatch(r.String()) 15 | if len(result) <= 1 { 16 | return "" 17 | } 18 | return result[len(result)-1] 19 | } 20 | 21 | return "" 22 | } 23 | 24 | func (j *Job) getCoin(r *req.Response) string { 25 | rule := regexp.MustCompile(`金钱: ([0-9]+)`) 26 | result := rule.FindStringSubmatch(r.String()) 27 | return result[len(result)-1] 28 | } 29 | 30 | func (j *Job) randomUID(count, max int) []int { 31 | rand.Seed(uint64(time.Now().UnixNano())) // 用当前时间作为种子 32 | numbers := make(map[int]struct{}) 33 | var result []int 34 | 35 | for len(result) < count { 36 | num := rand.Intn(max) 37 | if _, exists := numbers[num]; !exists { 38 | numbers[num] = struct{}{} 39 | result = append(result, num) 40 | } 41 | } 42 | 43 | return result 44 | } 45 | -------------------------------------------------------------------------------- /src/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/natefinch/lumberjack" 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | "os" 8 | ) 9 | 10 | func New() *zap.Logger { 11 | //获取编码器,NewJSONEncoder()输出json格式,NewConsoleEncoder()输出普通文本格式 12 | encoderConfig := zap.NewProductionEncoderConfig() 13 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder //指定时间格式 14 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder 15 | encoder := zapcore.NewConsoleEncoder(encoderConfig) 16 | 17 | //文件writeSyncer 18 | fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ 19 | Filename: "hostloc.log", //日志文件存放目录 20 | MaxSize: 3, //文件大小限制,单位MB 21 | MaxBackups: 15, //最大保留日志文件数量 22 | MaxAge: 30, //日志文件保留天数 23 | Compress: false, //是否压缩处理 24 | }) 25 | fileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer, zapcore.AddSync(os.Stdout)), zapcore.DebugLevel) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志 26 | 27 | return zap.New(fileCore, zap.AddCaller()) //AddCaller()为显示文件名和行号 28 | } 29 | -------------------------------------------------------------------------------- /src/request/option.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "github.com/imroc/req/v3" 5 | "net/http" 6 | ) 7 | 8 | type Request struct { 9 | client *req.Client 10 | cookies []*http.Cookie 11 | } 12 | 13 | func New() *Request { 14 | return &Request{ 15 | client: req.C(). 16 | SetUserAgent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"). 17 | SetTLSFingerprintChrome(), 18 | } 19 | } 20 | 21 | func (r *Request) UpdateCookies(cookies []*http.Cookie) { 22 | r.cookies = cookies 23 | } 24 | -------------------------------------------------------------------------------- /src/request/requests.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import ( 4 | "HostLoc-Daily-CheckIn/src/config" 5 | "fmt" 6 | "github.com/imroc/req/v3" 7 | "net/url" 8 | ) 9 | 10 | func (r *Request) Member() (*req.Response, error) { 11 | u := url.Values{} 12 | 13 | u.Add("mod", "logging") 14 | u.Add("action", "login") 15 | u.Add("infloat", "yes") 16 | u.Add("handlekey", "login") 17 | u.Add("inajax", "1") 18 | u.Add("ajaxtarget", "fwin_content_login") 19 | 20 | return r.client.R(). 21 | SetQueryString(u.Encode()). 22 | Get("https://hostloc.com/member.php") 23 | } 24 | 25 | func (r *Request) MainPage() (*req.Response, error) { 26 | return r.client.R(). 27 | Get("https://hostloc.com") 28 | } 29 | 30 | func (r *Request) Login(account *config.Accounts, formhash string) (*req.Response, error) { 31 | u := url.Values{} 32 | 33 | u.Add("mod", "logging") 34 | u.Add("action", "login") 35 | u.Add("loginsubmit", "yes") 36 | u.Add("infloat", "yes") 37 | u.Add("lssubmit", "yes") 38 | u.Add("inajax", "1") 39 | 40 | data := map[string]string{ 41 | "fastloginfield": "username", 42 | "username": account.Username, 43 | "cookietime": "2592000", 44 | "password": account.Password, 45 | "formhash": formhash, 46 | "quickforward": "yes", 47 | "handlekey": "ls", 48 | } 49 | 50 | return r.client.R(). 51 | SetQueryString(u.Encode()). 52 | SetFormData(data). 53 | SetCookies(r.cookies...). 54 | Post("https://hostloc.com/member.php") 55 | 56 | } 57 | 58 | func (r *Request) CheckCoin() (*req.Response, error) { 59 | u := url.Values{} 60 | 61 | u.Add("mod", "spacecp") 62 | u.Add("ac", "credit") 63 | u.Add("showcredit", "1") 64 | u.Add("inajax", "1") 65 | 66 | return r.client.R(). 67 | SetQueryString(u.Encode()). 68 | SetCookies(r.cookies...). 69 | Get("https://hostloc.com/home.php") 70 | } 71 | 72 | func (r *Request) Space(uid int) (*req.Response, error) { 73 | return r.client.R(). 74 | SetCookies(r.cookies...). 75 | Get(fmt.Sprintf("https://hostloc.com/space-uid-%v.html", uid)) 76 | } 77 | --------------------------------------------------------------------------------