init: 初始化仓库
This commit is contained in:
commit
4d6c22ff7b
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# ---> Go
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
jrrp.db
|
||||||
|
qqbot
|
||||||
|
stable
|
||||||
|
msg.json
|
||||||
|
bindss.db
|
||||||
|
.vscode/launch.json
|
9
LICENSE
Normal file
9
LICENSE
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 lixiangwuxian
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
action/send_msg.go
Normal file
1
action/send_msg.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package action
|
8
constants/gocq_event.go
Normal file
8
constants/gocq_event.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
PRIVATE_MSG = "private"
|
||||||
|
GROUP_MSG = "group"
|
||||||
|
PRIVATE_MSG_SEND = "send_private_msg"
|
||||||
|
GROUP_MSG_SEND = "send_group_msg"
|
||||||
|
)
|
39
go.mod
Normal file
39
go.mod
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
module git.lxtend.com/qqbot
|
||||||
|
|
||||||
|
go 1.23.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.23
|
||||||
|
github.com/sashabaranov/go-openai v1.30.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
95
go.sum
Normal file
95
go.sum
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
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-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
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/sashabaranov/go-openai v1.30.3 h1:TEdRP3otRXX2A7vLoU+kI5XpoSo7VUUlM/rEttUqgek=
|
||||||
|
github.com/sashabaranov/go-openai v1.30.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
21
handler/echo/echo.go
Normal file
21
handler/echo/echo.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.lxtend.com/qqbot/handler"
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
handler.RegisterHandler("echo", echo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func echo(msg model.Message) (reply model.Reply) {
|
||||||
|
if len(msg.Msg) <= 5 {
|
||||||
|
return model.Reply{}
|
||||||
|
}
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: msg.Msg[5:],
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
61
handler/handler.go
Normal file
61
handler/handler.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler func(msg model.Message) (reply model.Reply)
|
||||||
|
|
||||||
|
var handlers = make(map[string]Handler)
|
||||||
|
var liveHandlers = make(map[int64]map[int64]Handler)
|
||||||
|
var privateAllHandler Handler
|
||||||
|
|
||||||
|
func RegisterPrivateHandler(handler Handler) {
|
||||||
|
privateAllHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterHandler(trigger string, handler Handler) {
|
||||||
|
handlers[trigger] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterLiveHandler(groupID int64, userID int64, handler Handler) { //userID=-1 means for all users in groupID
|
||||||
|
if _, ok := liveHandlers[groupID]; !ok {
|
||||||
|
liveHandlers[groupID] = make(map[int64]Handler)
|
||||||
|
}
|
||||||
|
liveHandlers[groupID][userID] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func MsgInHandler(msg model.Message) (reply model.Reply) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Default().Printf("Recovered in MsgInHandler: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if msg.Msg != "" {
|
||||||
|
log.Default().Printf("M:%v", msg)
|
||||||
|
} else {
|
||||||
|
return model.Reply{}
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if reply.ReplyMsg != "" {
|
||||||
|
log.Default().Printf("R:%v", reply)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if handlerUserID, ok := liveHandlers[msg.GroupInfo.GroupId]; ok {
|
||||||
|
if handler, ok := handlerUserID[msg.UserId]; ok {
|
||||||
|
liveHandlers[msg.GroupInfo.GroupId][msg.UserId] = nil
|
||||||
|
return handler(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msgArray := strings.Split(msg.Msg, " ")
|
||||||
|
if handler, ok := handlers[msgArray[0]]; ok {
|
||||||
|
return handler(msg)
|
||||||
|
}
|
||||||
|
if !msg.GroupInfo.IsGroupMsg && privateAllHandler != nil {
|
||||||
|
return privateAllHandler(msg)
|
||||||
|
}
|
||||||
|
return model.Reply{}
|
||||||
|
}
|
90
handler/headmaster/headmaster.go
Normal file
90
handler/headmaster/headmaster.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package headmaster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.lxtend.com/qqbot/handler"
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
"git.lxtend.com/qqbot/util"
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
histories = make(map[string][]openai.ChatCompletionMessage)
|
||||||
|
histories_time = make(map[string]time.Time)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
handler.RegisterHandler("校长", headmasterHandler)
|
||||||
|
handler.RegisterPrivateHandler(headmasterHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func headmasterHandler(msg model.Message) (reply model.Reply) {
|
||||||
|
from := util.From(msg.GroupInfo.GroupId, msg.UserId)
|
||||||
|
if len(msg.Msg) > 7 && msg.Msg[0:7] == "校长 " {
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: ask(from, msg.Msg[7:]),
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// nickname := msg.UserNickName
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: ask(from, msg.Msg),
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ask(from string, question string) (reply string) {
|
||||||
|
client := openai.NewClientWithConfig(openai.DefaultAzureConfig("none", "http://127.0.0.1:8000/v1"))
|
||||||
|
resp, err := client.CreateChatCompletion(
|
||||||
|
context.Background(),
|
||||||
|
openai.ChatCompletionRequest{
|
||||||
|
Model: "minimind",
|
||||||
|
Messages: GenRequestFromUsr(from, question),
|
||||||
|
// Messages: []openai.ChatCompletionMessage{
|
||||||
|
// {
|
||||||
|
// Role: openai.ChatMessageRoleUser,
|
||||||
|
// Content: question,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ChatCompletion error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
AppendReplyToHistory(from, resp.Choices[0].Message.Content)
|
||||||
|
return enterFormatter(resp.Choices[0].Message.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enterFormatter(msgIn string) string {
|
||||||
|
return strings.ReplaceAll(msgIn, "\\n", "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenRequestFromUsr(from string, question string) []openai.ChatCompletionMessage {
|
||||||
|
if _, ok := histories[from]; !ok || histories_time[from].Add(2*time.Minute).Before(time.Now()) {
|
||||||
|
histories[from] = make([]openai.ChatCompletionMessage, 0)
|
||||||
|
}
|
||||||
|
histories[from] = append(histories[from], openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleUser,
|
||||||
|
Content: question,
|
||||||
|
})
|
||||||
|
return histories[from]
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendReplyToHistory(from string, reply string) {
|
||||||
|
histories[from] = append(histories[from], openai.ChatCompletionMessage{
|
||||||
|
Role: openai.ChatMessageRoleAssistant,
|
||||||
|
Content: reply,
|
||||||
|
})
|
||||||
|
histories_time[from] = time.Now()
|
||||||
|
for len(histories[from]) > 10 {
|
||||||
|
histories[from] = histories[from][1:]
|
||||||
|
}
|
||||||
|
}
|
30
handler/jrrp/jrrp.go
Normal file
30
handler/jrrp/jrrp.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package jrrp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.lxtend.com/qqbot/handler"
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
"git.lxtend.com/qqbot/service/jrrp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jrrpInstance *jrrp.Jrrp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
jrrpInstance = jrrp.NewJrrp()
|
||||||
|
handler.RegisterHandler("今日人品", jrrpHandler)
|
||||||
|
handler.RegisterHandler("jrrp", jrrpHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jrrpHandler(msg model.Message) (reply model.Reply) {
|
||||||
|
luck, _ := jrrpInstance.GetJrrp(fmt.Sprint(msg.UserId))
|
||||||
|
nickname := msg.UserNickName
|
||||||
|
if msg.GroupInfo.IsGroupMsg && msg.GroupInfo.UserCard != "" {
|
||||||
|
nickname = msg.GroupInfo.UserCard
|
||||||
|
}
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: nickname + "同学你好,你的今日人品是:" + fmt.Sprint(luck),
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
21
handler/say/say.go
Normal file
21
handler/say/say.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package say
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.lxtend.com/qqbot/handler"
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
handler.RegisterHandler("记下", say)
|
||||||
|
}
|
||||||
|
|
||||||
|
func say(msg model.Message) (reply model.Reply) {
|
||||||
|
if len(msg.Msg) <= 5 {
|
||||||
|
return model.Reply{}
|
||||||
|
}
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: msg.Msg[5:],
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
67
handler/scoresaber/score.go
Normal file
67
handler/scoresaber/score.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package scoresaber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.lxtend.com/qqbot/handler"
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
"git.lxtend.com/qqbot/service/scoresaber"
|
||||||
|
)
|
||||||
|
|
||||||
|
var scoresaberInstance *scoresaber.SSQuery
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
scoresaberInstance = scoresaber.NewSSQuery()
|
||||||
|
handler.RegisterHandler("查ss", getScore)
|
||||||
|
handler.RegisterHandler("绑定ss", bindSS)
|
||||||
|
handler.RegisterHandler("解绑ss", unbindSS)
|
||||||
|
handler.RegisterHandler("最新ss", getRecentScore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getScore(msg model.Message) (reply model.Reply) {
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: scoresaberInstance.GetScore(strconv.Itoa(int(msg.UserId))),
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindSS(msg model.Message) (reply model.Reply) {
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: scoresaberInstance.BindSS(strconv.Itoa(int(msg.UserId)), msg.Msg[len("绑定ss "):]),
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unbindSS(msg model.Message) (reply model.Reply) {
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: scoresaberInstance.UnbindSS(strconv.Itoa(int(msg.UserId))),
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRecentScore(msg model.Message) (reply model.Reply) {
|
||||||
|
count := 1
|
||||||
|
if len(msg.Msg) > len("最新ss ") {
|
||||||
|
var err error
|
||||||
|
count, err = strconv.Atoi(msg.Msg[len("最新ss "):])
|
||||||
|
if err != nil {
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: "",
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scoreMsg := ""
|
||||||
|
for _, v := range scoresaber.ScoresManager.GetRecentScores(count) {
|
||||||
|
scoreMsg += v.ToString() + "\n"
|
||||||
|
}
|
||||||
|
return model.Reply{
|
||||||
|
ReplyMsg: scoreMsg,
|
||||||
|
ReferOriginMsg: true,
|
||||||
|
FromMsg: msg,
|
||||||
|
}
|
||||||
|
}
|
20
main.go
Normal file
20
main.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
wsclient "git.lxtend.com/qqbot/ws_client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 创建 WebSocket 客户端
|
||||||
|
client, err := wsclient.NewWebSocketClient("ws", "localhost:3001", "")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error creating WebSocket client:", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
for {
|
||||||
|
time.Sleep(1000 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
145
model/gocq_message.go
Normal file
145
model/gocq_message.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.lxtend.com/qqbot/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 消息接口
|
||||||
|
|
||||||
|
// @某个QQ用户的结构体
|
||||||
|
type AtMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data AtData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @消息数据结构体
|
||||||
|
type AtData struct {
|
||||||
|
QQ string `json:"qq"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复消息结构体
|
||||||
|
type ReplyMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data ReplyData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复消息数据结构体
|
||||||
|
type ReplyData struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenReplyMsg(msgID int64) ReplyMessage {
|
||||||
|
return ReplyMessage{
|
||||||
|
Type: "reply",
|
||||||
|
Data: ReplyData{
|
||||||
|
ID: fmt.Sprint(msgID),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenAtMsg(qqId string) AtMessage {
|
||||||
|
return AtMessage{
|
||||||
|
Type: "at",
|
||||||
|
Data: AtData{
|
||||||
|
QQ: qqId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报的消息结构体
|
||||||
|
type SendPkg struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Params UpstreamParams `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上游包的参数结构体
|
||||||
|
type UpstreamParams struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
GroupID int64 `json:"group_id"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
AutoEscape bool `json:"auto_escape"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenSendPkg(userID int64, groupID int64, message string, autoEscape bool) SendPkg {
|
||||||
|
if groupID == 0 {
|
||||||
|
return SendPkg{
|
||||||
|
Action: constants.PRIVATE_MSG_SEND,
|
||||||
|
Params: UpstreamParams{
|
||||||
|
UserID: userID,
|
||||||
|
Message: message,
|
||||||
|
AutoEscape: autoEscape,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SendPkg{
|
||||||
|
Action: constants.GROUP_MSG_SEND,
|
||||||
|
Params: UpstreamParams{
|
||||||
|
UserID: userID,
|
||||||
|
GroupID: groupID,
|
||||||
|
Message: message,
|
||||||
|
AutoEscape: autoEscape,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件的消息结构体
|
||||||
|
|
||||||
|
type EventMessage struct {
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
SelfID int64 `json:"self_id"`
|
||||||
|
PostType string `json:"post_type"`
|
||||||
|
MessageType string `json:"message_type"`
|
||||||
|
SubType string `json:"sub_type"`
|
||||||
|
MessageID int32 `json:"message_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
// Message string `json:"message"` //just ignore it
|
||||||
|
RawMessage string `json:"raw_message"`
|
||||||
|
Font int32 `json:"font"`
|
||||||
|
Sender Sender `json:"sender"`
|
||||||
|
GroupID int64 `json:"group_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Sender struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Sex string `json:"sex"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Card string `json:"card"`
|
||||||
|
Area string `json:"area"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
GroupID int64 `json:"group_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "self_id": 12345,
|
||||||
|
// "user_id": 23456,
|
||||||
|
// "time": 23456,
|
||||||
|
// "message_id": 23456,
|
||||||
|
// "message_seq": 23456,
|
||||||
|
// "real_id": 23456,
|
||||||
|
// "message_type": "group",
|
||||||
|
// "sender": {
|
||||||
|
// "user_id": 23456,
|
||||||
|
// "nickname": "23456",
|
||||||
|
// "card": "",
|
||||||
|
// "role": "owner"
|
||||||
|
// },
|
||||||
|
// "raw_message": "test",
|
||||||
|
// "font": 14,
|
||||||
|
// "sub_type": "normal",
|
||||||
|
// "message": [
|
||||||
|
// {
|
||||||
|
// "type": "text",
|
||||||
|
// "data": {
|
||||||
|
// "text": "test"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "message_format": "array",
|
||||||
|
// "post_type": "message",
|
||||||
|
// "group_id": 781332574
|
||||||
|
// }
|
21
model/message.go
Normal file
21
model/message.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
UserId int64 `json:"userId"`
|
||||||
|
GroupInfo GroupInfo `json:"groupInfo"`
|
||||||
|
OriginMsgId int32 `json:"originMsgId"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
UserNickName string `json:"userNickName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupInfo struct {
|
||||||
|
IsGroupMsg bool `json:"isGroupMsg"`
|
||||||
|
GroupId int64 `json:"groupId"`
|
||||||
|
UserCard string `json:"userCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reply struct {
|
||||||
|
ReplyMsg string `json:"replyMsg"`
|
||||||
|
ReferOriginMsg bool `json:"referOriginMsg"`
|
||||||
|
FromMsg Message `json:"fromMsg"`
|
||||||
|
}
|
8
register.go
Normal file
8
register.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "git.lxtend.com/qqbot/handler/echo"
|
||||||
|
_ "git.lxtend.com/qqbot/handler/headmaster"
|
||||||
|
_ "git.lxtend.com/qqbot/handler/jrrp"
|
||||||
|
_ "git.lxtend.com/qqbot/handler/scoresaber"
|
||||||
|
)
|
126
service/jrrp/jrrp.go
Normal file
126
service/jrrp/jrrp.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package jrrp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化 SQLite 数据库
|
||||||
|
func initDB() {
|
||||||
|
db, err := sql.Open("sqlite3", "./jrrp.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
createTableSQL := `CREATE TABLE IF NOT EXISTS jrrp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
qqid TEXT UNIQUE,
|
||||||
|
date TEXT,
|
||||||
|
rp_value INTEGER
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = db.Exec(createTableSQL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取今天的日期
|
||||||
|
func getTodayFullDate() string {
|
||||||
|
today := time.Now().Format("2006-01-02")
|
||||||
|
return today
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造RP值
|
||||||
|
func rpValueConstructor() (string, int) {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
rpValue := int(math.Floor(math.Sqrt(rand.Float64()*100) * 10))
|
||||||
|
if rpValue == 0 {
|
||||||
|
rpValue = 1
|
||||||
|
}
|
||||||
|
return getTodayFullDate(), rpValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jrrp 结构体
|
||||||
|
type Jrrp struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJrrp 创建 Jrrp 实例
|
||||||
|
func NewJrrp() *Jrrp {
|
||||||
|
initDB()
|
||||||
|
db, err := sql.Open("sqlite3", "./jrrp.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return &Jrrp{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findJrrp 查找RP数据
|
||||||
|
func (j *Jrrp) findJrrp(qqid string) (string, int, error) {
|
||||||
|
var date string
|
||||||
|
var rpValue int
|
||||||
|
|
||||||
|
query := "SELECT date, rp_value FROM jrrp WHERE qqid = ?"
|
||||||
|
err := j.db.QueryRow(query, qqid).Scan(&date, &rpValue)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return "", 0, nil
|
||||||
|
}
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
return date, rpValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertJrrp 插入RP数据
|
||||||
|
func (j *Jrrp) insertJrrp(qqid string, date string, rpValue int) error {
|
||||||
|
query := "INSERT OR REPLACE INTO jrrp (qqid, date, rp_value) VALUES (?, ?, ?)"
|
||||||
|
_, err := j.db.Exec(query, qqid, date, rpValue)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateJrrp 更新RP数据
|
||||||
|
func (j *Jrrp) updateJrrp(qqid string) error {
|
||||||
|
date, _, err := j.findJrrp(qqid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if date == "" {
|
||||||
|
return fmt.Errorf("updateJrrp()-> Error: the qqid does not exist!")
|
||||||
|
}
|
||||||
|
newDate, newRpValue := rpValueConstructor()
|
||||||
|
return j.insertJrrp(qqid, newDate, newRpValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetJrrp 获取RP值
|
||||||
|
func (j *Jrrp) GetJrrp(qqid string) (int, error) {
|
||||||
|
today := getTodayFullDate()
|
||||||
|
|
||||||
|
date, rpValue, err := j.findJrrp(qqid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if date == "" || date != today {
|
||||||
|
newDate, newRpValue := rpValueConstructor()
|
||||||
|
err = j.insertJrrp(qqid, newDate, newRpValue)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return newRpValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭数据库连接
|
||||||
|
func (j *Jrrp) Close() {
|
||||||
|
j.db.Close()
|
||||||
|
}
|
95
service/scoresaber/bind_ss.go
Normal file
95
service/scoresaber/bind_ss.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package scoresaber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDB() {
|
||||||
|
db, err := sql.Open("sqlite3", "./bindss.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
createTableSQL := `CREATE TABLE IF NOT EXISTS ssBind (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
qqid TEXT UNIQUE,
|
||||||
|
ssid TEXT UNIQUE
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = db.Exec(createTableSQL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSQuery struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSSQuery() *SSQuery {
|
||||||
|
initDB()
|
||||||
|
db, err := sql.Open("sqlite3", "./bindss.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return &SSQuery{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *SSQuery) BindSS(qqId string, ssId string) (reply string) {
|
||||||
|
// ssId为数字
|
||||||
|
if _, isNum := strconv.Atoi(ssId); isNum != nil {
|
||||||
|
return "ssId格式错误,应当为一串数字"
|
||||||
|
}
|
||||||
|
data, _ := FetchPlayerData(ssId)
|
||||||
|
if data == nil {
|
||||||
|
return "未找到玩家"
|
||||||
|
}
|
||||||
|
//去重
|
||||||
|
if rows, err := ss.db.Query("SELECT * FROM ssBind WHERE qqid = ?", qqId); err == nil {
|
||||||
|
if rows.Next() {
|
||||||
|
return "您已绑定过ss账号,请先输入\"解绑ss\"解绑"
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
_, err := ss.db.Exec("INSERT INTO ssBind(qqid, ssid) VALUES(?, ?)", qqId, ssId)
|
||||||
|
if err != nil {
|
||||||
|
return "绑定失败"
|
||||||
|
}
|
||||||
|
return "和用户名为 " + data.Name + " 的用户绑定成功,输入\"查ss\"查看个人数据"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *SSQuery) UnbindSS(qqId string) (reply string) {
|
||||||
|
//是否已绑定
|
||||||
|
if rows, err := ss.db.Query("SELECT * FROM ssBind WHERE qqid = ?", qqId); err == nil {
|
||||||
|
if !rows.Next() {
|
||||||
|
return "您未绑定ss账号,输入\"绑定ss [ssId]\"绑定"
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
_, err := ss.db.Exec("DELETE FROM ssBind WHERE qqid = ?", qqId)
|
||||||
|
if err != nil {
|
||||||
|
return "解绑失败"
|
||||||
|
}
|
||||||
|
return "解绑成功,重新绑定请输入\"绑定ss [ssId]\""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *SSQuery) GetScore(qqId string) (reply string) {
|
||||||
|
//是否已绑定
|
||||||
|
if rows, err := ss.db.Query("SELECT * FROM ssBind WHERE qqid = ?", qqId); err == nil {
|
||||||
|
if !rows.Next() {
|
||||||
|
return "您未绑定ss账号,输入\"绑定ss [ssId]\"绑定"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ssId string
|
||||||
|
ss.db.QueryRow("SELECT ssid FROM ssBind WHERE qqid = ?", qqId).Scan(&ssId)
|
||||||
|
data, _ := FetchPlayerData(ssId)
|
||||||
|
if data == nil {
|
||||||
|
return "未找到玩家"
|
||||||
|
}
|
||||||
|
return data.ToString()
|
||||||
|
}
|
74
service/scoresaber/hot.go
Normal file
74
service/scoresaber/hot.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package scoresaber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wsURL = "wss://scoresaber.com/ws"
|
||||||
|
|
||||||
|
var ScoresManager = scoresManager{}
|
||||||
|
|
||||||
|
// scoresManager 管理最近的 5 条数据
|
||||||
|
type scoresManager struct {
|
||||||
|
recentScores []Command
|
||||||
|
mu sync.Mutex
|
||||||
|
conn *websocket.Conn
|
||||||
|
retryTimes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for err := ScoresManager.connect(); err != nil; err = ScoresManager.connect() {
|
||||||
|
log.Fatal("连接 WebSocket 失败:", err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *scoresManager) connect() error {
|
||||||
|
var err error
|
||||||
|
sm.conn, _, err = websocket.DefaultDialer.Dial(wsURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sm.retryTimes = 0
|
||||||
|
sm.recentScores = make([]Command, 0, 50)
|
||||||
|
go sm.receiveData()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *scoresManager) receiveData() {
|
||||||
|
defer func() {
|
||||||
|
for err := ScoresManager.connect(); err != nil; err = ScoresManager.connect() {
|
||||||
|
log.Printf("连接 WebSocket 失败:%v", err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var cmd Command
|
||||||
|
err := sm.conn.ReadJSON(&cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("读取数据失败:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sm.mu.Lock()
|
||||||
|
if len(sm.recentScores) >= 50 {
|
||||||
|
sm.recentScores = sm.recentScores[1:]
|
||||||
|
}
|
||||||
|
sm.recentScores = append(sm.recentScores, cmd)
|
||||||
|
sm.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *scoresManager) GetRecentScores(count int) []Command {
|
||||||
|
if count > len(sm.recentScores) {
|
||||||
|
count = len(sm.recentScores)
|
||||||
|
}
|
||||||
|
sm.mu.Lock()
|
||||||
|
defer sm.mu.Unlock()
|
||||||
|
scoresCopy := make([]Command, count)
|
||||||
|
copy(scoresCopy, sm.recentScores[len(sm.recentScores)-count:])
|
||||||
|
return scoresCopy
|
||||||
|
}
|
178
service/scoresaber/model.go
Normal file
178
service/scoresaber/model.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package scoresaber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command 表示从 WebSocket 收到的数据结构
|
||||||
|
type Command struct {
|
||||||
|
CommandName string `json:"commandName"`
|
||||||
|
CommandData CommandData `json:"commandData"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Command) ToString() string {
|
||||||
|
if c.CommandName != "score" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
strWithRank := "玩家 %s 使用 %s 在 %s 的 %s 难度中获得了 %d 分,排名第 %d,pp 为 %.2f。"
|
||||||
|
strWithoutRank := "玩家 %s 使用 %s 在 %s 的 %s 难度中获得了 %d 分,排名第 %d。"
|
||||||
|
if c.CommandData.Leaderboard.Ranked {
|
||||||
|
return fmt.Sprintf(strWithRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, *c.CommandData.Score.DeviceHmd, c.CommandData.Leaderboard.SongName, c.CommandData.Leaderboard.Difficulty.DifficultyRaw, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank, c.CommandData.Score.Pp)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf(strWithoutRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, *c.CommandData.Score.DeviceHmd, c.CommandData.Leaderboard.SongName, c.CommandData.Leaderboard.Difficulty.DifficultyRaw, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaderboardPlayerInfo 表示玩家的信息
|
||||||
|
type LeaderboardPlayerInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ProfilePicture string `json:"profilePicture"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Permissions int `json:"permissions"`
|
||||||
|
Badges *string `json:"badges"`
|
||||||
|
Role *string `json:"role"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score 表示分数的信息
|
||||||
|
type Score struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
LeaderboardPlayerInfo LeaderboardPlayerInfo `json:"leaderboardPlayerInfo"`
|
||||||
|
Rank int `json:"rank"`
|
||||||
|
BaseScore int `json:"baseScore"`
|
||||||
|
ModifiedScore int `json:"modifiedScore"`
|
||||||
|
Pp float64 `json:"pp"`
|
||||||
|
Weight float64 `json:"weight"`
|
||||||
|
Modifiers string `json:"modifiers"`
|
||||||
|
Multiplier float64 `json:"multiplier"`
|
||||||
|
BadCuts int `json:"badCuts"`
|
||||||
|
MissedNotes int `json:"missedNotes"`
|
||||||
|
MaxCombo int `json:"maxCombo"`
|
||||||
|
FullCombo bool `json:"fullCombo"`
|
||||||
|
Hmd int `json:"hmd"`
|
||||||
|
TimeSet time.Time `json:"timeSet"`
|
||||||
|
HasReplay bool `json:"hasReplay"`
|
||||||
|
DeviceHmd *string `json:"deviceHmd"`
|
||||||
|
DeviceControllerLeft *string `json:"deviceControllerLeft"`
|
||||||
|
DeviceControllerRight *string `json:"deviceControllerRight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difficulty 表示关卡难度信息
|
||||||
|
type Difficulty struct {
|
||||||
|
LeaderboardID int `json:"leaderboardId"`
|
||||||
|
Difficulty int `json:"difficulty"`
|
||||||
|
GameMode string `json:"gameMode"`
|
||||||
|
DifficultyRaw string `json:"difficultyRaw"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaderboard 表示排行榜的信息
|
||||||
|
type Leaderboard struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
SongHash string `json:"songHash"`
|
||||||
|
SongName string `json:"songName"`
|
||||||
|
SongSubName string `json:"songSubName"`
|
||||||
|
SongAuthorName string `json:"songAuthorName"`
|
||||||
|
LevelAuthorName string `json:"levelAuthorName"`
|
||||||
|
Difficulty Difficulty `json:"difficulty"`
|
||||||
|
MaxScore int `json:"maxScore"`
|
||||||
|
CreatedDate time.Time `json:"createdDate"`
|
||||||
|
RankedDate *time.Time `json:"rankedDate"`
|
||||||
|
QualifiedDate *time.Time `json:"qualifiedDate"`
|
||||||
|
LovedDate *time.Time `json:"lovedDate"`
|
||||||
|
Ranked bool `json:"ranked"`
|
||||||
|
Qualified bool `json:"qualified"`
|
||||||
|
Loved bool `json:"loved"`
|
||||||
|
MaxPP float64 `json:"maxPP"`
|
||||||
|
Stars float64 `json:"stars"`
|
||||||
|
Plays int `json:"plays"`
|
||||||
|
DailyPlays int `json:"dailyPlays"`
|
||||||
|
PositiveModifiers bool `json:"positiveModifiers"`
|
||||||
|
PlayerScore *string `json:"playerScore"`
|
||||||
|
CoverImage string `json:"coverImage"`
|
||||||
|
Difficulties *string `json:"difficulties"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandData 表示命令的数据
|
||||||
|
type CommandData struct {
|
||||||
|
Score Score `json:"score"`
|
||||||
|
Leaderboard Leaderboard `json:"leaderboard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//用户信息
|
||||||
|
|
||||||
|
// ScoreStats 存储分数统计信息
|
||||||
|
type ScoreStats struct {
|
||||||
|
TotalScore int `json:"totalScore" db:"total_score"`
|
||||||
|
TotalRankedScore int `json:"totalRankedScore" db:"total_ranked_score"`
|
||||||
|
AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"`
|
||||||
|
TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"`
|
||||||
|
RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"`
|
||||||
|
ReplaysWatched int `json:"replaysWatched" db:"replays_watched"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayerData 存储玩家的完整信息
|
||||||
|
type PlayerData struct {
|
||||||
|
ID string `json:"id" db:"id"`
|
||||||
|
Name string `json:"name" db:"name"`
|
||||||
|
ProfilePicture string `json:"profilePicture" db:"profile_picture"`
|
||||||
|
Bio *string `json:"bio" db:"bio"`
|
||||||
|
Country string `json:"country" db:"country"`
|
||||||
|
PP float64 `json:"pp" db:"pp"`
|
||||||
|
Rank int `json:"rank" db:"rank"`
|
||||||
|
CountryRank int `json:"countryRank" db:"country_rank"`
|
||||||
|
Role *string `json:"role" db:"role"`
|
||||||
|
Badges []string `json:"badges" db:"badges"`
|
||||||
|
Histories string `json:"histories" db:"histories"`
|
||||||
|
Permissions int `json:"permissions" db:"permissions"`
|
||||||
|
Banned bool `json:"banned" db:"banned"`
|
||||||
|
Inactive bool `json:"inactive" db:"inactive"`
|
||||||
|
ScoreStats ScoreStats `json:"scoreStats" db:"score_stats"`
|
||||||
|
FirstSeen time.Time `json:"firstSeen" db:"first_seen"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerDataLite struct {
|
||||||
|
ID string `json:"id" db:"id"`
|
||||||
|
Name string `json:"name" db:"name"`
|
||||||
|
Country string `json:"country" db:"country"`
|
||||||
|
PP float64 `json:"pp" db:"pp"`
|
||||||
|
Rank int `json:"rank" db:"rank"`
|
||||||
|
CountryRank int `json:"countryRank" db:"country_rank"`
|
||||||
|
TotalScore int `json:"totalScore" db:"total_score"`
|
||||||
|
TotalRankedScore int `json:"totalRankedScore" db:"total_ranked_score"`
|
||||||
|
AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"`
|
||||||
|
TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"`
|
||||||
|
RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"`
|
||||||
|
ReplaysWatched int `json:"replaysWatched" db:"replays_watched"`
|
||||||
|
GeneratedTime time.Time `json:"generatedTime" db:"generated_time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PlayerData) ToString() string {
|
||||||
|
formatedStr := "玩家 %s\n" +
|
||||||
|
"区域 %s\n" +
|
||||||
|
"PP %.1f\n" +
|
||||||
|
"全球排名 %d\n" +
|
||||||
|
"区域排名 %d\n" +
|
||||||
|
"总分 %d\n" +
|
||||||
|
"Ranked谱面总分 %d\n" +
|
||||||
|
"平均Ranked谱面准确率 %.2f\n" +
|
||||||
|
"总游玩次数 %d\n" +
|
||||||
|
"Ranked谱面游玩次数 %d\n" +
|
||||||
|
"回放被观看次数 %d"
|
||||||
|
return fmt.Sprintf(formatedStr, p.Name, p.Country, p.PP, p.Rank, p.CountryRank, p.ScoreStats.TotalScore, p.ScoreStats.TotalRankedScore, p.ScoreStats.AverageRankedAccuracy, p.ScoreStats.TotalPlayCount, p.ScoreStats.RankedPlayCount, p.ScoreStats.ReplaysWatched)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PlayerData) LastDiffToString(lastDayQueryData PlayerDataLite) string {
|
||||||
|
formatedStr := "玩家 %s\n" +
|
||||||
|
"区域 %s\n" +
|
||||||
|
"PP %.1f(%+.1f)\n" +
|
||||||
|
"全球排名 %d(%+d)\n" +
|
||||||
|
"区域排名 %d(%+d)\n" +
|
||||||
|
"总分 %d(%+d)\n" +
|
||||||
|
"Ranked谱面总分 %d(%+d)\n" +
|
||||||
|
"平均Ranked谱面准确率 %.2f(%+.2f)\n" +
|
||||||
|
"总游玩次数 %d(%+d)\n" +
|
||||||
|
"Ranked谱面游玩次数 %d(%+d)\n" +
|
||||||
|
"回放被观看次数 %d"
|
||||||
|
return fmt.Sprintf(formatedStr, p.Name, p.Country, p.PP, p.PP-lastDayQueryData.PP, p.Rank, p.Rank-lastDayQueryData.Rank, p.CountryRank, p.CountryRank-lastDayQueryData.CountryRank, p.ScoreStats.TotalScore, p.ScoreStats.TotalScore-lastDayQueryData.TotalScore, p.ScoreStats.TotalRankedScore, p.ScoreStats.TotalRankedScore-lastDayQueryData.TotalRankedScore, p.ScoreStats.AverageRankedAccuracy, p.ScoreStats.AverageRankedAccuracy-lastDayQueryData.AverageRankedAccuracy, p.ScoreStats.TotalPlayCount, p.ScoreStats.TotalPlayCount-lastDayQueryData.TotalPlayCount, p.ScoreStats.RankedPlayCount, p.ScoreStats.RankedPlayCount-lastDayQueryData.RankedPlayCount, p.ScoreStats.ReplaysWatched)
|
||||||
|
}
|
66
service/scoresaber/user_info.go
Normal file
66
service/scoresaber/user_info.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package scoresaber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fetchPlayerData 函数请求 Scoresaber API,并解析完整的玩家信息
|
||||||
|
func FetchPlayerData(ssID string) (*PlayerData, error) {
|
||||||
|
url := fmt.Sprintf("https://scoresaber.com/api/player/%s/full", ssID)
|
||||||
|
|
||||||
|
// 创建请求
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0")
|
||||||
|
req.Header.Set("Accept", "application/json, text/plain, */*")
|
||||||
|
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2")
|
||||||
|
req.Header.Set("Accept-Encoding", "gzip")
|
||||||
|
req.Header.Set("DNT", "1")
|
||||||
|
req.Header.Set("Connection", "keep-alive")
|
||||||
|
req.Header.Set("Referer", fmt.Sprintf("https://scoresaber.com/u/%s", ssID))
|
||||||
|
req.Header.Set("Sec-Fetch-Dest", "empty")
|
||||||
|
req.Header.Set("Sec-Fetch-Mode", "cors")
|
||||||
|
req.Header.Set("Sec-Fetch-Site", "same-origin")
|
||||||
|
req.Header.Set("Sec-GPC", "1")
|
||||||
|
req.Header.Set("Pragma", "no-cache")
|
||||||
|
req.Header.Set("Cache-Control", "no-cache")
|
||||||
|
req.Header.Set("TE", "trailers")
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 处理压缩响应
|
||||||
|
var reader io.Reader
|
||||||
|
if resp.Header.Get("Content-Encoding") == "gzip" {
|
||||||
|
reader, err = gzip.NewReader(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer reader.(*gzip.Reader).Close()
|
||||||
|
} else {
|
||||||
|
reader = resp.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应体
|
||||||
|
var playerData PlayerData
|
||||||
|
err = json.NewDecoder(reader).Decode(&playerData)
|
||||||
|
if err != nil {
|
||||||
|
// log.Printf("got body %v", reader.)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &playerData, nil
|
||||||
|
}
|
7
util/gen_from.go
Normal file
7
util/gen_from.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func From(groupId int64, userId int64) string {
|
||||||
|
return fmt.Sprintf("%d_%d", groupId, userId)
|
||||||
|
}
|
98
ws_client/client.go
Normal file
98
ws_client/client.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package wsclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"git.lxtend.com/qqbot/constants"
|
||||||
|
"git.lxtend.com/qqbot/handler"
|
||||||
|
"git.lxtend.com/qqbot/model"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebSocketClient struct {
|
||||||
|
conn *websocket.Conn
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebSocketClient(scheme, host, path string) (*WebSocketClient, error) {
|
||||||
|
u := url.URL{Scheme: scheme, Host: host, Path: path}
|
||||||
|
log.Printf("Connecting to %s", u.String())
|
||||||
|
|
||||||
|
conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &WebSocketClient{
|
||||||
|
conn: conn,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go client.receiveMessages()
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebSocketClient) receiveMessages() {
|
||||||
|
defer close(c.done)
|
||||||
|
for {
|
||||||
|
_, message, err := c.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error reading message:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var event model.EventMessage
|
||||||
|
if err = json.Unmarshal(message, &event); err != nil {
|
||||||
|
log.Println("Error unmarshalling message:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := model.Message{
|
||||||
|
UserId: event.UserID,
|
||||||
|
OriginMsgId: event.MessageID,
|
||||||
|
Msg: event.RawMessage,
|
||||||
|
UserNickName: event.Sender.Nickname,
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.MessageType == constants.GROUP_MSG {
|
||||||
|
msg.GroupInfo = model.GroupInfo{
|
||||||
|
IsGroupMsg: true,
|
||||||
|
GroupId: event.GroupID,
|
||||||
|
UserCard: event.Sender.Card,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg.GroupInfo = model.GroupInfo{
|
||||||
|
IsGroupMsg: false,
|
||||||
|
GroupId: event.Sender.GroupID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := handler.MsgInHandler(msg)
|
||||||
|
if reply.ReplyMsg != "" {
|
||||||
|
sendPkg := model.GenSendPkg(reply.FromMsg.UserId, reply.FromMsg.GroupInfo.GroupId, reply.ReplyMsg, false)
|
||||||
|
sendPkgJson, err := json.Marshal(sendPkg)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error marshalling sendPkg:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = c.SendMessage(websocket.TextMessage, sendPkgJson); err != nil {
|
||||||
|
log.Println("Error sending message:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebSocketClient) SendMessage(messageType int, message []byte) error {
|
||||||
|
return c.conn.WriteMessage(messageType, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebSocketClient) Close() error {
|
||||||
|
err := c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
<-c.done
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user