├── .github ├── nano.jpeg └── workflows │ ├── nightly.yml │ ├── pull.yml │ ├── push.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── README.md ├── console ├── console_ansi.go └── console_windows.go ├── go.mod ├── go.sum ├── kanban ├── banner │ └── banner.go ├── gen │ └── banner.go ├── init.go └── version_less_than_1.21.go ├── main.go ├── plugin ├── autowithdraw │ └── main.go ├── b14 │ └── main.go ├── base64gua │ └── main.go ├── baseamasiro │ └── main.go ├── chrev │ ├── init.go │ └── map.go ├── dish │ └── main.go ├── emojimix │ ├── emoji.go │ └── mix.go ├── fortune │ └── fortune.go ├── genshin │ ├── data.go │ └── ys.go ├── hyaku │ └── main.go ├── manager │ └── main.go ├── qqwife │ ├── dbfile.go │ ├── happyplay.go │ └── main.go ├── qunyou │ └── main.go ├── runcode │ └── code_runner.go ├── score │ ├── draw.go │ ├── model.go │ └── sign_in.go ├── status │ └── main.go ├── tarot │ └── main.go ├── wife │ └── main.go └── wordle │ └── main.go └── utils └── ctxext └── speed.go /.github/nano.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloatTech/NanoBot-Plugin/8273c9716a70ef65de56f29cd7988a97764e572b/.github/nano.jpeg -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | BINARY_PREFIX: "nbp_" 7 | BINARY_SUFFIX: "" 8 | PR_PROMPT: "::warning:: Build artifact will not be uploaded due to the workflow is trigged by pull request." 9 | LD_FLAGS: "-w -s" 10 | 11 | jobs: 12 | build: 13 | name: Build binary CI 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | # build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/amd64, darwin/arm64 18 | goos: [linux, windows] 19 | goarch: ["386", amd64, arm, arm64] 20 | exclude: 21 | - goos: windows 22 | goarch: arm 23 | - goos: windows 24 | goarch: arm64 25 | fail-fast: true 26 | steps: 27 | - uses: actions/checkout@master 28 | - name: Setup Go environment 29 | uses: actions/setup-go@master 30 | with: 31 | go-version: '1.20' 32 | - name: Cache downloaded module 33 | uses: actions/cache@master 34 | with: 35 | path: | 36 | ~/.cache/go-build 37 | ~/go/pkg/mod 38 | key: ${{ runner.os }}-go-${{ matrix.goos }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }} 39 | - name: Build binary file 40 | env: 41 | GOOS: ${{ matrix.goos }} 42 | GOARCH: ${{ matrix.goarch }} 43 | IS_PR: ${{ !!github.head_ref }} 44 | run: | 45 | if [ $GOOS = "windows" ]; then export BINARY_SUFFIX="$BINARY_SUFFIX.exe"; fi 46 | if $IS_PR ; then echo $PR_PROMPT; fi 47 | export BINARY_NAME="$BINARY_PREFIX$GOOS_$GOARCH$BINARY_SUFFIX" 48 | export CGO_ENABLED=0 49 | go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" . 50 | - name: Upload artifact 51 | uses: actions/upload-artifact@master 52 | if: ${{ !github.head_ref }} 53 | with: 54 | name: ${{ matrix.goos }}_${{ matrix.goarch }} 55 | path: output/ -------------------------------------------------------------------------------- /.github/workflows/pull.yml: -------------------------------------------------------------------------------- 1 | name: PullLint 2 | on: [ pull_request ] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 9 | uses: actions/setup-go@master 10 | with: 11 | go-version: '1.20' 12 | 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@master 15 | 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@master 18 | with: 19 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 20 | version: latest 21 | 22 | # Optional: working directory, useful for monorepos 23 | # working-directory: somedir 24 | 25 | # Optional: golangci-lint command line arguments. 26 | # args: --issues-exit-code=0 27 | 28 | # Optional: show only new issues if it's a pull request. The default value is `false`. 29 | # only-new-issues: true 30 | 31 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 32 | # skip-pkg-cache: true 33 | 34 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 35 | # skip-build-cache: true 36 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: PushLint 2 | on: [ push ] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 9 | uses: actions/setup-go@master 10 | with: 11 | go-version: '1.20' 12 | 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@master 15 | 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@master 18 | with: 19 | version: latest 20 | args: --issues-exit-code=0 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@master 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@master 18 | with: 19 | go-version: '1.20' 20 | 21 | - name: Run GoReleaser 22 | uses: goreleaser/goreleaser-action@master 23 | with: 24 | version: latest 25 | args: release --rm-dist 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | /data 18 | config.yaml 19 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | errcheck: 3 | ignore: fmt:.* 4 | ignoretests: true 5 | 6 | goimports: 7 | local-prefixes: github.com/FloatTech/NanoBot-Plugin 8 | 9 | forbidigo: 10 | # Forbid the following identifiers 11 | forbid: 12 | - ^fmt\.Errorf$ # consider errors.Errorf in github.com/pkg/errors 13 | 14 | linters: 15 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 16 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 17 | disable-all: true 18 | fast: false 19 | enable: 20 | - bodyclose 21 | - deadcode 22 | # - depguard 23 | - dogsled 24 | - errcheck 25 | - exportloopref 26 | - exhaustive 27 | #- funlen 28 | #- goconst 29 | - gocritic 30 | #- gocyclo 31 | - gofmt 32 | - goimports 33 | - goprintffuncname 34 | #- gosec 35 | - gosimple 36 | - govet 37 | - ineffassign 38 | #- misspell 39 | - nolintlint 40 | - rowserrcheck 41 | - staticcheck 42 | - structcheck 43 | - stylecheck 44 | - typecheck 45 | - unconvert 46 | - unparam 47 | - unused 48 | - varcheck 49 | - whitespace 50 | - prealloc 51 | - predeclared 52 | - asciicheck 53 | - revive 54 | - forbidigo 55 | - makezero 56 | 57 | run: 58 | # default concurrency is a available CPU number. 59 | # concurrency: 4 # explicitly omit this value to fully utilize available resources. 60 | deadline: 5m 61 | issues-exit-code: 1 62 | tests: false 63 | skip-dirs: 64 | - order 65 | go: '1.20' 66 | 67 | # output configuration options 68 | output: 69 | format: "colored-line-number" 70 | print-issued-lines: true 71 | print-linter-name: true 72 | uniq-by-line: true 73 | 74 | issues: 75 | # Fix found issues (if it's supported by the linter) 76 | fix: true 77 | exclude-use-default: false 78 | exclude: 79 | - "Error return value of .((os.)?std(out|err)..*|.*Close|.*Seek|.*Flush|os.Remove(All)?|.*print(f|ln)?|os.(Un)?Setenv). is not check" -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: nbp 2 | env: 3 | - GO111MODULE=on 4 | before: 5 | hooks: 6 | - go mod tidy 7 | builds: 8 | - id: nowin 9 | env: 10 | - CGO_ENABLED=0 11 | - GO111MODULE=on 12 | goos: 13 | - linux 14 | goarch: 15 | - 386 16 | - amd64 17 | - arm 18 | - arm64 19 | goarm: 20 | - 6 21 | - 7 22 | mod_timestamp: "{{ .CommitTimestamp }}" 23 | flags: 24 | - -trimpath 25 | ldflags: 26 | - -s -w 27 | - id: win 28 | env: 29 | - CGO_ENABLED=0 30 | - GO111MODULE=on 31 | goos: 32 | - windows 33 | goarch: 34 | - 386 35 | - amd64 36 | mod_timestamp: "{{ .CommitTimestamp }}" 37 | flags: 38 | - -trimpath 39 | ldflags: 40 | - -s -w 41 | 42 | checksum: 43 | name_template: "nbp_checksums.txt" 44 | changelog: 45 | sort: asc 46 | filters: 47 | exclude: 48 | - "^docs:" 49 | - "^test:" 50 | - fix typo 51 | - Merge pull request 52 | - Merge branch 53 | - Merge remote-tracking 54 | - go mod tidy 55 | 56 | archives: 57 | - id: nowin 58 | builds: 59 | - nowin 60 | - win 61 | name_template: "nbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 62 | format_overrides: 63 | - goos: windows 64 | format: zip 65 | 66 | nfpms: 67 | - license: GPL 3.0 68 | homepage: https://github.com/FloatTech/NanoBot-Plugin 69 | file_name_template: "nbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 70 | formats: 71 | - deb 72 | - rpm 73 | maintainer: FloatTech 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 东云名乃 3 |
4 | 5 |

NanoBot-Plugin

6 | 基于 NanoBot 的 QQ 机器人插件合集

7 | 8 |
9 | 10 | [![tencent-guild](https://img.shields.io/badge/%E9%A2%91%E9%81%93-Zer0BotPlugin-yellow?style=flat-square&logo=tencent-qq)](https://pd.qq.com/s/fjkx81mnr) 11 | 12 |
13 | 14 | ## 命令行参数 15 | > `[]`代表是可选参数 16 | ```bash 17 | nanobot [参数] ID1 ID2 ... 18 | 参数: 19 | -D enable debug-level log output 20 | -T int 21 | api timeout (s) (default 60) 22 | -a string 23 | qq appid 24 | -c string 25 | load from config 26 | -h print this help 27 | -public 28 | only listen to public intent 29 | -qq 30 | also listen QQ intent 31 | -s string 32 | qq secret 33 | -sandbox 34 | run in sandbox api 35 | -save string 36 | save bot config to filename (eg. config.yaml) 37 | -shardcount uint 38 | shard count 39 | -shardindex uint 40 | shard index 41 | -superallqq 42 | make all QQ users to be SuperUser 43 | -t string 44 | qq api token 45 | ``` 46 | 47 | 其中公域配置参考如下,为一个数组,可自行增加更多 bot 实例。注意`Properties`不可为`[]` 48 | ```yaml 49 | - AppID: "123456" 50 | Token: xxxxxxx 51 | Secret: "" 52 | SuperUsers: 53 | - "123456789" 54 | Timeout: 1m0s 55 | Intents: 1812730883 56 | ShardIndex: 0 57 | ShardCount: 0 58 | Properties: null 59 | ``` 60 | 61 | ## 功能 62 | > 在编译时,以下功能均可通过注释`main.go`中的相应`import`而物理禁用,减小插件体积。 63 | 64 |
65 | 触发者撤回时也自动撤回(仅私域可用) 66 | 67 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/autowithdraw"` 68 | 69 | - [x] 撤回一条消息 70 | 71 |
72 | 73 |
74 | base16384加解密 75 | 76 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/b14"` 77 | 78 | - [x] 加密xxx 79 | 80 | - [x] 解密xxx 81 | 82 | - [x] 用yyy加密xxx 83 | 84 | - [x] 用yyy解密xxx 85 | 86 |
87 | 88 |
89 | base64卦加解密 90 | 91 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/base64gua"` 92 | 93 | - [x] 六十四卦加密xxx 94 | 95 | - [x] 六十四卦解密xxx 96 | 97 | - [x] 六十四卦用yyy加密xxx 98 | 99 | - [x] 六十四卦用yyy解密xxx 100 | 101 |
102 | 103 |
104 | base天城文加解密 105 | 106 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/baseamasiro"` 107 | 108 | - [x] 天城文加密xxx 109 | 110 | - [x] 天城文解密xxx 111 | 112 | - [x] 天城文用yyy加密xxx 113 | 114 | - [x] 天城文用yyy解密xxx 115 | 116 |
117 | 118 |
119 | 英文字符翻转 120 | 121 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/chrev"` 122 | 123 | - [x] 翻转 I love you 124 | 125 |
126 | 127 |
128 | 程序员做饭指南 129 | 130 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/dish"` 131 | 132 | - [x] 怎么做[xxx] | 烹饪[xxx] 133 | 134 | - [x] 随机菜谱 | 随便做点菜 135 | 136 |
137 | 138 |
139 | 合成emoji 140 | 141 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/emojimix"` 142 | 143 | - [x] [emoji][emoji] 144 | 145 |
146 | 147 |
148 | 每日运势 149 | 150 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/fortune"` 151 | 152 | - [x] 运势 | 抽签 153 | 154 | - [x] 设置底图[车万 DC4 爱因斯坦 星空列车 樱云之恋 富婆妹 李清歌 公主连结 原神 明日方舟 碧蓝航线 碧蓝幻想 战双 阴阳师 赛马娘 东方归言录 奇异恩典 夏日口袋 ASoul] 155 | 156 |
157 | 158 |
159 | 原神抽卡 160 | 161 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/genshin"` 162 | 163 | - [x] 切换原神卡池 164 | 165 | - [x] 原神十连 166 | 167 |
168 | 169 |
170 | 百人一首 171 | 172 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/hyaku"` 173 | 174 | - [x] 百人一首 175 | 176 | - [x] 百人一首之n 177 | 178 |
179 | 180 |
181 | bot管理相关 182 | 183 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/manager"` 184 | 185 | - [x] /exposeid @user1 @user2 186 | 187 |
188 | 189 |
190 | 娶群友 191 | 192 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/qqwife"` 193 | 194 | - [x] 娶群友 195 | 196 | - [x] 群老婆列表 197 | 198 | - [x] (娶|嫁)@对方QQ 199 | 200 | - [x] 牛@对方QQ 201 | 202 | - [x] 闹离婚 203 | 204 | - [x] 买礼物给@对方QQ 205 | 206 | - [x] 做媒 @攻方QQ @受方QQ 207 | 208 | - [x] 查好感度@对方QQ 209 | 210 | - [x] 好感度列表 211 | 212 | - [x] [允许|禁止]自由恋爱 213 | 214 | - [x] [允许|禁止]牛头人 215 | 216 | - [x] 设置CD为xx小时 →(默认12小时) 217 | 218 |
219 | 220 |
221 | 在线代码运行 222 | 223 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/runcode"` 224 | 225 | - [x] >runcode [language] help 226 | 227 | - [x] >runcode [language] [code block] 228 | 229 | - [x] >runcoderaw [language] [code block] 230 | 231 |
232 | 233 |
234 | 签到 235 | 236 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/score"` 237 | 238 | - [x] 签到 239 | 240 | - [x] 获得签到背景 241 | 242 | - [x] 查看等级排名 243 | 244 |
245 | 246 |
247 | 自检 248 | 249 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/status"` 250 | 251 | - [x] [检查身体 | 自检 | 启动自检 | 系统状态] 252 | 253 |
254 | 255 |
256 | 塔罗牌 257 | 258 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/tarot"` 259 | 260 | - [x] 抽[塔罗牌|大阿卡纳|小阿卡纳] 261 | 262 | - [x] 解塔罗牌[牌名] 263 | 264 |
265 | 266 |
267 | 抽老婆 268 | 269 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/wife"` 270 | 271 | - [x] 抽老婆 272 | 273 |
274 | 275 |
276 | 猜单词 277 | 278 | `import _ "github.com/FloatTech/NanoBot-Plugin/plugin/wordle"` 279 | 280 | - [x] 个人猜单词 281 | 282 | - [x] 团队猜单词 283 | 284 | - [x] 团队六阶猜单词 285 | 286 | - [x] 团队七阶猜单词 287 | 288 |
289 | 290 | 291 | ## 特别感谢 292 | 293 | - [ZeroBot](https://github.com/wdvxdr1123/ZeroBot) 294 | -------------------------------------------------------------------------------- /console/console_ansi.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Package console sets console's behavior on init 4 | package console 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/FloatTech/NanoBot-Plugin/kanban/banner" 10 | ) 11 | 12 | func init() { 13 | fmt.Print("\033]0;NanoBot-Blugin " + banner.Version + " " + banner.Copyright + "\007") 14 | } 15 | -------------------------------------------------------------------------------- /console/console_windows.go: -------------------------------------------------------------------------------- 1 | // Package console sets console's behavior on init 2 | package console 3 | 4 | import ( 5 | "bytes" 6 | "os" 7 | "strings" 8 | "syscall" 9 | "unsafe" 10 | 11 | "golang.org/x/sys/windows" 12 | 13 | "github.com/sirupsen/logrus" 14 | 15 | "github.com/FloatTech/NanoBot-Plugin/kanban/banner" 16 | ) 17 | 18 | var ( 19 | //go:linkname modkernel32 golang.org/x/sys/windows.modkernel32 20 | modkernel32 *windows.LazyDLL 21 | procSetConsoleTitle = modkernel32.NewProc("SetConsoleTitleW") 22 | ) 23 | 24 | //go:linkname errnoErr golang.org/x/sys/windows.errnoErr 25 | func errnoErr(e syscall.Errno) error 26 | 27 | func setConsoleTitle(title string) (err error) { 28 | var p0 *uint16 29 | p0, err = syscall.UTF16PtrFromString(title) 30 | if err != nil { 31 | return 32 | } 33 | r1, _, e1 := syscall.Syscall(procSetConsoleTitle.Addr(), 1, uintptr(unsafe.Pointer(p0)), 0, 0) 34 | if r1 == 0 { 35 | err = errnoErr(e1) 36 | } 37 | return 38 | } 39 | 40 | func init() { 41 | stdin := windows.Handle(os.Stdin.Fd()) 42 | 43 | var mode uint32 44 | err := windows.GetConsoleMode(stdin, &mode) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | mode &^= windows.ENABLE_QUICK_EDIT_MODE // 禁用快速编辑模式 50 | mode |= windows.ENABLE_EXTENDED_FLAGS // 启用扩展标志 51 | 52 | mode &^= windows.ENABLE_MOUSE_INPUT // 禁用鼠标输入 53 | mode |= windows.ENABLE_PROCESSED_INPUT // 启用控制输入 54 | 55 | mode &^= windows.ENABLE_INSERT_MODE // 禁用插入模式 56 | mode |= windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT // 启用输入回显&逐行输入 57 | 58 | mode &^= windows.ENABLE_WINDOW_INPUT // 禁用窗口输入 59 | mode &^= windows.ENABLE_VIRTUAL_TERMINAL_INPUT // 禁用虚拟终端输入 60 | 61 | err = windows.SetConsoleMode(stdin, mode) 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | stdout := windows.Handle(os.Stdout.Fd()) 67 | err = windows.GetConsoleMode(stdout, &mode) 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING // 启用虚拟终端处理 73 | mode |= windows.ENABLE_PROCESSED_OUTPUT // 启用处理后的输出 74 | 75 | err = windows.SetConsoleMode(stdout, mode) 76 | // windows 带颜色 log 自定义格式 77 | logrus.SetFormatter(&logFormat{hasColor: err == nil}) 78 | if err != nil { 79 | logrus.Warnln("VT100设置失败, 将以无色模式输出") 80 | } 81 | 82 | err = setConsoleTitle("NanoBot-Plugin " + banner.Version + " " + banner.Copyright) 83 | if err != nil { 84 | panic(err) 85 | } 86 | } 87 | 88 | const ( 89 | colorCodePanic = "\x1b[1;31m" // color.Style{color.Bold, color.Red}.String() 90 | colorCodeFatal = "\x1b[1;31m" // color.Style{color.Bold, color.Red}.String() 91 | colorCodeError = "\x1b[31m" // color.Style{color.Red}.String() 92 | colorCodeWarn = "\x1b[33m" // color.Style{color.Yellow}.String() 93 | colorCodeInfo = "\x1b[37m" // color.Style{color.White}.String() 94 | colorCodeDebug = "\x1b[32m" // color.Style{color.Green}.String() 95 | colorCodeTrace = "\x1b[36m" // color.Style{color.Cyan}.String() 96 | colorReset = "\x1b[0m" 97 | ) 98 | 99 | // logFormat specialize for zbp 100 | type logFormat struct { 101 | hasColor bool 102 | } 103 | 104 | // Format implements logrus.Formatter 105 | func (f logFormat) Format(entry *logrus.Entry) ([]byte, error) { 106 | buf := new(bytes.Buffer) 107 | 108 | buf.WriteByte('[') 109 | if f.hasColor { 110 | buf.WriteString(getLogLevelColorCode(entry.Level)) 111 | } 112 | buf.WriteString(strings.ToUpper(entry.Level.String())) 113 | if f.hasColor { 114 | buf.WriteString(colorReset) 115 | } 116 | buf.WriteString("] ") 117 | buf.WriteString(entry.Message) 118 | buf.WriteString(" \n") 119 | 120 | return buf.Bytes(), nil 121 | } 122 | 123 | // getLogLevelColorCode 获取日志等级对应色彩code 124 | func getLogLevelColorCode(level logrus.Level) string { 125 | switch level { 126 | case logrus.PanicLevel: 127 | return colorCodePanic 128 | case logrus.FatalLevel: 129 | return colorCodeFatal 130 | case logrus.ErrorLevel: 131 | return colorCodeError 132 | case logrus.WarnLevel: 133 | return colorCodeWarn 134 | case logrus.InfoLevel: 135 | return colorCodeInfo 136 | case logrus.DebugLevel: 137 | return colorCodeDebug 138 | case logrus.TraceLevel: 139 | return colorCodeTrace 140 | 141 | default: 142 | return colorCodeInfo 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/FloatTech/NanoBot-Plugin 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/FloatTech/AnimeAPI v1.7.1-0.20231017135344-aefd1d56e900 7 | github.com/FloatTech/floatbox v0.0.0-20231107124407-e38535efa2a2 8 | github.com/FloatTech/gg v1.1.3-0.20230226151425-6ea91286ba08 9 | github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef 10 | github.com/FloatTech/rendercard v0.0.10-0.20230223064326-45d29fa4ede9 11 | github.com/FloatTech/sqlite v1.6.3 12 | github.com/FloatTech/zbpctrl v1.6.0 13 | github.com/FloatTech/zbputils v1.7.1-0.20231017135158-7e6c839764eb 14 | github.com/disintegration/imaging v1.6.2 15 | github.com/fumiama/NanoBot v0.0.0-20231122134259-c22d8183efca 16 | github.com/fumiama/go-base16384 v1.7.0 17 | github.com/fumiama/go-registry v0.2.6 18 | github.com/fumiama/unibase2n v0.0.0-20221020155353-02876e777430 19 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 20 | github.com/jinzhu/gorm v1.9.16 21 | github.com/shirou/gopsutil/v3 v3.23.9 22 | github.com/sirupsen/logrus v1.9.3 23 | github.com/wcharczuk/go-chart/v2 v2.1.1 24 | github.com/wdvxdr1123/ZeroBot v1.7.5-0.20231009162356-57f71b9f5258 25 | golang.org/x/sys v0.12.0 26 | golang.org/x/text v0.12.0 27 | gopkg.in/yaml.v3 v3.0.1 28 | ) 29 | 30 | require ( 31 | github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 // indirect 32 | github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e // indirect 33 | github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 // indirect 34 | github.com/blend/go-sdk v1.20220411.3 // indirect 35 | github.com/corona10/goimagehash v1.1.0 // indirect 36 | github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect 37 | github.com/fumiama/cron v1.3.0 // indirect 38 | github.com/fumiama/go-simple-protobuf v0.1.0 // indirect 39 | github.com/fumiama/gofastTEA v0.0.10 // indirect 40 | github.com/fumiama/imgsz v0.0.2 // indirect 41 | github.com/fumiama/imoto v0.1.3 // indirect 42 | github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 // indirect 43 | github.com/go-ole/go-ole v1.2.6 // indirect 44 | github.com/google/uuid v1.3.0 // indirect 45 | github.com/jinzhu/inflection v1.0.0 // indirect 46 | github.com/kr/pretty v0.3.1 // indirect 47 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 48 | github.com/mattn/go-isatty v0.0.16 // indirect 49 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect 50 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 53 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 54 | github.com/rogpeppe/go-internal v1.11.0 // indirect 55 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 56 | github.com/tidwall/gjson v1.14.4 // indirect 57 | github.com/tidwall/match v1.1.1 // indirect 58 | github.com/tidwall/pretty v1.2.0 // indirect 59 | github.com/tklauser/go-sysconf v0.3.12 // indirect 60 | github.com/tklauser/numcpus v0.6.1 // indirect 61 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 62 | golang.org/x/crypto v0.4.0 // indirect 63 | golang.org/x/image v0.11.0 // indirect 64 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 65 | modernc.org/libc v1.21.5 // indirect 66 | modernc.org/mathutil v1.5.0 // indirect 67 | modernc.org/memory v1.4.0 // indirect 68 | modernc.org/sqlite v1.20.0 // indirect 69 | ) 70 | 71 | replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.20.0-with-win386 72 | 73 | replace github.com/remyoudompheng/bigfft => github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b 74 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/FloatTech/AnimeAPI v1.7.1-0.20231017135344-aefd1d56e900 h1:UPXoj+lMHFBulp/m+F7uHju0MXslFKQqEplDDz/nOiU= 2 | github.com/FloatTech/AnimeAPI v1.7.1-0.20231017135344-aefd1d56e900/go.mod h1:7Olb5U9q1oeayRZQTNBhXQNMf8QT4T9hccsn38IEt/U= 3 | github.com/FloatTech/floatbox v0.0.0-20231107124407-e38535efa2a2 h1:O4kptIzgYzNwZlBARZFv8EkA40yB6M5LGxxIF7NKLR8= 4 | github.com/FloatTech/floatbox v0.0.0-20231107124407-e38535efa2a2/go.mod h1:TeTlp+hTxpJti4JSdmUqzxGEr4wUBOVct9YWBepilpc= 5 | github.com/FloatTech/gg v1.1.3-0.20230226151425-6ea91286ba08 h1:dPLeoiTVSBlgls+66EB/UJ2e38BaASmBN5nANaycSBU= 6 | github.com/FloatTech/gg v1.1.3-0.20230226151425-6ea91286ba08/go.mod h1:uzPzAeT35egARdRuu+1oyjU3CmTwCceoq3Vvje7LpcI= 7 | github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef h1:CJbK/2FRwPuZpeb6M4sWK2d7oXDnBEGhpkQuQrgc91A= 8 | github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef/go.mod h1:el5hGpj1C1bDRxcTXYRwEivDCr40zZeJpcrLrB1fajs= 9 | github.com/FloatTech/rendercard v0.0.10-0.20230223064326-45d29fa4ede9 h1:hffajvmQFfP68U6wUwHemPuuwCUoss+SEFfoLYwbGwE= 10 | github.com/FloatTech/rendercard v0.0.10-0.20230223064326-45d29fa4ede9/go.mod h1:NBFPhWae4hqVMeG8ELBBnUQkKce3nDjkljVn6PdiUNs= 11 | github.com/FloatTech/sqlite v1.6.3 h1:MQkqBNlkPuCoKQQgoNLuTL/2Ci3tBTFAnVYBdD0Wy4M= 12 | github.com/FloatTech/sqlite v1.6.3/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY= 13 | github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 h1:g4pTnDJUW4VbJ9NvoRfUvdjDrHz/6QhfN/LoIIpICbo= 14 | github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= 15 | github.com/FloatTech/zbpctrl v1.6.0 h1:BWg9aRR4bUCmNNKj6GPH0TmzFRWYImIi6rQcQTTYRs4= 16 | github.com/FloatTech/zbpctrl v1.6.0/go.mod h1:i3GGM5K4HiDsXzvmXQSYoH1QT3tsSaAHjRzHwKGsHG0= 17 | github.com/FloatTech/zbputils v1.7.1-0.20231017135158-7e6c839764eb h1:a2JVRNoleD7Ut8KJEh/5ZF7Lo/G54vcQIvn9Xczcf64= 18 | github.com/FloatTech/zbputils v1.7.1-0.20231017135158-7e6c839764eb/go.mod h1:cB09XWmHUgS5N7mmypi9Yys1+VEaXNFJJJhxEytu3e8= 19 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 20 | github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA= 21 | github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= 22 | github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs= 23 | github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5/go.mod h1:0UcFaCkhp6vZw6l5Dpq0Dp673CoF9GdvA8lTfst0GiU= 24 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 25 | github.com/blend/go-sdk v1.20220411.3 h1:GFV4/FQX5UzXLPwWV03gP811pj7B8J2sbuq+GJQofXc= 26 | github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg09qsQqY1+ak= 27 | github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI= 28 | github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI= 29 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 30 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 32 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= 34 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 35 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= 36 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 37 | github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 h1:BBade+JlV/f7JstZ4pitd4tHhpN+w+6I+LyOS7B4fyU= 38 | github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4/go.mod h1:H7chHJglrhPPzetLdzBleF8d22WYOv7UM/lEKYiwlKM= 39 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 40 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 41 | github.com/fumiama/NanoBot v0.0.0-20231122134259-c22d8183efca h1:9xjwTpVdCR5loEo7B7+FnYyt4HwqkFWVvRpXc54x0Ug= 42 | github.com/fumiama/NanoBot v0.0.0-20231122134259-c22d8183efca/go.mod h1:z9IDRRwntGIrnnxcwgjVge7lUa2GkGWFT7F1uYZbvh8= 43 | github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b h1:Zt3pFQditAdWTHCOVkiloc9ZauBoWrb37guFV4iIRvE= 44 | github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 45 | github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo= 46 | github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY= 47 | github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= 48 | github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= 49 | github.com/fumiama/go-registry v0.2.6 h1:+vEeBUwa1+GC87ujW3Km42fi8O/H7QcpVJWu1iuGNh0= 50 | github.com/fumiama/go-registry v0.2.6/go.mod h1:HjYagPZXzR2xCCxaSQerqX7JRzC0yiv2kslDdBiTq/g= 51 | github.com/fumiama/go-simple-protobuf v0.1.0 h1:rLzJgNqB6LHNDVMl81yyNt6ZKziWtVfu+ioF0edlEVw= 52 | github.com/fumiama/go-simple-protobuf v0.1.0/go.mod h1:5yYNapXq1tQMOZg9bOIVhQlZk9pQqpuFIO4DZLbsdy4= 53 | github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoGhhQ= 54 | github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk= 55 | github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= 56 | github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= 57 | github.com/fumiama/imoto v0.1.3 h1:7KNVUIVaLWerdZZ6i83dAvcLoeqzlGhrY2MSjqWTxlY= 58 | github.com/fumiama/imoto v0.1.3/go.mod h1:1MLP+qfEbhZOn+ETXd6k0xkjMyNGuqnaiblcQuWu9NQ= 59 | github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhiKdEg+Ig354TaqgCQRxd/0KgIOQ= 60 | github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565/go.mod h1:UUEvyLTJ7yoOA/viKG4wEis4ERydM7+Ny6gZUWgkS80= 61 | github.com/fumiama/sqlite3 v1.20.0-with-win386 h1:ZR1AXGBEtkfq9GAXehOVcwn+aaCG8itrkgEsz4ggx5k= 62 | github.com/fumiama/sqlite3 v1.20.0-with-win386/go.mod h1:Os58MHwYCcYZCy2PGChBrQtBAw5/LS1ZZOkfc+C/I7s= 63 | github.com/fumiama/unibase2n v0.0.0-20221020155353-02876e777430 h1:XL4SnagpaVHYybnnj6whQxmt8Ps9/kaG6sCNn4X1GGA= 64 | github.com/fumiama/unibase2n v0.0.0-20221020155353-02876e777430/go.mod h1:lEaZsT4FRSqcjnQ5q8y+mkenkzR/r1D3BJmfdp0vqDg= 65 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 66 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 67 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 68 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 69 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 70 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 71 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 72 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 73 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 74 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 75 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 76 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 77 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 78 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= 79 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= 80 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 81 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 82 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 83 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 84 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 85 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 86 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 87 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 88 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 89 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 90 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 91 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 92 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 93 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 94 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 95 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 96 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 97 | github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= 98 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 99 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 100 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 101 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= 102 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= 103 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 104 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 105 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 106 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 107 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 108 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 109 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 110 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 111 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 112 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 113 | github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= 114 | github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= 115 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 116 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 117 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 118 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 119 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 120 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 121 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 122 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 123 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 124 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 125 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 126 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 127 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 128 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 129 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 130 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 131 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 132 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 133 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 134 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 135 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 136 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 137 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 138 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 139 | github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= 140 | github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= 141 | github.com/wdvxdr1123/ZeroBot v1.7.5-0.20231009162356-57f71b9f5258 h1:Q0dKoj9SHrR8WjjlcX+eyYBjQKqBn/x1pdJJO1IIOxQ= 142 | github.com/wdvxdr1123/ZeroBot v1.7.5-0.20231009162356-57f71b9f5258/go.mod h1:y29UIOy0RD3P+0meDNIWRhcJF3jtWPN9xP9hgt/AJAU= 143 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 144 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 145 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 146 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 147 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 148 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 149 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 150 | golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= 151 | golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= 152 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 153 | golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= 154 | golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= 155 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 156 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 157 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 158 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 159 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 160 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 161 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 162 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 163 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 164 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 165 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 166 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 167 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 168 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 169 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 175 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 177 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 178 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 179 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 180 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 181 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 182 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 183 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 184 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 185 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 186 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 187 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 188 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 189 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 190 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 191 | golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= 192 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 193 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 194 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 195 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 196 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 197 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 198 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 199 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 200 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 201 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 202 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 203 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 204 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 205 | modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI= 206 | modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= 207 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 208 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 209 | modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= 210 | modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 211 | -------------------------------------------------------------------------------- /kanban/banner/banner.go: -------------------------------------------------------------------------------- 1 | // Code generated by kanban/gen. DO NOT EDIT. 2 | 3 | package banner 4 | 5 | // Version ... 6 | var Version = "v0.1.7" 7 | 8 | // Copyright ... 9 | var Copyright = "© 2023 - 2023 FloatTech" 10 | 11 | // Banner ... 12 | var Banner = "* QQ + NanoBot + Golang\n" + 13 | "* Version " + Version + " - 2023-11-22 18:09:56 +0900 JST\n" + 14 | "* Copyright " + Copyright + ". All Rights Reserved.\n" + 15 | "* Project: https://github.com/FloatTech/NanoBot-Plugin" 16 | -------------------------------------------------------------------------------- /kanban/gen/banner.go: -------------------------------------------------------------------------------- 1 | // Package main generates banner.go 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const banner = `// Code generated by kanban/gen. DO NOT EDIT. 14 | 15 | package banner 16 | 17 | // Version ... 18 | var Version = "%s" 19 | 20 | // Copyright ... 21 | var Copyright = "© 2023 - %d FloatTech" 22 | 23 | // Banner ... 24 | var Banner = "* QQ + NanoBot + Golang\n" + 25 | "* Version " + Version + " - %s\n" + 26 | "* Copyright " + Copyright + ". All Rights Reserved.\n" + 27 | "* Project: https://github.com/FloatTech/NanoBot-Plugin" 28 | ` 29 | 30 | const timeformat = `2006-01-02 15:04:05 +0900 JST` 31 | 32 | func main() { 33 | f, err := os.Create("banner/banner.go") 34 | if err != nil { 35 | panic(err) 36 | } 37 | defer f.Close() 38 | vartag := bytes.NewBuffer(nil) 39 | vartagcmd := exec.Command("git", "tag", "--sort=committerdate") 40 | vartagcmd.Stdout = vartag 41 | err = vartagcmd.Run() 42 | if err != nil { 43 | panic(err) 44 | } 45 | s := strings.Split(vartag.String(), "\n") 46 | now := time.Now() 47 | _, err = fmt.Fprintf(f, banner, s[len(s)-2], now.Year(), now.Format(timeformat)) 48 | if err != nil { 49 | panic(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /kanban/init.go: -------------------------------------------------------------------------------- 1 | // Package kanban 打印版本信息 2 | package kanban 3 | 4 | import ( 5 | "fmt" 6 | 7 | nano "github.com/fumiama/NanoBot" 8 | "github.com/fumiama/go-registry" 9 | 10 | "github.com/FloatTech/NanoBot-Plugin/kanban/banner" 11 | ) 12 | 13 | //go:generate go run github.com/FloatTech/NanoBot-Plugin/kanban/gen 14 | 15 | func init() { 16 | PrintBanner() 17 | } 18 | 19 | var reg = registry.NewRegReader("reilia.fumiama.top:32664", nano.Md5File, "fumiama") 20 | 21 | // PrintBanner ... 22 | func PrintBanner() { 23 | fmt.Print( 24 | "\n======================[NanoBot-Plugin]======================", 25 | "\n", banner.Banner, "\n", 26 | "----------------------[NanoBot-公告栏]----------------------", 27 | "\n", Kanban(), "\n", 28 | "============================================================\n\n", 29 | ) 30 | } 31 | 32 | // Kanban ... 33 | func Kanban() string { 34 | err := reg.Connect() 35 | if err != nil { 36 | return err.Error() 37 | } 38 | defer reg.Close() 39 | text, err := reg.Get("NanoBot-Plugin/kanban") 40 | if err != nil { 41 | return err.Error() 42 | } 43 | return text 44 | } 45 | -------------------------------------------------------------------------------- /kanban/version_less_than_1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package kanban 4 | 5 | const Error int = "请使用小于1.21版本的Go" 6 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Package main NanoBot-Plugin main file 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/FloatTech/NanoBot-Plugin/kanban" // 打印 banner 15 | 16 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/autowithdraw" 17 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/b14" 18 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/base64gua" 19 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/baseamasiro" 20 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/chrev" 21 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/dish" 22 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/emojimix" 23 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/fortune" 24 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/genshin" 25 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/hyaku" 26 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/manager" 27 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/qqwife" 28 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/qunyou" 29 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/runcode" 30 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/score" 31 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/status" 32 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/tarot" 33 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/wife" 34 | _ "github.com/FloatTech/NanoBot-Plugin/plugin/wordle" 35 | 36 | // -----------------------以下为内置依赖,勿动------------------------ // 37 | nano "github.com/fumiama/NanoBot" 38 | "github.com/sirupsen/logrus" 39 | "gopkg.in/yaml.v3" 40 | 41 | "github.com/FloatTech/floatbox/process" 42 | 43 | "github.com/FloatTech/NanoBot-Plugin/kanban/banner" 44 | // -----------------------以上为内置依赖,勿动------------------------ // 45 | ) 46 | 47 | func main() { 48 | if !strings.Contains(runtime.Version(), "go1.2") { // go1.20之前版本需要全局 seed,其他插件无需再 seed 49 | rand.Seed(time.Now().UnixNano()) //nolint: staticcheck 50 | } 51 | 52 | token := flag.String("t", "", "qq api token") 53 | appid := flag.String("a", "", "qq appid") 54 | secret := flag.String("s", "", "qq secret") 55 | debug := flag.Bool("D", false, "enable debug-level log output") 56 | timeout := flag.Int("T", 60, "api timeout (s)") 57 | help := flag.Bool("h", false, "print this help") 58 | loadconfig := flag.String("c", "", "load from config") 59 | sandbox := flag.Bool("sandbox", false, "run in sandbox api") 60 | onlypublic := flag.Bool("public", false, "only listen to public intent") 61 | addqqintent := flag.Bool("qq", false, "also listen QQ intent") 62 | shardindex := flag.Uint("shardindex", 0, "shard index") 63 | shardcount := flag.Uint("shardcount", 0, "shard count") 64 | savecfg := flag.String("save", "", "save bot config to filename (eg. config.yaml)") 65 | superallqqusers := flag.Bool("superallqq", false, "make all QQ users to be SuperUser") 66 | flag.Parse() 67 | if *help { 68 | fmt.Println("Usage:") 69 | flag.PrintDefaults() 70 | os.Exit(0) 71 | } 72 | 73 | if *debug { 74 | logrus.SetLevel(logrus.DebugLevel) 75 | } 76 | intent := uint32(nano.IntentGuildPrivate) 77 | if *onlypublic { 78 | intent = nano.IntentGuildPublic 79 | } 80 | if *addqqintent { 81 | intent |= nano.IntentQQ 82 | } 83 | 84 | sus := make([]string, 0, 16) 85 | if *superallqqusers { 86 | sus = append(sus, nano.SuperUserAllQQUsers) 87 | } 88 | for _, s := range flag.Args() { 89 | _, err := strconv.ParseInt(s, 10, 64) 90 | if err != nil { 91 | continue 92 | } 93 | sus = append(sus, s) 94 | } 95 | 96 | if *sandbox { 97 | nano.OpenAPI = nano.SandboxAPI 98 | } 99 | 100 | bot := []*nano.Bot{} 101 | if *loadconfig == "" { 102 | bot = append(bot, &nano.Bot{ 103 | AppID: *appid, 104 | Token: *token, 105 | Secret: *secret, 106 | SuperUsers: sus, 107 | Timeout: time.Duration(*timeout) * time.Second, 108 | Intents: intent, 109 | ShardIndex: uint8(*shardindex), 110 | ShardCount: uint8(*shardcount), 111 | }) 112 | } else { 113 | f, err := os.Open(*loadconfig) 114 | if err != nil { 115 | logrus.Fatal(err) 116 | } 117 | dec := yaml.NewDecoder(f) 118 | dec.KnownFields(true) 119 | err = dec.Decode(&bot) 120 | _ = f.Close() 121 | if err != nil { 122 | logrus.Fatal(err) 123 | } 124 | } 125 | if *savecfg != "" { 126 | f, err := os.Create(*savecfg) 127 | if err != nil { 128 | logrus.Fatal(err) 129 | } 130 | defer f.Close() 131 | err = yaml.NewEncoder(f).Encode(bot) 132 | if err != nil { 133 | logrus.Fatal(err) 134 | } 135 | logrus.Infoln("已将当前配置保存到", *savecfg) 136 | return 137 | } 138 | 139 | nano.OnMessageCommandGroup([]string{"help", "帮助", "menu", "菜单"}, nano.OnlyToMe).SetBlock(true). 140 | Handle(func(ctx *nano.Ctx) { 141 | _, _ = ctx.SendChain(nano.Text(banner.Banner)) 142 | }) 143 | nano.OnMessageFullMatch("查看nbp公告", nano.OnlyToMe, nano.AdminPermission).SetBlock(true). 144 | Handle(func(ctx *nano.Ctx) { 145 | _, _ = ctx.SendChain(nano.Text(strings.ReplaceAll(kanban.Kanban(), "\t", ""))) 146 | }) 147 | _ = nano.Run(process.GlobalInitMutex.Unlock, bot...) 148 | } 149 | -------------------------------------------------------------------------------- /plugin/autowithdraw/main.go: -------------------------------------------------------------------------------- 1 | // Package autowithdraw 触发者撤回时也自动撤回 2 | package autowithdraw 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/process" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | nano "github.com/fumiama/NanoBot" 8 | ) 9 | 10 | func init() { 11 | en := nano.Register("autowithdraw", &ctrl.Options[*nano.Ctx]{ 12 | DisableOnDefault: false, 13 | Brief: "触发者撤回时也自动撤回", 14 | Help: "- 撤回一条消息\n", 15 | }) 16 | en.OnMessageDelete(nano.OnlyPrivate).SetBlock(false).Handle(func(ctx *nano.Ctx) { 17 | delmsg := ctx.Value.(*nano.MessageDelete) 18 | for _, msg := range nano.GetTriggeredMessages(delmsg.Message.ID) { 19 | process.SleepAbout1sTo2s() 20 | _ = ctx.DeleteMessageInChannel(delmsg.Message.ChannelID, msg, false) 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /plugin/b14/main.go: -------------------------------------------------------------------------------- 1 | // Package b14coder base16384 与 tea 加解密 2 | package b14coder 3 | 4 | import ( 5 | nano "github.com/fumiama/NanoBot" 6 | 7 | base14 "github.com/fumiama/go-base16384" 8 | 9 | "github.com/FloatTech/floatbox/crypto" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | ) 12 | 13 | func init() { 14 | en := nano.Register("base16384", &ctrl.Options[*nano.Ctx]{ 15 | DisableOnDefault: false, 16 | Help: "base16384加解密\n" + 17 | "- 加密xxx\n- 解密xxx\n- 用yyy加密xxx\n- 用yyy解密xxx", 18 | }) 19 | en.OnMessageRegex(`^加密\s*(.+)$`).SetBlock(true). 20 | Handle(func(ctx *nano.Ctx) { 21 | str := ctx.State["regex_matched"].([]string)[1] 22 | es := base14.EncodeString(str) 23 | if es != "" { 24 | _, _ = ctx.SendPlainMessage(false, es) 25 | } else { 26 | _, _ = ctx.SendPlainMessage(false, "加密失败!") 27 | } 28 | }) 29 | en.OnMessageRegex(`^解密\s*([一-踀]+[㴁-㴆]?)$`).SetBlock(true). 30 | Handle(func(ctx *nano.Ctx) { 31 | str := ctx.State["regex_matched"].([]string)[1] 32 | es := base14.DecodeString(str) 33 | if es != "" { 34 | _, err := ctx.SendPlainMessage(false, es) 35 | if err != nil { 36 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 37 | } 38 | } else { 39 | _, _ = ctx.SendPlainMessage(false, "解密失败!") 40 | } 41 | }) 42 | en.OnMessageRegex(`^用(.+)加密\s*(.+)$`).SetBlock(true). 43 | Handle(func(ctx *nano.Ctx) { 44 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 45 | t := crypto.GetTEA(key) 46 | es, err := base14.UTF16BE2UTF8(base14.Encode(t.Encrypt(nano.StringToBytes(str)))) 47 | if err == nil { 48 | _, _ = ctx.SendPlainMessage(false, nano.BytesToString(es)) 49 | } else { 50 | _, _ = ctx.SendPlainMessage(false, "加密失败!") 51 | } 52 | }) 53 | en.OnMessageRegex(`^用(.+)解密\s*([一-踀]+[㴁-㴆]?)$`).SetBlock(true). 54 | Handle(func(ctx *nano.Ctx) { 55 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 56 | t := crypto.GetTEA(key) 57 | es, err := base14.UTF82UTF16BE(nano.StringToBytes(str)) 58 | if err == nil { 59 | _, err := ctx.SendPlainMessage(false, nano.BytesToString(t.Decrypt(base14.Decode(es)))) 60 | if err != nil { 61 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 62 | } 63 | } else { 64 | _, _ = ctx.SendPlainMessage(false, "解密失败!") 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /plugin/base64gua/main.go: -------------------------------------------------------------------------------- 1 | // Package base64gua base64卦 与 tea 加解密 2 | package base64gua 3 | 4 | import ( 5 | nano "github.com/fumiama/NanoBot" 6 | 7 | "github.com/fumiama/unibase2n" 8 | 9 | "github.com/FloatTech/floatbox/crypto" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | ) 12 | 13 | func init() { 14 | en := nano.Register("base64gua", &ctrl.Options[*nano.Ctx]{ 15 | DisableOnDefault: false, 16 | Help: "base64gua加解密\n" + 17 | "- 六十四卦加密xxx\n- 六十四卦解密xxx\n- 六十四卦用yyy加密xxx\n- 六十四卦用yyy解密xxx", 18 | }) 19 | en.OnMessageRegex(`^六十四卦加密\s*(.+)$`).SetBlock(true). 20 | Handle(func(ctx *nano.Ctx) { 21 | str := ctx.State["regex_matched"].([]string)[1] 22 | es := unibase2n.Base64Gua.EncodeString(str) 23 | if es != "" { 24 | _, _ = ctx.SendPlainMessage(false, es) 25 | } else { 26 | _, _ = ctx.SendPlainMessage(false, "加密失败!") 27 | } 28 | }) 29 | en.OnMessageRegex(`^六十四卦解密\s*([䷀-䷿]+[☰☱]?)$`).SetBlock(true). 30 | Handle(func(ctx *nano.Ctx) { 31 | str := ctx.State["regex_matched"].([]string)[1] 32 | es := unibase2n.Base64Gua.DecodeString(str) 33 | if es != "" { 34 | _, _ = ctx.SendPlainMessage(false, es) 35 | } else { 36 | _, _ = ctx.SendPlainMessage(false, "解密失败!") 37 | } 38 | }) 39 | en.OnMessageRegex(`^六十四卦用(.+)加密\s*(.+)$`).SetBlock(true). 40 | Handle(func(ctx *nano.Ctx) { 41 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 42 | t := crypto.GetTEA(key) 43 | es, err := unibase2n.UTF16BE2UTF8(unibase2n.Base64Gua.Encode(t.Encrypt(nano.StringToBytes(str)))) 44 | if err == nil { 45 | _, _ = ctx.SendPlainMessage(false, nano.BytesToString(es)) 46 | } else { 47 | _, _ = ctx.SendPlainMessage(false, "加密失败!") 48 | } 49 | }) 50 | en.OnMessageRegex(`^六十四卦用(.+)解密\s*([䷀-䷿]+[☰☱]?)$`).SetBlock(true). 51 | Handle(func(ctx *nano.Ctx) { 52 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 53 | t := crypto.GetTEA(key) 54 | es, err := unibase2n.UTF82UTF16BE(nano.StringToBytes(str)) 55 | if err == nil { 56 | _, _ = ctx.SendPlainMessage(false, nano.BytesToString(t.Decrypt(unibase2n.Base64Gua.Decode(es)))) 57 | } else { 58 | _, _ = ctx.SendPlainMessage(false, "解密失败!") 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /plugin/baseamasiro/main.go: -------------------------------------------------------------------------------- 1 | // Package baseamasiro base天城文 与 tea 加解密 2 | package baseamasiro 3 | 4 | import ( 5 | nano "github.com/fumiama/NanoBot" 6 | 7 | "github.com/fumiama/unibase2n" 8 | 9 | "github.com/FloatTech/floatbox/crypto" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | ) 12 | 13 | func init() { 14 | en := nano.Register("baseamasiro", &ctrl.Options[*nano.Ctx]{ 15 | DisableOnDefault: false, 16 | Help: "base天城文加解密\n" + 17 | "- 天城文加密xxx\n- 天城文解密xxx\n- 天城文用yyy加密xxx\n- 天城文用yyy解密xxx", 18 | }) 19 | en.OnMessageRegex(`^天城文加密\s*(.+)$`).SetBlock(true). 20 | Handle(func(ctx *nano.Ctx) { 21 | str := ctx.State["regex_matched"].([]string)[1] 22 | es := unibase2n.BaseDevanagari.EncodeString(str) 23 | if es != "" { 24 | _, _ = ctx.SendPlainMessage(false, es) 25 | } else { 26 | _, _ = ctx.SendPlainMessage(false, "加密失败!") 27 | } 28 | }) 29 | en.OnMessageRegex(`^天城文解密\s*([ऀ-ॿ]+[০-৫]?)$`).SetBlock(true). 30 | Handle(func(ctx *nano.Ctx) { 31 | str := ctx.State["regex_matched"].([]string)[1] 32 | es := unibase2n.BaseDevanagari.DecodeString(str) 33 | if es != "" { 34 | _, _ = ctx.SendPlainMessage(false, es) 35 | } else { 36 | _, _ = ctx.SendPlainMessage(false, "解密失败!") 37 | } 38 | }) 39 | en.OnMessageRegex(`^天城文用(.+)加密\s*(.+)$`).SetBlock(true). 40 | Handle(func(ctx *nano.Ctx) { 41 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 42 | t := crypto.GetTEA(key) 43 | es, err := unibase2n.UTF16BE2UTF8(unibase2n.BaseDevanagari.Encode(t.Encrypt(nano.StringToBytes(str)))) 44 | if err == nil { 45 | _, _ = ctx.SendPlainMessage(false, nano.BytesToString(es)) 46 | } else { 47 | _, _ = ctx.SendPlainMessage(false, "加密失败!") 48 | } 49 | }) 50 | en.OnMessageRegex(`^天城文用(.+)解密\s*([ऀ-ॿ]+[০-৫]?)$`).SetBlock(true). 51 | Handle(func(ctx *nano.Ctx) { 52 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 53 | t := crypto.GetTEA(key) 54 | es, err := unibase2n.UTF82UTF16BE(nano.StringToBytes(str)) 55 | if err == nil { 56 | _, _ = ctx.SendPlainMessage(false, nano.BytesToString(t.Decrypt(unibase2n.BaseDevanagari.Decode(es)))) 57 | } else { 58 | _, _ = ctx.SendPlainMessage(false, "解密失败!") 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /plugin/chrev/init.go: -------------------------------------------------------------------------------- 1 | // Package chrev 英文字符反转 2 | package chrev 3 | 4 | import ( 5 | "strings" 6 | 7 | nano "github.com/fumiama/NanoBot" 8 | 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | ) 11 | 12 | func init() { 13 | // 初始化engine 14 | engine := nano.Register("chrev", &ctrl.Options[*nano.Ctx]{ 15 | DisableOnDefault: false, 16 | Help: "字符翻转\n- 翻转 I love you", 17 | }) 18 | // 处理字符翻转指令 19 | engine.OnMessageRegex(`^翻转\s*([A-Za-z\s]*)$`).SetBlock(true). 20 | Handle(func(ctx *nano.Ctx) { 21 | // 获取需要翻转的字符串 22 | str := ctx.State["regex_matched"].([]string)[1] 23 | // 将字符顺序翻转 24 | tmp := strings.Builder{} 25 | for i := len(str) - 1; i >= 0; i-- { 26 | tmp.WriteRune(charMap[str[i]]) 27 | } 28 | // 发送翻转后的字符串 29 | _, _ = ctx.SendPlainMessage(false, tmp.String()) 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /plugin/chrev/map.go: -------------------------------------------------------------------------------- 1 | package chrev 2 | 3 | var charMap [256]rune 4 | 5 | func init() { 6 | charMap[' '] = ' ' 7 | charMap['a'] = 'ɐ' 8 | charMap['b'] = 'q' 9 | charMap['c'] = 'ɔ' 10 | charMap['d'] = 'p' 11 | charMap['e'] = 'ǝ' 12 | charMap['f'] = 'ɟ' 13 | charMap['g'] = 'ƃ' 14 | charMap['h'] = 'ɥ' 15 | charMap['i'] = 'ᴉ' 16 | charMap['j'] = 'ɾ' 17 | charMap['k'] = 'ʞ' 18 | charMap['l'] = 'l' 19 | charMap['m'] = 'ɯ' 20 | charMap['n'] = 'u' 21 | charMap['o'] = 'o' 22 | charMap['p'] = 'd' 23 | charMap['q'] = 'b' 24 | charMap['r'] = 'ɹ' 25 | charMap['s'] = 's' 26 | charMap['t'] = 'ʇ' 27 | charMap['u'] = 'n' 28 | charMap['v'] = 'ʌ' 29 | charMap['w'] = 'ʍ' 30 | charMap['x'] = 'x' 31 | charMap['y'] = 'ʎ' 32 | charMap['z'] = 'z' 33 | charMap['A'] = '∀' 34 | charMap['B'] = 'ᗺ' 35 | charMap['C'] = 'Ɔ' 36 | charMap['D'] = 'ᗡ' 37 | charMap['E'] = 'Ǝ' 38 | charMap['F'] = 'Ⅎ' 39 | charMap['G'] = '⅁' 40 | charMap['H'] = 'H' 41 | charMap['I'] = 'I' 42 | charMap['J'] = 'ſ' 43 | charMap['K'] = 'ʞ' 44 | charMap['L'] = '˥' 45 | charMap['M'] = 'W' 46 | charMap['N'] = 'N' 47 | charMap['O'] = 'O' 48 | charMap['P'] = 'Ԁ' 49 | charMap['Q'] = 'Ò' 50 | charMap['R'] = 'ᴚ' 51 | charMap['S'] = 'S' 52 | charMap['T'] = '⏊' 53 | charMap['U'] = '∩' 54 | charMap['V'] = 'Λ' 55 | charMap['W'] = 'M' 56 | charMap['X'] = 'X' 57 | charMap['Y'] = '⅄' 58 | charMap['Z'] = 'Z' 59 | } 60 | -------------------------------------------------------------------------------- /plugin/dish/main.go: -------------------------------------------------------------------------------- 1 | // Package dish 程序员做饭指南, 数据来源Anduin2017/HowToCook 2 | package dish 3 | 4 | import ( 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | 11 | sql "github.com/FloatTech/sqlite" 12 | ctrl "github.com/FloatTech/zbpctrl" 13 | nano "github.com/fumiama/NanoBot" 14 | 15 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 16 | ) 17 | 18 | type dish struct { 19 | ID uint32 `db:"id"` 20 | Name string `db:"name"` 21 | Materials string `db:"materials"` 22 | Steps string `db:"steps"` 23 | } 24 | 25 | var ( 26 | db = &sql.Sqlite{} 27 | initialized = false 28 | ) 29 | 30 | func init() { 31 | en := nano.Register("dish", &ctrl.Options[*nano.Ctx]{ 32 | DisableOnDefault: false, 33 | Brief: "程序员做饭指南", 34 | Help: "-怎么做[xxx]|烹饪[xxx]|随机菜谱|随便做点菜", 35 | PublicDataFolder: "Dish", 36 | }) 37 | 38 | db.DBPath = en.DataFolder() + "dishes.db" 39 | 40 | if _, err := en.GetLazyData("dishes.db", true); err != nil { 41 | logrus.Warnln("[dish]获取菜谱数据库文件失败") 42 | } else if err = db.Open(time.Hour); err != nil { 43 | logrus.Warnln("[dish]连接菜谱数据库失败") 44 | } else if err = db.Create("dish", &dish{}); err != nil { 45 | logrus.Warnln("[dish]同步菜谱数据表失败") 46 | } else if count, err := db.Count("dish"); err != nil { 47 | logrus.Warnln("[dish]统计菜谱数据失败") 48 | } else { 49 | logrus.Infoln("[dish]加载", count, "条菜谱") 50 | initialized = true 51 | } 52 | 53 | if !initialized { 54 | logrus.Warnln("[dish]插件未能成功初始化") 55 | } 56 | 57 | en.OnMessagePrefixGroup([]string{"怎么做", "烹饪"}).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 58 | if !initialized { 59 | _, err := ctx.SendPlainMessage(false, "客官,本店暂未开业") 60 | if err != nil { 61 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 62 | } 63 | return 64 | } 65 | 66 | name := ctx.Message.Author.Username 67 | dishName := ctx.State["args"].(string) 68 | 69 | if dishName == "" { 70 | return 71 | } 72 | 73 | if strings.Contains(dishName, "'") || 74 | strings.Contains(dishName, "\"") || 75 | strings.Contains(dishName, "\\") || 76 | strings.Contains(dishName, ";") { 77 | return 78 | } 79 | 80 | var d dish 81 | if err := db.Find("dish", &d, fmt.Sprintf("WHERE name like %%%s%%", dishName)); err != nil { 82 | return 83 | } 84 | 85 | _, err := ctx.SendPlainMessage(false, fmt.Sprintf( 86 | "已为客官%s找到%s的做法辣!\n"+ 87 | "原材料:%s\n"+ 88 | "步骤:\n"+ 89 | "%s", 90 | name, dishName, d.Materials, d.Steps), 91 | ) 92 | if err != nil { 93 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 94 | } 95 | }) 96 | 97 | en.OnMessagePrefixGroup([]string{"随机菜谱", "随便做点菜"}).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 98 | if !initialized { 99 | _, err := ctx.SendPlainMessage(false, "客官,本店暂未开业") 100 | if err != nil { 101 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 102 | } 103 | return 104 | } 105 | 106 | name := ctx.Message.Author.Username 107 | var d dish 108 | if err := db.Pick("dish", &d); err != nil { 109 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 110 | return 111 | } 112 | 113 | _, err := ctx.SendPlainMessage(false, fmt.Sprintf( 114 | "已为客官%s送上%s的做法\n"+ 115 | "原材料:%s\n"+ 116 | "步骤:\n"+ 117 | "%s", 118 | name, d.Name, d.Materials, d.Steps), 119 | ) 120 | if err != nil { 121 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 122 | } 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /plugin/emojimix/emoji.go: -------------------------------------------------------------------------------- 1 | // Package emojimix 合成emoji 2 | package emojimix 3 | 4 | var emojis = map[rune]int32{ 5 | 128516: 20201001, // 😄 grinning face with smiling eyes 6 | 128512: 20201001, // 😀 grinning face 7 | 128578: 20201001, // 🙂 slightly smiling face 8 | 128579: 20201001, // 🙃 upside-down face 9 | 128521: 20201001, // 😉 winking face 10 | 128522: 20201001, // 😊 smiling face with smiling eyes 11 | 128518: 20201001, // 😆 grinning squinting face 12 | 128515: 20201001, // 😃 grinning face with big eyes 13 | 128513: 20201001, // 😁 beaming face with smiling eyes 14 | 129315: 20201001, // 🤣 rolling on the floor laughing 15 | 128517: 20201001, // 😅 grinning face with sweat 16 | 128514: 20201001, // 😂 face with tears of joy 17 | 128519: 20201001, // 😇 smiling face with halo 18 | 129392: 20201001, // 🥰 smiling face with hearts 19 | 128525: 20201001, // 😍 smiling face with heart-eyes 20 | 128536: 20201001, // 😘 face blowing a kiss 21 | 129321: 20201001, // 🤩 star-struck 22 | 128535: 20201001, // 😗 kissing face 23 | 128538: 20201001, // 😚 kissing face with closed eyes 24 | 128537: 20201001, // 😙 kissing face with smiling eyes 25 | 128539: 20201001, // 😛 face with tongue 26 | 128541: 20201001, // 😝 squinting face with tongue 27 | 128523: 20201001, // 😋 face savoring food 28 | 129394: 20201001, // 🥲 smiling face with tear 29 | 129297: 20201001, // 🤑 money-mouth face 30 | 128540: 20201001, // 😜 winking face with tongue 31 | 129303: 20201001, // 🤗 smiling face with open hands hugs 32 | 129323: 20201001, // 🤫 shushing face quiet whisper 33 | 129300: 20201001, // 🤔 thinking face question hmmm 34 | 129325: 20201001, // 🤭 face with hand over mouth embarrassed 35 | 129320: 20201001, // 🤨 face with raised eyebrow question 36 | 129296: 20201001, // 🤐 zipper-mouth face 37 | 128528: 20201001, // 😐 neutral face 38 | 128529: 20201001, // 😑 expressionless face 39 | 128566: 20201001, // 😶 face without mouth 40 | 129322: 20201001, // 🤪 zany face 41 | 128527: 20201001, // 😏 smirking face suspicious 42 | 128530: 20201001, // 😒 unamused face 43 | 128580: 20201001, // 🙄 face with rolling eyes 44 | 128556: 20201001, // 😬 grimacing face 45 | 128558: 20210218, // 😮 face exhaling 46 | 129317: 20201001, // 🤥 lying face 47 | 128524: 20201001, // 😌 relieved face 48 | 128532: 20201001, // 😔 pensive face 49 | 128554: 20201001, // 😪 sleepy face 50 | 129316: 20201001, // 🤤 drooling face 51 | 128564: 20201001, // 😴 sleeping face 52 | 128567: 20201001, // 😷 face with medical mask 53 | 129298: 20201001, // 🤒 face with thermometer 54 | 129301: 20201001, // 🤕 face with head-bandage 55 | 129314: 20201001, // 🤢 nauseated face 56 | 129326: 20201001, // 🤮 face vomiting throw 57 | 129319: 20201001, // 🤧 sneezing face 58 | 129397: 20201001, // 🥵 hot face warm 59 | 129398: 20201001, // 🥶 cold face freezing ice 60 | 128565: 20201001, // 😵 face with crossed-out eyes 61 | 129396: 20201001, // 🥴 woozy face drunk tipsy drug high 62 | 129327: 20201001, // 🤯 exploding head mindblow 63 | 129312: 20201001, // 🤠 cowboy hat face 64 | 129395: 20201001, // 🥳 partying face 65 | 129400: 20201001, // 🥸 disguised face 66 | 129488: 20201001, // 🧐 face with monocle glasses 67 | 128526: 20201001, // 😎 smiling face with sunglasses 68 | 128533: 20201001, // 😕 confused face 69 | 128543: 20201001, // 😟 worried face 70 | 128577: 20201001, // 🙁 slightly frowning face 71 | 128559: 20201001, // 😯 hushed face 72 | 128562: 20201001, // 😲 astonished face 73 | 129299: 20201001, // 🤓 nerd face glasses 74 | 128563: 20201001, // 😳 flushed face 75 | 129402: 20201001, // 🥺 pleading face 76 | 128551: 20201001, // 😧 anguished face 77 | 128552: 20201001, // 😨 fearful face 78 | 128550: 20201001, // 😦 frowning face with open mouth 79 | 128560: 20201001, // 😰 anxious face with sweat 80 | 128549: 20201001, // 😥 sad but relieved face 81 | 128557: 20201001, // 😭 loudly crying face 82 | 128553: 20201001, // 😩 weary face 83 | 128546: 20201001, // 😢 crying face 84 | 128547: 20201001, // 😣 persevering face 85 | 128544: 20201001, // 😠 angry face 86 | 128531: 20201001, // 😓 downcast face with sweat 87 | 128534: 20201001, // 😖 confounded face 88 | 129324: 20201001, // 🤬 face with symbols on mouth 89 | 128542: 20201001, // 😞 disappointed face 90 | 128555: 20201001, // 😫 tired face 91 | 128548: 20201001, // 😤 face with steam from nose 92 | 129393: 20201001, // 🥱 yawning face 93 | 128169: 20201001, // 💩 pile of poo 94 | 128545: 20201001, // 😡 pouting face 95 | 128561: 20201001, // 😱 face screaming in fear 96 | 128127: 20201001, // 👿 angry face with horns 97 | 128128: 20201001, // 💀 skull 98 | 128125: 20201001, // 👽 alien 99 | 128520: 20201001, // 😈 smiling face with horns devil 100 | 129313: 20201001, // 🤡 clown face 101 | 128123: 20201001, // 👻 ghost 102 | 129302: 20201001, // 🤖 robot 103 | 128175: 20201001, // 💯 hundred points percent 104 | 128064: 20201001, // 👀 eyes 105 | 127801: 20201001, // 🌹 rose flower 106 | 127804: 20201001, // 🌼 blossom flower 107 | 127799: 20201001, // 🌷 tulip flower 108 | 127797: 20201001, // 🌵 cactus 109 | 127821: 20201001, // 🍍 pineapple 110 | 127874: 20201001, // 🎂 birthday cake 111 | 127751: 20210831, // 🌇 sunset 112 | 129473: 20201001, // 🧁 cupcake muffin 113 | 127911: 20210521, // 🎧 headphone earphone 114 | 127800: 20210218, // 🌸 cherry blossom flower 115 | 129440: 20201001, // 🦠 microbe germ bacteria virus covid corona 116 | 128144: 20201001, // 💐 bouquet flowers 117 | 127789: 20201001, // 🌭 hot dog food 118 | 128139: 20201001, // 💋 kiss mark lips 119 | 127875: 20201001, // 🎃 jack-o-lantern pumpkin 120 | 129472: 20201001, // 🧀 cheese wedge 121 | 9749: 20201001, // ☕ hot beverage coffee cup tea 122 | 127882: 20201001, // 🎊 confetti ball 123 | 127880: 20201001, // 🎈 balloon 124 | 9924: 20201001, // ⛄ snowman without snow 125 | 128142: 20201001, // 💎 gem stone crystal diamond 126 | 127794: 20201001, // 🌲 evergreen tree 127 | 129410: 20210218, // 🦂 scorpion 128 | 128584: 20201001, // 🙈 see-no-evil monkey 129 | 128148: 20201001, // 💔 broken heart 130 | 128140: 20201001, // 💌 love letter heart 131 | 128152: 20201001, // 💘 heart with arrow 132 | 128159: 20201001, // 💟 heart decoration 133 | 128158: 20201001, // 💞 revolving hearts 134 | 128147: 20201001, // 💓 beating heart 135 | 128149: 20201001, // 💕 two hearts 136 | 128151: 20201001, // 💗 growing heart 137 | 129505: 20201001, // 🧡 orange heart 138 | 128155: 20201001, // 💛 yellow heart 139 | 10084: 20210218, // ❤ mending heart 140 | 128156: 20201001, // 💜 purple heart 141 | 128154: 20201001, // 💚 green heart 142 | 128153: 20201001, // 💙 blue heart 143 | 129294: 20201001, // 🤎 brown heart 144 | 129293: 20201001, // 🤍 white heart 145 | 128420: 20201001, // 🖤 black heart 146 | 128150: 20201001, // 💖 sparkling heart 147 | 128157: 20201001, // 💝 heart with ribbon 148 | 127873: 20211115, // 🎁 wrapped-gift 149 | 129717: 20211115, // 🪵 wood 150 | 127942: 20211115, // 🏆 trophy 151 | 127838: 20210831, // 🍞 bread 152 | 128240: 20201001, // 📰 newspaper 153 | 128302: 20201001, // 🔮 crystal ball 154 | 128081: 20201001, // 👑 crown 155 | 128055: 20201001, // 🐷 pig face 156 | 129412: 20210831, // 🦄 unicorn 157 | 127771: 20201001, // 🌛 first quarter moon face 158 | 129420: 20201001, // 🦌 deer 159 | 129668: 20210521, // 🪄 magic wand 160 | 128171: 20201001, // 💫 dizzy 161 | 128049: 20201001, // 🐱 meow cat face 162 | 129409: 20201001, // 🦁 lion 163 | 128293: 20201001, // 🔥 fire 164 | 128038: 20210831, // 🐦 bird 165 | 129415: 20201001, // 🦇 bat 166 | 129417: 20210831, // 🦉 owl 167 | 127752: 20201001, // 🌈 rainbow 168 | 128053: 20201001, // 🐵 monkey face 169 | 128029: 20201001, // 🐝 honeybee bumblebee wasp 170 | 128034: 20201001, // 🐢 turtle 171 | 128025: 20201001, // 🐙 octopus 172 | 129433: 20201001, // 🦙 llama alpaca 173 | 128016: 20210831, // 🐐 goat 174 | 128060: 20201001, // 🐼 panda 175 | 128040: 20201001, // 🐨 koala 176 | 129445: 20201001, // 🦥 sloth 177 | 128059: 20210831, // 🐻 bear 178 | 128048: 20201001, // 🐰 rabbit face 179 | 129428: 20201001, // 🦔 hedgehog 180 | 128054: 20211115, // 🐶 dog puppy 181 | 128041: 20211115, // 🐩 poodle dog 182 | 129437: 20211115, // 🦝 raccoon 183 | 128039: 20211115, // 🐧 penguin 184 | 128012: 20210218, // 🐌 snail 185 | 128045: 20201001, // 🐭 mouse face rat 186 | 128031: 20210831, // 🐟 fish 187 | 127757: 20201001, // 🌍 globe showing Europe-Africa 188 | 127774: 20201001, // 🌞 sun with face 189 | 127775: 20201001, // 🌟 glowing star 190 | 11088: 20201001, // ⭐ star 191 | 127772: 20201001, // 🌜 last quarter moon face 192 | 129361: 20201001, // 🥑 avocado 193 | 127820: 20211115, // 🍌 banana 194 | 127827: 20210831, // 🍓 strawberry 195 | 127819: 20210521, // 🍋 lemon 196 | 127818: 20211115, // 🍊 tangerine orange 197 | } 198 | -------------------------------------------------------------------------------- /plugin/emojimix/mix.go: -------------------------------------------------------------------------------- 1 | // Package emojimix 合成emoji 2 | package emojimix 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | 8 | nano "github.com/fumiama/NanoBot" 9 | 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/sirupsen/logrus" 12 | 13 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 14 | ) 15 | 16 | const bed = "https://www.gstatic.com/android/keyboard/emojikitchen/%d/u%x/u%x_u%x.png" 17 | 18 | func init() { 19 | nano.Register("emojimix", &ctrl.Options[*nano.Ctx]{ 20 | DisableOnDefault: false, 21 | Help: "合成emoji\n" + 22 | "- [emoji][emoji]", 23 | }).OnMessage(match).SetBlock(true).Limit(ctxext.LimitByUser). 24 | Handle(func(ctx *nano.Ctx) { 25 | r := ctx.State["emojimix"].([]rune) 26 | logrus.Debugln("[emojimix] match:", r) 27 | r1, r2 := r[0], r[1] 28 | u1 := fmt.Sprintf(bed, emojis[r1], r1, r1, r2) 29 | u2 := fmt.Sprintf(bed, emojis[r2], r2, r2, r1) 30 | resp1, err := http.Head(u1) 31 | if err == nil { 32 | resp1.Body.Close() 33 | if resp1.StatusCode == http.StatusOK { 34 | _, _ = ctx.SendImage(u1, false) 35 | return 36 | } 37 | } 38 | resp2, err := http.Head(u2) 39 | if err == nil { 40 | resp2.Body.Close() 41 | if resp2.StatusCode == http.StatusOK { 42 | _, _ = ctx.SendImage(u2, false) 43 | return 44 | } 45 | } 46 | }) 47 | } 48 | 49 | func match(ctx *nano.Ctx) bool { 50 | r := []rune(ctx.Message.Content) 51 | if len(r) == 2 { 52 | if _, ok := emojis[r[0]]; !ok { 53 | return false 54 | } 55 | if _, ok := emojis[r[1]]; !ok { 56 | return false 57 | } 58 | ctx.State["emojimix"] = r 59 | return true 60 | } 61 | return false 62 | } 63 | -------------------------------------------------------------------------------- /plugin/fortune/fortune.go: -------------------------------------------------------------------------------- 1 | // Package fortune 每日运势 2 | package fortune 3 | 4 | import ( 5 | "archive/zip" 6 | "crypto/md5" 7 | "encoding/hex" 8 | "encoding/json" 9 | "image" 10 | "io" 11 | "os" 12 | "strconv" 13 | 14 | nano "github.com/fumiama/NanoBot" 15 | 16 | "github.com/FloatTech/gg" // 注册了 jpg png gif 17 | "github.com/FloatTech/zbputils/img/text" 18 | "github.com/sirupsen/logrus" 19 | 20 | fcext "github.com/FloatTech/floatbox/ctxext" 21 | "github.com/FloatTech/floatbox/file" 22 | "github.com/FloatTech/floatbox/math" 23 | "github.com/FloatTech/imgfactory" 24 | ctrl "github.com/FloatTech/zbpctrl" 25 | 26 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 27 | ) 28 | 29 | const ( 30 | // 底图缓存位置 31 | images = "data/Fortune/" 32 | // 基础文件位置 33 | omikujson = "data/Fortune/text.json" 34 | // 字体文件位置 35 | font = text.SakuraFontFile 36 | // 生成图缓存位置 37 | cache = images + "cache/" 38 | ) 39 | 40 | var ( 41 | // 底图类型列表 42 | table = [...]string{"车万", "DC4", "爱因斯坦", "星空列车", "樱云之恋", "富婆妹", "李清歌", "公主连结", "原神", "明日方舟", "碧蓝航线", "碧蓝幻想", "战双", "阴阳师", "赛马娘", "东方归言录", "奇异恩典", "夏日口袋", "ASoul"} 43 | // 映射底图与 index 44 | index = make(map[string]uint8) 45 | // 签文 46 | omikujis []map[string]string 47 | ) 48 | 49 | func init() { 50 | // 插件主体 51 | en := nano.Register("fortune", &ctrl.Options[*nano.Ctx]{ 52 | DisableOnDefault: false, 53 | Help: "每日运势: \n" + 54 | "- 运势 | 抽签\n" + 55 | "- 设置底图[车万 | DC4 | 爱因斯坦 | 星空列车 | 樱云之恋 | 富婆妹 | 李清歌 | 公主连结 | 原神 | 明日方舟 | 碧蓝航线 | 碧蓝幻想 | 战双 | 阴阳师 | 赛马娘 | 东方归言录 | 奇异恩典 | 夏日口袋 | ASoul]", 56 | PublicDataFolder: "Fortune", 57 | }).ApplySingle(nano.NewSingle( 58 | nano.WithKeyFn(func(ctx *nano.Ctx) int64 { 59 | gid := ctx.GroupID() 60 | return int64(gid) 61 | }), 62 | nano.WithPostFn[int64](func(ctx *nano.Ctx) { 63 | _, _ = ctx.SendPlainMessage(false, "有其他运势操作正在执行中, 不要着急哦") 64 | }))) 65 | _ = os.RemoveAll(cache) 66 | err := os.MkdirAll(cache, 0755) 67 | if err != nil { 68 | panic(err) 69 | } 70 | for i, s := range table { 71 | index[s] = uint8(i) 72 | } 73 | en.OnMessageRegex(`^设置底图\s?(.*)`).SetBlock(true). 74 | Handle(func(ctx *nano.Ctx) { 75 | gid := ctx.GroupID() 76 | i, ok := index[ctx.State["regex_matched"].([]string)[1]] 77 | if ok { 78 | c, ok := ctx.State["manager"].(*ctrl.Control[*nano.Ctx]) 79 | if ok { 80 | err := c.SetData(int64(gid), int64(i)&0xff) 81 | if err != nil { 82 | _, _ = ctx.SendPlainMessage(false, "设置失败: "+err.Error()) 83 | return 84 | } 85 | _, _ = ctx.SendPlainMessage(false, "设置成功~") 86 | return 87 | } 88 | _, _ = ctx.SendPlainMessage(false, "设置失败: 找不到插件") 89 | return 90 | } 91 | _, _ = ctx.SendPlainMessage(false, "没有这个底图哦~") 92 | }) 93 | en.OnMessageFullMatchGroup([]string{"运势", "抽签"}, fcext.DoOnceOnSuccess( 94 | func(ctx *nano.Ctx) bool { 95 | data, err := file.GetLazyData(omikujson, "data/control/stor.spb", false) 96 | if err != nil { 97 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 98 | return false 99 | } 100 | err = json.Unmarshal(data, &omikujis) 101 | if err != nil { 102 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 103 | return false 104 | } 105 | _, err = file.GetLazyData(font, "data/control/stor.spb", true) 106 | if err != nil { 107 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 108 | return false 109 | } 110 | return true 111 | }, 112 | )).Limit(ctxext.LimitByGroup).SetBlock(true). 113 | Handle(func(ctx *nano.Ctx) { 114 | // 获取该群背景类型,默认车万 115 | kind := "车万" 116 | gid := ctx.GroupID() 117 | logrus.Debugln("[fortune]gid:", ctx.Message.ChannelID, "uid:", ctx.Message.Author.ID) 118 | c, ok := ctx.State["manager"].(*ctrl.Control[*nano.Ctx]) 119 | if ok { 120 | v := uint8(c.GetData(int64(gid)) & 0xff) 121 | if int(v) < len(table) { 122 | kind = table[v] 123 | } 124 | } 125 | // 检查背景图片是否存在 126 | zipfile := images + kind + ".zip" 127 | _, err := file.GetLazyData(zipfile, nano.Md5File, false) 128 | if err != nil { 129 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 130 | return 131 | } 132 | 133 | uid := ctx.UserID() 134 | 135 | // 随机获取背景 136 | background, index, err := randimage(zipfile, int64(uid)) 137 | if err != nil { 138 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 139 | return 140 | } 141 | 142 | // 随机获取签文 143 | randtextindex := fcext.RandSenderPerDayN(int64(uid), len(omikujis)) 144 | title, text := omikujis[randtextindex]["title"], omikujis[randtextindex]["content"] 145 | digest := md5.Sum(nano.StringToBytes(zipfile + strconv.Itoa(index) + title + text)) 146 | cachefile := cache + hex.EncodeToString(digest[:]) 147 | if file.IsNotExist(cachefile) { 148 | f, err := os.Create(cachefile) 149 | if err != nil { 150 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 151 | return 152 | } 153 | _, err = draw(background, title, text, f) 154 | _ = f.Close() 155 | if err != nil { 156 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 157 | return 158 | } 159 | } 160 | _, err = ctx.SendImage("file:///"+file.BOTPATH+"/"+cachefile, false) 161 | if err != nil { 162 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 163 | return 164 | } 165 | }) 166 | } 167 | 168 | // @function randimage 随机选取zip内的文件 169 | // @param path zip路径 170 | // @param ctx *nano.Ctx 171 | // @return 文件路径 & 错误信息 172 | func randimage(path string, usr int64) (im image.Image, index int, err error) { 173 | reader, err := zip.OpenReader(path) 174 | if err != nil { 175 | return 176 | } 177 | defer reader.Close() 178 | 179 | file := reader.File[fcext.RandSenderPerDayN(usr, len(reader.File))] 180 | f, err := file.Open() 181 | if err != nil { 182 | return 183 | } 184 | defer f.Close() 185 | 186 | im, _, err = image.Decode(f) 187 | return 188 | } 189 | 190 | // @function draw 绘制运势图 191 | // @param background 背景图片路径 192 | // @param seed 随机数种子 193 | // @param title 签名 194 | // @param text 签文 195 | // @return 错误信息 196 | func draw(back image.Image, title, txt string, f io.Writer) (int64, error) { 197 | canvas := gg.NewContext(back.Bounds().Size().Y, back.Bounds().Size().X) 198 | canvas.DrawImage(back, 0, 0) 199 | // 写标题 200 | canvas.SetRGB(1, 1, 1) 201 | if err := canvas.LoadFontFace(font, 45); err != nil { 202 | return -1, err 203 | } 204 | sw, _ := canvas.MeasureString(title) 205 | canvas.DrawString(title, 140-sw/2, 112) 206 | // 写正文 207 | canvas.SetRGB(0, 0, 0) 208 | if err := canvas.LoadFontFace(font, 23); err != nil { 209 | return -1, err 210 | } 211 | tw, th := canvas.MeasureString("测") 212 | tw, th = tw+10, th+10 213 | r := []rune(txt) 214 | xsum := rowsnum(len(r), 9) 215 | switch xsum { 216 | default: 217 | for i, o := range r { 218 | xnow := rowsnum(i+1, 9) 219 | ysum := math.Min(len(r)-(xnow-1)*9, 9) 220 | ynow := i%9 + 1 221 | canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(ysum, ynow, th)+320.0) 222 | } 223 | case 2: 224 | div := rowsnum(len(r), 2) 225 | for i, o := range r { 226 | xnow := rowsnum(i+1, div) 227 | ysum := math.Min(len(r)-(xnow-1)*div, div) 228 | ynow := i%div + 1 229 | switch xnow { 230 | case 1: 231 | canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(9, ynow, th)+320.0) 232 | case 2: 233 | canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(9, ynow+(9-ysum), th)+320.0) 234 | } 235 | } 236 | } 237 | return imgfactory.WriteTo(canvas.Image(), f) 238 | } 239 | 240 | func offest(total, now int, distance float64) float64 { 241 | if total%2 == 0 { 242 | return (float64(now-total/2) - 1) * distance 243 | } 244 | return (float64(now-total/2) - 1.5) * distance 245 | } 246 | 247 | func rowsnum(total, div int) int { 248 | temp := total / div 249 | if total%div != 0 { 250 | temp++ 251 | } 252 | return temp 253 | } 254 | -------------------------------------------------------------------------------- /plugin/genshin/data.go: -------------------------------------------------------------------------------- 1 | package genshin 2 | 3 | type storage uint64 4 | 5 | func (s *storage) is5starsmode() bool { 6 | return *s&1 == 1 7 | } 8 | 9 | func (s *storage) setmode(is5stars bool) bool { 10 | if is5stars { 11 | *s |= 1 12 | } else { 13 | *s &= 0xffffffff_fffffffe 14 | } 15 | return is5stars 16 | } 17 | -------------------------------------------------------------------------------- /plugin/genshin/ys.go: -------------------------------------------------------------------------------- 1 | // Package genshin 原神抽卡 2 | package genshin 3 | 4 | import ( 5 | "archive/zip" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "image/jpeg" 10 | "image/png" 11 | "math/rand" 12 | "regexp" 13 | "strings" 14 | "sync/atomic" 15 | 16 | nano "github.com/fumiama/NanoBot" 17 | 18 | "github.com/golang/freetype" 19 | "github.com/sirupsen/logrus" 20 | 21 | fcext "github.com/FloatTech/floatbox/ctxext" 22 | "github.com/FloatTech/floatbox/process" 23 | "github.com/FloatTech/imgfactory" 24 | ctrl "github.com/FloatTech/zbpctrl" 25 | 26 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 27 | ) 28 | 29 | type zipfilestructure map[string][]*zip.File 30 | 31 | var ( 32 | totl uint64 // 累计抽奖次数 33 | filetree = make(zipfilestructure, 32) 34 | starN3, starN4, starN5 *zip.File 35 | namereg = regexp.MustCompile(`_(.*)\.png`) 36 | ) 37 | 38 | func init() { 39 | engine := nano.Register("genshin", &ctrl.Options[*nano.Ctx]{ 40 | DisableOnDefault: false, 41 | Help: "原神抽卡\n- 原神十连\n- 切换原神卡池", 42 | PublicDataFolder: "Genshin", 43 | }).ApplySingle(ctxext.DefaultSingle) 44 | 45 | engine.OnMessageFullMatch("切换原神卡池").SetBlock(true).Limit(ctxext.LimitByUser). 46 | Handle(func(ctx *nano.Ctx) { 47 | c, ok := ctx.State["manager"].(*ctrl.Control[*nano.Ctx]) 48 | if !ok { 49 | _, _ = ctx.SendPlainMessage(false, "找不到服务!") 50 | return 51 | } 52 | gid := ctx.GroupID() 53 | store := (storage)(c.GetData(int64(gid))) 54 | if store.setmode(!store.is5starsmode()) { 55 | process.SleepAbout1sTo2s() 56 | _, _ = ctx.SendPlainMessage(false, "切换到五星卡池~") 57 | } else { 58 | process.SleepAbout1sTo2s() 59 | _, _ = ctx.SendPlainMessage(false, "切换到普通卡池~") 60 | } 61 | err := c.SetData(int64(gid), int64(store)) 62 | if err != nil { 63 | process.SleepAbout1sTo2s() 64 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 65 | } 66 | }) 67 | 68 | engine.OnMessageFullMatch("原神十连", fcext.DoOnceOnSuccess( 69 | func(ctx *nano.Ctx) bool { 70 | zipfile := engine.DataFolder() + "Genshin.zip" 71 | _, err := engine.GetLazyData("Genshin.zip", false) 72 | if err != nil { 73 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 74 | return false 75 | } 76 | err = parsezip(zipfile) 77 | if err != nil { 78 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 79 | return false 80 | } 81 | return true 82 | }, 83 | )).SetBlock(true).Limit(ctxext.LimitByUser). 84 | Handle(func(ctx *nano.Ctx) { 85 | c, ok := ctx.State["manager"].(*ctrl.Control[*nano.Ctx]) 86 | if !ok { 87 | _, _ = ctx.SendPlainMessage(false, "找不到服务!") 88 | return 89 | } 90 | gid := ctx.GroupID() 91 | store := (storage)(c.GetData(int64(gid))) 92 | img, str, mode, err := randnums(10, store) 93 | if err != nil { 94 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 95 | return 96 | } 97 | b, err := imgfactory.ToBytes(img) 98 | if err != nil { 99 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 100 | return 101 | } 102 | _, err = ctx.SendImageBytes(b, true, func() string { 103 | if mode { 104 | return "恭喜你抽到了:\n" + str 105 | } 106 | return "十连成功~" 107 | }()) 108 | if err != nil { 109 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 110 | return 111 | } 112 | }) 113 | } 114 | 115 | func randnums(nums int, store storage) (rgba *image.RGBA, str string, replyMode bool, err error) { 116 | var ( 117 | fours, fives = make([]*zip.File, 0, 10), make([]*zip.File, 0, 10) // 抽到 四, 五星角色 118 | threeArms, fourArms, fiveArms = make([]*zip.File, 0, 10), make([]*zip.File, 0, 10), make([]*zip.File, 0, 10) // 抽到 三 , 四, 五星武器 119 | fourN, fiveN = 0, 0 // 抽到 四, 五星角色的数量 120 | bgs = make([]*zip.File, 0, 10) // 背景图片名 121 | threeN2, fourN2, fiveN2 = 0, 0, 0 // 抽到 三 , 四, 五星武器的数量 122 | hero, stars = make([]*zip.File, 0, 10), make([]*zip.File, 0, 10) // 角色武器名, 储存星级图标 123 | 124 | cicon = make([]*zip.File, 0, 10) // 元素图标 125 | fivebg, fourbg, threebg = filetree["five_bg.jpg"][0], filetree["four_bg.jpg"][0], filetree["three_bg.jpg"][0] // 背景图片名 126 | fivelen = len(filetree["five"]) 127 | five2len = len(filetree["five2"]) 128 | threelen = len(filetree["Three"]) 129 | fourlen = len(filetree["four"]) 130 | four2len = len(filetree["four2"]) 131 | ) 132 | 133 | if totl%9 == 0 { // 累计9次加入一个五星 134 | switch rand.Intn(2) { 135 | case 0: 136 | fiveN++ 137 | fives = append(fives, filetree["five"][rand.Intn(fivelen)]) 138 | case 1: 139 | fiveN2++ 140 | fiveArms = append(fiveArms, filetree["five2"][rand.Intn(five2len)]) 141 | } 142 | nums-- 143 | } 144 | 145 | if store.is5starsmode() { // 5星模式 146 | for i := 0; i < nums; i++ { 147 | switch rand.Intn(2) { 148 | case 0: 149 | fiveN++ 150 | fives = append(fives, filetree["five"][rand.Intn(fivelen)]) 151 | case 1: 152 | fiveN2++ 153 | fiveArms = append(fiveArms, filetree["five2"][rand.Intn(five2len)]) 154 | } 155 | } 156 | } else { // 默认模式 157 | for i := 0; i < nums; i++ { 158 | a := rand.Intn(1000) // 抽卡几率 三星80% 四星17% 五星3% 159 | switch { 160 | case a >= 0 && a <= 800: 161 | threeN2++ 162 | threeArms = append(threeArms, filetree["Three"][rand.Intn(threelen)]) 163 | case a > 800 && a <= 885: 164 | fourN++ 165 | fours = append(fours, filetree["four"][rand.Intn(fourlen)]) // 随机角色 166 | case a > 885 && a <= 970: 167 | fourN2++ 168 | fourArms = append(fourArms, filetree["four2"][rand.Intn(four2len)]) // 随机武器 169 | case a > 970 && a <= 985: 170 | fiveN++ 171 | fives = append(fives, filetree["five"][rand.Intn(fivelen)]) 172 | default: 173 | fiveN2++ 174 | fiveArms = append(fiveArms, filetree["five2"][rand.Intn(five2len)]) 175 | } 176 | } 177 | if fourN+fourN2 == 0 && threeN2 > 0 { // 没有四星时自动加入 178 | threeN2-- 179 | threeArms = threeArms[:len(threeArms)-1] 180 | switch rand.Intn(2) { 181 | case 0: 182 | fourN++ 183 | fours = append(fours, filetree["four"][rand.Intn(fourlen)]) // 随机角色 184 | case 1: 185 | fourN2++ 186 | fourArms = append(fourArms, filetree["four2"][rand.Intn(four2len)]) // 随机武器 187 | } 188 | } 189 | _ = atomic.AddUint64(&totl, 1) 190 | } 191 | 192 | icon := func(f *zip.File) *zip.File { 193 | name := f.Name 194 | name = name[strings.LastIndex(name, "/")+1:strings.Index(name, "_")] + ".png" 195 | logrus.Debugln("[genshin]get named file", name) 196 | return filetree[name][0] 197 | } 198 | 199 | he := func(cnt int, id int, f *zip.File, bg *zip.File) { 200 | var hen *[]*zip.File 201 | for i := 0; i < cnt; i++ { 202 | switch id { 203 | case 1: 204 | hen = &threeArms 205 | case 2: 206 | hen = &fourArms 207 | case 3: 208 | hen = &fours 209 | case 4: 210 | hen = &fiveArms 211 | case 5: 212 | hen = &fives 213 | } 214 | bgs = append(bgs, bg) // 加入颜色背景 215 | hero = append(hero, (*hen)[i]) 216 | stars = append(stars, f) // 加入星级图标 217 | cicon = append(cicon, icon((*hen)[i])) // 加入元素图标 218 | } 219 | } 220 | 221 | if fiveN > 0 { // 按顺序加入 222 | he(fiveN, 5, starN5, fivebg) // 五星角色 223 | str += reply(fives, 1, str) 224 | replyMode = true 225 | } 226 | if fourN > 0 { 227 | he(fourN, 3, starN4, fourbg) // 四星角色 228 | } 229 | if fiveN2 > 0 { 230 | he(fiveN2, 4, starN5, fivebg) // 五星武器 231 | str += reply(fiveArms, 2, str) 232 | replyMode = true 233 | } 234 | if fourN2 > 0 { 235 | he(fourN2, 2, starN4, fourbg) // 四星武器 236 | } 237 | if threeN2 > 0 { 238 | he(threeN2, 1, starN3, threebg) // 三星武器 239 | } 240 | 241 | var c1, c2, c3 uint8 = 50, 50, 50 // 背景颜色 242 | 243 | img00, err := filetree["bg0.jpg"][0].Open() // 打开背景图片 244 | if err != nil { 245 | return 246 | } 247 | 248 | rectangle := image.Rect(0, 0, 1920, 1080) // 图片宽度, 图片高度 249 | rgba = image.NewRGBA(rectangle) 250 | draw.Draw(rgba, rgba.Bounds(), image.NewUniform(color.RGBA{c1, c2, c3, 255}), image.Point{}, draw.Over) 251 | context := freetype.NewContext() // 创建一个新的上下文 252 | context.SetDPI(72) // 每英寸 dpi 253 | context.SetClip(rgba.Bounds()) 254 | context.SetDst(rgba) 255 | 256 | defer img00.Close() 257 | img0, err := jpeg.Decode(img00) // 读取一个本地图像 258 | if err != nil { 259 | return 260 | } 261 | 262 | offset := image.Pt(0, 0) // 图片在背景上的位置 263 | draw.Draw(rgba, img0.Bounds().Add(offset), img0, image.Point{}, draw.Over) 264 | 265 | w1, h1 := 230, 0 266 | for i := 0; i < len(hero); i++ { 267 | if i > 0 { 268 | w1 += 146 // 图片宽度 269 | } 270 | 271 | imgs, err := bgs[i].Open() // 取出背景图片 272 | if err != nil { 273 | return nil, "", false, err 274 | } 275 | defer imgs.Close() 276 | 277 | img, _ := jpeg.Decode(imgs) 278 | offset := image.Pt(w1, h1) 279 | draw.Draw(rgba, img.Bounds().Add(offset), img, image.Point{}, draw.Over) 280 | 281 | imgs1, err := hero[i].Open() // 取出图片名 282 | if err != nil { 283 | return nil, "", false, err 284 | } 285 | defer imgs1.Close() 286 | 287 | img1, _ := png.Decode(imgs1) 288 | offset1 := image.Pt(w1, h1) 289 | draw.Draw(rgba, img1.Bounds().Add(offset1), img1, image.Point{}, draw.Over) 290 | 291 | imgs2, err := stars[i].Open() // 取出星级图标 292 | if err != nil { 293 | return nil, "", false, err 294 | } 295 | defer imgs2.Close() 296 | 297 | img2, _ := png.Decode(imgs2) 298 | offset2 := image.Pt(w1, h1) 299 | draw.Draw(rgba, img2.Bounds().Add(offset2), img2, image.Point{}, draw.Over) 300 | 301 | imgs3, err := cicon[i].Open() // 取出类型图标 302 | if err != nil { 303 | return nil, "", false, err 304 | } 305 | defer imgs3.Close() 306 | 307 | img3, _ := png.Decode(imgs3) 308 | offset3 := image.Pt(w1, h1) 309 | draw.Draw(rgba, img3.Bounds().Add(offset3), img3, image.Point{}, draw.Over) 310 | } 311 | imgs4, err := filetree["Reply.png"][0].Open() // "分享" 图标 312 | if err != nil { 313 | return nil, "", false, err 314 | } 315 | defer imgs4.Close() 316 | img4, err := png.Decode(imgs4) 317 | if err != nil { 318 | return nil, "", false, err 319 | } 320 | offset4 := image.Pt(1270, 945) // 宽, 高 321 | draw.Draw(rgba, img4.Bounds().Add(offset4), img4, image.Point{}, draw.Over) 322 | return 323 | } 324 | 325 | func parsezip(zipFile string) error { 326 | zipReader, err := zip.OpenReader(zipFile) // will not close 327 | if err != nil { 328 | return err 329 | } 330 | for _, f := range zipReader.File { 331 | if f.FileInfo().IsDir() { 332 | filetree[f.Name] = make([]*zip.File, 0, 32) 333 | continue 334 | } 335 | f.Name = f.Name[8:] 336 | i := strings.LastIndex(f.Name, "/") 337 | if i < 0 { 338 | filetree[f.Name] = []*zip.File{f} 339 | logrus.Debugln("[genshin]insert file", f.Name) 340 | continue 341 | } 342 | folder := f.Name[:i] 343 | if folder != "" { 344 | filetree[folder] = append(filetree[folder], f) 345 | logrus.Debugln("[genshin]insert file into", folder) 346 | if folder == "gacha" { 347 | switch f.Name[i+1:] { 348 | case "ThreeStar.png": 349 | starN3 = f 350 | case "FourStar.png": 351 | starN4 = f 352 | case "FiveStar.png": 353 | starN5 = f 354 | } 355 | } 356 | } 357 | } 358 | return nil 359 | } 360 | 361 | // 取出角色武器名 362 | func reply(z []*zip.File, num int, nameStr string) string { 363 | var tmp strings.Builder 364 | tmp.Grow(128) 365 | switch { 366 | case num == 1: 367 | tmp.WriteString("★五星角色★\n") 368 | case num == 2 && len(nameStr) > 0: 369 | tmp.WriteString("\n★五星武器★\n") 370 | default: 371 | tmp.WriteString("★五星武器★\n") 372 | } 373 | for i := range z { 374 | tmp.WriteString(namereg.FindStringSubmatch(z[i].Name)[1] + " * ") 375 | } 376 | return tmp.String() 377 | } 378 | -------------------------------------------------------------------------------- /plugin/hyaku/main.go: -------------------------------------------------------------------------------- 1 | // Package hyaku 百人一首 2 | package hyaku 3 | 4 | import ( 5 | "encoding/csv" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "os" 10 | "reflect" 11 | "strconv" 12 | "unsafe" 13 | 14 | nano "github.com/fumiama/NanoBot" 15 | 16 | "github.com/FloatTech/floatbox/binary" 17 | "github.com/FloatTech/floatbox/file" 18 | "github.com/FloatTech/floatbox/web" 19 | ctrl "github.com/FloatTech/zbpctrl" 20 | 21 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 22 | ) 23 | 24 | const bed = "https://gitea.seku.su/fumiama/OguraHyakuninIsshu/raw/branch/master/" 25 | 26 | //nolint:asciicheck, structcheck 27 | type line struct { 28 | 番号, 歌人, 上の句, 下の句, 上の句ひらがな, 下の句ひらがな string 29 | } 30 | 31 | func (l *line) String() string { 32 | b := binary.NewWriterF(func(w *binary.Writer) { 33 | r := reflect.ValueOf(l).Elem().Type() 34 | for i := 0; i < r.NumField(); i++ { 35 | switch i { 36 | case 0: 37 | w.WriteString("●") 38 | case 1: 39 | w.WriteString("◉") 40 | case 2, 3: 41 | w.WriteString("○") 42 | case 4, 5: 43 | w.WriteString("◎") 44 | } 45 | w.WriteString(r.Field(i).Name) 46 | w.WriteString(": ") 47 | w.WriteString((*[6]string)(unsafe.Pointer(l))[i]) 48 | w.WriteString("\n") 49 | } 50 | }) 51 | return binary.BytesToString(b) 52 | } 53 | 54 | var lines [100]*line 55 | 56 | func init() { 57 | engine := nano.Register("hyaku", &ctrl.Options[*nano.Ctx]{ 58 | DisableOnDefault: false, 59 | Help: "百人一首\n" + 60 | "- 百人一首(随机发一首)\n" + 61 | "- 百人一首之n", 62 | PrivateDataFolder: "hyaku", 63 | }) 64 | csvfile := engine.DataFolder() + "hyaku.csv" 65 | go func() { 66 | var f *os.File 67 | if file.IsNotExist(csvfile) { 68 | data, err := web.RequestDataWith(web.NewTLS12Client(), bed+"小倉百人一首.csv", "GET", "gitcode.net", web.RandUA(), nil) 69 | if err != nil { 70 | _ = os.Remove(csvfile) 71 | panic(err) 72 | } 73 | f, err = os.Create(csvfile) 74 | if err != nil { 75 | panic(err) 76 | } 77 | _, _ = f.Write(data) 78 | _, _ = f.Seek(0, io.SeekStart) 79 | } else { 80 | var err error 81 | f, err = os.Open(csvfile) 82 | if err != nil { 83 | panic(err) 84 | } 85 | } 86 | records, err := csv.NewReader(f).ReadAll() 87 | if err != nil { 88 | panic(err) 89 | } 90 | _ = f.Close() 91 | records = records[1:] // skip title 92 | if len(records) != 100 { 93 | panic("invalid csvfile") 94 | } 95 | for j, r := range records { 96 | if len(r) != 6 { 97 | panic("invalid csvfile") 98 | } 99 | i, err := strconv.Atoi(r[0]) 100 | if err != nil { 101 | panic(err) 102 | } 103 | i-- 104 | if j != i { 105 | panic("invalid csvfile") 106 | } 107 | lines[i] = (*line)(*(*unsafe.Pointer)(unsafe.Pointer(&r))) 108 | } 109 | }() 110 | engine.OnMessageFullMatch("百人一首").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 111 | i := rand.Intn(100) 112 | _, _ = ctx.SendImage(fmt.Sprintf(bed+"img/%03d.jpg", i+1), false, lines[i].String()) 113 | _, _ = ctx.SendImage(fmt.Sprintf(bed+"img/%03d.png", i+1), false) 114 | }) 115 | engine.OnMessageRegex(`^百人一首之\s?(\d+)$`).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 116 | i, err := strconv.Atoi(ctx.State["regex_matched"].([]string)[1]) 117 | if err != nil { 118 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 119 | return 120 | } 121 | if i > 100 || i < 1 { 122 | _, _ = ctx.SendPlainMessage(false, "ERROR:超出范围") 123 | return 124 | } 125 | _, _ = ctx.SendImage(fmt.Sprintf(bed+"img/%03d.jpg", i), false) 126 | _, _ = ctx.SendImage(fmt.Sprintf(bed+"img/%03d.png", i), false, lines[i-1].String()) 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /plugin/manager/main.go: -------------------------------------------------------------------------------- 1 | // Package manager bot管理相关 2 | package manager 3 | 4 | import ( 5 | "strconv" 6 | "strings" 7 | 8 | nano "github.com/fumiama/NanoBot" 9 | 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | ) 12 | 13 | func init() { 14 | en := nano.Register("manager", &ctrl.Options[*nano.Ctx]{ 15 | DisableOnDefault: false, 16 | Help: "bot管理相关\n" + 17 | "- /exposeid", 18 | }) 19 | en.OnMessageCommand("exposeid").SetBlock(true). 20 | Handle(func(ctx *nano.Ctx) { 21 | msg := "" 22 | if nano.OnlyQQ(ctx) { 23 | msg = "*报告*\n- 群ID: `" + strconv.FormatInt(int64(ctx.GroupID()), 10) + "`\n- 触发用户ID: `" + strconv.FormatInt(int64(ctx.UserID()), 10) + "`" 24 | for _, e := range strings.Split(ctx.State["args"].(string), " ") { 25 | e = strings.TrimSpace(e) 26 | if e == "" { 27 | continue 28 | } 29 | if strings.HasPrefix(e, "<@!") { 30 | uid := strings.TrimSuffix(e[3:], ">") 31 | msg += "\n- 用户: " + e + " ID: `" + uid + "`" 32 | } 33 | } 34 | } else { 35 | msg = "*报告*\n- 频道ID: `" + ctx.Message.ChannelID + "`" 36 | for _, e := range strings.Split(ctx.State["args"].(string), " ") { 37 | e = strings.TrimSpace(e) 38 | if e == "" { 39 | continue 40 | } 41 | if strings.HasPrefix(e, "<@!") { 42 | uid := strings.TrimSuffix(e[3:], ">") 43 | msg += "\n- 用户: " + e + " ID: `" + uid + "`" 44 | } 45 | } 46 | } 47 | _, _ = ctx.SendPlainMessage(true, msg) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /plugin/qqwife/dbfile.go: -------------------------------------------------------------------------------- 1 | // Package qqwife 娶群友 2 | package qqwife 3 | 4 | import ( 5 | "errors" 6 | "path" 7 | "runtime" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | 13 | nano "github.com/fumiama/NanoBot" 14 | 15 | fcext "github.com/FloatTech/floatbox/ctxext" 16 | "github.com/FloatTech/gg" 17 | sql "github.com/FloatTech/sqlite" 18 | ) 19 | 20 | type userInfo struct { 21 | ID string 22 | Nick string 23 | Avatar string 24 | } 25 | 26 | type dbData struct { 27 | db *sql.Sqlite 28 | sync.RWMutex 29 | } 30 | 31 | // 群设置 32 | type setting struct { 33 | GID string 34 | LastTime int 35 | CanMatch int // 嫁婚开关 36 | CanNtr int // Ntr开关 37 | CDtime int64 // CD时间 38 | } 39 | 40 | // 结婚证信息 41 | type marriage struct { 42 | Users string // 双方QQ号 43 | Sname string // 户主名称 44 | Mname string // 对象名称 45 | Updatetime string // 登记时间 46 | Spic string 47 | Mpic string 48 | } 49 | 50 | // 预留10个为后续扩展 51 | type cdsheet struct { 52 | User string `db:"User"` 53 | Mar int64 `db:"CD0"` // 娶 54 | Rob int64 `db:"CD1"` // 强 55 | Lef int64 `db:"CD2"` // 离 56 | Buy int64 `db:"CD3"` // 礼 57 | MMk int64 `db:"CD4"` // 做媒 58 | CD5 int64 `db:"CD5"` 59 | CD6 int64 `db:"CD6"` 60 | CD7 int64 `db:"CD7"` 61 | CD8 int64 `db:"CD8"` 62 | CD9 int64 `db:"CD9"` 63 | } 64 | 65 | // 好感度列表 66 | type favor struct { 67 | Users string // 双方QQ号 68 | Favor int // 好感度 69 | } 70 | 71 | var ( 72 | wifeData = &dbData{ 73 | db: &sql.Sqlite{}, 74 | } 75 | getdb = fcext.DoOnceOnSuccess(func(ctx *nano.Ctx) bool { 76 | wifeData.db.DBPath = engine.DataFolder() + "结婚登记表.db" 77 | err := wifeData.db.Open(time.Hour) 78 | if err == nil { 79 | // 创建群配置表 80 | err = wifeData.db.Create("setting", &setting{}) 81 | if err != nil { 82 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 83 | return false 84 | } 85 | // 创建CD表 86 | err = wifeData.db.Create("cdsheet", &cdsheet{}) 87 | if err != nil { 88 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 89 | return false 90 | } 91 | // 创建好感度表 92 | err = wifeData.db.Create("favor", &favor{}) 93 | if err != nil { 94 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 95 | return false 96 | } 97 | // 刷新列表 98 | err = wifeData.refresh(ctx.Message.GuildID) 99 | if err != nil { 100 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 101 | return false 102 | } 103 | return true 104 | } 105 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 106 | return false 107 | }) 108 | ) 109 | 110 | func getUserInfoIn(ctx *nano.Ctx, gid, uid string) (info userInfo, err error) { 111 | uInfo, err := ctx.GetGuildMemberOf(gid, uid) 112 | if err != nil { 113 | return 114 | } 115 | nick := uInfo.Nick 116 | if nick == "" { 117 | nick = uInfo.User.Username 118 | } 119 | info = userInfo{ 120 | ID: uInfo.User.ID, 121 | Nick: nick, 122 | Avatar: uInfo.User.Avatar, 123 | } 124 | return 125 | } 126 | 127 | func (sql *dbData) getSet(gid string) (dbinfo setting, err error) { 128 | sql.Lock() 129 | defer sql.Unlock() 130 | // 创建群表格 131 | err = sql.db.Create("setting", &dbinfo) 132 | if err != nil { 133 | return 134 | } 135 | if !sql.db.CanFind("setting", "where gid is "+gid) { 136 | // 没有记录 137 | return setting{ 138 | GID: gid, 139 | CanMatch: 1, 140 | CanNtr: 1, 141 | CDtime: 12, 142 | }, nil 143 | } 144 | _ = sql.db.Find("setting", &dbinfo, "where gid is "+gid) 145 | return 146 | } 147 | 148 | func (sql *dbData) updateSet(dbinfo setting) error { 149 | sql.Lock() 150 | defer sql.Unlock() 151 | return sql.db.Insert("setting", &dbinfo) 152 | } 153 | 154 | func (sql *dbData) refresh(gid string) error { 155 | sql.Lock() 156 | defer sql.Unlock() 157 | // 创建群表格 158 | err := sql.db.Create("setting", &setting{}) 159 | if err != nil { 160 | return err 161 | } 162 | if !sql.db.CanFind("setting", "where gid is "+gid) { 163 | return nil 164 | } 165 | dbinfo := setting{} 166 | _ = sql.db.Find("setting", &dbinfo, "where gid is "+gid) 167 | if time.Now().Day() != dbinfo.LastTime && time.Now().Hour() >= 4 { 168 | _ = sql.db.Drop("group" + gid) 169 | // 更新数据时间 170 | dbinfo.GID = gid 171 | dbinfo.LastTime = time.Now().Day() 172 | return sql.db.Insert("setting", &dbinfo) 173 | } 174 | return nil 175 | } 176 | 177 | func (sql *dbData) checkUser(gid, uid string) (userinfo marriage, err error) { 178 | sql.Lock() 179 | defer sql.Unlock() 180 | gidstr := "group" + gid 181 | // 创建群表格 182 | err = sql.db.Create(gidstr, &userinfo) 183 | if err != nil { 184 | return 185 | } 186 | if !sql.db.CanFind(gidstr, "where Users glob '*"+uid+"*'") { 187 | return 188 | } 189 | err = sql.db.Find(gidstr, &userinfo, "where Users glob '*"+uid+"*'") 190 | return 191 | } 192 | 193 | // 民政局登记数据 194 | func (sql *dbData) register(gid string, uid, target userInfo) error { 195 | sql.Lock() 196 | defer sql.Unlock() 197 | gidstr := "group" + gid 198 | uidinfo := marriage{ 199 | Users: uid.ID + " & " + target.ID, 200 | Sname: uid.Nick, 201 | Spic: uid.Avatar, 202 | Mname: target.Nick, 203 | Mpic: target.Avatar, 204 | Updatetime: time.Now().Format("15:04:05"), 205 | } 206 | return sql.db.Insert(gidstr, &uidinfo) 207 | } 208 | 209 | // 民政局离婚 210 | func (sql *dbData) divorce(gid, uid string) error { 211 | sql.Lock() 212 | defer sql.Unlock() 213 | gidstr := "group" + gid 214 | // 创建群表格 215 | userinfo := marriage{} 216 | err := sql.db.Create(gidstr, &userinfo) 217 | if err != nil { 218 | return err 219 | } 220 | if !sql.db.CanFind(gidstr, "where Users glob '*"+uid+"*'") { 221 | return errors.New("user(" + uid + ") not found") 222 | } 223 | return sql.db.Del(gidstr, "where Users glob '*"+uid+"*'") 224 | } 225 | 226 | func (sql *dbData) getlist(gid string) (list []marriage, err error) { 227 | sql.Lock() 228 | defer sql.Unlock() 229 | gidstr := "group" + gid 230 | number, _ := sql.db.Count(gidstr) 231 | if number <= 0 { 232 | return 233 | } 234 | var info marriage 235 | err = sql.db.FindFor(gidstr, &info, "GROUP BY Users", func() error { 236 | users := strings.Split(info.Users, " & ") 237 | if users[0] == "" || users[1] == "" { 238 | return nil 239 | } 240 | list = append(list, info) 241 | return nil 242 | }) 243 | return 244 | } 245 | 246 | func slicename(name string, canvas *gg.Context) (resultname string) { 247 | usermane := []rune(name) // 将每个字符单独放置 248 | widthlen := 0 249 | numberlen := 0 250 | for i, v := range usermane { 251 | width, _ := canvas.MeasureString(string(v)) // 获取单个字符的宽度 252 | widthlen += int(width) 253 | if widthlen > 650 { 254 | break // 总宽度不能超过350 255 | } 256 | numberlen = i 257 | } 258 | if widthlen > 650 { 259 | resultname = string(usermane[:numberlen-1]) + "......" // 名字切片 260 | } else { 261 | resultname = name 262 | } 263 | return 264 | } 265 | 266 | func (sql *dbData) favorFor(uid, target string, add int) (favorValue int, err error) { 267 | sql.Lock() 268 | defer sql.Unlock() 269 | // 创建群表格 270 | err = sql.db.Create("favor", &favor{}) 271 | if err != nil { 272 | return 273 | } 274 | key := uid + " & " + target 275 | uidInt64, _ := strconv.ParseInt(uid, 10, 64) 276 | targetInt64, _ := strconv.ParseInt(target, 10, 64) 277 | if uidInt64 < targetInt64 { 278 | key = target + " & " + uid 279 | } 280 | info := favor{} 281 | _ = sql.db.Find("favor", &info, "where Users is '"+key+"'") 282 | if add > 0 { 283 | info.Users = key 284 | info.Favor += add 285 | err = sql.db.Insert("favor", &info) 286 | } 287 | return info.Favor, err 288 | } 289 | 290 | func (sql *dbData) getGroupFavorability(uid string) (list []favor, err error) { 291 | sql.RLock() 292 | defer sql.RUnlock() 293 | info := favor{} 294 | err = sql.db.FindFor("favor", &info, "where Users glob '*"+uid+"*' AND Favor > 0 ORDER BY DESC", func() error { 295 | var target string 296 | userList := strings.Split(info.Users, " & ") 297 | switch { 298 | case len(userList) == 0: 299 | return errors.New("好感度系统数据存在错误") 300 | case userList[0] == uid: 301 | target = userList[1] 302 | default: 303 | target = userList[0] 304 | } 305 | list = append(list, favor{ 306 | Users: target, 307 | Favor: info.Favor, 308 | }) 309 | return nil 310 | }) 311 | return 312 | } 313 | 314 | func (sql *dbData) checkCD(gid, uid string, funcType string) (cdTime time.Duration, err error) { 315 | setting, err := wifeData.getSet(gid) 316 | if err != nil { 317 | return 318 | } 319 | sql.Lock() 320 | defer sql.Unlock() 321 | // 创建群表格 322 | err = sql.db.Create("cdsheet", &cdsheet{}) 323 | if err != nil { 324 | return 325 | } 326 | number, _ := sql.db.Count("cdsheet") 327 | if number <= 0 { 328 | return 329 | } 330 | info := cdsheet{} 331 | if !sql.db.CanFind("cdsheet", "where User is '"+uid+"'") { 332 | return 333 | } 334 | err = sql.db.Find("cdsheet", &info, "where User is '"+uid+"'") 335 | if err != nil { 336 | return 337 | } 338 | switch funcType { 339 | case "娶", "嫁": 340 | cdTime = time.Duration(setting.CDtime)*time.Hour - time.Since(time.Unix(info.Mar, 0)) 341 | case "牛": 342 | cdTime = time.Duration(setting.CDtime)*time.Hour - time.Since(time.Unix(info.Rob, 0)) 343 | case "离": 344 | cdTime = time.Duration(setting.CDtime)*time.Hour - time.Since(time.Unix(info.Lef, 0)) 345 | case "媒": 346 | cdTime = time.Duration(setting.CDtime)*time.Hour - time.Since(time.Unix(info.MMk, 0)) 347 | case "买": 348 | cdTime = time.Duration(setting.CDtime)*time.Hour - time.Since(time.Unix(info.Buy, 0)) 349 | } 350 | return 351 | } 352 | 353 | func (sql *dbData) setCD(uid string, funcType string) error { 354 | sql.Lock() 355 | defer sql.Unlock() 356 | // 创建群表格 357 | err := sql.db.Create("cdsheet", &cdsheet{}) 358 | if err != nil { 359 | return err 360 | } 361 | info := cdsheet{} 362 | _ = sql.db.Find("cdsheet", &info, "where User is '"+uid+"'") 363 | info.User = uid 364 | switch funcType { 365 | case "娶", "嫁": 366 | info.Mar = time.Now().Unix() 367 | case "牛": 368 | info.Rob = time.Now().Unix() 369 | case "离": 370 | info.Lef = time.Now().Unix() 371 | case "媒": 372 | info.MMk = time.Now().Unix() 373 | case "买": 374 | info.Buy = time.Now().Unix() 375 | } 376 | return sql.db.Insert("cdsheet", &info) 377 | } 378 | 379 | func getLine() string { 380 | _, file, line, ok := runtime.Caller(1) 381 | if ok { 382 | return path.Base(file) + "." + strconv.Itoa(line) 383 | } 384 | return "" 385 | } 386 | -------------------------------------------------------------------------------- /plugin/qqwife/happyplay.go: -------------------------------------------------------------------------------- 1 | package qqwife 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/FloatTech/AnimeAPI/wallet" 9 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 10 | "github.com/FloatTech/floatbox/file" 11 | "github.com/FloatTech/floatbox/math" 12 | "github.com/FloatTech/gg" 13 | "github.com/FloatTech/imgfactory" 14 | "github.com/FloatTech/zbputils/img/text" 15 | nano "github.com/fumiama/NanoBot" 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | var sendtext = [...][]string{ 20 | { // 表白成功 21 | "是个勇敢的孩子(*/ω\*) 今天的运气都降临在你的身边~\n\n", 22 | "(´・ω・`)对方答应了你 并表示愿意当今天的CP\n\n", 23 | }, 24 | { // 表白失败 25 | "今天的运气有一点背哦~明天再试试叭", 26 | "_(:з」∠)_下次还有机会 咱抱抱你w", 27 | "今天失败了惹. 摸摸头~咱明天还有机会", 28 | }, 29 | { // ntr成功 30 | "因为你的个人魅力~~今天他就是你的了w\n\n", 31 | }, 32 | { // 离婚失败 33 | "打是情,骂是爱,不打不亲不相爱。答应我不要分手。", 34 | "床头打架床尾和,夫妻没有隔夜仇。安啦安啦,不要闹变扭。", 35 | }, 36 | { // 离婚成功 37 | "离婚成功力\n话说你不考虑当个1?", 38 | "离婚成功力\n天涯何处无芳草,何必单恋一枝花?不如再摘一支(bushi", 39 | }, 40 | } 41 | 42 | func init() { 43 | engine.OnMessageRegex(`^设置CD为(\d+)小时`, nano.OnlyChannel, nano.AdminPermission, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 44 | cdTime, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) 45 | if err != nil { 46 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:请设置纯数字\n", err) 47 | return 48 | } 49 | groupInfo, err := wifeData.getSet(ctx.Message.GuildID) 50 | if err != nil { 51 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 52 | return 53 | } 54 | groupInfo.CDtime = cdTime 55 | err = wifeData.updateSet(groupInfo) 56 | if err != nil { 57 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]设置CD时长失败\n", err) 58 | return 59 | } 60 | _, _ = ctx.SendPlainMessage(true, "设置成功") 61 | }) 62 | engine.OnMessageRegex(`^(允许|禁止)(自由恋爱|牛头人)$`, nano.OnlyChannel, nano.AdminPermission, getdb).SetBlock(true).Handle(func(ctx *nano.Ctx) { 63 | status := ctx.State["regex_matched"].([]string)[1] 64 | mode := ctx.State["regex_matched"].([]string)[2] 65 | groupInfo, err := wifeData.getSet(ctx.Message.GuildID) 66 | switch { 67 | case err != nil: 68 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 69 | return 70 | case mode == "自由恋爱": 71 | if status == "允许" { 72 | groupInfo.CanMatch = 1 73 | } else { 74 | groupInfo.CanMatch = 0 75 | } 76 | case mode == "牛头人": 77 | if status == "允许" { 78 | groupInfo.CanNtr = 1 79 | } else { 80 | groupInfo.CanNtr = 0 81 | } 82 | } 83 | err = wifeData.updateSet(groupInfo) 84 | if err != nil { 85 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 86 | return 87 | } 88 | _, _ = ctx.SendPlainMessage(true, "设置成功") 89 | }) 90 | // 单身技能 91 | engine.OnMessageRegex(`^(娶|嫁)\s*<@!(\d+)>$`, nano.OnlyChannel, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 92 | gid := ctx.Message.GuildID 93 | setting, err := wifeData.getSet(gid) 94 | if err != nil { 95 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 96 | return 97 | } 98 | if setting.CanMatch == 0 { 99 | _, _ = ctx.SendPlainMessage(true, "该频道已发布了禁止自由恋爱,请认真水群") 100 | return 101 | } 102 | uid := ctx.Message.Author.ID 103 | choice := ctx.State["regex_matched"].([]string)[1] 104 | cdTime, err := wifeData.checkCD(gid, uid, choice) 105 | if err != nil { 106 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 107 | return 108 | } 109 | if cdTime > 0 { 110 | _, _ = ctx.SendPlainMessage(true, "你的技能CD还有", cdTime) 111 | return 112 | } 113 | fiance := ctx.State["regex_matched"].([]string)[2] 114 | uInfo, err := wifeData.checkUser(gid, uid) 115 | if err != nil { 116 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 117 | return 118 | } 119 | if uInfo.Users != "" { 120 | info := strings.Split(uInfo.Users, " & ") 121 | switch { 122 | case info[0] == "" || info[1] == "": 123 | _, _ = ctx.SendPlainMessage(true, "今天的你是单身贵族噢") 124 | return 125 | case info[0] == fiance || info[1] == fiance: 126 | _, _ = ctx.SendPlainMessage(true, "笨蛋!你们已经在一起了!") 127 | return 128 | case info[0] == uid: // 如果如为攻 129 | _, _ = ctx.SendPlainMessage(true, "笨蛋~你家里还有个吃白饭的w") 130 | return 131 | case info[1] == uid: // 如果为受 132 | _, _ = ctx.SendPlainMessage(true, "该是0就是0,当0有什么不好") 133 | return 134 | } 135 | } 136 | fInfo, err := wifeData.checkUser(gid, uid) 137 | if err != nil { 138 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 139 | return 140 | } 141 | if fInfo.Users != "" { 142 | info := strings.Split(fInfo.Users, " & ") 143 | switch { 144 | case info[0] == "" || info[1] == "": 145 | _, _ = ctx.SendPlainMessage(true, "今天的ta是单身贵族噢") 146 | return 147 | case info[0] == uid: // 如果如为攻 148 | _, _ = ctx.SendPlainMessage(true, "他有别的女人了,你该放下了") 149 | return 150 | case info[1] == uid: // 如果为受 151 | _, _ = ctx.SendPlainMessage(true, "ta被别人娶了,你来晚力") 152 | return 153 | } 154 | } 155 | // 写入CD 156 | err = wifeData.setCD(uid, choice) 157 | if err != nil { 158 | log.Warnln("[qqwife]你的技能CD记录失败,", err) 159 | } 160 | uBook, err := getUserInfoIn(ctx, gid, uid) 161 | if err != nil { 162 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 163 | return 164 | } 165 | if uid == fiance { // 如果是自己 166 | switch rand.Intn(3) { 167 | case 1: 168 | err := wifeData.register(gid, uBook, userInfo{}) 169 | if err != nil { 170 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 171 | return 172 | } 173 | _, _ = ctx.SendPlainMessage(true, "今日获得成就:单身贵族") 174 | default: 175 | _, _ = ctx.SendPlainMessage(true, "今日获得成就:自恋狂") 176 | } 177 | return 178 | } 179 | fBook, err := getUserInfoIn(ctx, gid, fiance) 180 | if err != nil { 181 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 182 | return 183 | } 184 | favor, err := wifeData.favorFor(uid, fiance, 0) 185 | if err != nil { 186 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 187 | return 188 | } 189 | if favor < 30 { 190 | favor = 30 // 保底30%概率 191 | } 192 | if rand.Intn(101) >= favor { 193 | _, _ = ctx.SendPlainMessage(true, sendtext[1][rand.Intn(len(sendtext[1]))]) 194 | return 195 | } 196 | // 去民政局登记 197 | var choicetext string 198 | switch choice { 199 | case "娶": 200 | err = wifeData.register(gid, uBook, fBook) 201 | choicetext = "\n今天你的群老婆是" 202 | default: 203 | err = wifeData.register(gid, fBook, uBook) 204 | choicetext = "\n今天你的群老公是" 205 | } 206 | if err != nil { 207 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 208 | return 209 | } 210 | // 请大家吃席 211 | _, err = ctx.SendChain(nano.ReplyTo(ctx.Message.ID), 212 | nano.Text(sendtext[0][rand.Intn(len(sendtext[0]))], "\n", 213 | choicetext, "[", fBook.Nick, "]\n", 214 | "当前你们好感度为", favor), nano.Image(fBook.Avatar)) 215 | if err != nil { 216 | _, _ = ctx.SendPlainMessage(false, "", getLine(), " ->ERROR: ", err) 217 | } 218 | }) 219 | // NTR技能 220 | engine.OnMessageRegex(`^牛\s*<@!(\d+)>$`, nano.OnlyChannel, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 221 | gid := ctx.Message.GuildID 222 | setting, err := wifeData.getSet(gid) 223 | if err != nil { 224 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 225 | return 226 | } 227 | if setting.CanNtr == 0 { 228 | _, _ = ctx.SendPlainMessage(true, "该频道已发布了禁止牛头人,请认真水群") 229 | return 230 | } 231 | uid := ctx.Message.Author.ID 232 | cdTime, err := wifeData.checkCD(gid, uid, "牛") 233 | if err != nil { 234 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 235 | return 236 | } 237 | if cdTime > 0 { 238 | _, _ = ctx.SendPlainMessage(true, "你的技能CD还有", cdTime) 239 | return 240 | } 241 | fiance := ctx.State["regex_matched"].([]string)[1] 242 | uInfo, err := wifeData.checkUser(gid, uid) 243 | if err != nil { 244 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 245 | return 246 | } 247 | if uInfo.Users != "" { 248 | info := strings.Split(uInfo.Users, " & ") 249 | switch { 250 | case info[0] == "" || info[1] == "": 251 | _, _ = ctx.SendPlainMessage(true, "今天的你是单身贵族噢") 252 | return 253 | case info[0] == fiance || info[1] == fiance: 254 | _, _ = ctx.SendPlainMessage(true, "笨蛋!你们已经在一起了!") 255 | return 256 | case info[0] == uid: // 如果如为攻 257 | _, _ = ctx.SendPlainMessage(true, "笨蛋~你家里还有个吃白饭的w") 258 | return 259 | case info[1] == uid: // 如果为受 260 | _, _ = ctx.SendPlainMessage(true, "该是0就是0,当0有什么不好") 261 | return 262 | } 263 | } 264 | fInfo, err := wifeData.checkUser(gid, fiance) 265 | if err != nil { 266 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 267 | return 268 | } 269 | if fInfo.Users == "" { 270 | _, _ = ctx.SendPlainMessage(true, "今天的ta是单身噢,快去明媒正娶吧!") 271 | return 272 | } 273 | // 写入CD 274 | err = wifeData.setCD(uid, "牛") 275 | if err != nil { 276 | log.Warnln("[qqwife]你的技能CD记录失败,", err) 277 | } 278 | if fiance == uid { 279 | _, _ = ctx.SendPlainMessage(true, "今日获得成就:自我攻略") 280 | return 281 | } 282 | favor, err := wifeData.favorFor(uid, fiance, 0) 283 | if err != nil { 284 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 285 | return 286 | } 287 | if favor < 30 { 288 | favor = 30 // 保底10%概率 289 | } 290 | if rand.Intn(101) >= favor/3 { 291 | _, _ = ctx.SendPlainMessage(true, "失败了!可惜") 292 | return 293 | } 294 | // 判断target是老公还是老婆 295 | choicetext := "老公" 296 | ntrID := uid 297 | targetID := fiance 298 | greenID := "" // 被牛的 299 | 300 | err = wifeData.divorce(gid, fiance) 301 | if err != nil { 302 | _, _ = ctx.SendPlainMessage(true, "ta不想和原来的对象分手...\n[error]", err) 303 | return 304 | } 305 | user := strings.Split(fInfo.Users, " & ") 306 | switch { 307 | case user[0] == fiance: // 是1 308 | ntrID = fiance 309 | targetID = uid 310 | greenID = user[1] 311 | case user[1] == fiance: // 是0 312 | greenID = user[0] 313 | choicetext = "老婆" 314 | default: 315 | _, _ = ctx.SendPlainMessage(true, "数据库发生问题力") 316 | return 317 | } 318 | userInfo, err := getUserInfoIn(ctx, gid, ntrID) 319 | if err != nil { 320 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 321 | return 322 | } 323 | fianceInfo, err := getUserInfoIn(ctx, gid, targetID) 324 | if err != nil { 325 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 326 | return 327 | } 328 | err = wifeData.register(gid, userInfo, fianceInfo) 329 | if err != nil { 330 | _, _ = ctx.SendPlainMessage(true, "[qqwife]复婚登记失败力\n", err) 331 | return 332 | } 333 | favor, err = wifeData.favorFor(uid, fiance, -5) 334 | if err != nil { 335 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 336 | } 337 | _, err = wifeData.favorFor(uid, greenID, 5) 338 | if err != nil { 339 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 340 | } 341 | // 输出结果 342 | _, err = ctx.SendChain(nano.ReplyTo(ctx.Message.ID), 343 | nano.Text(sendtext[2][rand.Intn(len(sendtext[2]))], "\n", 344 | choicetext, "[", fianceInfo.Nick, "]\n", 345 | "当前你们好感度为", favor), nano.Image(fianceInfo.Avatar)) 346 | if err != nil { 347 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 348 | } 349 | }) 350 | // 做媒技能 351 | engine.OnMessageRegex(`^做媒\s*<@!(\d+)>\s*<@!(\d+)>`, nano.OnlyChannel, nano.AdminPermission, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 352 | gid := ctx.Message.GuildID 353 | uid := ctx.Message.Author.ID 354 | cdTime, err := wifeData.checkCD(gid, uid, "媒") 355 | if err != nil { 356 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 357 | return 358 | } 359 | if cdTime > 0 { 360 | _, _ = ctx.SendPlainMessage(true, "你的技能CD还有", cdTime) 361 | return 362 | } 363 | gayOne := ctx.State["regex_matched"].([]string)[1] 364 | gaynano := ctx.State["regex_matched"].([]string)[2] 365 | uInfo, err := wifeData.checkUser(gid, gayOne) 366 | if err != nil { 367 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 368 | return 369 | } 370 | if uInfo.Users != "" { 371 | _, _ = ctx.SendChain(nano.ReplyTo(ctx.Message.ID), nano.At(gayOne), nano.Text("已有家妻")) 372 | return 373 | } 374 | fInfo, err := wifeData.checkUser(gid, gaynano) 375 | if err != nil { 376 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 377 | return 378 | } 379 | if fInfo.Users != "" { 380 | _, _ = ctx.SendChain(nano.ReplyTo(ctx.Message.ID), nano.At(gaynano), nano.Text("已有所属")) 381 | return 382 | } 383 | // 写入CD 384 | err = wifeData.setCD(uid, "媒") 385 | if err != nil { 386 | log.Warnln("[qqwife]你的技能CD记录失败,", err) 387 | } 388 | favor, err := wifeData.favorFor(gayOne, gaynano, 0) 389 | if err != nil { 390 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 391 | return 392 | } 393 | if favor < 30 { 394 | favor = 30 // 保底30%概率 395 | } 396 | if rand.Intn(101) >= favor { 397 | _, err = wifeData.favorFor(uid, gayOne, -1) 398 | if err != nil { 399 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 400 | } 401 | _, err = wifeData.favorFor(uid, gaynano, -1) 402 | if err != nil { 403 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 404 | } 405 | _, _ = ctx.SendPlainMessage(true, sendtext[1][rand.Intn(len(sendtext[1]))]) 406 | return 407 | } 408 | // 去民政局登记 409 | userInfo, err := getUserInfoIn(ctx, gid, gayOne) 410 | if err != nil { 411 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 412 | return 413 | } 414 | fianceInfo, err := getUserInfoIn(ctx, gid, gaynano) 415 | if err != nil { 416 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 417 | return 418 | } 419 | err = wifeData.register(gid, userInfo, fianceInfo) 420 | if err != nil { 421 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 422 | return 423 | } 424 | _, err = wifeData.favorFor(uid, gayOne, 1) 425 | if err != nil { 426 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 427 | } 428 | _, err = wifeData.favorFor(uid, gaynano, 1) 429 | if err != nil { 430 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 431 | } 432 | _, err = wifeData.favorFor(gayOne, gaynano, 1) 433 | if err != nil { 434 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 435 | } 436 | // 请大家吃席 437 | _, err = ctx.SendChain(nano.ReplyTo(ctx.Message.ID), 438 | nano.Text("恭喜你成功撮合了一对CP\n\n"), nano.At(gayOne), nano.Text("今天你的群老婆是[", fianceInfo.Nick, "]"), 439 | nano.Image(fianceInfo.Avatar)) 440 | if err != nil { 441 | _, _ = ctx.SendPlainMessage(false, "", getLine(), " ->ERROR: ", err) 442 | } 443 | }) 444 | engine.OnMessageFullMatchGroup([]string{"闹离婚", "办离婚"}, nano.OnlyChannel, getdb).Limit(ctxext.LimitByUser).SetBlock(true).Handle(func(ctx *nano.Ctx) { 445 | gid := ctx.Message.GuildID 446 | uid := ctx.Message.Author.ID 447 | cdTime, err := wifeData.checkCD(gid, uid, "离") 448 | if err != nil { 449 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 450 | return 451 | } 452 | if cdTime > 0 { 453 | _, _ = ctx.SendPlainMessage(true, "你的技能CD还有", cdTime) 454 | return 455 | } 456 | uInfo, err := wifeData.checkUser(gid, uid) 457 | if err != nil { 458 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 459 | return 460 | } 461 | if uInfo.Users == "" { 462 | _, _ = ctx.SendPlainMessage(true, "你还是单身噢,快去娶群友吧!") 463 | return 464 | } 465 | // 写入CD 466 | err = wifeData.setCD(uid, "离") 467 | if err != nil { 468 | _, _ = ctx.SendPlainMessage(true, "[qqwife]你的技能CD记录失败\n", err) 469 | } 470 | user := strings.Split(uInfo.Users, " & ") 471 | mun := 0 472 | if user[1] == uid { 473 | mun = 1 474 | } 475 | favor, err := wifeData.favorFor(user[0], user[1], 0) 476 | if err != nil { 477 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 478 | return 479 | } 480 | if favor < 30 { 481 | favor = 10 482 | } 483 | if rand.Intn(101) > 110-favor { 484 | _, _ = ctx.SendPlainMessage(true, sendtext[3][rand.Intn(len(sendtext[3]))]) 485 | return 486 | } 487 | err = wifeData.divorce(gid, uid) 488 | if err != nil { 489 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 490 | return 491 | } 492 | /* 493 | if rand.Intn(100) > 50 { 494 | _, _ = wifeData.favorFor(user[0], user[1], -rand.Intn(favor/2)) 495 | } 496 | */ 497 | _, _ = ctx.SendPlainMessage(true, sendtext[4][mun]) 498 | }) 499 | 500 | // 好感度系统 501 | engine.OnMessageRegex(`^查好感度\s*<@!(\d+)>`, nano.OnlyChannel, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 502 | fiance := ctx.State["regex_matched"].([]string)[1] 503 | uid := ctx.Message.Author.ID 504 | favor, err := wifeData.favorFor(uid, fiance, 0) 505 | if err != nil { 506 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 507 | return 508 | } 509 | // 输出结果 510 | _, _ = ctx.SendPlainMessage(true, "当前你们好感度为", favor) 511 | }) 512 | // 礼物系统 513 | engine.OnMessageRegex(`^买礼物给\s*<@!(\d+)>`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 514 | gid := ctx.Message.GuildID 515 | uid := ctx.Message.Author.ID 516 | fiance := ctx.State["regex_matched"].([]string)[1] 517 | if fiance == uid { 518 | _, _ = ctx.SendPlainMessage(true, "你想给自己买什么礼物呢?") 519 | return 520 | } 521 | // 获取CD 522 | cdTime, err := wifeData.checkCD(gid, uid, "买") 523 | if err != nil { 524 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 525 | return 526 | } 527 | if cdTime > 0 { 528 | _, _ = ctx.SendPlainMessage(true, "你的技能CD还有", cdTime) 529 | return 530 | } 531 | // 获取好感度 532 | favor, err := wifeData.favorFor(uid, fiance, 0) 533 | if err != nil { 534 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 535 | return 536 | } 537 | // 对接小熊饼干 538 | uidint64, _ := strconv.ParseInt(uid, 10, 64) 539 | walletinfo := wallet.GetWalletOf(uidint64) 540 | if walletinfo < 1 { 541 | _, _ = ctx.SendPlainMessage(true, "你钱包没钱啦!") 542 | return 543 | } 544 | moneyToFavor := rand.Intn(math.Min(walletinfo, 100)) + 1 545 | // 计算钱对应的好感值 546 | newFavor := 1 547 | moodMax := 2 548 | if favor > 50 { 549 | newFavor = moneyToFavor % 10 // 礼物厌倦 550 | } else { 551 | moodMax = 5 552 | newFavor += rand.Intn(moneyToFavor) 553 | } 554 | // 随机对方心情 555 | mood := rand.Intn(moodMax) 556 | if mood == 0 { 557 | newFavor = -newFavor 558 | } 559 | // 记录结果 560 | err = wallet.InsertWalletOf(uidint64, -moneyToFavor) 561 | if err != nil { 562 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 563 | return 564 | } 565 | lastfavor, err := wifeData.favorFor(uid, fiance, newFavor) 566 | if err != nil { 567 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 568 | return 569 | } 570 | // 写入CD 571 | err = wifeData.setCD(uid, "买") 572 | if err != nil { 573 | _, _ = ctx.SendPlainMessage(true, "[qqwife]你的技能CD记录失败\n", err) 574 | } 575 | // 输出结果 576 | if mood == 0 { 577 | _, _ = ctx.SendPlainMessage(true, "你花了", moneyToFavor, "ATRI币买了一件女装送给了ta,ta很不喜欢,你们的好感度降低至", lastfavor) 578 | } else { 579 | _, _ = ctx.SendPlainMessage(true, "你花了", moneyToFavor, "ATRI币买了一件女装送给了ta,ta很喜欢,你们的好感度升至", lastfavor) 580 | } 581 | }) 582 | engine.OnMessageFullMatch("好感度列表", nano.OnlyChannel, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 583 | gid := ctx.Message.GuildID 584 | uid := ctx.Message.Author.ID 585 | fianceeInfo, err := wifeData.getGroupFavorability(uid) 586 | if err != nil { 587 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 588 | return 589 | } 590 | /***********设置图片的大小和底色***********/ 591 | number := len(fianceeInfo) 592 | fontSize := 50.0 593 | canvas := gg.NewContext(1150, int(170+(50+70)*float64(number))) 594 | canvas.SetRGB(1, 1, 1) // 白色 595 | canvas.Clear() 596 | /***********下载字体***********/ 597 | data, err := file.GetLazyData(text.BoldFontFile, nano.Md5File, true) 598 | if err != nil { 599 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 600 | } 601 | /***********设置字体颜色为黑色***********/ 602 | canvas.SetRGB(0, 0, 0) 603 | /***********设置字体大小,并获取字体高度用来定位***********/ 604 | if err = canvas.ParseFontFace(data, fontSize*2); err != nil { 605 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 606 | return 607 | } 608 | sl, h := canvas.MeasureString("你的好感度排行列表") 609 | /***********绘制标题***********/ 610 | canvas.DrawString("你的好感度排行列表", (1100-sl)/2, 100) // 放置在中间位置 611 | canvas.DrawString("————————————————————", 0, 160) 612 | /***********设置字体大小,并获取字体高度用来定位***********/ 613 | if err = canvas.ParseFontFace(data, fontSize); err != nil { 614 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 615 | return 616 | } 617 | i := 0 618 | for _, info := range fianceeInfo { 619 | if info.Favor == 0 { 620 | break 621 | } 622 | if info.Users == "" { 623 | continue 624 | } 625 | user, err := getUserInfoIn(ctx, gid, info.Users) 626 | if err != nil { 627 | log.Warnln("[", getLine(), " ->ERROR]:", err.Error()) 628 | continue 629 | } 630 | canvas.SetRGB255(0, 0, 0) 631 | canvas.DrawString(user.Nick+"("+user.ID+")", 10, float64(180+(50+70)*i)) 632 | canvas.DrawString(strconv.Itoa(info.Favor), 1020, float64(180+60+(50+70)*i)) 633 | canvas.DrawRectangle(10, float64(180+60+(50+70)*i)-h/2, 1000, 50) 634 | canvas.SetRGB255(150, 150, 150) 635 | canvas.Fill() 636 | canvas.SetRGB255(0, 0, 0) 637 | canvas.DrawRectangle(10, float64(180+60+(50+70)*i)-h/2, float64(info.Favor)*10, 50) 638 | canvas.SetRGB255(231, 27, 100) 639 | canvas.Fill() 640 | i++ 641 | } 642 | data, err = imgfactory.ToBytes(canvas.Image()) 643 | if err != nil { 644 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 645 | return 646 | } 647 | _, _ = ctx.SendImageBytes(data, true) 648 | }) 649 | } 650 | -------------------------------------------------------------------------------- /plugin/qqwife/main.go: -------------------------------------------------------------------------------- 1 | // Package qqwife 娶群友 2 | package qqwife 3 | 4 | import ( 5 | "math/rand" 6 | "strings" 7 | 8 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 9 | "github.com/FloatTech/imgfactory" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | nano "github.com/fumiama/NanoBot" 12 | 13 | "github.com/FloatTech/floatbox/file" 14 | "github.com/FloatTech/gg" 15 | "github.com/FloatTech/zbputils/img/text" 16 | ) 17 | 18 | var ( 19 | engine = nano.Register("qqwife", &ctrl.Options[*nano.Ctx]{ 20 | DisableOnDefault: false, 21 | Brief: "娶群友", 22 | Help: "- 娶群友\n- 群老婆列表\n" + 23 | "- [允许|禁止]自由恋爱\n- [允许|禁止]牛头人\n" + 24 | "- 设置CD为xx小时 →(默认12小时)\n" + 25 | "- 查好感度@对方QQ\n" + 26 | "- 好感度列表\n" + 27 | "--------------------------------\n以下指令存在CD,频道共用,不跨天刷新,前两个受指令开关\n--------------------------------\n" + 28 | "- (娶|嫁)@对方QQ\n (好感度越高成功率越高,保底30%概率)\n" + 29 | "- 牛@对方QQ\n (好感度越高成功率越高,保底10%概率)\n" + 30 | "- 闹离婚\n (好感度越高成功率越低)\n" + 31 | "- 买礼物给@对方QQ\n (使用bot钱包插件的金额获取好感度)\n" + 32 | "- 做媒 @攻方QQ @受方QQ\n (攻受双方好感度越高成功率越高,保底30%概率)\n" + 33 | "--------------------------------\n好感度规则\n--------------------------------\n" + 34 | "\"娶群友\"指令好感度随机增加1~5。\n\"A牛B的C\"会导致C恨A, 好感度-5;\nB为了报复A, 好感度+5(什么柜子play)\nA为BC做媒,成功B、C对A好感度+1反之-1\n做媒成功BC好感度+1" + 35 | "\nTips: 群老婆列表每天4点刷新", 36 | PrivateDataFolder: "qqwife", 37 | }).ApplySingle(nano.NewSingle( 38 | nano.WithKeyFn(func(ctx *nano.Ctx) int64 { 39 | return int64(ctx.GroupID()) 40 | }), 41 | nano.WithPostFn[int64](func(ctx *nano.Ctx) { 42 | _, _ = ctx.SendPlainMessage(true, "别着急,民政局门口排长队了!") 43 | }), 44 | )) 45 | ) 46 | 47 | func init() { 48 | engine.OnMessageFullMatch("娶群友", nano.OnlyChannel, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 49 | gid := ctx.Message.GuildID 50 | uid := ctx.Message.Author.ID 51 | 52 | info, err := wifeData.checkUser(gid, uid) 53 | if err != nil { 54 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 55 | return 56 | } 57 | uInfo, err := getUserInfoIn(ctx, gid, uid) 58 | if err != nil { 59 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 60 | return 61 | } 62 | if info.Users == "" { 63 | menbers, err := ctx.GetGuildMembersIn(gid, "0", 1000) 64 | if err != nil { 65 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 66 | return 67 | } 68 | list := make(map[int]userInfo, 1000) 69 | for i, member := range menbers { 70 | nick := member.Nick 71 | if nick == "" { 72 | nick = member.User.Username 73 | } 74 | list[i] = userInfo{ 75 | ID: member.User.ID, 76 | Nick: nick, 77 | Avatar: member.User.Avatar, 78 | } 79 | } 80 | target := list[rand.Intn(len(list))] 81 | if target.ID == uid { 82 | err = wifeData.register(gid, uInfo, userInfo{}) 83 | if err != nil { 84 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 85 | return 86 | } 87 | _, err = ctx.SendChain(nano.At(uid), nano.Text("今日获得成就:单身贵族")) 88 | if err != nil { 89 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 90 | } 91 | } 92 | info, err = wifeData.checkUser(gid, target.ID) 93 | if err != nil { 94 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 95 | return 96 | } 97 | if info.Users != "" { 98 | _, _ = ctx.SendPlainMessage(true, "呜...没娶到,你可以再尝试一次") 99 | return 100 | } 101 | err = wifeData.register(gid, uInfo, target) 102 | if err != nil { 103 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 104 | return 105 | } 106 | favor, err := wifeData.favorFor(uid, target.ID, rand.Intn(5)+1) 107 | if err != nil { 108 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 109 | return 110 | } 111 | _, err = ctx.SendChain(nano.At(uid), nano.Text("\n今天你的群老婆是\n[", target.Nick, "]哒\n当前你们好感度为", favor), nano.Image(target.Avatar)) 112 | if err != nil { 113 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 114 | } 115 | return 116 | } 117 | users := strings.Split(info.Users, " & ") 118 | switch { 119 | case (users[0] == uid && users[1] == "") || (users[1] == uid && users[0] == ""): // 如果是单身贵族 120 | _, _ = ctx.SendPlainMessage(true, "今天你是单身贵族噢") 121 | return 122 | case users[0] == uid: // 娶过别人 123 | favor, err := wifeData.favorFor(uid, users[1], 0) 124 | if err != nil { 125 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 126 | return 127 | } 128 | _, err = ctx.SendChain(nano.At(uid), 129 | nano.Text("\n今天你在", info.Updatetime, "娶了群友[", info.Mname, "]\n", 130 | "当前你们好感度为", favor), nano.Image(info.Mpic)) 131 | if err != nil { 132 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 133 | } 134 | return 135 | case users[1] == uid: // 嫁给别人 136 | favor, err := wifeData.favorFor(users[0], uid, 0) 137 | if err != nil { 138 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 139 | return 140 | } 141 | _, err = ctx.SendChain(nano.At(uid), 142 | nano.Text("\n今天你在", info.Updatetime, "被群友[", info.Sname, "]娶了\n", 143 | "当前你们好感度为", favor), nano.Image(info.Spic)) 144 | if err != nil { 145 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 146 | } 147 | return 148 | } 149 | }) 150 | engine.OnMessageFullMatch("群老婆列表", nano.OnlyChannel, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *nano.Ctx) { 151 | gid := ctx.Message.GuildID 152 | list, err := wifeData.getlist(gid) 153 | if err != nil { 154 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]:", err) 155 | return 156 | } 157 | number := len(list) 158 | if number <= 0 { 159 | _, _ = ctx.SendPlainMessage(false, "今天没有人结婚哦: ") 160 | return 161 | } 162 | /***********设置图片的大小和底色***********/ 163 | fontSize := 50.0 164 | if number < 10 { 165 | number = 10 166 | } 167 | canvas := gg.NewContext(1500, int(250+fontSize*float64(number))) 168 | canvas.SetRGB(1, 1, 1) // 白色 169 | canvas.Clear() 170 | /***********下载字体,可以注销掉***********/ 171 | data, err := file.GetLazyData(text.BoldFontFile, nano.Md5File, true) 172 | if err != nil { 173 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 174 | } 175 | /***********设置字体颜色为黑色***********/ 176 | canvas.SetRGB(0, 0, 0) 177 | /***********设置字体大小,并获取字体高度用来定位***********/ 178 | if err = canvas.ParseFontFace(data, fontSize*2); err != nil { 179 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 180 | return 181 | } 182 | sl, h := canvas.MeasureString("群老婆列表") 183 | /***********绘制标题***********/ 184 | canvas.DrawString("群老婆列表", (1500-sl)/2, 160-h) // 放置在中间位置 185 | canvas.DrawString("————————————————————", 0, 250-h) 186 | /***********设置字体大小,并获取字体高度用来定位***********/ 187 | if err = canvas.ParseFontFace(data, fontSize); err != nil { 188 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 189 | return 190 | } 191 | _, h = canvas.MeasureString("焯") 192 | for i, info := range list { 193 | canvas.DrawString(slicename(info.Sname, canvas), 0, float64(260+50*i)-h) 194 | canvas.DrawString("←→", 700, float64(260+50*i)-h) 195 | canvas.DrawString(slicename(info.Mname, canvas), 800, float64(260+50*i)-h) 196 | } 197 | data, err = imgfactory.ToBytes(canvas.Image()) 198 | if err != nil { 199 | _, _ = ctx.SendPlainMessage(false, "[", getLine(), " ->ERROR]: ", err) 200 | return 201 | } 202 | _, _ = ctx.SendImageBytes(data, false) 203 | }) 204 | } 205 | -------------------------------------------------------------------------------- /plugin/qunyou/main.go: -------------------------------------------------------------------------------- 1 | // Package qunyou ... 2 | package qunyou 3 | 4 | import ( 5 | "os/exec" 6 | "strings" 7 | 8 | nano "github.com/fumiama/NanoBot" 9 | 10 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 11 | ctrl "github.com/FloatTech/zbpctrl" 12 | ) 13 | 14 | func init() { 15 | en := nano.Register("qunyou", &ctrl.Options[*nano.Ctx]{ 16 | DisableOnDefault: true, 17 | Help: "随机群友怪话\n- 看看群友", 18 | }) 19 | en.OnMessagePrefix("看看群友").Limit(ctxext.LimitByGroup).Handle(func(ctx *nano.Ctx) { 20 | prompt := ctx.State["args"].(string) 21 | sb := strings.Builder{} 22 | errsb := strings.Builder{} 23 | cmd := exec.Cmd{ 24 | Path: "/usr/local/bin/llama2.run", 25 | Args: []string{"/usr/local/bin/llama2.run", "model.bin"}, 26 | Dir: "/usr/local/src/llama2.c", 27 | Stdout: &sb, 28 | Stderr: &errsb, 29 | } 30 | if prompt != "" { 31 | cmd.Args = append(cmd.Args, "-i", prompt) 32 | } 33 | err := cmd.Run() 34 | if err != nil { 35 | ctx.SendChain(nano.Text("ERROR: ", err, errsb.String())) 36 | return 37 | } 38 | ctx.SendChain(nano.Text(sb.String())) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /plugin/runcode/code_runner.go: -------------------------------------------------------------------------------- 1 | // Package runcode 基于 https://tool.runoob.com 的在线运行代码 2 | package runcode 3 | 4 | import ( 5 | "strings" 6 | 7 | nano "github.com/fumiama/NanoBot" 8 | 9 | "github.com/FloatTech/AnimeAPI/runoob" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | 12 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 13 | ) 14 | 15 | var ro = runoob.NewRunOOB("066417defb80d038228de76ec581a50a") 16 | 17 | func init() { 18 | nano.Register("runcode", &ctrl.Options[*nano.Ctx]{ 19 | DisableOnDefault: false, 20 | Help: "在线代码运行: \n" + 21 | ">runcode [language] [code block]\n" + 22 | "模板查看: \n" + 23 | ">runcode [language] help\n" + 24 | "支持语种: \n" + 25 | "Go || Python || C/C++ || C# || Java || Lua \n" + 26 | "JavaScript || TypeScript || PHP || Shell \n" + 27 | "Kotlin || Rust || Erlang || Ruby || Swift \n" + 28 | "R || VB || Py2 || Perl || Pascal || Scala", 29 | }).ApplySingle(ctxext.DefaultSingle).OnMessageRegex(`^\s*[(>)>]runcode(raw)?\s(.+?)\s([\s\S]+)$`).SetBlock(true).Limit(ctxext.LimitByUser). 30 | Handle(func(ctx *nano.Ctx) { 31 | israw := ctx.State["regex_matched"].([]string)[1] != "" 32 | language := ctx.State["regex_matched"].([]string)[2] 33 | language = strings.ToLower(language) 34 | if _, exist := runoob.LangTable[language]; !exist { 35 | // 不支持语言 36 | msg := "> " + ctx.Message.Author.Username + "\n语言" + language + "不是受支持的编程语种呢~" 37 | if nano.OnlyQQ(ctx) { 38 | _, _ = ctx.SendPlainMessage(false, msg) 39 | } else { 40 | _, _ = ctx.SendPlainMessage(false, nano.MessageEscape(msg)) 41 | } 42 | } else { 43 | // 执行运行 44 | block := ctx.State["regex_matched"].([]string)[3] 45 | switch block { 46 | case "help": 47 | msg := "> " + ctx.Message.Author.Username + " " + language + "-template:\n>runcode " + language + "\n" + runoob.Templates[language] 48 | var err error 49 | if nano.OnlyQQ(ctx) { 50 | _, err = ctx.SendPlainMessage(false, msg) 51 | } else { 52 | _, err = ctx.SendPlainMessage(false, nano.MessageEscape(msg)) 53 | } 54 | if err != nil { 55 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 56 | } 57 | default: 58 | output, err := ro.Run(block, language, "") 59 | if err != nil { 60 | output = "ERROR:\n" + nano.MessageEscape(err.Error()) 61 | } 62 | output = cutTooLong(strings.Trim(output, "\n")) 63 | if israw { 64 | _, err = ctx.SendPlainMessage(false, output) 65 | } else { 66 | head := "> " + ctx.Message.Author.Username + "\n" 67 | if nano.OnlyQQ(ctx) { 68 | _, err = ctx.SendPlainMessage(false, head+output) 69 | } else { 70 | _, err = ctx.SendPlainMessage(false, nano.MessageEscape(head+output)) 71 | } 72 | } 73 | if err != nil { 74 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 75 | } 76 | } 77 | } 78 | }) 79 | } 80 | 81 | // 截断过长文本 82 | func cutTooLong(text string) string { 83 | temp := []rune(text) 84 | count := 0 85 | for i := range temp { 86 | switch { 87 | case temp[i] == 13 && i < len(temp)-1 && temp[i+1] == 10: 88 | // 匹配 \r\n 跳过,等 \n 自己加 89 | case temp[i] == 10: 90 | count++ 91 | case temp[i] == 13: 92 | count++ 93 | } 94 | if count > 30 || i > 1000 { 95 | temp = append(temp[:i-1], []rune("\n............\n............")...) 96 | break 97 | } 98 | } 99 | return string(temp) 100 | } 101 | -------------------------------------------------------------------------------- /plugin/score/draw.go: -------------------------------------------------------------------------------- 1 | // Package score 签到 2 | package score 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "image" 8 | "image/color" 9 | "strconv" 10 | "sync" 11 | "time" 12 | 13 | "github.com/FloatTech/floatbox/file" 14 | "github.com/FloatTech/gg" 15 | "github.com/FloatTech/rendercard" 16 | "github.com/FloatTech/zbputils/img/text" 17 | "github.com/disintegration/imaging" 18 | nano "github.com/fumiama/NanoBot" 19 | 20 | "github.com/FloatTech/NanoBot-Plugin/kanban/banner" 21 | ) 22 | 23 | func floatstyle(a *scoredata) (img image.Image, err error) { 24 | fontdata, err := file.GetLazyData(text.GlowSansFontFile, nano.Md5File, false) 25 | if err != nil { 26 | return 27 | } 28 | 29 | picFile, getAvatar, err := initPic(a.avatarurl) 30 | if err != nil { 31 | return 32 | } 33 | a.picfile = picFile 34 | 35 | back, err := gg.LoadImage(a.picfile) 36 | if err != nil { 37 | return 38 | } 39 | 40 | bx, by := float64(back.Bounds().Dx()), float64(back.Bounds().Dy()) 41 | 42 | sc := 1280 / bx 43 | 44 | colors := gg.TakeColor(back, 3) 45 | 46 | canvas := gg.NewContext(1280, 1280*int(by)/int(bx)) 47 | 48 | cw, ch := float64(canvas.W()), float64(canvas.H()) 49 | 50 | sch := ch * 6 / 10 51 | 52 | var blurback, scbackimg, backshadowimg, avatarimg, avatarbackimg, avatarshadowimg, whitetext, blacktext image.Image 53 | var wg sync.WaitGroup 54 | 55 | wg.Add(2) 56 | go func() { 57 | defer wg.Done() 58 | scback := gg.NewContext(canvas.W(), canvas.H()) 59 | scback.ScaleAbout(sc, sc, cw/2, ch/2) 60 | scback.DrawImageAnchored(back, canvas.W()/2, canvas.H()/2, 0.5, 0.5) 61 | scback.Identity() 62 | 63 | go func() { 64 | defer wg.Done() 65 | blurback = imaging.Blur(scback.Image(), 20) 66 | }() 67 | 68 | scbackimg = rendercard.Fillet(scback.Image(), 12) 69 | }() 70 | 71 | wg.Add(1) 72 | go func() { 73 | defer wg.Done() 74 | pureblack := gg.NewContext(canvas.W(), canvas.H()) 75 | pureblack.SetRGBA255(0, 0, 0, 255) 76 | pureblack.Clear() 77 | 78 | shadow := gg.NewContext(canvas.W(), canvas.H()) 79 | shadow.ScaleAbout(0.6, 0.6, cw-cw/3, ch/2) 80 | shadow.DrawImageAnchored(pureblack.Image(), canvas.W()-canvas.W()/3, canvas.H()/2, 0.5, 0.5) 81 | shadow.Identity() 82 | 83 | backshadowimg = imaging.Blur(shadow.Image(), 8) 84 | }() 85 | 86 | aw, ah := (ch-sch)/2/2/2*3, (ch-sch)/2/2/2*3 87 | 88 | if getAvatar != nil { 89 | wg.Add(1) 90 | go func() { 91 | defer wg.Done() 92 | avatar, _, err := image.Decode(bytes.NewReader(getAvatar)) 93 | if err != nil { 94 | return 95 | } 96 | 97 | isc := (ch - sch) / 2 / 2 / 2 * 3 / float64(avatar.Bounds().Dy()) 98 | 99 | scavatar := gg.NewContext(int(aw), int(ah)) 100 | 101 | scavatar.ScaleAbout(isc, isc, aw/2, ah/2) 102 | scavatar.DrawImageAnchored(avatar, scavatar.W()/2, scavatar.H()/2, 0.5, 0.5) 103 | scavatar.Identity() 104 | 105 | avatarimg = rendercard.Fillet(scavatar.Image(), 8) 106 | }() 107 | } 108 | 109 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/2/2) 110 | if err != nil { 111 | return 112 | } 113 | namew, _ := canvas.MeasureString(a.nickname) 114 | 115 | wg.Add(1) 116 | go func() { 117 | defer wg.Done() 118 | avatarshadowimg = imaging.Blur(customrectangle(cw, ch, aw, ah, namew, color.Black), 8) 119 | }() 120 | 121 | wg.Add(1) 122 | go func() { 123 | defer wg.Done() 124 | avatarbackimg = customrectangle(cw, ch, aw, ah, namew, colors[0]) 125 | }() 126 | 127 | wg.Add(1) 128 | go func() { 129 | defer wg.Done() 130 | whitetext, err = customtext(a, fontdata, cw, ch, aw, color.White) 131 | if err != nil { 132 | return 133 | } 134 | }() 135 | 136 | wg.Add(1) 137 | go func() { 138 | defer wg.Done() 139 | blacktext, err = customtext(a, fontdata, cw, ch, aw, color.Black) 140 | if err != nil { 141 | return 142 | } 143 | }() 144 | 145 | wg.Wait() 146 | if scbackimg == nil || backshadowimg == nil || avatarbackimg == nil || avatarshadowimg == nil || whitetext == nil || blacktext == nil { 147 | err = errors.New("图片渲染失败") 148 | return 149 | } 150 | 151 | canvas.DrawImageAnchored(blurback, canvas.W()/2, canvas.H()/2, 0.5, 0.5) 152 | 153 | canvas.DrawImage(backshadowimg, 0, 0) 154 | 155 | canvas.ScaleAbout(0.6, 0.6, cw-cw/3, ch/2) 156 | canvas.DrawImageAnchored(scbackimg, canvas.W()-canvas.W()/3, canvas.H()/2, 0.5, 0.5) 157 | canvas.Identity() 158 | 159 | canvas.DrawImage(avatarshadowimg, 0, 0) 160 | canvas.DrawImage(avatarbackimg, 0, 0) 161 | if avatarimg != nil { 162 | canvas.DrawImageAnchored(avatarimg, int((ch-sch)/2/2), int((ch-sch)/2/2), 0.5, 0.5) 163 | } 164 | 165 | canvas.DrawImage(blacktext, 2, 2) 166 | canvas.DrawImage(whitetext, 0, 0) 167 | 168 | img = canvas.Image() 169 | return 170 | } 171 | 172 | func customrectangle(cw, ch, aw, ah, namew float64, rtgcolor color.Color) (img image.Image) { 173 | canvas := gg.NewContext(int(cw), int(ch)) 174 | sch := ch * 6 / 10 175 | canvas.DrawRoundedRectangle((ch-sch)/2/2-aw/2-aw/40, (ch-sch)/2/2-aw/2-ah/40, aw+aw/40*2, ah+ah/40*2, 8) 176 | canvas.SetColor(rtgcolor) 177 | canvas.Fill() 178 | canvas.DrawRoundedRectangle((ch-sch)/2/2, (ch-sch)/2/2-ah/4, aw/2+aw/40*5+namew, ah/2, 8) 179 | canvas.Fill() 180 | 181 | img = canvas.Image() 182 | return 183 | } 184 | 185 | func customtext(a *scoredata, fontdata []byte, cw, ch, aw float64, textcolor color.Color) (img image.Image, err error) { 186 | canvas := gg.NewContext(int(cw), int(ch)) 187 | canvas.SetColor(textcolor) 188 | scw, sch := cw*6/10, ch*6/10 189 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/2/2) 190 | if err != nil { 191 | return 192 | } 193 | canvas.DrawStringAnchored(a.nickname, (ch-sch)/2/2+aw/2+aw/40*2, (ch-sch)/2/2, 0, 0.5) 194 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/2/3*2) 195 | if err != nil { 196 | return 197 | } 198 | canvas.DrawStringAnchored(time.Now().Format("2006/01/02"), cw-cw/6, ch/2-sch/2-canvas.FontHeight(), 0.5, 0.5) 199 | 200 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/2/2) 201 | if err != nil { 202 | return 203 | } 204 | nextrankScore := 0 205 | if a.rank < 10 { 206 | nextrankScore = rankArray[a.rank+1] 207 | } else { 208 | nextrankScore = scoreMax 209 | } 210 | nextLevelStyle := strconv.Itoa(a.level) + "/" + strconv.Itoa(nextrankScore) 211 | 212 | canvas.DrawStringAnchored("Level "+strconv.Itoa(a.rank), cw/3*2-scw/2, ch/2+sch/2+canvas.FontHeight(), 0, 0.5) 213 | canvas.DrawStringAnchored(nextLevelStyle, cw/3*2+scw/2, ch/2+sch/2+canvas.FontHeight(), 1, 0.5) 214 | 215 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/2/3) 216 | if err != nil { 217 | return 218 | } 219 | 220 | canvas.DrawStringAnchored("Create By NanoBot-Plugin "+banner.Version, 0+4, ch, 0, -0.5) 221 | 222 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/5*3) 223 | if err != nil { 224 | return 225 | } 226 | 227 | tempfh := canvas.FontHeight() 228 | 229 | canvas.DrawStringAnchored(getHourWord(time.Now()), ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4, 0, 0.5) 230 | 231 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/5) 232 | if err != nil { 233 | return 234 | } 235 | 236 | canvas.DrawStringAnchored("ATRI币 + "+strconv.Itoa(a.inc), ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4+tempfh, 0, 0.5) 237 | canvas.DrawStringAnchored("EXP + 1", ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4+tempfh+canvas.FontHeight(), 0, 1) 238 | 239 | err = canvas.ParseFontFace(fontdata, (ch-sch)/2/4) 240 | if err != nil { 241 | return 242 | } 243 | 244 | canvas.DrawStringAnchored("你有 "+strconv.Itoa(a.score)+" 枚ATRI币", ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4*3, 0, 0.5) 245 | 246 | img = canvas.Image() 247 | return 248 | } 249 | -------------------------------------------------------------------------------- /plugin/score/model.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/jinzhu/gorm" 8 | ) 9 | 10 | // sdb 得分数据库 11 | var sdb *scoredb 12 | 13 | // scoredb 分数数据库 14 | type scoredb gorm.DB 15 | 16 | // scoretable 分数结构体 17 | type scoretable struct { 18 | UID int64 `gorm:"column:uid;primary_key"` 19 | Score int `gorm:"column:score;default:0"` 20 | } 21 | 22 | // TableName ... 23 | func (scoretable) TableName() string { 24 | return "score" 25 | } 26 | 27 | // signintable 签到结构体 28 | type signintable struct { 29 | UID int64 `gorm:"column:uid;primary_key"` 30 | Count int `gorm:"column:count;default:0"` 31 | UpdatedAt time.Time 32 | } 33 | 34 | // TableName ... 35 | func (signintable) TableName() string { 36 | return "sign_in" 37 | } 38 | 39 | // initialize 初始化ScoreDB数据库 40 | func initialize(dbpath string) *scoredb { 41 | var err error 42 | if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) { 43 | // 生成文件 44 | f, err := os.Create(dbpath) 45 | if err != nil { 46 | return nil 47 | } 48 | defer f.Close() 49 | } 50 | gdb, err := gorm.Open("sqlite3", dbpath) 51 | if err != nil { 52 | panic(err) 53 | } 54 | gdb.AutoMigrate(&scoretable{}).AutoMigrate(&signintable{}) 55 | return (*scoredb)(gdb) 56 | } 57 | 58 | // Close ... 59 | func (sdb *scoredb) Close() error { 60 | db := (*gorm.DB)(sdb) 61 | return db.Close() 62 | } 63 | 64 | // GetScoreByUID 取得分数 65 | func (sdb *scoredb) GetScoreByUID(uid int64) (s scoretable) { 66 | db := (*gorm.DB)(sdb) 67 | db.Model(&scoretable{}).FirstOrCreate(&s, "uid = ? ", uid) 68 | return s 69 | } 70 | 71 | // InsertOrUpdateScoreByUID 插入或更新分数 72 | func (sdb *scoredb) InsertOrUpdateScoreByUID(uid int64, score int) (err error) { 73 | db := (*gorm.DB)(sdb) 74 | s := scoretable{ 75 | UID: uid, 76 | Score: score, 77 | } 78 | if err = db.Model(&scoretable{}).First(&s, "uid = ? ", uid).Error; err != nil { 79 | // error handling... 80 | if gorm.IsRecordNotFoundError(err) { 81 | err = db.Model(&scoretable{}).Create(&s).Error // newUser not user 82 | } 83 | } else { 84 | err = db.Model(&scoretable{}).Where("uid = ? ", uid).Update( 85 | map[string]any{ 86 | "score": score, 87 | }).Error 88 | } 89 | return 90 | } 91 | 92 | // GetSignInByUID 取得签到次数 93 | func (sdb *scoredb) GetSignInByUID(uid int64) (si signintable) { 94 | db := (*gorm.DB)(sdb) 95 | db.Model(&signintable{}).FirstOrCreate(&si, "uid = ? ", uid) 96 | return si 97 | } 98 | 99 | // InsertOrUpdateSignInCountByUID 插入或更新签到次数 100 | func (sdb *scoredb) InsertOrUpdateSignInCountByUID(uid int64, count int) (err error) { 101 | db := (*gorm.DB)(sdb) 102 | si := signintable{ 103 | UID: uid, 104 | Count: count, 105 | } 106 | if err = db.Model(&signintable{}).First(&si, "uid = ? ", uid).Error; err != nil { 107 | // error handling... 108 | if gorm.IsRecordNotFoundError(err) { 109 | err = db.Model(&signintable{}).Create(&si).Error // newUser not user 110 | } 111 | } else { 112 | err = db.Model(&signintable{}).Where("uid = ? ", uid).Update( 113 | map[string]any{ 114 | "count": count, 115 | }).Error 116 | } 117 | return 118 | } 119 | 120 | func (sdb *scoredb) GetScoreRankByTopN(n int) (st []scoretable, err error) { 121 | db := (*gorm.DB)(sdb) 122 | err = db.Model(&scoretable{}).Order("score desc").Limit(n).Find(&st).Error 123 | return 124 | } 125 | 126 | type scoredata struct { 127 | drawedfile string 128 | picfile string 129 | avatarurl string 130 | nickname string 131 | inc int // 增加币 132 | score int // 钱包 133 | level int 134 | rank int 135 | } 136 | -------------------------------------------------------------------------------- /plugin/score/sign_in.go: -------------------------------------------------------------------------------- 1 | // Package score 签到 2 | package score 3 | 4 | import ( 5 | "math" 6 | "math/rand" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/FloatTech/AnimeAPI/setu" 12 | "github.com/FloatTech/AnimeAPI/wallet" 13 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 14 | "github.com/FloatTech/floatbox/file" 15 | "github.com/FloatTech/floatbox/process" 16 | "github.com/FloatTech/floatbox/web" 17 | "github.com/FloatTech/imgfactory" 18 | ctrl "github.com/FloatTech/zbpctrl" 19 | "github.com/FloatTech/zbputils/img/text" 20 | nano "github.com/fumiama/NanoBot" 21 | "github.com/golang/freetype" 22 | "github.com/wcharczuk/go-chart/v2" 23 | ) 24 | 25 | const ( 26 | signinMax = 1 27 | // scoreMax 分数上限定为1200 28 | scoreMax = 1200 29 | ) 30 | 31 | var ( 32 | rankArray = [...]int{0, 10, 20, 50, 100, 200, 350, 550, 750, 1000, 1200} 33 | engine = nano.Register("score", &ctrl.Options[*nano.Ctx]{ 34 | DisableOnDefault: false, 35 | Brief: "签到", 36 | Help: "- 签到\n | 获得签到背景\n- 查看等级排名\n注:为跨群排名\n- 查看我的钱包\n- 查看钱包排名\n注:为本群排行,若群人数太多不建议使用该功能!!!", 37 | PrivateDataFolder: "score", 38 | }) 39 | ) 40 | 41 | func init() { 42 | cachePath := engine.DataFolder() + "cache/" 43 | go func() { 44 | sdb = initialize(engine.DataFolder() + "score.db") 45 | ok := file.IsExist(cachePath) 46 | if !ok { 47 | err := os.MkdirAll(cachePath, 0777) 48 | if err != nil { 49 | panic(err) 50 | } 51 | return 52 | } 53 | files, err := os.ReadDir(cachePath) 54 | if err == nil { 55 | for _, f := range files { 56 | if !strings.Contains(f.Name(), time.Now().Format("20060102")) { 57 | _ = os.Remove(cachePath + f.Name()) 58 | } 59 | } 60 | } 61 | }() 62 | engine.OnMessageFullMatch("签到").Limit(ctxext.LimitByUser).SetBlock(true).Handle(func(ctx *nano.Ctx) { 63 | uid := ctx.Message.Author.ID 64 | if uid == "" { 65 | _, _ = ctx.SendPlainMessage(false, "ERROR: 未获取到用户uid") 66 | return 67 | } 68 | uidint := ctx.UserID() 69 | today := time.Now().Format("20060102") 70 | // 签到图片 71 | drawedFile := cachePath + uid + today + "signin.png" 72 | // 获取签到时间 73 | si := sdb.GetSignInByUID(int64(uidint)) 74 | siUpdateTimeStr := si.UpdatedAt.Format("20060102") 75 | switch { 76 | case si.Count >= signinMax && siUpdateTimeStr == today: 77 | // 如果签到时间是今天 78 | _, err := ctx.SendPlainMessage(true, "今天你已经签到过了!") 79 | if err != nil { 80 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 81 | } 82 | if file.IsExist(drawedFile) { 83 | _, err := ctx.SendImage("file:///"+file.BOTPATH+"/"+drawedFile, false) 84 | if err != nil { 85 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 86 | } 87 | } 88 | return 89 | case siUpdateTimeStr != today: 90 | // 如果是跨天签到就清数据 91 | err := sdb.InsertOrUpdateSignInCountByUID(int64(uidint), 0) 92 | if err != nil { 93 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 94 | return 95 | } 96 | } 97 | // 更新签到次数 98 | err := sdb.InsertOrUpdateSignInCountByUID(int64(uidint), si.Count+1) 99 | if err != nil { 100 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 101 | return 102 | } 103 | // 更新经验 104 | level := sdb.GetScoreByUID(int64(uidint)).Score + 1 105 | if level > scoreMax { 106 | level = scoreMax 107 | _, err := ctx.SendPlainMessage(true, "你的等级已经达到上限") 108 | if err != nil { 109 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 110 | } 111 | } 112 | err = sdb.InsertOrUpdateScoreByUID(int64(uidint), level) 113 | if err != nil { 114 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 115 | return 116 | } 117 | // 更新钱包 118 | rank := getrank(level) 119 | add := 1 + rand.Intn(10) + rank*5 // 等级越高获得的钱越高 120 | 121 | go func() { 122 | err = wallet.InsertWalletOf(int64(uidint), add) 123 | if err != nil { 124 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 125 | return 126 | } 127 | }() 128 | alldata := &scoredata{ 129 | drawedfile: drawedFile, 130 | avatarurl: ctx.Message.Author.Avatar, 131 | inc: add, 132 | score: wallet.GetWalletOf(int64(uidint)), 133 | level: level, 134 | rank: rank, 135 | } 136 | if ctx.Message.Author.Username != "" { 137 | alldata.nickname = ctx.Message.Author.Username 138 | } else { 139 | alldata.nickname = "您好" 140 | } 141 | drawimage, err := floatstyle(alldata) 142 | if err != nil { 143 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 144 | return 145 | } 146 | // done. 147 | f, err := os.Create(drawedFile) 148 | if err != nil { 149 | data, err := imgfactory.ToBytes(drawimage) 150 | if err != nil { 151 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 152 | return 153 | } 154 | _, err = ctx.SendImageBytes(data, false) 155 | if err != nil { 156 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 157 | } 158 | return 159 | } 160 | _, err = imgfactory.WriteTo(drawimage, f) 161 | defer f.Close() 162 | if err != nil { 163 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 164 | return 165 | } 166 | _, err = ctx.SendImage("file:///"+file.BOTPATH+"/"+drawedFile, false) 167 | if err != nil { 168 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 169 | } 170 | }) 171 | 172 | engine.OnMessageFullMatch("获得签到背景", nano.OnlyChannel).Limit(ctxext.LimitByGroup).SetBlock(true). 173 | Handle(func(ctx *nano.Ctx) { 174 | uidStr := ctx.Message.Author.ID 175 | picFile := cachePath + uidStr + time.Now().Format("20060102") + ".png" 176 | if file.IsNotExist(picFile) { 177 | _, err := ctx.SendPlainMessage(true, "请先签到!") 178 | if err != nil { 179 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 180 | } 181 | return 182 | } 183 | if _, err := ctx.SendImage("file:///"+file.BOTPATH+"/"+picFile, false); err != nil { 184 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 185 | } 186 | }) 187 | engine.OnMessageFullMatch("查看等级排名", nano.OnlyChannel).Limit(ctxext.LimitByGroup).SetBlock(true). 188 | Handle(func(ctx *nano.Ctx) { 189 | today := time.Now().Format("20060102") 190 | drawedFile := cachePath + today + "scoreRank.png" 191 | if file.IsExist(drawedFile) { 192 | _, err := ctx.SendImage("file:///"+file.BOTPATH+"/"+drawedFile, false) 193 | if err != nil { 194 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 195 | 196 | } 197 | return 198 | } 199 | st, err := sdb.GetScoreRankByTopN(10) 200 | if err != nil { 201 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 202 | return 203 | } 204 | if len(st) == 0 { 205 | _, _ = ctx.SendPlainMessage(false, "ERROR: 目前还没有人签到过") 206 | return 207 | } 208 | _, err = file.GetLazyData(text.FontFile, nano.Md5File, true) 209 | if err != nil { 210 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 211 | return 212 | } 213 | b, err := os.ReadFile(text.FontFile) 214 | if err != nil { 215 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 216 | return 217 | } 218 | font, err := freetype.ParseFont(b) 219 | if err != nil { 220 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 221 | return 222 | } 223 | f, err := os.Create(drawedFile) 224 | if err != nil { 225 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 226 | return 227 | } 228 | var bars []chart.Value 229 | for _, v := range st { 230 | if v.Score != 0 { 231 | bars = append(bars, chart.Value{ 232 | Label: ctx.Message.Author.Username, 233 | Value: float64(v.Score), 234 | }) 235 | } 236 | } 237 | err = chart.BarChart{ 238 | Font: font, 239 | Title: "等级排名(1天只刷新1次)", 240 | Background: chart.Style{ 241 | Padding: chart.Box{ 242 | Top: 40, 243 | }, 244 | }, 245 | YAxis: chart.YAxis{ 246 | Range: &chart.ContinuousRange{ 247 | Min: 0, 248 | Max: math.Ceil(bars[0].Value/10) * 10, 249 | }, 250 | }, 251 | Height: 500, 252 | BarWidth: 50, 253 | Bars: bars, 254 | }.Render(chart.PNG, f) 255 | _ = f.Close() 256 | if err != nil { 257 | _ = os.Remove(drawedFile) 258 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 259 | return 260 | } 261 | if _, err := ctx.SendImage("file:///"+file.BOTPATH+"/"+drawedFile, false); err != nil { 262 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 263 | } 264 | }) 265 | } 266 | 267 | func getHourWord(t time.Time) string { 268 | h := t.Hour() 269 | switch { 270 | case 6 <= h && h < 12: 271 | return "早上好" 272 | case 12 <= h && h < 14: 273 | return "中午好" 274 | case 14 <= h && h < 19: 275 | return "下午好" 276 | case 19 <= h && h < 24: 277 | return "晚上好" 278 | case 0 <= h && h < 6: 279 | return "凌晨好" 280 | default: 281 | return "" 282 | } 283 | } 284 | 285 | func getrank(count int) int { 286 | for k, v := range rankArray { 287 | if count == v { 288 | return k 289 | } else if count < v { 290 | return k - 1 291 | } 292 | } 293 | return -1 294 | } 295 | 296 | func initPic(avatarurl string) (pic string, avatar []byte, err error) { 297 | if avatarurl != "" { 298 | avatar, err = web.GetData(avatarurl) 299 | if err != nil { 300 | return 301 | } 302 | } 303 | defer process.SleepAbout1sTo2s() 304 | pic, err = setu.DefaultPool.Roll("") 305 | return 306 | } 307 | -------------------------------------------------------------------------------- /plugin/status/main.go: -------------------------------------------------------------------------------- 1 | // Package status 服务器状态 2 | package status 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "image" 8 | "image/color" 9 | "math" 10 | "os" 11 | "runtime" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "sync/atomic" 16 | "time" 17 | "unsafe" 18 | 19 | "github.com/disintegration/imaging" 20 | "github.com/shirou/gopsutil/v3/cpu" 21 | "github.com/shirou/gopsutil/v3/disk" 22 | "github.com/shirou/gopsutil/v3/host" 23 | "github.com/shirou/gopsutil/v3/mem" 24 | "golang.org/x/text/cases" 25 | "golang.org/x/text/language" 26 | 27 | nano "github.com/fumiama/NanoBot" 28 | 29 | "github.com/FloatTech/AnimeAPI/setu" 30 | "github.com/FloatTech/floatbox/file" 31 | "github.com/FloatTech/floatbox/web" 32 | "github.com/FloatTech/gg" 33 | "github.com/FloatTech/imgfactory" 34 | "github.com/FloatTech/rendercard" 35 | ctrl "github.com/FloatTech/zbpctrl" 36 | "github.com/FloatTech/zbputils/img/text" 37 | 38 | "github.com/FloatTech/NanoBot-Plugin/kanban/banner" 39 | ) 40 | 41 | var ( 42 | boottime = time.Now() 43 | bgdata *[]byte 44 | bgcount uintptr 45 | isday bool 46 | lightcolor = [3][4]uint8{{255, 70, 0, 255}, {255, 165, 0, 255}, {145, 240, 145, 255}} 47 | darkcolor = [3][4]uint8{{215, 50, 0, 255}, {205, 135, 0, 255}, {115, 200, 115, 255}} 48 | ) 49 | 50 | func init() { // 插件主体 51 | engine := nano.Register("status", &ctrl.Options[*nano.Ctx]{ 52 | DisableOnDefault: false, 53 | Brief: "自检", 54 | Help: "- 查询计算机当前活跃度: [检查身体 | 自检 | 启动自检 | 系统状态]", 55 | }) 56 | 57 | engine.OnMessageFullMatchGroup([]string{"检查身体", "自检", "启动自检", "系统状态"}).SetBlock(true). 58 | Handle(func(ctx *nano.Ctx) { 59 | now := time.Now().Hour() 60 | isday = now > 7 && now < 19 61 | bot, err := ctx.GetMyInfo() 62 | if err != nil { 63 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 64 | return 65 | } 66 | img, err := drawstatus(ctx.State["manager"].(*ctrl.Control[*nano.Ctx]), bot.Avatar, bot.Username) 67 | if err != nil { 68 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 69 | return 70 | } 71 | sendimg, err := imgfactory.ToBytes(img) 72 | if err != nil { 73 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 74 | return 75 | } 76 | if _, err := ctx.SendImageBytes(sendimg, false); err != nil { 77 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 78 | } 79 | }) 80 | 81 | } 82 | 83 | func drawstatus(m *ctrl.Control[*nano.Ctx], botavartarurl, botname string) (sendimg image.Image, err error) { 84 | diskstate, err := diskstate() 85 | if err != nil { 86 | return 87 | } 88 | diskcardh := 40 + (20+50)*len(diskstate) + 40 - 20 89 | 90 | moreinfo, err := moreinfo(m) 91 | if err != nil { 92 | return 93 | } 94 | moreinfocardh := 30 + (20+32*72/96)*len(moreinfo) + 30 - 20 95 | 96 | basicstate, err := basicstate() 97 | if err != nil { 98 | return 99 | } 100 | 101 | dldata := (*[]byte)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&bgdata)))) 102 | if dldata == (*[]byte)(nil) || uintptr(time.Since(boottime).Hours()/24) >= atomic.LoadUintptr(&bgcount) { 103 | pic, err1 := setu.DefaultPool.Roll("") 104 | if err1 != nil { 105 | return nil, err1 106 | } 107 | data, err1 := os.ReadFile(pic) 108 | if err1 != nil { 109 | return nil, err1 110 | } 111 | atomic.AddUintptr(&bgcount, 1) 112 | atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&bgdata)), unsafe.Pointer(&data)) 113 | dldata = &data 114 | } 115 | data := *dldata 116 | 117 | back, _, err := image.Decode(bytes.NewReader(data)) 118 | if err != nil { 119 | return 120 | } 121 | 122 | data, err = web.GetData(botavartarurl) 123 | if err != nil { 124 | return 125 | } 126 | avatar, _, err := image.Decode(bytes.NewReader(data)) 127 | if err != nil { 128 | return 129 | } 130 | avatarf := imgfactory.Size(avatar, 200, 200) 131 | 132 | fontbyte, err := file.GetLazyData(text.GlowSansFontFile, nano.Md5File, true) 133 | if err != nil { 134 | return 135 | } 136 | 137 | canvas := gg.NewContext(1280, 70+250+40+380+diskcardh+40+moreinfocardh+40+70) 138 | 139 | bh, bw, ch, cw := float64(back.Bounds().Dy()), float64(back.Bounds().Dx()), float64(canvas.H()), float64(canvas.W()) 140 | 141 | if bh/bw < ch/cw { 142 | back = imgfactory.Size(back, int(bw*ch/bh), int(bh*ch/bh)).Image() 143 | canvas.DrawImageAnchored(back, canvas.W()/2, canvas.H()/2, 0.5, 0.5) 144 | } else { 145 | back = imgfactory.Size(back, int(bw*cw/bw), int(bh*cw/bw)).Image() 146 | canvas.DrawImage(back, 0, 0) 147 | } 148 | var blurback image.Image 149 | bwg := &sync.WaitGroup{} 150 | bwg.Add(1) 151 | go func() { 152 | defer bwg.Done() 153 | blurback = imaging.Blur(canvas.Image(), 8) 154 | }() 155 | 156 | if !isday { 157 | canvas.SetRGBA255(0, 0, 0, 50) 158 | canvas.DrawRectangle(0, 0, cw, ch) 159 | canvas.Fill() 160 | } 161 | 162 | wg := &sync.WaitGroup{} 163 | wg.Add(5) 164 | 165 | cardw := canvas.W() - 70 - 70 166 | 167 | titlecardh := 250 168 | basiccardh := 380 169 | 170 | var titleimg, basicimg, diskimg, moreinfoimg, shadowimg image.Image 171 | go func() { 172 | defer wg.Done() 173 | titlecard := gg.NewContext(cardw, titlecardh) 174 | bwg.Wait() 175 | titlecard.DrawImage(blurback, -70, -70) 176 | 177 | titlecard.DrawRoundedRectangle(1, 1, float64(titlecard.W()-1*2), float64(titlecardh-1*2), 16) 178 | titlecard.SetLineWidth(3) 179 | titlecard.SetColor(colorswitch(100)) 180 | titlecard.StrokePreserve() 181 | titlecard.SetColor(colorswitch(140)) 182 | titlecard.Fill() 183 | 184 | titlecard.DrawImage(avatarf.Circle(0).Image(), (titlecardh-avatarf.H())/2, (titlecardh-avatarf.H())/2) 185 | 186 | err = titlecard.ParseFontFace(fontbyte, 72) 187 | if err != nil { 188 | return 189 | } 190 | fw, _ := titlecard.MeasureString(botname) 191 | 192 | titlecard.SetColor(fontcolorswitch()) 193 | 194 | titlecard.DrawStringAnchored(botname, float64(titlecardh)+fw/2, float64(titlecardh)*0.5/2, 0.5, 0.5) 195 | 196 | err = titlecard.ParseFontFace(fontbyte, 24) 197 | if err != nil { 198 | return 199 | } 200 | titlecard.SetColor(fontcolorswitch()) 201 | 202 | titlecard.NewSubPath() 203 | titlecard.MoveTo(float64(titlecardh), float64(titlecardh)/2) 204 | titlecard.LineTo(float64(titlecard.W()-titlecardh), float64(titlecardh)/2) 205 | titlecard.Stroke() 206 | 207 | brt, err := botruntime() 208 | if err != nil { 209 | return 210 | } 211 | fw, _ = titlecard.MeasureString(brt) 212 | 213 | titlecard.DrawStringAnchored(brt, float64(titlecardh)+fw/2, float64(titlecardh)*(0.5+0.5/2), 0.5, 0.5) 214 | 215 | bs, err := botstatus() 216 | if err != nil { 217 | return 218 | } 219 | fw, _ = titlecard.MeasureString(bs) 220 | 221 | titlecard.DrawStringAnchored(bs, float64(titlecardh)+fw/2, float64(titlecardh)*(0.5+0.75/2), 0.5, 0.5) 222 | titleimg = rendercard.Fillet(titlecard.Image(), 16) 223 | }() 224 | go func() { 225 | defer wg.Done() 226 | basiccard := gg.NewContext(cardw, basiccardh) 227 | bwg.Wait() 228 | basiccard.DrawImage(blurback, -70, -70-titlecardh-40) 229 | 230 | basiccard.DrawRoundedRectangle(1, 1, float64(basiccard.W()-1*2), float64(basiccardh-1*2), 16) 231 | basiccard.SetLineWidth(3) 232 | basiccard.SetColor(colorswitch(100)) 233 | basiccard.StrokePreserve() 234 | basiccard.SetColor(colorswitch(140)) 235 | basiccard.Fill() 236 | 237 | bslen := len(basicstate) 238 | for i, v := range basicstate { 239 | offset := float64(i) * ((float64(basiccard.W())-200*float64(bslen))/float64(bslen+1) + 200) 240 | 241 | basiccard.SetRGBA255(57, 57, 57, 255) 242 | if isday { 243 | basiccard.SetRGBA255(235, 235, 235, 255) 244 | } 245 | basiccard.DrawCircle((float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200/2, 100) 246 | basiccard.Fill() 247 | 248 | colors := darkcolor 249 | if isday { 250 | colors = lightcolor 251 | } 252 | 253 | switch { 254 | case v.precent > 90: 255 | basiccard.SetColor(slice2color(colors[0])) 256 | case v.precent > 70: 257 | basiccard.SetColor(slice2color(colors[1])) 258 | default: 259 | basiccard.SetColor(slice2color(colors[2])) 260 | } 261 | 262 | basiccard.NewSubPath() 263 | basiccard.MoveTo((float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200/2) 264 | basiccard.DrawEllipticalArc((float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200/2, 100, 100, -0.5*math.Pi, -0.5*math.Pi+2*v.precent*0.01*math.Pi) 265 | basiccard.Fill() 266 | 267 | basiccard.SetColor(colorswitch(255)) 268 | basiccard.DrawCircle((float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200/2, 80) 269 | basiccard.Fill() 270 | 271 | err = basiccard.ParseFontFace(fontbyte, 42) 272 | if err != nil { 273 | return 274 | } 275 | 276 | basiccard.SetRGBA255(213, 213, 213, 255) 277 | basiccard.DrawStringAnchored(strconv.FormatFloat(v.precent, 'f', 0, 64)+"%", (float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200/2, 0.5, 0.5) 278 | 279 | basiccard.SetColor(fontcolorswitch()) 280 | 281 | _, fw := basiccard.MeasureString(v.name) 282 | basiccard.DrawStringAnchored(v.name, (float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200+15+basiccard.FontHeight()/2, 0.5, 0.5) 283 | 284 | err = basiccard.ParseFontFace(fontbyte, 20) 285 | if err != nil { 286 | return 287 | } 288 | basiccard.SetColor(fontcolorswitch()) 289 | 290 | textoffsety := basiccard.FontHeight() + 10 291 | for k, s := range v.text { 292 | basiccard.DrawStringAnchored(s, (float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200+15+fw+15+basiccard.FontHeight()/2+float64(k)*textoffsety, 0.5, 0.5) 293 | } 294 | } 295 | basicimg = rendercard.Fillet(basiccard.Image(), 16) 296 | }() 297 | go func() { 298 | defer wg.Done() 299 | diskcard := gg.NewContext(cardw, diskcardh) 300 | bwg.Wait() 301 | diskcard.DrawImage(blurback, -70, -70-titlecardh-40-basiccardh-40) 302 | 303 | diskcard.DrawRoundedRectangle(1, 1, float64(diskcard.W()-1*2), float64(diskcardh-1*2), 16) 304 | diskcard.SetLineWidth(3) 305 | diskcard.SetColor(colorswitch(100)) 306 | diskcard.StrokePreserve() 307 | diskcard.SetColor(colorswitch(140)) 308 | diskcard.Fill() 309 | 310 | err = diskcard.ParseFontFace(fontbyte, 32) 311 | if err != nil { 312 | return 313 | } 314 | 315 | dslen := len(diskstate) 316 | if dslen == 1 { 317 | diskcard.SetRGBA255(57, 57, 57, 255) 318 | if isday { 319 | diskcard.SetRGBA255(192, 192, 192, 255) 320 | } 321 | diskcard.DrawRoundedRectangle(40, 40, float64(diskcard.W())-40-100, 50, 12) 322 | diskcard.ClipPreserve() 323 | diskcard.Fill() 324 | 325 | colors := darkcolor 326 | if isday { 327 | colors = lightcolor 328 | } 329 | 330 | switch { 331 | case diskstate[0].precent > 90: 332 | diskcard.SetColor(slice2color(colors[0])) 333 | case diskstate[0].precent > 70: 334 | diskcard.SetColor(slice2color(colors[1])) 335 | default: 336 | diskcard.SetColor(slice2color(colors[2])) 337 | } 338 | 339 | diskcard.DrawRoundedRectangle(40, 40, (float64(diskcard.W())-40-100)*diskstate[0].precent*0.01, 50, 12) 340 | diskcard.Fill() 341 | diskcard.ResetClip() 342 | 343 | diskcard.SetColor(fontcolorswitch()) 344 | 345 | fw, _ := diskcard.MeasureString(diskstate[0].name) 346 | fw1, _ := diskcard.MeasureString(diskstate[0].text[0]) 347 | 348 | diskcard.DrawStringAnchored(diskstate[0].name, 40+10+fw/2, 40+50/2, 0.5, 0.5) 349 | diskcard.DrawStringAnchored(diskstate[0].text[0], (float64(diskcard.W())-100-10)-fw1/2, 40+50/2, 0.5, 0.5) 350 | diskcard.DrawStringAnchored(strconv.FormatFloat(diskstate[0].precent, 'f', 0, 64)+"%", float64(diskcard.W())-100/2, 40+50/2, 0.5, 0.5) 351 | } else { 352 | for i, v := range diskstate { 353 | offset := float64(i)*(50+20) - 20 354 | 355 | diskcard.SetRGBA255(57, 57, 57, 255) 356 | if isday { 357 | diskcard.SetRGBA255(192, 192, 192, 255) 358 | } 359 | 360 | diskcard.DrawRoundedRectangle(40, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+offset, float64(diskcard.W())-40-100, 50, 12) 361 | diskcard.Fill() 362 | 363 | colors := darkcolor 364 | if isday { 365 | colors = lightcolor 366 | } 367 | 368 | switch { 369 | case v.precent > 90: 370 | diskcard.SetColor(slice2color(colors[0])) 371 | case v.precent > 70: 372 | diskcard.SetColor(slice2color(colors[1])) 373 | default: 374 | diskcard.SetColor(slice2color(colors[2])) 375 | } 376 | 377 | diskcard.DrawRoundedRectangle(40, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+offset, (float64(diskcard.W())-40-100)*v.precent*0.01, 50, 12) 378 | diskcard.Fill() 379 | 380 | diskcard.SetColor(fontcolorswitch()) 381 | 382 | fw, _ := diskcard.MeasureString(v.name) 383 | fw1, _ := diskcard.MeasureString(v.text[0]) 384 | 385 | diskcard.DrawStringAnchored(v.name, 40+10+fw/2, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+50/2+offset, 0.5, 0.5) 386 | diskcard.DrawStringAnchored(v.text[0], (float64(diskcard.W())-100-10)-fw1/2, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+50/2+offset, 0.5, 0.5) 387 | diskcard.DrawStringAnchored(strconv.FormatFloat(v.precent, 'f', 0, 64)+"%", float64(diskcard.W())-100/2, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+50/2+offset, 0.5, 0.5) 388 | } 389 | } 390 | diskimg = rendercard.Fillet(diskcard.Image(), 16) 391 | }() 392 | go func() { 393 | defer wg.Done() 394 | moreinfocard := gg.NewContext(cardw, moreinfocardh) 395 | bwg.Wait() 396 | moreinfocard.DrawImage(blurback, -70, -70-titlecardh-40-basiccardh-40-diskcardh-40) 397 | 398 | moreinfocard.DrawRoundedRectangle(1, 1, float64(moreinfocard.W()-1*2), float64(moreinfocard.H()-1*2), 16) 399 | moreinfocard.SetLineWidth(3) 400 | moreinfocard.SetColor(colorswitch(100)) 401 | moreinfocard.StrokePreserve() 402 | moreinfocard.SetColor(colorswitch(140)) 403 | moreinfocard.Fill() 404 | 405 | err = moreinfocard.ParseFontFace(fontbyte, 32) 406 | if err != nil { 407 | return 408 | } 409 | 410 | milen := len(moreinfo) 411 | for i, v := range moreinfo { 412 | offset := float64(i)*(20+moreinfocard.FontHeight()) - 20 413 | 414 | moreinfocard.SetColor(fontcolorswitch()) 415 | 416 | fw, _ := moreinfocard.MeasureString(v.name) 417 | fw1, _ := moreinfocard.MeasureString(v.text[0]) 418 | 419 | moreinfocard.DrawStringAnchored(v.name, 20+fw/2, 30+(float64(moreinfocardh-30*2)-moreinfocard.FontHeight()*float64(milen))/float64(milen-1)+moreinfocard.FontHeight()/2+offset, 0.5, 0.5) 420 | moreinfocard.DrawStringAnchored(v.text[0], float64(moreinfocard.W())-20-fw1/2, 30+(float64(moreinfocardh-30*2)-moreinfocard.FontHeight()*float64(milen))/float64(milen-1)+moreinfocard.FontHeight()/2+offset, 0.5, 0.5) 421 | } 422 | moreinfoimg = rendercard.Fillet(moreinfocard.Image(), 16) 423 | }() 424 | go func() { 425 | defer wg.Done() 426 | shadow := gg.NewContext(canvas.W(), canvas.H()) 427 | shadow.SetRGBA255(0, 0, 0, 100) 428 | shadow.SetLineWidth(12) 429 | shadow.DrawRoundedRectangle(70, 70, float64(cardw), float64(titlecardh), 16) 430 | shadow.Stroke() 431 | shadow.DrawRoundedRectangle(70, float64(70+titlecardh+40), float64(cardw), float64(basiccardh), 16) 432 | shadow.Stroke() 433 | shadow.DrawRoundedRectangle(70, float64(70+titlecardh+40+basiccardh+40), float64(cardw), float64(diskcardh), 16) 434 | shadow.Stroke() 435 | shadow.DrawRoundedRectangle(70, float64(70+titlecardh+40+basiccardh+40+diskcardh+40), float64(cardw), float64(moreinfocardh), 16) 436 | shadow.Stroke() 437 | shadowimg = imaging.Blur(shadow.Image(), 24) 438 | }() 439 | 440 | wg.Wait() 441 | if shadowimg == nil || titleimg == nil || basicimg == nil || diskimg == nil || moreinfoimg == nil { 442 | err = errors.New("图片渲染失败") 443 | return 444 | } 445 | canvas.DrawImage(shadowimg, 0, 0) 446 | canvas.DrawImage(titleimg, 70, 70) 447 | canvas.DrawImage(basicimg, 70, 70+titlecardh+40) 448 | canvas.DrawImage(diskimg, 70, 70+titlecardh+40+basiccardh+40) 449 | canvas.DrawImage(moreinfoimg, 70, 70+titlecardh+40+basiccardh+40+diskcardh+40) 450 | 451 | err = canvas.ParseFontFace(fontbyte, 28) 452 | if err != nil { 453 | return 454 | } 455 | canvas.SetRGBA255(0, 0, 0, 255) 456 | canvas.DrawStringAnchored("Created By NanoBot-Plugin "+banner.Version, float64(canvas.W())/2+3, float64(canvas.H())-70/2+3, 0.5, 0.5) 457 | canvas.SetRGBA255(255, 255, 255, 255) 458 | canvas.DrawStringAnchored("Created By NanoBot-Plugin "+banner.Version, float64(canvas.W())/2, float64(canvas.H())-70/2, 0.5, 0.5) 459 | 460 | sendimg = canvas.Image() 461 | return 462 | } 463 | 464 | func botruntime() (string, error) { 465 | hostinfo, err := host.Info() 466 | if err != nil { 467 | return "", err 468 | } 469 | t := &strings.Builder{} 470 | t.WriteString("NanoBot-Plugin 已运行 ") 471 | t.WriteString(strconv.FormatInt((time.Now().Unix()-boottime.Unix())/86400, 10)) 472 | t.WriteString(" 天 ") 473 | t.WriteString(time.Unix(time.Now().Unix()-boottime.Unix(), 0).UTC().Format("15:04:05")) 474 | t.WriteString(" | 系统运行 ") 475 | t.WriteString(strconv.FormatInt(int64(hostinfo.Uptime)/86400, 10)) 476 | t.WriteString(" 天 ") 477 | t.WriteString(time.Unix(int64(hostinfo.Uptime), 0).UTC().Format("15:04:05")) 478 | return t.String(), nil 479 | } 480 | 481 | func botstatus() (string, error) { 482 | hostinfo, err := host.Info() 483 | if err != nil { 484 | return "", err 485 | } 486 | t := &strings.Builder{} 487 | t.WriteString(time.Now().Format("2006-01-02 15:04:05")) 488 | t.WriteString(" | Compiled by ") 489 | t.WriteString(runtime.Version()) 490 | t.WriteString(" | ") 491 | t.WriteString(cases.Title(language.English).String(hostinfo.OS)) 492 | t.WriteString(" ") 493 | t.WriteString(runtime.GOARCH) 494 | return t.String(), nil 495 | } 496 | 497 | type status struct { 498 | precent float64 499 | name string 500 | text []string 501 | } 502 | 503 | func basicstate() (stateinfo [3]*status, err error) { 504 | percent, err := cpu.Percent(time.Second, true) 505 | if err != nil { 506 | return 507 | } 508 | cpuinfo, err := cpu.Info() 509 | if err != nil { 510 | return 511 | } 512 | cpucore, err := cpu.Counts(false) 513 | if err != nil { 514 | return 515 | } 516 | cputhread, err := cpu.Counts(true) 517 | if err != nil { 518 | return 519 | } 520 | cores := strconv.Itoa(cpucore) + "C" + strconv.Itoa(cputhread) + "T" 521 | times := "最大 " + strconv.FormatFloat(cpuinfo[0].Mhz/1000, 'f', 1, 64) + "Ghz" 522 | 523 | stateinfo[0] = &status{ 524 | precent: math.Round(percent[0]), 525 | name: "CPU", 526 | text: []string{cores, times}, 527 | } 528 | 529 | raminfo, err := mem.VirtualMemory() 530 | if err != nil { 531 | return 532 | } 533 | total := "总共 " + storagefmt(float64(raminfo.Total)) 534 | used := "已用 " + storagefmt(float64(raminfo.Used)) 535 | free := "剩余 " + storagefmt(float64(raminfo.Free)) 536 | 537 | stateinfo[1] = &status{ 538 | precent: math.Round(raminfo.UsedPercent), 539 | name: "RAM", 540 | text: []string{total, used, free}, 541 | } 542 | 543 | swapinfo, err := mem.SwapMemory() 544 | if err != nil { 545 | return 546 | } 547 | total = "总共 " + storagefmt(float64(swapinfo.Total)) 548 | used = "已用 " + storagefmt(float64(swapinfo.Used)) 549 | free = "剩余 " + storagefmt(float64(swapinfo.Free)) 550 | 551 | stateinfo[2] = &status{ 552 | precent: math.Round(swapinfo.UsedPercent), 553 | name: "SWAP", 554 | text: []string{total, used, free}, 555 | } 556 | return 557 | } 558 | 559 | func storagefmt(num float64) string { 560 | if num /= 1024; num < 1 { 561 | return strconv.FormatFloat(num*1024, 'f', 2, 64) + "B" 562 | } 563 | if num /= 1024; num < 1 { 564 | return strconv.FormatFloat(num*1024, 'f', 2, 64) + "KB" 565 | } 566 | if num /= 1024; num < 1 { 567 | return strconv.FormatFloat(num*1024, 'f', 2, 64) + "MB" 568 | } 569 | if num /= 1024; num < 1 { 570 | return strconv.FormatFloat(num*1024, 'f', 2, 64) + "GB" 571 | } 572 | return strconv.FormatFloat(num, 'f', 2, 64) + "TB" 573 | } 574 | 575 | func diskstate() (stateinfo []*status, err error) { 576 | parts, err := disk.Partitions(false) 577 | if err != nil { 578 | return 579 | } 580 | stateinfo = make([]*status, 0, len(parts)) 581 | for _, v := range parts { 582 | mp := v.Mountpoint 583 | if strings.HasPrefix(mp, "/snap/") || strings.HasPrefix(mp, "/apex/") { 584 | continue 585 | } 586 | diskusage, err := disk.Usage(mp) 587 | if err != nil { 588 | continue 589 | } 590 | stateinfo = append(stateinfo, &status{ 591 | precent: math.Round(diskusage.UsedPercent), 592 | name: mp, 593 | text: []string{storagefmt(float64(diskusage.Used)) + " / " + storagefmt(float64(diskusage.Total))}, 594 | }) 595 | } 596 | return stateinfo, nil 597 | } 598 | 599 | func moreinfo(m *ctrl.Control[*nano.Ctx]) (stateinfo []*status, err error) { 600 | var mems runtime.MemStats 601 | runtime.ReadMemStats(&mems) 602 | fmtmem := storagefmt(float64(mems.Sys)) 603 | 604 | hostinfo, err := host.Info() 605 | if err != nil { 606 | return 607 | } 608 | cpuinfo, err := cpu.Info() 609 | if err != nil { 610 | return 611 | } 612 | 613 | count := len(m.Manager.M) 614 | stateinfo = []*status{ 615 | {name: "OS", text: []string{hostinfo.Platform}}, 616 | {name: "CPU", text: []string{strings.TrimSpace(cpuinfo[0].ModelName)}}, 617 | {name: "Version", text: []string{hostinfo.PlatformVersion}}, 618 | {name: "Plugin", text: []string{"共 " + strconv.Itoa(count) + " 个"}}, 619 | {name: "Memory", text: []string{"已用 " + fmtmem}}, 620 | } 621 | return 622 | } 623 | 624 | func colorswitch(a uint8) color.Color { 625 | if isday { 626 | return color.NRGBA{255, 255, 255, a} 627 | } 628 | return color.NRGBA{0, 0, 0, a} 629 | } 630 | 631 | func fontcolorswitch() color.Color { 632 | if isday { 633 | return color.NRGBA{30, 30, 30, 255} 634 | } 635 | return color.NRGBA{235, 235, 235, 255} 636 | } 637 | 638 | func slice2color(c [4]uint8) color.Color { 639 | return color.NRGBA{c[0], c[1], c[2], c[3]} 640 | } 641 | -------------------------------------------------------------------------------- /plugin/tarot/main.go: -------------------------------------------------------------------------------- 1 | // Package tarot 塔罗牌 2 | package tarot 3 | 4 | import ( 5 | "encoding/json" 6 | "math/rand" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | fcext "github.com/FloatTech/floatbox/ctxext" 12 | "github.com/FloatTech/floatbox/file" 13 | "github.com/FloatTech/floatbox/process" 14 | "github.com/FloatTech/floatbox/web" 15 | "github.com/FloatTech/imgfactory" 16 | ctrl "github.com/FloatTech/zbpctrl" 17 | 18 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 19 | "github.com/FloatTech/zbputils/img/text" 20 | nano "github.com/fumiama/NanoBot" 21 | "github.com/sirupsen/logrus" 22 | ) 23 | 24 | const bed = "https://gitcode.net/shudorcl/zbp-tarot/-/raw/master/" 25 | 26 | type cardInfo struct { 27 | Description string `json:"description"` 28 | ReverseDescription string `json:"reverseDescription"` 29 | ImgURL string `json:"imgUrl"` 30 | } 31 | type card struct { 32 | Name string `json:"name"` 33 | cardInfo `json:"info"` 34 | } 35 | 36 | type formation struct { 37 | CardsNum int `json:"cards_num"` 38 | IsCut bool `json:"is_cut"` 39 | Represent [][]string `json:"represent"` 40 | } 41 | type cardSet = map[string]card 42 | 43 | var ( 44 | cardMap = make(cardSet, 80) 45 | infoMap = make(map[string]cardInfo, 80) 46 | formationMap = make(map[string]formation, 10) 47 | majorArcanaName = make([]string, 0, 80) 48 | formationName = make([]string, 0, 10) 49 | ) 50 | 51 | func init() { 52 | engine := nano.Register("tarot", &ctrl.Options[*nano.Ctx]{ 53 | DisableOnDefault: false, 54 | Brief: "塔罗牌", 55 | Help: "- 抽[塔罗牌|大阿卡纳|小阿卡纳]\n" + 56 | "- 解塔罗牌[牌名]", 57 | PublicDataFolder: "Tarot", 58 | }).ApplySingle(ctxext.DefaultSingle) 59 | 60 | cache := engine.DataFolder() + "cache" 61 | _ = os.RemoveAll(cache) 62 | err := os.MkdirAll(cache, 0755) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | getTarot := fcext.DoOnceOnSuccess(func(ctx *nano.Ctx) bool { 68 | data, err := engine.GetLazyData("tarots.json", true) 69 | if err != nil { 70 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 71 | return false 72 | } 73 | err = json.Unmarshal(data, &cardMap) 74 | if err != nil { 75 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 76 | return false 77 | } 78 | for _, card := range cardMap { 79 | infoMap[card.Name] = card.cardInfo 80 | } 81 | for i := 0; i < 22; i++ { 82 | majorArcanaName = append(majorArcanaName, cardMap[strconv.Itoa(i)].Name) 83 | } 84 | logrus.Infof("[tarot]读取%d张塔罗牌", len(cardMap)) 85 | formation, err := engine.GetLazyData("formation.json", true) 86 | if err != nil { 87 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 88 | return false 89 | } 90 | err = json.Unmarshal(formation, &formationMap) 91 | if err != nil { 92 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 93 | return false 94 | } 95 | for k := range formationMap { 96 | formationName = append(formationName, k) 97 | } 98 | logrus.Infof("[tarot]读取%d组塔罗牌阵", len(formationMap)) 99 | return true 100 | }) 101 | engine.OnMessageRegex(`^抽((塔罗牌|大阿(尔)?卡纳)|小阿(尔)?卡纳)$`, getTarot).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *nano.Ctx) { 102 | cardType := ctx.State["regex_matched"].([]string)[1] 103 | 104 | reasons := [...]string{"您抽到的是~\n", "锵锵锵,塔罗牌的预言是~\n", "诶,让我看看您抽到了~\n"} 105 | position := [...]string{"『正位』", "『逆位』"} 106 | reverse := [...]string{"", "Reverse/"} 107 | start := 0 108 | length := 22 109 | if strings.Contains(cardType, "小") { 110 | start = 22 111 | length = 55 112 | } 113 | 114 | i := rand.Intn(length) + start 115 | p := rand.Intn(2) 116 | card := cardMap[strconv.Itoa(i)] 117 | name := card.Name 118 | description := card.Description 119 | if p == 1 { 120 | description = card.ReverseDescription 121 | } 122 | imgurl := bed + reverse[p] + card.ImgURL 123 | imgname := "" 124 | if p == 1 { 125 | imgname = reverse[p][:len(reverse[p])-1] + name 126 | } else { 127 | imgname = name 128 | } 129 | imgpath := cache + "/" + imgname + ".png" 130 | data, err := web.RequestDataWith(web.NewTLS12Client(), imgurl, "GET", "gitcode.net", web.RandUA(), nil) 131 | if err != nil { 132 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 133 | return 134 | } 135 | f, err := os.Create(imgpath) 136 | if err != nil { 137 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 138 | return 139 | } 140 | defer f.Close() 141 | err = os.WriteFile(f.Name(), data, 0755) 142 | if err != nil { 143 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 144 | return 145 | } 146 | process.SleepAbout1sTo2s() 147 | _, err = ctx.SendImage("file:///"+file.BOTPATH+"/"+imgpath, false, reasons[rand.Intn(len(reasons))], position[p], "的『", name, "』\n其释义为: ", description) 148 | if err != nil { 149 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 150 | } 151 | 152 | }) 153 | 154 | engine.OnMessageRegex(`^解塔罗牌\s?(.*)`, getTarot).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *nano.Ctx) { 155 | match := ctx.State["regex_matched"].([]string)[1] 156 | info, ok := infoMap[match] 157 | if ok { 158 | imgpath := cache + "/" + match + ".png" 159 | if file.IsNotExist(imgpath) { 160 | imgurl := bed + info.ImgURL 161 | data, err := web.RequestDataWith(web.NewTLS12Client(), imgurl, "GET", "gitcode.net", web.RandUA(), nil) 162 | if err != nil { 163 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 164 | return 165 | } 166 | f, err := os.Create(imgpath) 167 | if err != nil { 168 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 169 | return 170 | } 171 | defer f.Close() 172 | err = os.WriteFile(f.Name(), data, 0755) 173 | if err != nil { 174 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 175 | return 176 | } 177 | process.SleepAbout1sTo2s() 178 | } 179 | _, err = ctx.SendImage("file:///"+file.BOTPATH+"/"+imgpath, false, match, "的含义是~\n『正位』:", info.Description, "\n『逆位』:", info.ReverseDescription) 180 | if err != nil { 181 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 182 | } 183 | return 184 | } 185 | var build strings.Builder 186 | build.WriteString("塔罗牌列表\n大阿尔卡纳:\n") 187 | build.WriteString(strings.Join(majorArcanaName[:7], " ")) 188 | build.WriteString("\n") 189 | build.WriteString(strings.Join(majorArcanaName[7:14], " ")) 190 | build.WriteString("\n") 191 | build.WriteString(strings.Join(majorArcanaName[14:22], " ")) 192 | build.WriteString("\n小阿尔卡纳:\n[圣杯|星币|宝剑|权杖] [0-10|侍从|骑士|王后|国王]") 193 | txt := build.String() 194 | cardList, err := text.Render(txt, text.FontFile, 420, 20) 195 | if err != nil { 196 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 197 | return 198 | } 199 | data, err := imgfactory.ToBytes(cardList) 200 | if err != nil { 201 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 202 | return 203 | } 204 | _, err = ctx.SendImageBytes(data, false, "没有找到", match, "噢~") 205 | if err != nil { 206 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 207 | } 208 | }) 209 | } 210 | -------------------------------------------------------------------------------- /plugin/wife/main.go: -------------------------------------------------------------------------------- 1 | // Package wife 抽老婆 2 | package wife 3 | 4 | import ( 5 | "encoding/json" 6 | "os" 7 | "strings" 8 | 9 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 10 | fcext "github.com/FloatTech/floatbox/ctxext" 11 | ctrl "github.com/FloatTech/zbpctrl" 12 | nano "github.com/fumiama/NanoBot" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | func init() { 17 | engine := nano.Register("wife", &ctrl.Options[*nano.Ctx]{ 18 | DisableOnDefault: false, 19 | Help: "- 抽老婆", 20 | Brief: "从老婆库抽每日老婆", 21 | PublicDataFolder: "Wife", 22 | }).ApplySingle(ctxext.DefaultSingle) 23 | _ = os.MkdirAll(engine.DataFolder()+"wives", 0755) 24 | cards := []string{} 25 | engine.OnMessageFullMatch("抽老婆", fcext.DoOnceOnSuccess( 26 | func(ctx *nano.Ctx) bool { 27 | data, err := engine.GetLazyData("wife.json", true) 28 | if err != nil { 29 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 30 | return false 31 | } 32 | err = json.Unmarshal(data, &cards) 33 | if err != nil { 34 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 35 | return false 36 | } 37 | logrus.Infof("[wife]加载%d个老婆", len(cards)) 38 | return true 39 | }, 40 | )).SetBlock(true). 41 | Handle(func(ctx *nano.Ctx) { 42 | uid := ctx.Message.Author.ID 43 | if uid == "" { 44 | _, _ = ctx.SendPlainMessage(false, "ERROR: 未获取到用户") 45 | return 46 | } 47 | uidint := int64(ctx.UserID()) 48 | card := cards[fcext.RandSenderPerDayN(uidint, len(cards))] 49 | data, err := engine.GetLazyData("wives/"+card, true) 50 | card, _, _ = strings.Cut(card, ".") 51 | if err != nil { 52 | if nano.OnlyQQ(ctx) { 53 | _, err = ctx.SendChain(nano.Text("今天的二次元老婆是~【", card, "】哒\n【图片下载失败: ", err, "】")) 54 | } else { 55 | _, err = ctx.SendChain(nano.At(uid), nano.Text("今天的二次元老婆是~【", card, "】哒\n【图片下载失败: ", err, "】")) 56 | } 57 | if err != nil { 58 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 59 | } 60 | return 61 | } 62 | if nano.OnlyQQ(ctx) { 63 | _, err = ctx.SendChain(nano.Text("今天的二次元老婆是~【", card, "】哒"), nano.ImageBytes(data)) 64 | } else { 65 | _, err = ctx.SendChain(nano.At(uid), nano.Text("今天的二次元老婆是~【", card, "】哒"), nano.ImageBytes(data)) 66 | } 67 | if err != nil { 68 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 69 | } 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /plugin/wordle/main.go: -------------------------------------------------------------------------------- 1 | // Package wordle 猜单词 2 | package wordle 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "image/color" 8 | "math/rand" 9 | "sort" 10 | "strings" 11 | "sync" 12 | "sync/atomic" 13 | "time" 14 | 15 | "github.com/FloatTech/AnimeAPI/tl" 16 | "github.com/FloatTech/imgfactory" 17 | 18 | "github.com/FloatTech/NanoBot-Plugin/utils/ctxext" 19 | fcext "github.com/FloatTech/floatbox/ctxext" 20 | "github.com/FloatTech/gg" 21 | ctrl "github.com/FloatTech/zbpctrl" 22 | nano "github.com/fumiama/NanoBot" 23 | ) 24 | 25 | var ( 26 | errLengthNotEnough = errors.New("length not enough") 27 | errUnknownWord = errors.New("unknown word") 28 | errTimesRunOut = errors.New("times run out") 29 | ) 30 | 31 | const ( 32 | match = iota 33 | exist 34 | notexist 35 | undone 36 | ) 37 | 38 | var colors = [...]color.RGBA{ 39 | {125, 166, 108, 255}, 40 | {199, 183, 96, 255}, 41 | {123, 123, 123, 255}, 42 | {219, 219, 219, 255}, 43 | } 44 | 45 | var classdict = map[string]int{ 46 | "": 5, 47 | "五阶": 5, 48 | "六阶": 6, 49 | "七阶": 7, 50 | } 51 | 52 | type dictionary map[int]struct { 53 | dict []string 54 | cet4 []string 55 | } 56 | 57 | var words = make(dictionary) 58 | 59 | func init() { 60 | en := nano.Register("wordle", &ctrl.Options[*nano.Ctx]{ 61 | DisableOnDefault: false, 62 | Brief: "猜单词", 63 | Help: "- 个人猜单词\n" + 64 | "- 团队猜单词\n" + 65 | "- 团队六阶猜单词\n" + 66 | "- 团队七阶猜单词", 67 | PublicDataFolder: "Wordle", 68 | }).ApplySingle(nano.NewSingle( 69 | nano.WithKeyFn(func(ctx *nano.Ctx) int64 { 70 | return int64(ctx.GroupID()) 71 | }), 72 | nano.WithPostFn[int64](func(ctx *nano.Ctx) { 73 | _, _ = ctx.SendPlainMessage(true, "已经有正在进行的游戏了") 74 | }), 75 | )) 76 | 77 | en.OnMessageRegex(`^(个人|团队)(五阶|六阶|七阶)?猜单词$`, fcext.DoOnceOnSuccess( 78 | func(ctx *nano.Ctx) bool { 79 | var errcnt uint32 80 | var wg sync.WaitGroup 81 | var mu sync.Mutex 82 | for i := 5; i <= 7; i++ { 83 | wg.Add(2) 84 | go func(i int) { 85 | defer wg.Done() 86 | dc, err := en.GetLazyData(fmt.Sprintf("cet-4_%d.txt", i), true) 87 | if err != nil { 88 | atomic.AddUint32(&errcnt, 1) 89 | return 90 | } 91 | c := strings.Split(nano.BytesToString(dc), "\n") 92 | sort.Strings(c) 93 | mu.Lock() 94 | tmp := words[i] 95 | tmp.cet4 = c 96 | words[i] = tmp 97 | mu.Unlock() 98 | }(i) 99 | go func(i int) { 100 | defer wg.Done() 101 | dd, err := en.GetLazyData(fmt.Sprintf("dict_%d.txt", i), true) 102 | if err != nil { 103 | atomic.AddUint32(&errcnt, 1) 104 | return 105 | } 106 | d := strings.Split(nano.BytesToString(dd), "\n") 107 | sort.Strings(d) 108 | mu.Lock() 109 | tmp := words[i] 110 | tmp.dict = d 111 | words[i] = tmp 112 | mu.Unlock() 113 | }(i) 114 | } 115 | wg.Wait() 116 | if errcnt > 0 { 117 | _, _ = ctx.SendPlainMessage(false, "ERROR: 下载字典时发生", errcnt, "个错误") 118 | return false 119 | } 120 | return true 121 | }, 122 | )).SetBlock(true).Limit(ctxext.LimitByUser). 123 | Handle(func(ctx *nano.Ctx) { 124 | class := classdict[ctx.State["regex_matched"].([]string)[2]] 125 | target := words[class].cet4[rand.Intn(len(words[class].cet4))] 126 | tt, err := tl.Translate(target) 127 | if err != nil { 128 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 129 | return 130 | } 131 | game := newWordleGame(target) 132 | _, img, _ := game("") 133 | _, err = ctx.SendImageBytes(img, true, "你有", class+1, "次机会猜出单词,单词长度为", class, ",请发送单词") 134 | if err != nil { 135 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 136 | return 137 | } 138 | var next *nano.FutureEvent 139 | if ctx.State["regex_matched"].([]string)[1] == "团队" && !nano.OnlyPrivate(ctx) { 140 | next = nano.NewFutureEvent("Message", 999, false, nano.RegexRule(fmt.Sprintf(`^([A-Z]|[a-z]){%d}$`, class)), 141 | nano.OnlyPublic, nano.CheckChannel(ctx.Message.ChannelID)) 142 | } else { 143 | next = nano.NewFutureEvent("Message", 999, false, nano.RegexRule(fmt.Sprintf(`^([A-Z]|[a-z]){%d}$`, class)), 144 | ctx.CheckSession()) 145 | } 146 | var win bool 147 | recv, cancel := next.Repeat() 148 | defer cancel() 149 | tick := time.NewTimer(105 * time.Second) 150 | after := time.NewTimer(120 * time.Second) 151 | for { 152 | select { 153 | case <-tick.C: 154 | _, err := ctx.SendPlainMessage(false, "猜单词, 你还有15s作答时间") 155 | if err != nil { 156 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 157 | return 158 | } 159 | case <-after.C: 160 | _, err := ctx.SendPlainMessage(false, "猜单词超时,游戏结束...答案是: ", target, "(", tt, ")") 161 | if err != nil { 162 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 163 | } 164 | return 165 | case c := <-recv: 166 | tick.Reset(105 * time.Second) 167 | after.Reset(120 * time.Second) 168 | win, img, err = game(strings.TrimSpace(c.Message.Content)) 169 | switch { 170 | case win: 171 | tick.Stop() 172 | after.Stop() 173 | _, err := c.SendImageBytes(img, true, "太棒了,你猜出来了!答案是: ", target, "(", tt, ")") 174 | if err != nil { 175 | _, _ = c.SendPlainMessage(false, "ERROR: ", err) 176 | } 177 | return 178 | case err == errTimesRunOut: 179 | tick.Stop() 180 | after.Stop() 181 | _, err := c.SendPlainMessage(false, "游戏结束...答案是: ", target, "(", tt, ")") 182 | if err != nil { 183 | _, _ = c.SendPlainMessage(false, "ERROR: ", err) 184 | } 185 | return 186 | case err == errLengthNotEnough: 187 | _, err := c.SendPlainMessage(true, "单词长度错误") 188 | if err != nil { 189 | _, _ = c.SendPlainMessage(false, "ERROR: ", err) 190 | return 191 | } 192 | case err == errUnknownWord: 193 | _, err := c.SendPlainMessage(true, "你确定存在这样的单词吗?") 194 | if err != nil { 195 | _, _ = c.SendPlainMessage(false, "ERROR: ", err) 196 | return 197 | } 198 | default: 199 | _, err := c.SendImageBytes(img, true) 200 | if err != nil { 201 | _, _ = c.SendPlainMessage(false, "ERROR: ", err) 202 | return 203 | } 204 | } 205 | } 206 | } 207 | }) 208 | } 209 | 210 | func newWordleGame(target string) func(string) (bool, []byte, error) { 211 | var class = len(target) 212 | record := make([]string, 0, len(target)+1) 213 | return func(s string) (win bool, data []byte, err error) { 214 | if s != "" { 215 | s = strings.ToLower(s) 216 | if target == s { 217 | win = true 218 | } else { 219 | if len(s) != len(target) { 220 | err = errLengthNotEnough 221 | return 222 | } 223 | i := sort.SearchStrings(words[class].dict, s) 224 | if i >= len(words[class].dict) || words[class].dict[i] != s { 225 | err = errUnknownWord 226 | return 227 | } 228 | } 229 | record = append(record, s) 230 | if len(record) >= cap(record) { 231 | err = errTimesRunOut 232 | return 233 | } 234 | } 235 | var side = 20 236 | var space = 10 237 | ctx := gg.NewContext((side+4)*class+space*2-4, (side+4)*(class+1)+space*2-4) 238 | ctx.SetColor(color.RGBA{255, 255, 255, 255}) 239 | ctx.Clear() 240 | for i := 0; i < class+1; i++ { 241 | for j := 0; j < class; j++ { 242 | if len(record) > i { 243 | ctx.DrawRectangle(float64(space+j*(side+4)), float64(space+i*(side+4)), float64(side), float64(side)) 244 | switch { 245 | case record[i][j] == target[j]: 246 | ctx.SetColor(colors[match]) 247 | case strings.IndexByte(target, record[i][j]) != -1: 248 | ctx.SetColor(colors[exist]) 249 | default: 250 | ctx.SetColor(colors[notexist]) 251 | } 252 | ctx.Fill() 253 | ctx.SetColor(color.RGBA{255, 255, 255, 255}) 254 | ctx.DrawString(strings.ToUpper(string(record[i][j])), float64(10+j*(side+4)+7), float64(10+i*(side+4)+15)) 255 | } else { 256 | ctx.DrawRectangle(float64(10+j*(side+4)+1), float64(10+i*(side+4)+1), float64(side-2), float64(side-2)) 257 | ctx.SetLineWidth(1) 258 | ctx.SetColor(colors[undone]) 259 | ctx.Stroke() 260 | } 261 | } 262 | } 263 | data, err = imgfactory.ToBytes(ctx.Image()) 264 | return 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /utils/ctxext/speed.go: -------------------------------------------------------------------------------- 1 | // Package ctxext ctx扩展 2 | package ctxext 3 | 4 | import ( 5 | "strconv" 6 | "time" 7 | "unsafe" 8 | 9 | nano "github.com/fumiama/NanoBot" 10 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 11 | ) 12 | 13 | // DefaultSingle 默认反并发处理 14 | // 15 | // 按 发送者 反并发 16 | // 并发时返回 "您有操作正在执行, 请稍后再试!" 17 | var DefaultSingle = nano.NewSingle( 18 | nano.WithKeyFn(func(ctx *nano.Ctx) int64 { 19 | switch ctx.Value.(type) { 20 | case *nano.Message: 21 | return int64(ctx.UserID()) 22 | } 23 | return 0 24 | }), 25 | nano.WithPostFn[int64](func(ctx *nano.Ctx) { 26 | _, _ = ctx.SendPlainMessage(false, "您有操作正在执行, 请稍后再试!") 27 | }), 28 | ) 29 | 30 | // defaultLimiterManager 默认限速器管理 31 | // 32 | // 每 10s 5次触发 33 | var defaultLimiterManager = rate.NewManager[int64](time.Second*10, 5) 34 | 35 | //nolint:structcheck 36 | type fakeLM struct { 37 | limiters unsafe.Pointer 38 | interval time.Duration 39 | burst int 40 | } 41 | 42 | // SetDefaultLimiterManagerParam 设置默认限速器参数 43 | // 44 | // 每 interval 时间 burst 次触发 45 | func SetDefaultLimiterManagerParam(interval time.Duration, burst int) { 46 | f := (*fakeLM)(unsafe.Pointer(defaultLimiterManager)) 47 | f.interval = interval 48 | f.burst = burst 49 | } 50 | 51 | // LimitByUser 默认限速器 每 10s 5次触发 52 | // 53 | // 按 发送者 限制 54 | func LimitByUser(ctx *nano.Ctx) *rate.Limiter { 55 | if _, ok := ctx.Value.(*nano.Message); ok { 56 | return defaultLimiterManager.Load(int64(ctx.UserID())) 57 | } 58 | return defaultLimiterManager.Load(0) 59 | } 60 | 61 | // LimitByGroup 默认限速器 每 10s 5次触发 62 | // 63 | // 按 group 限制 64 | func LimitByGroup(ctx *nano.Ctx) *rate.Limiter { 65 | if _, ok := ctx.Value.(*nano.Message); ok { 66 | return defaultLimiterManager.Load(int64(ctx.GroupID())) 67 | } 68 | return defaultLimiterManager.Load(0) 69 | } 70 | 71 | // LimitByGuild 默认限速器 每 10s 5次触发 72 | // 73 | // 按 guild 限制 74 | func LimitByGuild(ctx *nano.Ctx) *rate.Limiter { 75 | if msg, ok := ctx.Value.(*nano.Message); ok { 76 | id, _ := strconv.ParseUint(msg.GuildID, 10, 64) 77 | return defaultLimiterManager.Load(int64(id)) 78 | } 79 | return defaultLimiterManager.Load(0) 80 | } 81 | 82 | // LimitByChannel 默认限速器 每 10s 5次触发 83 | // 84 | // 按 channel 限制 85 | func LimitByChannel(ctx *nano.Ctx) *rate.Limiter { 86 | if _, ok := ctx.Value.(*nano.Message); ok { 87 | return defaultLimiterManager.Load(int64(ctx.GroupID())) 88 | } 89 | return defaultLimiterManager.Load(0) 90 | } 91 | 92 | // LimiterManager 自定义限速器管理 93 | type LimiterManager struct { 94 | m *rate.LimiterManager[int64] 95 | } 96 | 97 | // NewLimiterManager 新限速器管理 98 | func NewLimiterManager(interval time.Duration, burst int) (m LimiterManager) { 99 | m.m = rate.NewManager[int64](interval, burst) 100 | return 101 | } 102 | 103 | // LimitByUser 自定义限速器 104 | // 105 | // 按 发送者 限制 106 | func (m LimiterManager) LimitByUser(ctx *nano.Ctx) *rate.Limiter { 107 | if _, ok := ctx.Value.(*nano.Message); ok { 108 | return defaultLimiterManager.Load(int64(ctx.UserID())) 109 | } 110 | return defaultLimiterManager.Load(0) 111 | } 112 | 113 | // LimitByGuild 自定义限速器 114 | // 115 | // 按 guild 限制 116 | func (m LimiterManager) LimitByGuild(ctx *nano.Ctx) *rate.Limiter { 117 | if msg, ok := ctx.Value.(*nano.Message); ok { 118 | id, _ := strconv.ParseUint(msg.GuildID, 10, 64) 119 | return defaultLimiterManager.Load(int64(id)) 120 | } 121 | return defaultLimiterManager.Load(0) 122 | } 123 | 124 | // LimitByGroup 自定义限速器 125 | // 126 | // 按 group 限制 127 | func (m LimiterManager) LimitByGroup(ctx *nano.Ctx) *rate.Limiter { 128 | if _, ok := ctx.Value.(*nano.Message); ok { 129 | return defaultLimiterManager.Load(int64(ctx.GroupID())) 130 | } 131 | return defaultLimiterManager.Load(0) 132 | } 133 | 134 | // LimitByChannel 自定义限速器 135 | // 136 | // 按 channel 限制 137 | func (m LimiterManager) LimitByChannel(ctx *nano.Ctx) *rate.Limiter { 138 | if _, ok := ctx.Value.(*nano.Message); ok { 139 | return defaultLimiterManager.Load(int64(ctx.GroupID())) 140 | } 141 | return defaultLimiterManager.Load(0) 142 | } 143 | 144 | // MustMessageNotNil 消息是否不为空 145 | func MustMessageNotNil(ctx *nano.Ctx) bool { 146 | return ctx.Message != nil 147 | } 148 | --------------------------------------------------------------------------------