🎨 重构代码以使用新的微信客户端库,更新相关函数以支持新的API;优化内存和网络统计显示逻辑
All checks were successful
BuildImage / build-image (push) Successful in 2m37s

This commit is contained in:
李寻欢 2025-04-21 16:02:39 +08:00
parent dc28090064
commit e6457b7b22
16 changed files with 330 additions and 1015 deletions

61
go.mod
View File

@ -3,13 +3,14 @@ module gitee.ltd/lxh/wechat-robot
go 1.24.0
require (
github.com/docker/docker v25.0.3+incompatible
gitee.ltd/lxh/xybot v0.0.3
github.com/docker/docker v28.1.1+incompatible
github.com/docker/go-connections v0.5.0
github.com/go-co-op/gocron/v2 v2.16.1
github.com/go-resty/resty/v2 v2.10.0
github.com/goccy/go-json v0.10.5
github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/template/html/v2 v2.1.3
github.com/google/uuid v1.6.0
github.com/spf13/viper v1.20.1
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.11
@ -19,64 +20,70 @@ require (
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/go-sql-driver/mysql v1.9.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.4 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/logto-io/go v1.0.6 // indirect
github.com/logto-io/go/v2 v2.0.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/valyala/fasthttp v1.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
)

178
go.sum
View File

@ -1,9 +1,13 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gitee.ltd/lxh/xybot v0.0.3 h1:/xRCnW2nMtx/hdV7TdpEcL3Sh8f9oBGHToONk0yGstA=
gitee.ltd/lxh/xybot v0.0.3/go.mod h1:jYfEAQ3WPsST/PY4fEEVFjU6KtMocxn3sQi78I+vdxc=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@ -13,8 +17,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@ -23,19 +27,24 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-co-op/gocron/v2 v2.16.1 h1:ux/5zxVRveCaCuTtNI3DiOk581KC1KpJbpJFYUEVYwo=
github.com/go-co-op/gocron/v2 v2.16.1/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
@ -50,6 +59,8 @@ github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -58,12 +69,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@ -74,22 +85,31 @@ github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbd
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/logto-io/go v1.0.6 h1:p+KHiXk6v0vIuq+1sYabjbgsnHQP5lV4t2ZnkLNtRkM=
github.com/logto-io/go v1.0.6/go.mod h1:owKJJjlaiQxbBGeIDFQTTtufp7ANb9odBXyqUheqWFI=
github.com/logto-io/go/v2 v2.0.0 h1:2Eo/S8nfIVNEa2VnzFODc3796vD2BwEOS4f+QboPG3U=
github.com/logto-io/go/v2 v2.0.0/go.mod h1:SqrSoACLSnGgO7yam0ecpEjc+ze1tbBaj+Q6OJVL7G8=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@ -98,26 +118,27 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
@ -133,13 +154,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw=
github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
@ -150,100 +170,74 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMey
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -10,7 +10,6 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
@ -109,7 +108,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
isUserNetwork := false
if cfg.Network != "bridge" && cfg.Network != "host" && cfg.Network != "none" {
// 检查网络是否存在
_, err := cli.NetworkInspect(ctx, cfg.Network, types.NetworkInspectOptions{})
_, err := cli.NetworkInspect(ctx, cfg.Network, network.InspectOptions{})
if err == nil {
isUserNetwork = true
}
@ -153,7 +152,7 @@ func CreateContainer(ctx context.Context, cfg *config.DockerConfig, name string,
// getNextAvailableIPInNetwork 获取网络中下一个可用的IP地址
func getNextAvailableIPInNetwork(ctx context.Context, cli *client.Client, networkName string) (string, error) {
// 获取网络信息
networkResource, err := cli.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
networkResource, err := cli.NetworkInspect(ctx, networkName, network.InspectOptions{})
if err != nil {
return "", fmt.Errorf("无法检查网络: %w", err)
}
@ -176,7 +175,7 @@ func getNextAvailableIPInNetwork(ctx context.Context, cli *client.Client, networ
}
// 获取网络中的所有容器
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
if err != nil {
return "", fmt.Errorf("无法列出容器: %w", err)
}
@ -300,7 +299,7 @@ func incrementIP(ip net.IP) net.IP {
// getNextAvailableIP 获取网络中下一个可用的IP地址
func getNextAvailableIP(ctx context.Context, cli *client.Client, networkName string) (string, error) {
// 获取网络信息
networkResource, err := cli.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
networkResource, err := cli.NetworkInspect(ctx, networkName, network.InspectOptions{})
if err != nil {
return "", fmt.Errorf("无法检查网络: %w", err)
}
@ -324,7 +323,7 @@ func getNextAvailableIP(ctx context.Context, cli *client.Client, networkName str
// 获取现有容器的IP地址
existingIPs := make(map[string]bool)
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
if err != nil {
return "", fmt.Errorf("无法列出容器: %w", err)
}
@ -493,9 +492,7 @@ func findMaxPortForImage(ctx context.Context, cli *client.Client, imageName stri
maxPort := 9000
// 获取所有容器
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
All: true,
})
containers, err := cli.ContainerList(ctx, container.ListOptions{All: true})
if err != nil {
return maxPort, err
}
@ -518,7 +515,7 @@ func findMaxPortForImage(ctx context.Context, cli *client.Client, imageName stri
// StartContainer 启动容器
func StartContainer(ctx context.Context, containerID string) error {
cli := GetClient()
return cli.ContainerStart(ctx, containerID, types.ContainerStartOptions{})
return cli.ContainerStart(ctx, containerID, container.StartOptions{})
}
// StopContainer 停止容器
@ -531,7 +528,7 @@ func StopContainer(ctx context.Context, containerID string, timeout *time.Durati
// RemoveContainer 删除容器
func RemoveContainer(ctx context.Context, containerID string, force bool) error {
cli := GetClient()
return cli.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
return cli.ContainerRemove(ctx, containerID, container.RemoveOptions{
Force: force,
})
}
@ -548,7 +545,7 @@ func ListContainers(ctx context.Context, filterArgs map[string][]string) ([]Cont
}
}
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
containers, err := cli.ContainerList(ctx, container.ListOptions{
All: true, // 包括未运行的容器
Filters: filterSet,
})
@ -575,7 +572,7 @@ func ListContainers(ctx context.Context, filterArgs map[string][]string) ([]Cont
func GetContainerLogs(ctx context.Context, containerID string, tail string) (string, error) {
cli := GetClient()
options := types.ContainerLogsOptions{
options := container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Tail: tail,

View File

@ -1,97 +0,0 @@
package docker
import (
"bufio"
"context"
"io"
"github.com/docker/docker/api/types"
)
// ExecConfig 命令执行配置
type ExecConfig struct {
Cmd []string
AttachStdout bool
AttachStderr bool
Detach bool
Tty bool
}
// DefaultExecConfig 返回默认的命令执行配置
func DefaultExecConfig(cmd []string) ExecConfig {
return ExecConfig{
Cmd: cmd,
AttachStdout: true,
AttachStderr: true,
Detach: false,
Tty: false,
}
}
// ExecuteCommand 在容器中执行命令并返回执行ID
func ExecuteCommand(ctx context.Context, containerID string, config ExecConfig) (string, error) {
cli := GetClient()
execConfig := types.ExecConfig{
Cmd: config.Cmd,
AttachStdout: config.AttachStdout,
AttachStderr: config.AttachStderr,
Detach: config.Detach,
Tty: config.Tty,
}
execResp, err := cli.ContainerExecCreate(ctx, containerID, execConfig)
if err != nil {
return "", err
}
return execResp.ID, nil
}
// StartExecCommand 启动已创建的命令执行
func StartExecCommand(ctx context.Context, execID string) (*bufio.Reader, error) {
cli := GetClient()
resp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
if err != nil {
return nil, err
}
return resp.Reader, nil
}
// GetExecOutput 获取执行命令的输出
func GetExecOutput(ctx context.Context, execID string) (string, error) {
cli := GetClient()
// 附加到命令执行
resp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
if err != nil {
return "", err
}
defer resp.Close()
// 读取输出
output, err := io.ReadAll(resp.Reader)
if err != nil {
return "", err
}
return string(output), nil
}
// GetExecStatus 获取命令执行状态
func GetExecStatus(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
cli := GetClient()
return cli.ContainerExecInspect(ctx, execID)
}
// ExecuteCommandWithOutput 执行命令并直接返回输出
func ExecuteCommandWithOutput(ctx context.Context, containerID string, cmd []string) (string, error) {
execID, err := ExecuteCommand(ctx, containerID, DefaultExecConfig(cmd))
if err != nil {
return "", err
}
return GetExecOutput(ctx, execID)
}

View File

@ -2,13 +2,10 @@ package docker
import (
"context"
"log"
"sync"
"time"
"gorm.io/gorm"
"gitee.ltd/lxh/wechat-robot/internal/model"
)
// ContainerMonitor 容器监控器
@ -37,34 +34,34 @@ func NewContainerMonitor(db *gorm.DB, interval time.Duration) *ContainerMonitor
// Start 启动监控
func (m *ContainerMonitor) Start(ctx context.Context) {
m.mutex.Lock()
if m.monitorActive {
m.mutex.Unlock()
return
}
m.monitorActive = true
m.mutex.Unlock()
log.Println("Starting container monitor...")
// 启动监控协程
go func() {
ticker := time.NewTicker(m.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
m.checkRobots(context.Background())
case <-m.stopChan:
log.Println("Container monitor stopped")
return
case <-ctx.Done():
log.Println("Container monitor stopped due to context cancellation")
return
}
}
}()
//m.mutex.Lock()
//if m.monitorActive {
// m.mutex.Unlock()
// return
//}
//m.monitorActive = true
//m.mutex.Unlock()
//
//log.Println("Starting container monitor...")
//
//// 启动监控协程
//go func() {
// ticker := time.NewTicker(m.interval)
// defer ticker.Stop()
//
// for {
// select {
// case <-ticker.C:
// m.checkRobots(context.Background())
// case <-m.stopChan:
// log.Println("Container monitor stopped")
// return
// case <-ctx.Done():
// log.Println("Container monitor stopped due to context cancellation")
// return
// }
// }
//}()
}
// Stop 停止监控
@ -93,50 +90,50 @@ func (m *ContainerMonitor) RemoveRobot(containerID string) {
}
// checkRobots 检查所有机器人状态
func (m *ContainerMonitor) checkRobots(ctx context.Context) {
m.mutex.RLock()
robotIDs := make([]string, 0, len(m.robots))
for id := range m.robots {
robotIDs = append(robotIDs, id)
}
m.mutex.RUnlock()
for _, containerID := range robotIDs {
// 使用新的上下文,避免一个检查失败影响其他检查
checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// 获取机器人的当前状态
status, errMsg, err := GetWechatBotStatus(checkCtx, containerID)
if err != nil {
log.Printf("Error checking robot %s: %v", containerID, err)
continue
}
// 更新数据库中的状态
robot := &model.Robot{}
result := m.db.Where("container_id = ?", containerID).First(robot)
if result.Error != nil {
log.Printf("Error finding robot %s in database: %v", containerID, result.Error)
continue
}
// 只有状态变化时才更新
if robot.Status != status {
robot.Status = status
robot.ErrorMessage = errMsg
// 如果状态变为在线,更新登录时间
if status == model.RobotStatusOnline {
now := time.Now()
robot.LastLoginAt = &now
}
if err := m.db.Save(robot).Error; err != nil {
log.Printf("Error updating robot %s status: %v", containerID, err)
} else {
log.Printf("Robot %s status updated to %s", containerID, status)
}
}
}
}
//func (m *ContainerMonitor) checkRobots(ctx context.Context) {
// m.mutex.RLock()
// robotIDs := make([]string, 0, len(m.robots))
// for id := range m.robots {
// robotIDs = append(robotIDs, id)
// }
// m.mutex.RUnlock()
//
// for _, containerID := range robotIDs {
// // 使用新的上下文,避免一个检查失败影响其他检查
// checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
// defer cancel()
//
// // 获取机器人的当前状态
// status, errMsg, err := GetWechatBotStatus(checkCtx, containerID)
// if err != nil {
// log.Printf("Error checking robot %s: %v", containerID, err)
// continue
// }
//
// // 更新数据库中的状态
// robot := &model.Robot{}
// result := m.db.Where("container_id = ?", containerID).First(robot)
// if result.Error != nil {
// log.Printf("Error finding robot %s in database: %v", containerID, result.Error)
// continue
// }
//
// // 只有状态变化时才更新
// if robot.Status != status {
// robot.Status = status
// robot.ErrorMessage = errMsg
//
// // 如果状态变为在线,更新登录时间
// if status == model.RobotStatusOnline {
// now := time.Now()
// robot.LastLoginAt = &now
// }
//
// if err := m.db.Save(robot).Error; err != nil {
// log.Printf("Error updating robot %s status: %v", containerID, err)
// } else {
// log.Printf("Robot %s status updated to %s", containerID, status)
// }
// }
// }
//}

View File

@ -2,15 +2,8 @@ package docker
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/go-resty/resty/v2"
"gitee.ltd/lxh/wechat-robot/internal/config"
"gitee.ltd/lxh/wechat-robot/internal/model"
"log"
)
const (
@ -20,13 +13,6 @@ const (
WechatBotLabelValue = "wechat-bot"
)
// 定义一个HTTP客户端生成函数用于向容器内的API发出请求
func newHTTPClient() *resty.Client {
return resty.New().
SetTimeout(30*time.Second).
SetHeader("Content-Type", "application/json")
}
// CreateRobotContainer 创建微信机器人容器
func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotName string, port int) (string, string, error) {
// 创建容器标签
@ -63,377 +49,3 @@ func CreateRobotContainer(ctx context.Context, cfg *config.DockerConfig, robotNa
return containerID, containerHost, nil
}
// GetLoginQRCode 获取登录二维码(旧方法)
func GetLoginQRCode(ctx context.Context, containerID string) (string, error) {
return ExecuteCommandWithOutput(ctx, containerID, []string{"cat", "/data/qrcode.png"})
}
// GetQRCode 获取登录二维码
func GetQRCode(ctx context.Context, containerHost string) (*BaseResponse[QRCodeResponse], error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/GetQRCode", containerHost)
var response BaseResponse[QRCodeResponse]
resp, err := client.R().
SetContext(ctx).
SetBody("{}").
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("获取二维码请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("获取二维码API返回错误状态码: %d", resp.StatusCode())
}
if !response.Success {
return nil, fmt.Errorf("获取二维码API错误: %s", response.Message)
}
return &response, nil
}
// CheckUuid 检查二维码状态
func CheckUuid(ctx context.Context, uuid string, containerHost string) (*BaseResponse[CheckUuidResponse], error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/CheckUuid", containerHost)
reqBody := map[string]string{"Uuid": uuid}
var response BaseResponse[CheckUuidResponse]
resp, err := client.R().
SetContext(ctx).
SetBody(reqBody).
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("检查二维码状态请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("检查二维码状态API返回错误状态码: %d", resp.StatusCode())
}
return &response, nil
}
// AwakenLogin 唤醒登录
func AwakenLogin(ctx context.Context, wxid string, containerHost string) (*BaseResponse[AwakenLoginResponse], error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/AwakenLogin", containerHost)
reqBody := map[string]string{"Wxid": wxid}
var response BaseResponse[AwakenLoginResponse]
resp, err := client.R().
SetContext(ctx).
SetBody(reqBody).
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("唤醒登录请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("唤醒登录API返回错误状态码: %d", resp.StatusCode())
}
return &response, nil
}
// AutoHeartbeatStart 开启自动心跳
func AutoHeartbeatStart(ctx context.Context, wxid string, containerHost string) (*BaseResponse[AutoHeartbeatResponse], error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/AutoHeartbeatStart", containerHost)
reqBody := map[string]string{"Wxid": wxid}
var response BaseResponse[AutoHeartbeatResponse]
resp, err := client.R().
SetContext(ctx).
SetBody(reqBody).
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("启动自动心跳请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("启动自动心跳API返回错误状态码: %d", resp.StatusCode())
}
if !response.Success {
return nil, fmt.Errorf("启动自动心跳API错误: %s", response.Message)
}
return &response, nil
}
// AutoHeartbeatStatus 获取自动心跳状态
func AutoHeartbeatStatus(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatStatusResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/AutoHeartbeatStatus", containerHost)
reqBody := map[string]string{"Wxid": wxid}
var response AutoHeartbeatStatusResponse
resp, err := client.R().
SetContext(ctx).
SetBody(reqBody).
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("获取自动心跳状态请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("获取自动心跳状态API返回错误状态码: %d", resp.StatusCode())
}
return &response, nil
}
// AutoHeartbeatStop 停止自动心跳
func AutoHeartbeatStop(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/AutoHeartbeatStop", containerHost)
reqBody := map[string]string{"Wxid": wxid}
var response AutoHeartbeatResponse
resp, err := client.R().
SetContext(ctx).
SetBody(reqBody).
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("停止自动心跳请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("停止自动心跳API返回错误状态码: %d", resp.StatusCode())
}
if !response.Success {
return nil, fmt.Errorf("停止自动心跳API错误: %s", response.Message)
}
return &response, nil
}
// LogOut 登出微信
func LogOut(ctx context.Context, wxid string, containerHost string) (*AutoHeartbeatResponse, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/LogOut", containerHost)
reqBody := map[string]string{"Wxid": wxid}
var response AutoHeartbeatResponse
resp, err := client.R().
SetContext(ctx).
SetBody(reqBody).
SetResult(&response).
Post(url)
if err != nil {
return nil, fmt.Errorf("退出登录请求失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("退出登录API返回错误状态码: %d", resp.StatusCode())
}
if !response.Success {
return nil, fmt.Errorf("退出登录API错误: %s", response.Message)
}
return &response, nil
}
// GetWechatBotStatus 获取微信机器人状态(使用HTTP请求替代)
func GetWechatBotStatus(ctx context.Context, containerID string) (model.RobotStatus, string, error) {
// 检查容器状态
status, err := GetContainerStatus(ctx, containerID)
if err != nil {
return model.RobotStatusError, "", fmt.Errorf("failed to get container status: %w", err)
}
if status != "running" {
return model.RobotStatusOffline, "", nil
}
// 获取容器IP(简化处理默认使用localhost)
hostIP := "localhost"
client := newHTTPClient()
url := fmt.Sprintf("http://%s:3000/api/status", hostIP)
var response BaseResponse[any]
resp, err := client.R().
SetContext(ctx).
SetResult(&response).
Get(url)
if err != nil {
return model.RobotStatusError, "", fmt.Errorf("failed to get status result: %w", err)
}
if resp.StatusCode() != 200 {
return model.RobotStatusError, "", fmt.Errorf("status API returned non-200 status code: %d", resp.StatusCode())
}
if !response.Success {
return model.RobotStatusError, response.Message, nil
}
// 解析状态数据
data, ok := response.Data.(map[string]interface{})
if !ok {
return model.RobotStatusError, "invalid status data format", nil
}
isLoggedIn, ok := data["isLoggedIn"].(bool)
if !ok {
return model.RobotStatusError, "invalid login status format", nil
}
if isLoggedIn {
return model.RobotStatusOnline, "", nil
}
return model.RobotStatusOffline, "", nil
}
// ListWechatBots 列出所有微信机器人容器
func ListWechatBots(ctx context.Context) ([]ContainerInfo, error) {
// 过滤条件:所有微信机器人容器
filter := map[string][]string{
"label": {fmt.Sprintf("%s=%s", WechatBotLabelKey, WechatBotLabelValue)},
}
return ListContainers(ctx, filter)
}
// GetWechatContacts 获取微信联系人列表
func GetWechatContacts(ctx context.Context, containerHost string) (string, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/api/contacts", containerHost)
resp, err := client.R().
SetContext(ctx).
Get(url)
if err != nil {
return "", fmt.Errorf("获取联系人列表请求失败: %w", err)
}
return resp.String(), nil
}
// GetWechatGroupMembers 获取微信群成员
func GetWechatGroupMembers(ctx context.Context, groupID string, containerHost string) (string, error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/api/group/%s/members", containerHost, groupID)
resp, err := client.R().
SetContext(ctx).
Get(url)
if err != nil {
return "", fmt.Errorf("获取群成员请求失败: %w", err)
}
return resp.String(), nil
}
// GetWechatMessages 获取微信消息历史
func GetWechatMessages(ctx context.Context, containerHost, robotWxId string) (msg []Message, isOffline bool, err error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/Sync", containerHost)
var response BaseResponse[Sync]
_, err = client.R().
SetContext(ctx).
SetBody(map[string]any{
"Scene": 0,
"Synckey": "",
"Wxid": robotWxId,
}).
SetResult(&response).
Post(url)
if err != nil {
err = fmt.Errorf("获取消息历史请求失败: %w", err)
return
}
// 判断是否离线
if strings.Contains(response.Message, "用户可能退出") || strings.Contains(response.Message, "数据[DecryptData]失败") {
isOffline = true
}
msg = response.Data.AddMsgs
return
}
// GetContactList
// @description: 获取联系人微信Id
// @param ctx
// @param containerHost
// @param robotWxId
// @return list
// @return err
func GetContactList(ctx context.Context, containerHost, robotWxId string) (list []string, err error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/GetContractList", containerHost)
var response BaseResponse[ContactListResponse]
_, err = client.R().
SetContext(ctx).
SetBody(map[string]any{
"CurrentChatroomContactSeq": 0,
"CurrentWxcontactSeq": 0,
"Wxid": robotWxId,
}).
SetResult(&response).
Post(url)
list = response.Data.ContactUsernameList
return
}
// GetContactDetail
// @description: 获取联系人详细信息
// @param ctx
// @param containerHost
// @param robotWxId
// @param wxId
// @return info
// @return err
func GetContactDetail(ctx context.Context, containerHost, robotWxId string, wxId []string) (info []ContactDetailInfoItem, err error) {
client := newHTTPClient()
url := fmt.Sprintf("http://%s/GetContact", containerHost)
var response BaseResponse[ContactDetailInfoResponse]
_, err = client.R().
SetContext(ctx).
SetBody(map[string]any{
"Chatroom": "",
"RequestWxids": strings.Join(wxId, ","),
"Wxid": robotWxId,
}).
SetResult(&response).
Post(url)
info = response.Data.ContactList
return
}

View File

@ -1,218 +0,0 @@
package docker
import "gitee.ltd/lxh/wechat-robot/internal/types"
// BaseResponse API响应结构
type BaseResponse[T any] struct {
Success bool `json:"Success"`
Code int `json:"Code"`
Message string `json:"Message"`
Data T `json:"Data"`
}
// QRCodeResponse 获取二维码响应
type QRCodeResponse struct {
UUID string `json:"Uuid"`
ExpiredTime string `json:"ExpiredTime"`
QRCodeBase64 string `json:"QRCodeBase64"`
QRCodeURL string `json:"QRCodeURL"`
}
// CheckUuidResponse 检查二维码状态响应
type CheckUuidResponse struct {
Uuid string `json:"uuid"`
Status int `json:"status"` // 状态
PushLoginUrlExpiredTime int `json:"pushLoginUrlexpiredTime"` // 推送登录url过期时间
ExpiredTime int `json:"expiredTime"` // 过期时间(秒)
HeadImgUrl string `json:"headImgUrl"` // 头像
NickName string `json:"nickName"` // 昵称
AcctSectResp map[string]any `json:"acctSectResp"` // 账号信息-登录成功之后才有
}
// AwakenLoginResponse 唤醒登录响应
type AwakenLoginResponse struct {
QrCodeResponse struct {
BaseResponse any `json:"BaseResponse"`
BlueToothBroadCastContent any `json:"BlueToothBroadCastContent"`
BlueToothBroadCastUuid string `json:"BlueToothBroadCastUuid"`
CheckTime int `json:"CheckTime"`
ExpiredTime int `json:"ExpiredTime"`
NotifyKey any `json:"NotifyKey"`
Uuid string `json:"Uuid"`
} `json:"QrCodeResponse"`
}
// AutoHeartbeatResponse 心跳响应
type AutoHeartbeatResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data struct{}
}
// AutoHeartbeatStatusResponse 心跳状态响应
type AutoHeartbeatStatusResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Running bool `json:"running"`
}
// Sync
// @description: 同步微信消息返回值
type Sync struct {
ModUserInfos any `json:"ModUserInfos"`
ModContacts any `json:"ModContacts"`
DelContacts any `json:"DelContacts"`
ModUserImgs any `json:"ModUserImgs"`
FunctionSwitchs any `json:"FunctionSwitchs"`
UserInfoExts any `json:"UserInfoExts"`
AddMsgs []Message `json:"AddMsgs"`
ContinueFlag int `json:"ContinueFlag"`
KeyBuf struct {
ILen int `json:"iLen"`
Buffer string `json:"buffer"`
} `json:"KeyBuf"`
Status int `json:"Status"`
Continue int `json:"Continue"`
Time int `json:"Time"`
UnknownCmdId string `json:"UnknownCmdId"`
Remarks string `json:"Remarks"`
}
// Message
// @description: 微信消息
type Message struct {
MsgId int `json:"MsgId"`
FromUserName struct {
String string `json:"string"`
} `json:"FromUserName"`
ToWxid struct {
String string `json:"string"`
} `json:"ToWxid"`
MsgType types.MessageType `json:"MsgType"`
Content struct {
String string `json:"string"`
} `json:"Content"`
Status int `json:"Status"`
ImgStatus int `json:"ImgStatus"`
ImgBuf struct {
ILen int `json:"iLen"`
} `json:"ImgBuf"`
CreateTime int `json:"CreateTime"`
MsgSource string `json:"MsgSource"`
NewMsgId int64 `json:"NewMsgId"`
MsgSeq int `json:"MsgSeq"`
PushContent string `json:"PushContent,omitempty"`
}
// ===================================================================
type ContactListResponse struct {
BaseResponse struct {
Ret int `json:"ret"`
ErrMsg struct {
String string `json:"string"`
} `json:"errMsg"`
} `json:"BaseResponse"`
CurrentWxcontactSeq int `json:"CurrentWxcontactSeq"`
CurrentChatRoomContactSeq int `json:"CurrentChatRoomContactSeq"`
CountinueFlag int `json:"CountinueFlag"`
ContactUsernameList []string `json:"ContactUsernameList"` // 联系人微信Id列表
}
// ContactDetailInfo
// @description: 联系人详情
type ContactDetailInfoResponse struct {
BaseResponse struct {
Ret int `json:"ret"`
ErrMsg struct {
} `json:"errMsg"`
} `json:"BaseResponse"`
ContactCount int `json:"ContactCount"`
ContactList []ContactDetailInfoItem `json:"ContactList"`
Ret []int `json:"Ret"`
Ticket []struct {
} `json:"Ticket"`
}
// ContactDetailInfoItem
// @description: 详情
type ContactDetailInfoItem struct {
UserName struct {
String string `json:"string"`
} `json:"UserName"` // 微信Id
NickName struct {
String string `json:"string"`
} `json:"NickName"` // 昵称
Pyinitial struct {
String string `json:"string"`
} `json:"Pyinitial"` // 昵称拼音首字母大写
QuanPin struct {
String string `json:"string"`
} `json:"QuanPin"` // 昵称拼音全拼小写
Sex int `json:"Sex"` // 性别 0未知 1男 2
ImgBuf struct {
ILen int `json:"iLen"`
} `json:"ImgBuf"`
BitMask int64 `json:"BitMask"`
BitVal int `json:"BitVal"`
ImgFlag int `json:"ImgFlag"`
Remark struct {
} `json:"Remark"`
RemarkPyinitial struct {
} `json:"RemarkPyinitial"`
RemarkQuanPin struct {
} `json:"RemarkQuanPin"`
ContactType int `json:"ContactType"`
RoomInfoCount int `json:"RoomInfoCount"`
DomainList struct {
} `json:"DomainList"`
ChatRoomNotify int `json:"ChatRoomNotify"`
AddContactScene int `json:"AddContactScene"`
Province string `json:"Province"` // 省份
City string `json:"City"` // 城市
Signature string `json:"Signature"` // 个性签名
PersonalCard int `json:"PersonalCard"`
HasWeiXinHdHeadImg int `json:"HasWeiXinHdHeadImg"`
VerifyFlag int `json:"VerifyFlag"`
Level int `json:"Level"`
Source int `json:"Source"`
Alias string `json:"Alias"` // 微信号
WeiboFlag int `json:"WeiboFlag"`
AlbumStyle int `json:"AlbumStyle"`
AlbumFlag int `json:"AlbumFlag"`
SnsUserInfo struct {
SnsFlag int `json:"SnsFlag"`
SnsBgimgId string `json:"SnsBgimgId"` // 朋友圈背景图
SnsBgobjectId float64 `json:"SnsBgobjectId"`
SnsFlagEx int `json:"SnsFlagEx"`
} `json:"SnsUserInfo"`
Country string `json:"Country"` // 国家
BigHeadImgUrl string `json:"BigHeadImgUrl"` // 大头像地址
SmallHeadImgUrl string `json:"SmallHeadImgUrl"` // 小头像地址
MyBrandList string `json:"MyBrandList"`
CustomizedInfo struct {
BrandFlag int `json:"BrandFlag"`
} `json:"CustomizedInfo"`
HeadImgMd5 string `json:"HeadImgMd5"`
EncryptUserName string `json:"EncryptUserName"`
AdditionalContactList struct {
LinkedinContactItem struct {
} `json:"LinkedinContactItem"`
} `json:"AdditionalContactList"`
ChatroomVersion int `json:"ChatroomVersion"`
ChatroomMaxCount int `json:"ChatroomMaxCount"`
ChatroomAccessType int `json:"ChatroomAccessType"`
NewChatroomData struct {
MemberCount int `json:"MemberCount"`
InfoMask int `json:"InfoMask"`
} `json:"NewChatroomData"`
DeleteFlag int `json:"DeleteFlag"`
LabelIdlist string `json:"LabelIdlist"`
PhoneNumListInfo struct {
Count int `json:"Count"`
} `json:"PhoneNumListInfo"`
ChatroomInfoVersion int `json:"ChatroomInfoVersion"`
DeleteContactScene int `json:"DeleteContactScene"`
ChatroomStatus int `json:"ChatroomStatus"`
ExtFlag int `json:"ExtFlag"`
}

View File

@ -4,9 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/docker/docker/api/types/container"
"time"
"github.com/docker/docker/api/types"
)
// ContainerStats 容器统计数据结构
@ -78,7 +77,7 @@ func GetStats(ctx context.Context, containerID string) (*ContainerStats, error)
defer stats.Body.Close()
// 解析统计数据JSON
var statsJSON types.StatsJSON
var statsJSON container.StatsResponse
if err := json.NewDecoder(stats.Body).Decode(&statsJSON); err != nil {
return nil, fmt.Errorf("解析容器统计数据失败: %w", err)
}
@ -111,7 +110,7 @@ func GetStats(ctx context.Context, containerID string) (*ContainerStats, error)
}
// 计算CPU使用百分比
func calculateCPUPercentage(stats *types.StatsJSON) float64 {
func calculateCPUPercentage(stats *container.StatsResponse) float64 {
if stats == nil {
return 0.0
}
@ -133,7 +132,7 @@ func calculateCPUPercentage(stats *types.StatsJSON) float64 {
}
// 获取内存使用量
func getMemoryUsage(stats *types.StatsJSON) int64 {
func getMemoryUsage(stats *container.StatsResponse) int64 {
if stats == nil || stats.MemoryStats.Usage == 0 {
return 0
}
@ -147,7 +146,7 @@ func getMemoryUsage(stats *types.StatsJSON) int64 {
}
// 获取内存限制
func getMemoryLimit(stats *types.StatsJSON) int64 {
func getMemoryLimit(stats *container.StatsResponse) int64 {
if stats == nil || stats.MemoryStats.Limit == 0 {
return 0
}
@ -155,7 +154,7 @@ func getMemoryLimit(stats *types.StatsJSON) int64 {
}
// 计算内存使用百分比
func calculateMemoryPercentage(stats *types.StatsJSON) float64 {
func calculateMemoryPercentage(stats *container.StatsResponse) float64 {
if stats == nil {
return 0.0
}
@ -177,7 +176,7 @@ type networkStats struct {
}
// 获取网络统计数据
func getNetworkStats(stats *types.StatsJSON) networkStats {
func getNetworkStats(stats *container.StatsResponse) networkStats {
if stats == nil || len(stats.Networks) == 0 {
return networkStats{}
}
@ -198,7 +197,7 @@ type ioStats struct {
}
// 获取IO统计数据
func getIOStats(stats *types.StatsJSON) ioStats {
func getIOStats(stats *container.StatsResponse) ioStats {
if stats == nil {
return ioStats{}
}

View File

@ -1,11 +1,11 @@
package handler
import (
"context"
"encoding/json"
"errors"
"gitee.ltd/lxh/wechat-robot/internal/config"
"gitee.ltd/lxh/wechat-robot/internal/tasks"
"gitee.ltd/lxh/xybot"
"github.com/gofiber/fiber/v2/log"
"strconv"
"time"
@ -13,7 +13,6 @@ import (
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitee.ltd/lxh/wechat-robot/internal/docker"
"gitee.ltd/lxh/wechat-robot/internal/model"
)
@ -51,12 +50,15 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
})
}
// 调用CheckUuid API检查二维码状态
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 调用CheckUuid API检查二维码状态传递容器访问地址
response, err := docker.CheckUuid(ctx, uuid, robot.ContainerHost)
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
if err != nil {
return c.JSON(fiber.Map{
"success": false,
"message": "创建微信客户端失败: " + err.Error(),
})
}
response, err := robotCli.Login.CheckUuid(uuid)
if err != nil {
return c.JSON(fiber.Map{
"success": false,
@ -70,20 +72,20 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
}
// 如果返回status=1表示已扫码暂存一下昵称和头像
if response.Data.Status == 1 {
robot.Nickname = response.Data.NickName
robot.Avatar = response.Data.HeadImgUrl
if response.Status == 1 {
robot.Nickname = response.NickName
robot.Avatar = response.HeadImgUrl
db.Save(&robot)
}
// 如果检测到已登录,更新机器人状态
if response.Success && response.Data.AcctSectResp != nil {
response.Data.Status = 99
robot.WechatID = response.Data.AcctSectResp["userName"].(string)
if response.AcctSectResp.Username != "" {
response.Status = 99
robot.WechatID = response.AcctSectResp.Username
// 开启自动心跳,传递容器访问地址
if robot.WechatID != "" {
_, _ = docker.AutoHeartbeatStart(ctx, robot.WechatID, robot.ContainerHost)
_ = robotCli.Login.AutoHeartbeatStart()
}
// 更新机器人状态
@ -97,9 +99,9 @@ func CheckQRCodeStatus(c *fiber.Ctx) error {
}
return c.JSON(fiber.Map{
"success": response.Success,
"status": response.Data.Status,
"message": response.Message,
"userInfo": response.Data.AcctSectResp,
"success": true,
"status": response.Status,
"message": "success",
"userInfo": response.AcctSectResp,
})
}

View File

@ -22,12 +22,12 @@ func HealthCheck(c *fiber.Ctx) error {
"status": "ok",
"message": "service is healthy",
"timestamp": time.Now().Format(time.RFC3339),
"database": map[string]interface{}{
"database": map[string]any{
"connected": dbErr == nil,
},
"system": map[string]interface{}{
"system": map[string]any{
"goroutines": runtime.NumGoroutine(),
"memory": map[string]interface{}{
"memory": map[string]any{
"alloc": m.Alloc / 1024 / 1024,
"total_alloc": m.TotalAlloc / 1024 / 1024,
"sys": m.Sys / 1024 / 1024,

View File

@ -3,6 +3,7 @@ package handler
import (
"context"
"errors"
"gitee.ltd/lxh/xybot"
"log"
"strconv"
"strings"
@ -168,8 +169,13 @@ func DeleteRobot(c *fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
if err != nil {
log.Printf("创建微信客户端失败: %v", err)
}
if robot.Status == model.RobotStatusOnline {
if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil {
if err = robotCli.Login.Logout(); err != nil {
log.Printf("登出机器人失败: %v", err)
// 继续删除流程,不因登出失败而中断
}
@ -242,13 +248,20 @@ func RobotLogin(c *fiber.Ctx) error {
// 检查是否指定使用二维码登录
forceQrcode := c.Query("qrcode") == "1"
// 创建微信客户端
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
if err != nil {
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"Message": "创建微信客户端失败: " + err.Error(),
"IsError": true,
})
}
// 如果存在WechatID且没有强制要求使用二维码则使用唤醒登录
if robot.WechatID != "" && !forceQrcode {
// 尝试唤醒登录
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
awakenResp, err := docker.AwakenLogin(ctx, robot.WechatID, robot.ContainerHost)
awakenResp, err := robotCli.Login.AwakenLogin()
if err != nil {
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
@ -262,18 +275,14 @@ func RobotLogin(c *fiber.Ctx) error {
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"UUID": awakenResp.Data.QrCodeResponse.Uuid,
"Expired": awakenResp.Data.QrCodeResponse.ExpiredTime,
"UUID": awakenResp.QrCodeResponse.Uuid,
"Expired": awakenResp.QrCodeResponse.ExpiredTime,
"IsAwaken": true,
})
}
// 获取登录二维码
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 使用新的GetQRCode接口获取二维码并传递容器访问地址
qrcodeResp, err := docker.GetQRCode(ctx, robot.ContainerHost)
qrcodeResp, err := robotCli.Login.GetQRCode(robot.DeviceId, robot.DeviceName)
if err != nil {
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
@ -287,9 +296,9 @@ func RobotLogin(c *fiber.Ctx) error {
return c.Render("robot/login", fiber.Map{
"Title": "微信登录",
"Robot": robot,
"QRCode": qrcodeResp.Data.QRCodeURL,
"UUID": qrcodeResp.Data.UUID,
"Expired": qrcodeResp.Data.ExpiredTime,
"QRCode": qrcodeResp.QRCodeURL,
"UUID": qrcodeResp.Uuid,
"Expired": qrcodeResp.ExpiredTime,
"IsAwaken": false,
})
}
@ -311,13 +320,14 @@ func RobotLogout(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "查询数据库失败")
}
// 登出微信
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 使用新的LogOut API接口传递容器访问地址
// 创建微信客户端
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "创建微信客户端失败: "+err.Error())
}
// 使用新的Logout API接口传递容器访问地址
if robot.WechatID != "" {
if _, err = docker.LogOut(ctx, robot.WechatID, robot.ContainerHost); err != nil {
if err = robotCli.Login.Logout(); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "登出微信失败: "+err.Error())
}
}

View File

@ -45,12 +45,12 @@ func New(cfg *config.Config) *Server {
})
// 添加map函数用于在模板中创建映射
engine.AddFunc("map", func(values ...interface{}) map[string]interface{} {
engine.AddFunc("map", func(values ...any) map[string]any {
if len(values)%2 != 0 {
return nil
}
m := make(map[string]interface{}, len(values)/2)
m := make(map[string]any, len(values)/2)
for i := 0; i < len(values); i += 2 {
if key, ok := values[i].(string); ok {
m[key] = values[i+1]

View File

@ -1,13 +1,10 @@
package tasks
import (
"context"
"gitee.ltd/lxh/wechat-robot/internal/docker"
"gitee.ltd/lxh/wechat-robot/internal/model"
"gitee.ltd/lxh/xybot"
"github.com/gofiber/fiber/v2/log"
"slices"
"strings"
"time"
)
// syncContact
@ -16,27 +13,21 @@ import (
// @param robotWxId 机器人微信号
// @param robotId 机器人ID
func syncContact(containerHost, robotWxId string, robotId uint) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
robotCli, err := xybot.NewClient(robotWxId, containerHost, false)
if err != nil {
log.Errorf("创建微信客户端失败: %v", err)
}
// 先获取全部id
ids, err := docker.GetContactList(ctx, containerHost, robotWxId)
ids, err := robotCli.Friend.GetContractList(true)
if err != nil {
// 处理错误
log.Errorf("[%s]获取联系人列表失败: %v", robotWxId, err)
return
}
// 过滤掉特殊微信Id
var specialId = []string{"filehelper", "newsapp", "fmessage", "weibo", "qqmail", "tmessage", "qmessage", "qqsync",
"floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp",
"meishiapp", "feedsapp", "voip", "blogappweixin", "weixin", "brandsessionholder", "weixinreminder", "officialaccounts",
"notification_messages", "wxitil", "userexperience_alarm", "notification_messages", "exmail_tool", "mphelper"}
ids = slices.DeleteFunc(ids, func(id string) bool {
return slices.Contains(specialId, id) || strings.HasPrefix(id, "gh_") || strings.TrimSpace(id) == ""
})
// 获取昵称等详细信息
contacts, err := docker.GetContactDetail(ctx, containerHost, robotWxId, ids)
contacts, err := robotCli.Friend.GetContractDetail(ids)
if err != nil {
// 处理错误
log.Errorf("[%s]获取联系人详情失败: %v", robotWxId, err)
@ -71,7 +62,7 @@ func syncContact(containerHost, robotWxId string, robotId uint) {
"province": contact.Province,
"city": contact.City,
"signature": contact.Signature,
"sns_background": contact.SnsUserInfo.SnsBgimgId,
"sns_background": contact.SnsUserInfo.SnsBgImgId,
}
if contact.BigHeadImgUrl == "" {
pm["avatar"] = contact.SmallHeadImgUrl
@ -100,7 +91,7 @@ func syncContact(containerHost, robotWxId string, robotId uint) {
c.Province = contact.Province
c.City = contact.City
c.Signature = contact.Signature
c.SnsBackground = contact.SnsUserInfo.SnsBgimgId
c.SnsBackground = contact.SnsUserInfo.SnsBgImgId
err = db.Create(&c).Error
}

View File

@ -1,10 +1,9 @@
package tasks
import (
"context"
"gitee.ltd/lxh/wechat-robot/internal/docker"
"gitee.ltd/lxh/wechat-robot/internal/model"
"gitee.ltd/lxh/wechat-robot/internal/types"
"gitee.ltd/lxh/xybot"
"strings"
"time"
)
@ -14,21 +13,22 @@ import (
// @param containerHost 机器人接口地址
// @param robotWxId 机器人微信Id
// @param robotId 机器人数据Id
func syncMessage(containerHost, robotWxId string, robotId uint) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
messages, isOffline, err := docker.GetWechatMessages(ctx, containerHost, robotWxId)
if err != nil {
// 处理错误
return
}
func syncMessage(client *xybot.Client, robotId uint) {
// 获取数据库连接
db := model.GetDB()
if isOffline {
// 删除定时任务
DeleteJob(robotId)
// 修改机器人状态
db.Model(&model.Robot{}).Where("id = ?", robotId).Update("status", "offline")
// 获取新消息
messages, err := client.Message.Sync()
if err != nil {
// 手动处理一下是否离线了
isOffline := strings.Contains(err.Error(), "用户可能退出") || strings.Contains(err.Error(), "数据[DecryptData]失败")
if isOffline {
// 删除定时任务
DeleteJob(robotId)
// 修改机器人状态
db.Model(&model.Robot{}).Where("id = ?", robotId).Update("status", "offline")
}
return
}
@ -45,7 +45,7 @@ func syncMessage(containerHost, robotWxId string, robotId uint) {
m.MsgId = message.NewMsgId
m.CreateTime = message.CreateTime
m.CreateAt = time.Unix(int64(message.CreateTime), 0)
m.Type = message.MsgType
m.Type = types.MessageType(message.MsgType)
m.Content = message.Content.String
m.DisplayFullContent = message.PushContent
m.FromUser = message.FromUserName.String
@ -53,7 +53,7 @@ func syncMessage(containerHost, robotWxId string, robotId uint) {
// 如果是群聊消息,单独处理一下
// Sys类型的消息正文不包含微信 Id所以不需要处理
if strings.HasSuffix(message.FromUserName.String, "@chatroom") && message.MsgType != types.MsgTypeSys {
if strings.HasSuffix(message.FromUserName.String, "@chatroom") && m.Type != types.MsgTypeSys {
// 群消息,处理一下消息和发信人
groupUser := strings.Split(m.Content, "\n")[0]
groupUser = strings.ReplaceAll(groupUser, ":", "")

View File

@ -2,6 +2,7 @@ package tasks
import (
"gitee.ltd/lxh/wechat-robot/internal/model"
"gitee.ltd/lxh/xybot"
"github.com/go-co-op/gocron/v2"
"github.com/google/uuid"
"log"
@ -33,10 +34,15 @@ func Start() {
}
// 遍历机器人,添加任务
for _, robot := range robots {
// 初始化微信客户端
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
if err != nil {
log.Panicf("初始化微信客户端失败: %v", err)
}
var job gocron.Job
job, err = scheduler.NewJob(
gocron.CronJob("*/5 * * * * *", true), // 五秒钟同步一次
gocron.NewTask(syncMessage, robot.ContainerHost, robot.WechatID, robot.ID),
gocron.NewTask(syncMessage, robotCli, robot.ID),
)
if err != nil {
log.Panicf("添加定时任务失败: %v", err)
@ -64,9 +70,15 @@ func Start() {
// AddJob
// @description: 添加任务
func AddJob(robot model.Robot) {
// 初始化微信客户端
robotCli, err := xybot.NewClient(robot.WechatID, robot.ContainerHost, false)
if err != nil {
log.Panicf("初始化微信客户端失败: %v", err)
}
job, err := scheduler.NewJob(
gocron.CronJob("*/5 * * * * *", true), // 五秒钟同步一次
gocron.NewTask(syncMessage, robot.ContainerHost, robot.WechatID, robot.ID),
gocron.NewTask(syncMessage, robotCli, robot.ID),
)
if err != nil {
log.Printf("添加定时任务失败: %v", err)

View File

@ -108,28 +108,33 @@
<div class="w-full bg-gray-100 rounded-full h-2 mr-3">
<div id="cpu-bar" class="bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
</div>
<span id="cpu-usage" class="text-sm font-medium text-gray-700 min-w-[40px]">0%</span>
<span id="cpu-usage" class="text-sm font-medium text-gray-700 min-w-[120px]">0%</span>
</div>
</div>
<div class="space-y-2">
<p class="text-sm text-gray-500">内存使用率</p>
<div class="flex items-center">
<div class="w-full bg-gray-100 rounded-full h-2 mr-3">
<div id="memory-bar" class="bg-green-500 h-2 rounded-full" style="width: 0%"></div>
<div class="w-full bg-gray-100 rounded-full h-6 mr-3 overflow-hidden relative">
<div id="memory-bar" class="bg-green-500 h-6 rounded-full" style="width: 0%"></div>
<div class="absolute inset-0 flex items-center px-3">
<span id="memory-usage-display" class="text-xs font-medium text-gray-800">0MB/0MB</span>
</div>
</div>
<div class="text-sm font-medium text-gray-700 min-w-[50px]">
<span id="memory-percent">0%</span>
</div>
<span id="memory-usage" class="text-sm font-medium text-gray-700 min-w-[40px]">0%</span>
</div>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">网络接收</p>
<p id="network-rx" class="text-lg font-medium text-gray-800">0 KB/s</p>
<p id="network-rx" class="text-lg font-medium text-gray-800">0 KB</p>
</div>
<div>
<p class="text-sm text-gray-500 mb-1">网络发送</p>
<p id="network-tx" class="text-lg font-medium text-gray-800">0 KB/s</p>
<p id="network-tx" class="text-lg font-medium text-gray-800">0 KB</p>
</div>
</div>
</div>
@ -210,12 +215,16 @@
const memoryUsage = data.stats.memory_usage || 0;
const memoryLimit = data.stats.memory_limit || 1;
const memoryPercent = ((memoryUsage / memoryLimit) * 100).toFixed(2);
document.getElementById('memory-usage').textContent = `${memoryPercent}%`;
const formattedMemoryUsage = formatBytes(memoryUsage);
const formattedMemoryTotal = formatBytes(memoryLimit);
document.getElementById('memory-usage-display').textContent = `${formattedMemoryUsage}/${formattedMemoryTotal}`;
document.getElementById('memory-percent').textContent = `${memoryPercent}%`;
document.getElementById('memory-bar').style.width = `${Math.min(memoryPercent, 100)}%`;
// 更新网络信息
const networkRx = formatBytes(data.stats.network_rx || 0) + '/s';
const networkTx = formatBytes(data.stats.network_tx || 0) + '/s';
const networkRx = formatBytes(data.stats.network_rx || 0);
const networkTx = formatBytes(data.stats.network_tx || 0);
document.getElementById('network-rx').textContent = networkRx;
document.getElementById('network-tx').textContent = networkTx;
}