├── .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 | [](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 |
--------------------------------------------------------------------------------