├── .envrc ├── .gitattributes ├── .github ├── hua_nobg_512.gif └── workflows │ ├── gomod2nix.yml │ ├── nightly-docker.yml │ ├── nightly.yml │ ├── pull.yml │ ├── push.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── README.md ├── console ├── console_ansi.go └── console_windows.go ├── custom ├── .gitignore ├── doc.go └── plugin │ └── .gitignore ├── default.nix ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── gomod2nix.toml ├── kanban ├── banner │ └── banner.go ├── gen │ └── banner.go ├── init.go └── version_less_than_1.21.go ├── main.go ├── plugin ├── ahsai │ └── ahsai.go ├── aichat │ ├── cfg.go │ └── main.go ├── aifalse │ └── main.go ├── aiwife │ └── non-existent.go ├── alipayvoice │ └── alipayvoice.go ├── animetrace │ └── main.go ├── antiabuse │ ├── anti.go │ └── db.go ├── atri │ └── atri.go ├── autowithdraw │ └── main.go ├── baiduaudit │ ├── audit.go │ └── model.go ├── base16384 │ └── main.go ├── base64gua │ └── main.go ├── baseamasiro │ └── main.go ├── bilibili │ ├── bilibili.go │ ├── bilibili_parse.go │ ├── bilibilimodel.go │ ├── bilibilipush.go │ ├── bilibilipushmodel.go │ ├── card2msg.go │ └── card2msg_test.go ├── bookreview │ ├── book_review.go │ └── model.go ├── breakrepeat │ └── breakrepeat.go ├── chat │ └── chat.go ├── chatcount │ ├── chatcount.go │ └── model.go ├── chess │ ├── chess.go │ ├── core.go │ ├── db.go │ ├── elo.go │ └── elo_test.go ├── choose │ └── choose.go ├── chouxianghua │ ├── chouxianghua.go │ └── model.go ├── chrev │ ├── init.go │ └── map.go ├── coser │ └── coser.go ├── cpstory │ ├── cpstory.go │ └── model.go ├── curse │ ├── curse.go │ └── model.go ├── dailynews │ └── dailynews.go ├── danbooru │ ├── main.go │ └── tag.go ├── diana │ ├── bing.go │ └── data │ │ └── text.go ├── dish │ └── dish.go ├── drawlots │ └── main.go ├── driftbottle │ └── main.go ├── emojimix │ ├── emoji.go │ └── mix.go ├── emozi │ └── main.go ├── event │ ├── data.go │ └── event.go ├── font │ └── main.go ├── fortune │ └── fortune.go ├── funny │ └── laugh.go ├── genshin │ ├── data.go │ └── ys.go ├── gif │ ├── README.md │ ├── context.go │ ├── gif.go │ ├── logo.go │ ├── png.go │ └── run.go ├── github │ └── repo_searcher.go ├── guessmusic │ ├── apiservice.go │ ├── guessmusic.go │ ├── main.go │ └── struct.go ├── hitokoto │ ├── hitokoto.go │ └── model.go ├── hs │ └── run.go ├── hyaku │ └── main.go ├── inject │ └── main.go ├── jandan │ ├── data.go │ └── jandan.go ├── jptingroom │ ├── jptingroom.go │ └── model.go ├── kfccrazythursday │ └── kfccrazythursday.go ├── lolicon │ └── lolicon.go ├── lolimi │ └── lolimi.go ├── magicprompt │ └── magicprompt.go ├── manager │ ├── gist.go │ ├── manager.go │ ├── model.go │ ├── slow.go │ └── timer │ │ ├── msg.go │ │ ├── parse.go │ │ ├── sleep.go │ │ ├── timer.db.go │ │ ├── timer.go │ │ ├── timer_test.go │ │ └── wrap.go ├── mcfish │ ├── fish.go │ ├── main.go │ ├── pack.go │ ├── pole.go │ └── store.go ├── midicreate │ └── midicreate.go ├── minecraftobserver │ ├── minecraftobserver.go │ ├── minecraftobserver_test.go │ ├── model.go │ ├── ping.go │ ├── ping_test.go │ ├── store.go │ └── store_test.go ├── movies │ └── main.go ├── moyu │ ├── holiday_test.go │ ├── nowork.go │ └── run.go ├── moyucalendar │ └── calendar.go ├── music │ └── selecter.go ├── nativesetu │ ├── data.go │ └── main.go ├── nbnhhsh │ └── nbnhhsh.go ├── nihongo │ ├── model.go │ └── nihongo.go ├── niuniu │ ├── draw.go │ └── main.go ├── novel │ └── qianbi.go ├── nsfw │ └── main.go ├── nwife │ └── main.go ├── omikuji │ ├── model.go │ └── sensou.go ├── poker │ └── poker.go ├── qqwife │ ├── command.go │ ├── favorSystem.go │ └── function.go ├── qzone │ ├── README.md │ ├── model.go │ └── qzone.go ├── realcugan │ └── realcugan.go ├── reborn │ ├── born.go │ ├── load.go │ └── main.go ├── robbery │ └── robbery.go ├── runcode │ └── code_runner.go ├── saucenao │ └── searcher.go ├── score │ ├── draw.go │ ├── model.go │ └── sign_in.go ├── setutime │ └── setu_geter.go ├── shadiao │ ├── ergofabulous.go │ ├── shadiao.go │ ├── sweetnothings.go │ └── yduanzi.go ├── shindan │ └── shindan.go ├── sleepmanage │ ├── model.go │ └── sleep_manage.go ├── steam │ ├── listenter.go │ ├── steam.go │ └── store.go ├── tarot │ ├── README.md │ └── tarot.go ├── thesaurus │ └── chat.go ├── tiangou │ └── tiangou.go ├── tracemoe │ └── moe.go ├── translation │ └── tl.go ├── wallet │ └── wallet.go ├── wantquotes │ └── wantquotes.go ├── warframeapi │ ├── api.go │ ├── main.go │ ├── types.go │ └── world.go ├── wenxinvilg │ └── main.go ├── wife │ └── main.go ├── wordcount │ └── main.go ├── wordle │ └── wordle.go ├── wtf │ ├── main.go │ └── model.go ├── ygo │ ├── ygocdb.go │ └── ygotrade.go ├── ymgal │ ├── model.go │ └── ymgal.go └── yujn │ └── yujn.go ├── run.bat ├── run.sh ├── shell.nix └── winres ├── gen └── json.go ├── icon.png ├── icon16.png ├── init.go └── winres.json /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pb linguist-language=go 2 | -------------------------------------------------------------------------------- /.github/hua_nobg_512.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloatTech/ZeroBot-Plugin/43b45ce6c5f9bccf8489b122de8c8453550733f1/.github/hua_nobg_512.gif -------------------------------------------------------------------------------- /.github/workflows/gomod2nix.yml: -------------------------------------------------------------------------------- 1 | name: 自动更新 nix 依赖 2 | on: 3 | push: 4 | paths: 5 | - 'go.mod' 6 | - 'go.sum' 7 | - '.github/workflows/gomod2nix.yml' 8 | jobs: 9 | gomod2nix: 10 | name: gomod2nix update 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Set up nix 14 | uses: cachix/install-nix-action@v27 15 | with: 16 | nix_path: nixpkgs=channel:nixos-unstable 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@master 20 | with: 21 | go-version: "1.20" 22 | 23 | - name: Check out code into the Go module directory 24 | uses: actions/checkout@master 25 | 26 | - name: gomod2nix update 27 | run: | 28 | nix run github:nix-community/gomod2nix 29 | - name: Commit back 30 | if: ${{ !github.head_ref }} 31 | continue-on-error: true 32 | run: | 33 | git config --local user.name 'github-actions[bot]' 34 | git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' 35 | git add --all 36 | git commit -m "chore: bump deps" 37 | - name: Create Pull Request 38 | if: ${{ !github.head_ref }} 39 | continue-on-error: true 40 | uses: peter-evans/create-pull-request@v4 41 | with: 42 | delete-branch: true 43 | branch-suffix: short-commit-hash 44 | -------------------------------------------------------------------------------- /.github/workflows/nightly-docker.yml: -------------------------------------------------------------------------------- 1 | name: 打包最新版为 Docker Image 2 | 3 | on: [push] 4 | jobs: 5 | docker-builder: 6 | name: build docker 7 | runs-on: ubuntu-23.04 8 | steps: 9 | - name: Check out code into the Go module directory 10 | uses: actions/checkout@master 11 | - run: sudo apt-get install -y qemu-user-static 12 | 13 | - name: Set up nix 14 | uses: cachix/install-nix-action@v27 15 | with: 16 | nix_path: nixpkgs=channel:nixos-unstable 17 | extra_nix_config: | 18 | sandbox = true 19 | 20 | - name: Speed Up nix 21 | uses: DeterminateSystems/magic-nix-cache-action@main 22 | 23 | - name: build docker 24 | run: | 25 | mkdir output/ 26 | 27 | # https://discourse.nixos.org/t/nix-github-actions-aarch64/11034 28 | nix build .#packages.aarch64-linux.docker_builder -o aarch64-linux.docker --print-out-paths --option system aarch64-linux --extra-platforms aarch64-linux 29 | cp $(readlink aarch64-linux.docker) ./output/aarch64-linux.docker.tar.gz 30 | 31 | nix build .#packages.x86_64-linux.docker_builder -o x86_64-linux.docker --print-out-paths --option system x86_64-linux --extra-platforms x86_64-linux 32 | cp $(readlink x86_64-linux.docker) ./output/x86_64-linux.docker.tar.gz 33 | 34 | # gomod2nix did not provide this 35 | # nix build .#packages.i686-linux.docker_builder -o i686-linux.docker --print-out-paths --option system i686-linux --extra-platforms i686-linux 36 | # cp $(readlink i686-linux.docker) ./output/i686-linux.docker.tar.gz 37 | 38 | - name: Upload artifact 39 | uses: actions/upload-artifact@master 40 | if: ${{ !github.head_ref }} 41 | with: 42 | path: output/ 43 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: 最新版 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | BINARY_PREFIX: "zbp_" 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 | - goos: windows 26 | goarch: 386 27 | fail-fast: true 28 | steps: 29 | - uses: actions/checkout@master 30 | - name: Setup Go environment 31 | uses: actions/setup-go@master 32 | with: 33 | go-version: '1.20' 34 | - name: Cache downloaded module 35 | uses: actions/cache@master 36 | continue-on-error: true 37 | with: 38 | path: | 39 | ~/.cache/go-build 40 | ~/go/pkg/mod 41 | key: ${{ runner.os }}-go-${{ matrix.goos }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }} 42 | - name: Build binary file 43 | env: 44 | GOOS: ${{ matrix.goos }} 45 | GOARCH: ${{ matrix.goarch }} 46 | IS_PR: ${{ !!github.head_ref }} 47 | run: | 48 | if [ $GOOS = "windows" ]; then export BINARY_SUFFIX="$BINARY_SUFFIX.exe"; fi 49 | if $IS_PR ; then echo $PR_PROMPT; fi 50 | export BINARY_NAME="$BINARY_PREFIX$GOOS_$GOARCH$BINARY_SUFFIX" 51 | export CGO_ENABLED=0 52 | go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" . 53 | - name: Upload artifact 54 | uses: actions/upload-artifact@master 55 | if: ${{ !github.head_ref }} 56 | with: 57 | name: ${{ matrix.goos }}_${{ matrix.goarch }} 58 | path: output/ 59 | -------------------------------------------------------------------------------- /.github/workflows/pull.yml: -------------------------------------------------------------------------------- 1 | name: PullLint 2 | on: 3 | pull_request_target: 4 | types: [assigned, opened, synchronize, reopened] 5 | jobs: 6 | # This workflow closes invalid PR 7 | close-pr: 8 | name: closepr 9 | # The type of runner that the job will run on 10 | runs-on: ubuntu-latest 11 | permissions: write-all 12 | 13 | # Steps represent a sequence of tasks that will be executed as part of the job 14 | steps: 15 | - name: Close PR if commit message contains ".go" 16 | if: contains(github.event.pull_request.title, '.go') 17 | uses: superbrothers/close-pull-request@v3 18 | with: 19 | # Optional. Post a issue comment just before closing a pull request. 20 | comment: "非法PR. 请`fork`后修改自己的仓库, 而不是向主仓库提交更改. 如果您确信您的PR是为了给主仓库新增功能或修复bug, 请更改默认PR标题. **注意**: 如果您再次触发本提示, 则有可能导致账号被封禁." 21 | 22 | golangci: 23 | name: lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Set up Go 27 | uses: actions/setup-go@master 28 | with: 29 | go-version: '1.20' 30 | 31 | - name: Check out code into the Go module directory 32 | uses: actions/checkout@v4 33 | with: 34 | ref: ${{ github.event.pull_request.head.sha }} 35 | 36 | - name: Tidy Modules 37 | run: go mod tidy 38 | 39 | - name: golangci-lint 40 | uses: golangci/golangci-lint-action@master 41 | with: 42 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 43 | version: latest 44 | 45 | # Optional: working directory, useful for monorepos 46 | # working-directory: somedir 47 | 48 | # Optional: golangci-lint command line arguments. 49 | # args: --issues-exit-code=0 50 | 51 | # Optional: show only new issues if it's a pull request. The default value is `false`. 52 | # only-new-issues: true 53 | 54 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 55 | # skip-pkg-cache: true 56 | 57 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 58 | # skip-build-cache: true 59 | -------------------------------------------------------------------------------- /.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: Tidy Modules 17 | run: go mod tidy 18 | 19 | - name: Run Lint 20 | uses: golangci/golangci-lint-action@master 21 | with: 22 | version: latest 23 | 24 | - name: Commit back 25 | if: ${{ !github.head_ref }} 26 | continue-on-error: true 27 | run: | 28 | git config --local user.name 'github-actions[bot]' 29 | git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com' 30 | git add --all 31 | git commit -m "chore(lint): 改进代码样式" 32 | 33 | - name: Create Pull Request 34 | if: ${{ !github.head_ref }} 35 | continue-on-error: true 36 | uses: peter-evans/create-pull-request@v4 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 发行版 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: "~> v2" 25 | args: release --clean 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | plugins/*.so 2 | plugins/*.dll 3 | .idea/ 4 | .DS_Store 5 | .vscode 6 | go-zero* 7 | nohup.out 8 | zerobot 9 | ZeroBot-Plugin* 10 | *.syso 11 | /.direnv 12 | /result -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "data"] 2 | path = data 3 | url = https://github.com/FloatTech/zbpdata 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | errcheck: 3 | ignoretests: true 4 | 5 | goimports: 6 | local-prefixes: github.com/FloatTech/ZeroBot-Plugin 7 | 8 | forbidigo: 9 | # Forbid the following identifiers 10 | forbid: 11 | - ^fmt\.Errorf$ # consider errors.Errorf in github.com/pkg/errors 12 | 13 | linters: 14 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 15 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 16 | disable-all: true 17 | fast: false 18 | enable: 19 | - bodyclose 20 | #- depguard 21 | - dogsled 22 | - errcheck 23 | #- exportloopref 24 | - exhaustive 25 | #- funlen 26 | #- goconst 27 | - gocritic 28 | #- gocyclo 29 | - gofmt 30 | - goimports 31 | - goprintffuncname 32 | #- gosec 33 | - gosimple 34 | - govet 35 | - ineffassign 36 | #- misspell 37 | - nolintlint 38 | - rowserrcheck 39 | - staticcheck 40 | - stylecheck 41 | - typecheck 42 | - unconvert 43 | - unparam 44 | - unused 45 | - whitespace 46 | - prealloc 47 | - predeclared 48 | - asciicheck 49 | - revive 50 | - forbidigo 51 | - makezero 52 | 53 | run: 54 | # default concurrency is a available CPU number. 55 | # concurrency: 4 # explicitly omit this value to fully utilize available resources. 56 | deadline: 5m 57 | issues-exit-code: 1 58 | tests: false 59 | go: '1.20' 60 | 61 | # output configuration options 62 | output: 63 | formats: 64 | - format: "colored-line-number" 65 | print-issued-lines: true 66 | print-linter-name: true 67 | uniq-by-line: true 68 | 69 | issues: 70 | # Fix found issues (if it's supported by the linter) 71 | fix: true 72 | exclude-use-default: false 73 | exclude: 74 | - "Error return value of .((os.)?std(out|err)..*|.*Close|.*Seek|.*Flush|os.Remove(All)?|.*print(f|ln)?|os.(Un)?Setenv). is not check" 75 | - 'identifier ".*" contain non-ASCII character: U\+.*' 76 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: zbp 2 | env: 3 | - GO111MODULE=on 4 | before: 5 | hooks: 6 | - go mod tidy 7 | - go install github.com/tc-hib/go-winres@latest 8 | - go-winres make 9 | builds: 10 | - id: nowin 11 | env: 12 | - CGO_ENABLED=0 13 | - GO111MODULE=on 14 | goos: 15 | - linux 16 | goarch: 17 | - 386 18 | - amd64 19 | - arm 20 | - arm64 21 | goarm: 22 | - 6 23 | - 7 24 | mod_timestamp: "{{ .CommitTimestamp }}" 25 | flags: 26 | - -trimpath 27 | ldflags: 28 | - -s -w 29 | - id: win 30 | env: 31 | - CGO_ENABLED=0 32 | - GO111MODULE=on 33 | goos: 34 | - windows 35 | goarch: 36 | - amd64 37 | mod_timestamp: "{{ .CommitTimestamp }}" 38 | flags: 39 | - -trimpath 40 | ldflags: 41 | - -s -w 42 | 43 | checksum: 44 | name_template: "zbp_checksums.txt" 45 | changelog: 46 | sort: asc 47 | filters: 48 | exclude: 49 | - "^docs:" 50 | - "^test:" 51 | - fix typo 52 | - Merge pull request 53 | - Merge branch 54 | - Merge remote-tracking 55 | - go mod tidy 56 | 57 | archives: 58 | - id: nowin 59 | builds: 60 | - nowin 61 | - win 62 | name_template: "zbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 63 | format_overrides: 64 | - goos: windows 65 | formats: zip 66 | 67 | nfpms: 68 | - license: AGPL 3.0 69 | homepage: https://github.com/FloatTech/ZeroBot-Plugin 70 | file_name_template: "zbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 71 | formats: 72 | - deb 73 | - rpm 74 | maintainer: FloatTech 75 | -------------------------------------------------------------------------------- /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/ZeroBot-Plugin/kanban/banner" 10 | ) 11 | 12 | func init() { 13 | fmt.Print("\033]0;ZeroBot-Blugin " + banner.Version + " " + banner.Copyright + "\007") 14 | } 15 | -------------------------------------------------------------------------------- /custom/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | !doc.go 3 | !plugin 4 | * 5 | -------------------------------------------------------------------------------- /custom/doc.go: -------------------------------------------------------------------------------- 1 | // Package custom 注册用户自定义插件于此 2 | package custom 3 | -------------------------------------------------------------------------------- /custom/plugin/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | * 3 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? ( 3 | let 4 | inherit (builtins) fetchTree fromJSON readFile; 5 | inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; 6 | in 7 | import (fetchTree nixpkgs.locked) { 8 | overlays = [ 9 | (import "${fetchTree gomod2nix.locked}/overlay.nix") 10 | ]; 11 | } 12 | ), 13 | buildGoApplication ? pkgs.buildGoApplication, 14 | ... 15 | }: 16 | buildGoApplication { 17 | pname = "ZeroBot-Plugin"; 18 | version = "1.8.0"; 19 | pwd = ./.; 20 | src = ./.; 21 | # spec go version manually bcs 22 | # https://github.com/nix-community/gomod2nix/blob/30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6/builder/default.nix#L130 23 | # do not work 24 | go = pkgs.go_1_20; 25 | modules = ./gomod2nix.toml; 26 | } 27 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "gomod2nix": { 22 | "inputs": { 23 | "flake-utils": [ 24 | "flake-utils" 25 | ], 26 | "nixpkgs": [ 27 | "nixpkgs" 28 | ] 29 | }, 30 | "locked": { 31 | "lastModified": 1742209644, 32 | "narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=", 33 | "owner": "nix-community", 34 | "repo": "gomod2nix", 35 | "rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884", 36 | "type": "github" 37 | }, 38 | "original": { 39 | "owner": "nix-community", 40 | "repo": "gomod2nix", 41 | "type": "github" 42 | } 43 | }, 44 | "nixpkgs": { 45 | "locked": { 46 | "lastModified": 1745391562, 47 | "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=", 48 | "owner": "NixOS", 49 | "repo": "nixpkgs", 50 | "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7", 51 | "type": "github" 52 | }, 53 | "original": { 54 | "owner": "NixOS", 55 | "ref": "nixos-unstable", 56 | "repo": "nixpkgs", 57 | "type": "github" 58 | } 59 | }, 60 | "nixpkgs-with-go_1_20": { 61 | "locked": { 62 | "lastModified": 1710843028, 63 | "narHash": "sha256-CMbK45c4nSkGvayiEHFkGFH+doGPbgo3AWfecd2t1Fk=", 64 | "owner": "NixOS", 65 | "repo": "nixpkgs", 66 | "rev": "33c51330782cb486764eb598d5907b43dc87b4c2", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "NixOS", 71 | "repo": "nixpkgs", 72 | "rev": "33c51330782cb486764eb598d5907b43dc87b4c2", 73 | "type": "github" 74 | } 75 | }, 76 | "root": { 77 | "inputs": { 78 | "flake-utils": "flake-utils", 79 | "gomod2nix": "gomod2nix", 80 | "nixpkgs": "nixpkgs", 81 | "nixpkgs-with-go_1_20": "nixpkgs-with-go_1_20" 82 | } 83 | }, 84 | "systems": { 85 | "locked": { 86 | "lastModified": 1681028828, 87 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 88 | "owner": "nix-systems", 89 | "repo": "default", 90 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 91 | "type": "github" 92 | }, 93 | "original": { 94 | "owner": "nix-systems", 95 | "repo": "default", 96 | "type": "github" 97 | } 98 | } 99 | }, 100 | "root": "root", 101 | "version": 7 102 | } 103 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "基于 ZeroBot 的 OneBot 插件"; 3 | 4 | inputs.nixpkgs-with-go_1_20.url = "github:NixOS/nixpkgs/33c51330782cb486764eb598d5907b43dc87b4c2"; 5 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | inputs.flake-utils.url = "github:numtide/flake-utils"; 7 | inputs.gomod2nix.url = "github:nix-community/gomod2nix"; 8 | inputs.gomod2nix.inputs.nixpkgs.follows = "nixpkgs"; 9 | inputs.gomod2nix.inputs.flake-utils.follows = "flake-utils"; 10 | 11 | outputs = { 12 | self, 13 | nixpkgs, 14 | nixpkgs-with-go_1_20, 15 | flake-utils, 16 | gomod2nix, 17 | ... 18 | } @ inputs: let 19 | allSystems = flake-utils.lib.allSystems; 20 | in ( 21 | flake-utils.lib.eachSystem allSystems 22 | (system: let 23 | old-nixpkgs = nixpkgs-with-go_1_20.legacyPackages.${system}; 24 | pkgs = import nixpkgs { 25 | inherit system; 26 | 27 | overlays = [ 28 | (_: _: { 29 | go_1_20 = old-nixpkgs.go_1_20; 30 | }) 31 | ]; 32 | }; 33 | 34 | # The current default sdk for macOS fails to compile go projects, so we use a newer one for now. 35 | # This has no effect on other platforms. 36 | callPackage = pkgs.darwin.apple_sdk_11_0.callPackage or pkgs.callPackage; 37 | in { 38 | # doCheck will fail at write files 39 | packages = rec { 40 | ZeroBot-Plugin = (callPackage ./. (inputs 41 | // { 42 | inherit (gomod2nix.legacyPackages.${system}) buildGoApplication; 43 | })) 44 | .overrideAttrs (_: {doCheck = false;}); 45 | 46 | default = ZeroBot-Plugin; 47 | 48 | docker_builder = pkgs.dockerTools.buildLayeredImage { 49 | name = "ZeroBot-Plugin"; 50 | tag = "latest"; 51 | contents = [ 52 | self.packages.${system}.ZeroBot-Plugin 53 | pkgs.cacert 54 | ]; 55 | }; 56 | }; 57 | devShells.default = callPackage ./shell.nix { 58 | inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix; 59 | }; 60 | formatter = pkgs.alejandra; 61 | }) 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /kanban/banner/banner.go: -------------------------------------------------------------------------------- 1 | // Code generated by kanban/gen. DO NOT EDIT. 2 | 3 | package banner 4 | 5 | // Version ... 6 | var Version = "v1.9.8" 7 | 8 | // Copyright ... 9 | var Copyright = "© 2020 - 2025 FloatTech" 10 | 11 | // Banner ... 12 | var Banner = "* OneBot + ZeroBot + Golang\n" + 13 | "* Version " + Version + " - 2025-06-01 18:52:48 +0900 JST\n" + 14 | "* Copyright " + Copyright + ". All Rights Reserved.\n" + 15 | "* Project: https://github.com/FloatTech/ZeroBot-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 = "© 2020 - %d FloatTech" 22 | 23 | // Banner ... 24 | var Banner = "* OneBot + ZeroBot + Golang\n" + 25 | "* Version " + Version + " - %s\n" + 26 | "* Copyright " + Copyright + ". All Rights Reserved.\n" + 27 | "* Project: https://github.com/FloatTech/ZeroBot-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 | "github.com/FloatTech/zbputils/control" 8 | "github.com/fumiama/go-registry" 9 | 10 | "github.com/FloatTech/ZeroBot-Plugin/kanban/banner" 11 | ) 12 | 13 | //go:generate go run github.com/FloatTech/ZeroBot-Plugin/kanban/gen 14 | 15 | func init() { 16 | PrintBanner() 17 | } 18 | 19 | var reg = registry.NewRegReader("reilia.fumiama.top:32664", control.Md5File, "fumiama") 20 | 21 | // PrintBanner ... 22 | func PrintBanner() { 23 | fmt.Print( 24 | "\n======================[ZeroBot-Plugin]======================", 25 | "\n", banner.Banner, "\n", 26 | "----------------------[ZeroBot-公告栏]----------------------", 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("ZeroBot-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 | -------------------------------------------------------------------------------- /plugin/ahsai/ahsai.go: -------------------------------------------------------------------------------- 1 | // Package ahsai AH Soft フリーテキスト音声合成 demo API 2 | package ahsai 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "sort" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/FloatTech/floatbox/file" 13 | ctrl "github.com/FloatTech/zbpctrl" 14 | "github.com/FloatTech/zbputils/control" 15 | ahsaitts "github.com/fumiama/ahsai" 16 | zero "github.com/wdvxdr1123/ZeroBot" 17 | "github.com/wdvxdr1123/ZeroBot/message" 18 | ) 19 | 20 | var ( 21 | namelist = [...]string{"伊織弓鶴", "紲星あかり", "結月ゆかり", "京町セイカ", "東北きりたん", "東北イタコ", "ついなちゃん標準語", "ついなちゃん関西弁", "音街ウナ", "琴葉茜", "吉田くん", "民安ともえ", "桜乃そら", "月読アイ", "琴葉葵", "東北ずん子", "月読ショウタ", "水奈瀬コウ"} 22 | namesort = func() []string { 23 | nl := namelist[:] 24 | sort.Strings(nl) 25 | return nl 26 | }() 27 | ) 28 | 29 | func init() { 30 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 31 | DisableOnDefault: false, 32 | Brief: "フリーテキスト音声合成", 33 | Help: "- 使[伊織弓鶴|紲星あかり|結月ゆかり|京町セイカ|東北きりたん|東北イタコ|ついなちゃん標準語|ついなちゃん関西弁|音街ウナ|琴葉茜|吉田くん|民安ともえ|桜乃そら|月読アイ|琴葉葵|東北ずん子|月読ショウタ|水奈瀬コウ]说(日语)", 34 | PrivateDataFolder: "ahsai", 35 | }) 36 | cachePath := engine.DataFolder() + "cache/" 37 | _ = os.RemoveAll(cachePath) 38 | _ = os.MkdirAll(cachePath, 0755) 39 | engine.OnRegex("^使(.{0,10})说([A-Za-z\\s\\d\u3005\u3040-\u30ff\u4e00-\u9fff\uff11-\uff19\uff21-\uff3a\uff41-\uff5a\uff66-\uff9d\\pP]+)$", selectName).SetBlock(true).Handle(func(ctx *zero.Ctx) { 40 | ctx.SendChain(message.Text("少女祈祷中...")) 41 | uid := ctx.Event.UserID 42 | today := time.Now().Format("20060102150405") 43 | ahsaiFile := cachePath + strconv.FormatInt(uid, 10) + today + "ahsai.wav" 44 | s := ahsaitts.NewSpeaker() 45 | err := s.SetName(ctx.State["ahsainame"].(string)) 46 | if err != nil { 47 | ctx.SendChain(message.Text("ERROR: ", err)) 48 | return 49 | } 50 | u, err := s.Speak(ctx.State["ahsaitext"].(string)) 51 | if err != nil { 52 | ctx.SendChain(message.Text("ERROR: ", err)) 53 | return 54 | } 55 | err = ahsaitts.SaveOggToFile(u, ahsaiFile) 56 | if err != nil { 57 | ctx.SendChain(message.Text("ERROR: ", err)) 58 | return 59 | } 60 | ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + ahsaiFile)) 61 | }) 62 | } 63 | 64 | func selectName(ctx *zero.Ctx) bool { 65 | regexMatched := ctx.State["regex_matched"].([]string) 66 | ctx.State["ahsaitext"] = regexMatched[2] 67 | name := regexMatched[1] 68 | index := sort.SearchStrings(namesort, name) 69 | if index < len(namelist) && namesort[index] == name { 70 | ctx.State["ahsainame"] = name 71 | return true 72 | } 73 | speaktext := "" 74 | for i, v := range namelist { 75 | speaktext += fmt.Sprintf("%d. %s\n", i, v) 76 | } 77 | ctx.SendChain(message.Text("输入的音源为空, 请输入音源序号\n", speaktext)) 78 | next, cancel := zero.NewFutureEvent("message", 999, false, ctx.CheckSession(), zero.RegexRule(`\d{0,2}`)).Repeat() 79 | defer cancel() 80 | for { 81 | select { 82 | case <-time.After(time.Second * 10): 83 | ctx.State["ahsainame"] = namelist[rand.Intn(len(namelist))] 84 | ctx.SendChain(message.Text("时间太久啦!", zero.BotConfig.NickName[0], "帮你选择", ctx.State["ahsainame"])) 85 | return true 86 | case c := <-next: 87 | msg := c.Event.Message.ExtractPlainText() 88 | num, _ := strconv.Atoi(msg) 89 | if num < 0 || num >= len(namelist) { 90 | ctx.SendChain(message.Text("序号非法!")) 91 | continue 92 | } 93 | ctx.State["ahsainame"] = namelist[num] 94 | return true 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /plugin/aiwife/non-existent.go: -------------------------------------------------------------------------------- 1 | // Package aiwife 随机老婆 2 | package aiwife 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | "github.com/FloatTech/zbputils/ctxext" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/message" 13 | ) 14 | 15 | const ( 16 | bed = "https://www.thiswaifudoesnotexist.net/example-%d.jpg" 17 | ) 18 | 19 | func init() { // 插件主体 20 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 21 | DisableOnDefault: false, 22 | Brief: "ai随机生成老婆", 23 | Help: "- waifu | 随机waifu", 24 | }).ApplySingle(ctxext.DefaultSingle).OnFullMatchGroup([]string{"waifu", "随机waifu"}).SetBlock(true). 25 | Handle(func(ctx *zero.Ctx) { 26 | miku := rand.Intn(100000) + 1 27 | ctx.SendChain(message.At(ctx.Event.UserID), message.Image(fmt.Sprintf(bed, miku))) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /plugin/alipayvoice/alipayvoice.go: -------------------------------------------------------------------------------- 1 | // Package alipayvoice 支付宝到账语音 2 | package alipayvoice 3 | 4 | import ( 5 | "fmt" 6 | "strings" 7 | 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | const ( 15 | alipayvoiceURL = "https://mm.cqu.cc/share/zhifubaodaozhang/mp3/%v.mp3" 16 | ) 17 | 18 | func init() { // 插件主体 19 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 20 | DisableOnDefault: false, 21 | Brief: "支付宝到账语音", 22 | Help: "- 支付宝到账 1", 23 | PrivateDataFolder: "alipayvoice", 24 | }) 25 | 26 | // 开启 27 | engine.OnPrefix(`支付宝到账`).SetBlock(true). 28 | Handle(func(ctx *zero.Ctx) { 29 | args := ctx.State["args"].(string) 30 | ctx.SendChain(message.Record(fmt.Sprintf(alipayvoiceURL, strings.TrimSpace(args)))) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /plugin/antiabuse/db.go: -------------------------------------------------------------------------------- 1 | package antiabuse 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | sqlite "github.com/FloatTech/sqlite" 11 | ) 12 | 13 | type antidb struct { 14 | sync.RWMutex 15 | sqlite.Sqlite 16 | } 17 | 18 | type banWord struct { 19 | Word string `db:"word"` 20 | } 21 | 22 | type banTime struct { 23 | ID int64 `db:"id"` 24 | Time int64 `db:"time"` 25 | } 26 | 27 | var ( 28 | nilban = &banWord{} 29 | nilbt = &banTime{} 30 | ) 31 | 32 | func newantidb(path string) (*antidb, error) { 33 | db := &antidb{Sqlite: sqlite.New(path)} 34 | err := db.Open(bandur) 35 | if err != nil { 36 | return nil, err 37 | } 38 | _ = db.FindFor("__bantime__", nilbt, "", func() error { 39 | t := time.Unix(nilbt.Time, 0) 40 | ttl := time.Until(t.Add(bandur)) 41 | if ttl < time.Minute { 42 | _ = managers.DoUnblock(nilbt.ID) 43 | return nil 44 | } 45 | cache.Set(nilbt.ID, struct{}{}) 46 | cache.Touch(nilbt.ID, -time.Since(t)) 47 | return nil 48 | }) 49 | _ = db.Del("__bantime__", "WHERE time <= ?", strconv.FormatInt(time.Now().Add(time.Minute-bandur).Unix(), 10)) 50 | return db, nil 51 | } 52 | 53 | func (db *antidb) isInAntiList(gid int64, msg string) bool { 54 | grp := strconv.FormatInt(gid, 36) 55 | db.RLock() 56 | defer db.RUnlock() 57 | return db.CanFind(grp, "WHERE instr('"+msg+"', word)>0") 58 | } 59 | 60 | func (db *antidb) insertWord(gid int64, word string) error { 61 | grp := strconv.FormatInt(gid, 36) 62 | db.Lock() 63 | defer db.Unlock() 64 | err := db.Create(grp, nilban) 65 | if err != nil { 66 | return err 67 | } 68 | return db.Insert(grp, &banWord{Word: word}) 69 | } 70 | 71 | func (db *antidb) deleteWord(gid int64, word string) error { 72 | grp := strconv.FormatInt(gid, 36) 73 | db.Lock() 74 | defer db.Unlock() 75 | if n, _ := db.Count(grp); n == 0 { 76 | return errors.New("本群还没有违禁词~") 77 | } 78 | return db.Del(grp, "WHERE word='"+word+"'") 79 | } 80 | 81 | func (db *antidb) listWords(gid int64) string { 82 | grp := strconv.FormatInt(gid, 36) 83 | word := &banWord{} 84 | sb := strings.Builder{} 85 | sb.WriteByte('[') 86 | i := 0 87 | db.RLock() 88 | defer db.RUnlock() 89 | _ = db.FindFor(grp, word, "", func() error { 90 | if i > 0 { 91 | sb.WriteString(" | ") 92 | } 93 | sb.WriteString(word.Word) 94 | i++ 95 | return nil 96 | }) 97 | if sb.Len() <= 4 { 98 | return "[]" 99 | } 100 | sb.WriteByte(']') 101 | return sb.String() 102 | } 103 | -------------------------------------------------------------------------------- /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 | "github.com/FloatTech/zbputils/control" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | ) 11 | 12 | func init() { 13 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 14 | DisableOnDefault: false, 15 | Brief: "触发者撤回时也自动撤回", 16 | Help: "- 撤回一条消息\n", 17 | }).OnNotice(func(ctx *zero.Ctx) bool { 18 | return ctx.Event.NoticeType == "group_recall" || ctx.Event.NoticeType == "friend_recall" 19 | }).SetBlock(false).Handle(func(ctx *zero.Ctx) { 20 | id, ok := ctx.Event.MessageID.(int64) 21 | if !ok { 22 | return 23 | } 24 | for _, msg := range zero.GetTriggeredMessages(message.NewMessageIDFromInteger(id)) { 25 | process.SleepAbout1sTo2s() 26 | ctx.DeleteMessage(msg) 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /plugin/base16384/main.go: -------------------------------------------------------------------------------- 1 | // Package b14coder base16384 与 tea 加解密 2 | package b14coder 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/crypto" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/FloatTech/zbputils/control" 8 | base14 "github.com/fumiama/go-base16384" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 12 | ) 13 | 14 | func init() { 15 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 16 | DisableOnDefault: false, 17 | Brief: "base16384加解密", 18 | Help: "- 加密xxx\n- 解密xxx\n- 用yyy加密xxx\n- 用yyy解密xxx", 19 | }) 20 | en.OnRegex(`^加密\s*(.+)$`).SetBlock(true). 21 | Handle(func(ctx *zero.Ctx) { 22 | str := ctx.State["regex_matched"].([]string)[1] 23 | es := base14.EncodeString(str) 24 | if es != "" { 25 | ctx.SendChain(message.Text(es)) 26 | } else { 27 | ctx.SendChain(message.Text("加密失败!")) 28 | } 29 | }) 30 | en.OnRegex(`^解密\s*([一-踀]+[㴁-㴆]?)$`).SetBlock(true). 31 | Handle(func(ctx *zero.Ctx) { 32 | str := ctx.State["regex_matched"].([]string)[1] 33 | es := base14.DecodeString(str) 34 | if es != "" { 35 | ctx.SendChain(message.Text(es)) 36 | } else { 37 | ctx.SendChain(message.Text("解密失败!")) 38 | } 39 | }) 40 | en.OnRegex(`^用(.+)加密\s*(.+)$`).SetBlock(true). 41 | Handle(func(ctx *zero.Ctx) { 42 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 43 | t := crypto.GetTEA(key) 44 | es, err := base14.UTF16BE2UTF8(base14.Encode(t.Encrypt(helper.StringToBytes(str)))) 45 | if err == nil { 46 | ctx.SendChain(message.Text(helper.BytesToString(es))) 47 | } else { 48 | ctx.SendChain(message.Text("加密失败!")) 49 | } 50 | }) 51 | en.OnRegex(`^用(.+)解密\s*([一-踀]+[㴁-㴆]?)$`).SetBlock(true). 52 | Handle(func(ctx *zero.Ctx) { 53 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 54 | t := crypto.GetTEA(key) 55 | es, err := base14.UTF82UTF16BE(helper.StringToBytes(str)) 56 | if err == nil { 57 | ctx.SendChain(message.Text(helper.BytesToString(t.Decrypt(base14.Decode(es))))) 58 | } else { 59 | ctx.SendChain(message.Text("解密失败!")) 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /plugin/base64gua/main.go: -------------------------------------------------------------------------------- 1 | // Package base64gua base64卦 与 tea 加解密 2 | package base64gua 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/crypto" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/FloatTech/zbputils/control" 8 | "github.com/fumiama/unibase2n" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 12 | ) 13 | 14 | func init() { 15 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 16 | DisableOnDefault: false, 17 | Brief: "六十四卦加解密", 18 | Help: "- 六十四卦加密xxx\n- 六十四卦解密xxx\n- 六十四卦用yyy加密xxx\n- 六十四卦用yyy解密xxx", 19 | }) 20 | en.OnRegex(`^六十四卦加密\s*(.+)$`).SetBlock(true). 21 | Handle(func(ctx *zero.Ctx) { 22 | str := ctx.State["regex_matched"].([]string)[1] 23 | es := unibase2n.Base64Gua.EncodeString(str) 24 | if es != "" { 25 | ctx.SendChain(message.Text(es)) 26 | } else { 27 | ctx.SendChain(message.Text("加密失败!")) 28 | } 29 | }) 30 | en.OnRegex(`^六十四卦解密\s*([䷀-䷿]+[☰☱]?)$`).SetBlock(true). 31 | Handle(func(ctx *zero.Ctx) { 32 | str := ctx.State["regex_matched"].([]string)[1] 33 | es := unibase2n.Base64Gua.DecodeString(str) 34 | if es != "" { 35 | ctx.SendChain(message.Text(es)) 36 | } else { 37 | ctx.SendChain(message.Text("解密失败!")) 38 | } 39 | }) 40 | en.OnRegex(`^六十四卦用(.+)加密\s*(.+)$`).SetBlock(true). 41 | Handle(func(ctx *zero.Ctx) { 42 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 43 | t := crypto.GetTEA(key) 44 | es, err := unibase2n.UTF16BE2UTF8(unibase2n.Base64Gua.Encode(t.Encrypt(helper.StringToBytes(str)))) 45 | if err == nil { 46 | ctx.SendChain(message.Text(helper.BytesToString(es))) 47 | } else { 48 | ctx.SendChain(message.Text("加密失败!")) 49 | } 50 | }) 51 | en.OnRegex(`^六十四卦用(.+)解密\s*([䷀-䷿]+[☰☱]?)$`).SetBlock(true). 52 | Handle(func(ctx *zero.Ctx) { 53 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 54 | t := crypto.GetTEA(key) 55 | es, err := unibase2n.UTF82UTF16BE(helper.StringToBytes(str)) 56 | if err == nil { 57 | ctx.SendChain(message.Text(helper.BytesToString(t.Decrypt(unibase2n.Base64Gua.Decode(es))))) 58 | } else { 59 | ctx.SendChain(message.Text("解密失败!")) 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /plugin/baseamasiro/main.go: -------------------------------------------------------------------------------- 1 | // Package baseamasiro base天城文 与 tea 加解密 2 | package baseamasiro 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/crypto" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/FloatTech/zbputils/control" 8 | "github.com/fumiama/unibase2n" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 12 | ) 13 | 14 | func init() { 15 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 16 | DisableOnDefault: false, 17 | Brief: "天城文加解密", 18 | Help: "- 天城文加密xxx\n- 天城文解密xxx\n- 天城文用yyy加密xxx\n- 天城文用yyy解密xxx", 19 | }) 20 | en.OnRegex(`^天城文加密\s*(.+)$`).SetBlock(true). 21 | Handle(func(ctx *zero.Ctx) { 22 | str := ctx.State["regex_matched"].([]string)[1] 23 | es := unibase2n.BaseDevanagari.EncodeString(str) 24 | if es != "" { 25 | ctx.SendChain(message.Text(es)) 26 | } else { 27 | ctx.SendChain(message.Text("加密失败!")) 28 | } 29 | }) 30 | en.OnRegex(`^天城文解密\s*([ऀ-ॿ]+[০-৫]?)$`).SetBlock(true). 31 | Handle(func(ctx *zero.Ctx) { 32 | str := ctx.State["regex_matched"].([]string)[1] 33 | es := unibase2n.BaseDevanagari.DecodeString(str) 34 | if es != "" { 35 | ctx.SendChain(message.Text(es)) 36 | } else { 37 | ctx.SendChain(message.Text("解密失败!")) 38 | } 39 | }) 40 | en.OnRegex(`^天城文用(.+)加密\s*(.+)$`).SetBlock(true). 41 | Handle(func(ctx *zero.Ctx) { 42 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 43 | t := crypto.GetTEA(key) 44 | es, err := unibase2n.UTF16BE2UTF8(unibase2n.BaseDevanagari.Encode(t.Encrypt(helper.StringToBytes(str)))) 45 | if err == nil { 46 | ctx.SendChain(message.Text(helper.BytesToString(es))) 47 | } else { 48 | ctx.SendChain(message.Text("加密失败!")) 49 | } 50 | }) 51 | en.OnRegex(`^天城文用(.+)解密\s*([ऀ-ॿ]+[০-৫]?)$`).SetBlock(true). 52 | Handle(func(ctx *zero.Ctx) { 53 | key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2] 54 | t := crypto.GetTEA(key) 55 | es, err := unibase2n.UTF82UTF16BE(helper.StringToBytes(str)) 56 | if err == nil { 57 | ctx.SendChain(message.Text(helper.BytesToString(t.Decrypt(unibase2n.BaseDevanagari.Decode(es))))) 58 | } else { 59 | ctx.SendChain(message.Text("解密失败!")) 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /plugin/bilibili/bilibilimodel.go: -------------------------------------------------------------------------------- 1 | package bilibili 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/FloatTech/floatbox/binary" 7 | "github.com/FloatTech/floatbox/web" 8 | "github.com/jinzhu/gorm" 9 | "github.com/tidwall/gjson" 10 | ) 11 | 12 | var ( 13 | vtbURLs = [...]string{"https://api.vtbs.moe/v1/short", "https://api.tokyo.vtbs.moe/v1/short", "https://vtbs.musedash.moe/v1/short"} 14 | vdb *vupdb 15 | ) 16 | 17 | // vupdb 分数数据库 18 | type vupdb gorm.DB 19 | 20 | type vup struct { 21 | Mid int64 `gorm:"column:mid;primary_key"` 22 | Uname string `gorm:"column:uname"` 23 | Roomid int64 `gorm:"column:roomid"` 24 | } 25 | 26 | func (vup) TableName() string { 27 | return "vup" 28 | } 29 | 30 | // initializeVup 初始化vup数据库 31 | func initializeVup(dbpath string) (*vupdb, error) { 32 | if _, err := os.Stat(dbpath); err != nil || os.IsNotExist(err) { 33 | // 生成文件 34 | f, err := os.Create(dbpath) 35 | if err != nil { 36 | return nil, err 37 | } 38 | defer f.Close() 39 | } 40 | gdb, err := gorm.Open("sqlite3", dbpath) 41 | if err != nil { 42 | return nil, err 43 | } 44 | gdb.AutoMigrate(&vup{}) 45 | return (*vupdb)(gdb), nil 46 | } 47 | 48 | func (vdb *vupdb) insertVupByMid(mid int64, uname string, roomid int64) (err error) { 49 | db := (*gorm.DB)(vdb) 50 | v := vup{ 51 | Mid: mid, 52 | Uname: uname, 53 | Roomid: roomid, 54 | } 55 | if err = db.Model(&vup{}).First(&v, "mid = ? ", mid).Error; err != nil { 56 | if gorm.IsRecordNotFoundError(err) { 57 | err = db.Model(&vup{}).Create(&v).Error 58 | } 59 | } 60 | return 61 | } 62 | 63 | // filterVup 筛选vup 64 | func (vdb *vupdb) filterVup(ids []int64) (vups []vup, err error) { 65 | db := (*gorm.DB)(vdb) 66 | if err = db.Model(&vup{}).Find(&vups, "mid in (?)", ids).Error; err != nil { 67 | return vups, err 68 | } 69 | return 70 | } 71 | 72 | func updateVup() error { 73 | for _, v := range vtbURLs { 74 | data, err := web.GetData(v) 75 | if err != nil { 76 | return err 77 | } 78 | gjson.Get(binary.BytesToString(data), "@this").ForEach(func(_, value gjson.Result) bool { 79 | mid := value.Get("mid").Int() 80 | uname := value.Get("uname").String() 81 | roomid := value.Get("roomid").Int() 82 | err = vdb.insertVupByMid(mid, uname, roomid) 83 | return err == nil 84 | }) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /plugin/bilibili/card2msg_test.go: -------------------------------------------------------------------------------- 1 | package bilibili 2 | 3 | import ( 4 | "testing" 5 | 6 | bz "github.com/FloatTech/AnimeAPI/bilibili" 7 | ) 8 | 9 | func TestArticleInfo(t *testing.T) { 10 | card, err := bz.GetArticleInfo("17279244") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | t.Log(articleCard2msg(card, "17279244")) 15 | 16 | } 17 | 18 | func TestMemberCard(t *testing.T) { 19 | card, err := bz.GetMemberCard(2) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | t.Logf("%+v\n", card) 24 | } 25 | 26 | func TestVideoInfo(t *testing.T) { 27 | card, err := bz.GetVideoInfo("10007") 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | t.Log(videoCard2msg(card)) 32 | card, err = bz.GetVideoInfo("BV1xx411c7mD") 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | t.Log(videoCard2msg(card)) 37 | card, err = bz.GetVideoInfo("bv1xx411c7mD") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | t.Log(videoCard2msg(card)) 42 | card, err = bz.GetVideoInfo("BV1mF411j7iU") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | t.Log(videoCard2msg(card)) 47 | } 48 | 49 | func TestLiveRoomInfo(t *testing.T) { 50 | card, err := bz.GetLiveRoomInfo("83171") 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | t.Log(liveCard2msg(card)) 55 | } 56 | -------------------------------------------------------------------------------- /plugin/bookreview/book_review.go: -------------------------------------------------------------------------------- 1 | // Package bookreview 书评 2 | package bookreview 3 | 4 | import ( 5 | "time" 6 | 7 | log "github.com/sirupsen/logrus" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | 11 | "github.com/FloatTech/floatbox/binary" 12 | fcext "github.com/FloatTech/floatbox/ctxext" 13 | sql "github.com/FloatTech/sqlite" 14 | ctrl "github.com/FloatTech/zbpctrl" 15 | "github.com/FloatTech/zbputils/control" 16 | "github.com/FloatTech/zbputils/img/text" 17 | ) 18 | 19 | func init() { 20 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 21 | DisableOnDefault: false, 22 | Brief: "哀伤雪刃推书书评", 23 | Help: "- 书评[xxx]\n- 随机书评", 24 | PublicDataFolder: "BookReview", 25 | }) 26 | 27 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 28 | db = sql.New(engine.DataFolder() + "bookreview.db") 29 | // os.RemoveAll(dbpath) 30 | _, _ = engine.GetLazyData("bookreview.db", true) 31 | err := db.Open(time.Hour) 32 | if err != nil { 33 | ctx.SendChain(message.Text("ERROR: ", err)) 34 | return false 35 | } 36 | err = db.Create("bookreview", &book{}) 37 | if err != nil { 38 | ctx.SendChain(message.Text("ERROR: ", err)) 39 | return false 40 | } 41 | n, err := db.Count("bookreview") 42 | if err != nil { 43 | ctx.SendChain(message.Text("ERROR: ", err)) 44 | return false 45 | } 46 | log.Infof("[bookreview]读取%d条书评", n) 47 | return true 48 | }) 49 | 50 | // 中文、英文、数字但不包括下划线等符号 51 | engine.OnRegex("^书评([\u4E00-\u9FA5A-Za-z0-9]{1,25})$", getdb).SetBlock(true). 52 | Handle(func(ctx *zero.Ctx) { 53 | b := getBookReviewByKeyword(ctx.State["regex_matched"].([]string)[1]) 54 | data, err := text.RenderToBase64(b.BookReview, text.FontFile, 400, 20) 55 | if err != nil { 56 | ctx.SendChain(message.Text("ERROR: ", err)) 57 | return 58 | } 59 | if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 { 60 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 61 | } 62 | }) 63 | 64 | engine.OnFullMatch("随机书评", getdb).SetBlock(true). 65 | Handle(func(ctx *zero.Ctx) { 66 | br := getRandomBookReview() 67 | data, err := text.RenderToBase64(br.BookReview, text.FontFile, 400, 20) 68 | if err != nil { 69 | ctx.SendChain(message.Text("ERROR: ", err)) 70 | return 71 | } 72 | if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 { 73 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 74 | } 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /plugin/bookreview/model.go: -------------------------------------------------------------------------------- 1 | package bookreview 2 | 3 | import sql "github.com/FloatTech/sqlite" 4 | 5 | type book struct { 6 | ID uint64 `db:"id"` 7 | BookReview string `db:"bookreview"` 8 | } 9 | 10 | var db sql.Sqlite 11 | 12 | // 暂时随机选择一个书评 13 | func getBookReviewByKeyword(keyword string) (b book) { 14 | _ = db.Find("bookreview", &b, "WHERE bookreview LIKE ?", "%"+keyword+"%") 15 | return 16 | } 17 | 18 | func getRandomBookReview() (b book) { 19 | _ = db.Pick("bookreview", &b) 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /plugin/breakrepeat/breakrepeat.go: -------------------------------------------------------------------------------- 1 | // Package breakrepeat 打断复读 2 | package breakrepeat 3 | 4 | import ( 5 | "math/rand" 6 | "strconv" 7 | 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | "github.com/RomiChan/syncx" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | ) 13 | 14 | const throttle = 3 // 不可超过 9 15 | 16 | var sm syncx.Map[int64, string] 17 | 18 | func init() { 19 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 20 | DisableOnDefault: false, 21 | Brief: "打断复读", 22 | Help: "- 打断" + strconv.Itoa(throttle) + "次以上复读\n", 23 | }) 24 | engine.On("message/group", zero.OnlyGroup, func(ctx *zero.Ctx) bool { 25 | return !zero.HasPicture(ctx) 26 | }).SetBlock(false). 27 | Handle(func(ctx *zero.Ctx) { 28 | gid := ctx.Event.GroupID 29 | raw := ctx.Event.RawMessage 30 | r, ok := sm.Load(gid) 31 | if !ok || len(r) <= 3 || r[3:] != raw { 32 | sm.Store(gid, "0: "+raw) 33 | return 34 | } 35 | c := int(r[0] - '0') 36 | if c < throttle { 37 | sm.Store(gid, strconv.Itoa(c+1)+": "+raw) 38 | return 39 | } 40 | sm.Delete(gid) 41 | if len(r) > 5 { 42 | ru := []rune(r[3:]) 43 | rand.Shuffle(len(ru), func(i, j int) { 44 | ru[i], ru[j] = ru[j], ru[i] 45 | }) 46 | r = string(ru) 47 | } 48 | ctx.Send(r) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /plugin/chat/chat.go: -------------------------------------------------------------------------------- 1 | // Package chat 对话插件 2 | package chat 3 | 4 | import ( 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 13 | "github.com/wdvxdr1123/ZeroBot/message" 14 | ) 15 | 16 | var ( 17 | poke = rate.NewManager[int64](time.Minute*5, 8) // 戳一戳 18 | engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 19 | DisableOnDefault: false, 20 | Brief: "基础反应, 群空调", 21 | Help: "chat\n- [BOT名字]\n- [戳一戳BOT]\n- 空调开\n- 空调关\n- 群温度\n- 设置温度[正整数]", 22 | }) 23 | ) 24 | 25 | func init() { // 插件主体 26 | // 被喊名字 27 | engine.OnFullMatch("", zero.OnlyToMe).SetBlock(true). 28 | Handle(func(ctx *zero.Ctx) { 29 | var nickname = zero.BotConfig.NickName[0] 30 | time.Sleep(time.Second * 1) 31 | ctx.SendChain(message.Text( 32 | []string{ 33 | nickname + "在此,有何贵干~", 34 | "(っ●ω●)っ在~", 35 | "这里是" + nickname + "(っ●ω●)っ", 36 | nickname + "不在呢~", 37 | }[rand.Intn(4)], 38 | )) 39 | }) 40 | // 戳一戳 41 | engine.On("notice/notify/poke", zero.OnlyToMe).SetBlock(false). 42 | Handle(func(ctx *zero.Ctx) { 43 | var nickname = zero.BotConfig.NickName[0] 44 | switch { 45 | case poke.Load(ctx.Event.GroupID).AcquireN(3): 46 | // 5分钟共8块命令牌 一次消耗3块命令牌 47 | time.Sleep(time.Second * 1) 48 | ctx.SendChain(message.Text("请不要戳", nickname, " >_<")) 49 | case poke.Load(ctx.Event.GroupID).Acquire(): 50 | // 5分钟共8块命令牌 一次消耗1块命令牌 51 | time.Sleep(time.Second * 1) 52 | ctx.SendChain(message.Text("喂(#`O′) 戳", nickname, "干嘛!")) 53 | default: 54 | // 频繁触发,不回复 55 | } 56 | }) 57 | // 群空调 58 | var AirConditTemp = map[int64]int{} 59 | var AirConditSwitch = map[int64]bool{} 60 | engine.OnFullMatch("空调开").SetBlock(true). 61 | Handle(func(ctx *zero.Ctx) { 62 | AirConditSwitch[ctx.Event.GroupID] = true 63 | ctx.SendChain(message.Text("❄️哔~")) 64 | }) 65 | engine.OnFullMatch("空调关").SetBlock(true). 66 | Handle(func(ctx *zero.Ctx) { 67 | AirConditSwitch[ctx.Event.GroupID] = false 68 | delete(AirConditTemp, ctx.Event.GroupID) 69 | ctx.SendChain(message.Text("💤哔~")) 70 | }) 71 | engine.OnRegex(`设置温度(\d+)`).SetBlock(true). 72 | Handle(func(ctx *zero.Ctx) { 73 | if _, exist := AirConditTemp[ctx.Event.GroupID]; !exist { 74 | AirConditTemp[ctx.Event.GroupID] = 26 75 | } 76 | if AirConditSwitch[ctx.Event.GroupID] { 77 | temp := ctx.State["regex_matched"].([]string)[1] 78 | AirConditTemp[ctx.Event.GroupID], _ = strconv.Atoi(temp) 79 | ctx.SendChain(message.Text( 80 | "❄️风速中", "\n", 81 | "群温度 ", AirConditTemp[ctx.Event.GroupID], "℃", 82 | )) 83 | } else { 84 | ctx.SendChain(message.Text( 85 | "💤", "\n", 86 | "群温度 ", AirConditTemp[ctx.Event.GroupID], "℃", 87 | )) 88 | } 89 | }) 90 | engine.OnFullMatch(`群温度`).SetBlock(true). 91 | Handle(func(ctx *zero.Ctx) { 92 | if _, exist := AirConditTemp[ctx.Event.GroupID]; !exist { 93 | AirConditTemp[ctx.Event.GroupID] = 26 94 | } 95 | if AirConditSwitch[ctx.Event.GroupID] { 96 | ctx.SendChain(message.Text( 97 | "❄️风速中", "\n", 98 | "群温度 ", AirConditTemp[ctx.Event.GroupID], "℃", 99 | )) 100 | } else { 101 | ctx.SendChain(message.Text( 102 | "💤", "\n", 103 | "群温度 ", AirConditTemp[ctx.Event.GroupID], "℃", 104 | )) 105 | } 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /plugin/chatcount/chatcount.go: -------------------------------------------------------------------------------- 1 | // Package chatcount 聊天时长统计 2 | package chatcount 3 | 4 | import ( 5 | "fmt" 6 | "image" 7 | "net/http" 8 | "strconv" 9 | "sync" 10 | 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/message" 13 | 14 | "github.com/FloatTech/floatbox/file" 15 | "github.com/FloatTech/imgfactory" 16 | "github.com/FloatTech/rendercard" 17 | ctrl "github.com/FloatTech/zbpctrl" 18 | "github.com/FloatTech/zbputils/control" 19 | "github.com/FloatTech/zbputils/ctxext" 20 | "github.com/FloatTech/zbputils/img/text" 21 | ) 22 | 23 | const ( 24 | rankSize = 10 25 | ) 26 | 27 | func init() { 28 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 29 | DisableOnDefault: false, 30 | Brief: "聊天时长统计", 31 | Help: "- 查询水群@xxx\n- 查看水群排名", 32 | PrivateDataFolder: "chatcount", 33 | }) 34 | go func() { 35 | ctdb = initialize(engine.DataFolder() + "chatcount.db") 36 | }() 37 | engine.OnMessage(zero.OnlyGroup).SetBlock(false). 38 | Handle(func(ctx *zero.Ctx) { 39 | remindTime, remindFlag := ctdb.updateChatTime(ctx.Event.GroupID, ctx.Event.UserID) 40 | if remindFlag { 41 | ctx.SendChain(message.At(ctx.Event.UserID), message.Text(fmt.Sprintf("BOT提醒:你今天已经水群%d分钟了!", remindTime))) 42 | } 43 | }) 44 | 45 | engine.OnPrefix(`查询水群`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { 46 | name := ctx.NickName() 47 | todayTime, todayMessage, totalTime, totalMessage := ctdb.getChatTime(ctx.Event.GroupID, ctx.Event.UserID) 48 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("%s今天水了%d分%d秒,发了%d条消息;总计水了%d分%d秒,发了%d条消息。", name, todayTime/60, todayTime%60, todayMessage, totalTime/60, totalTime%60, totalMessage))) 49 | }) 50 | engine.OnFullMatch("查看水群排名", zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true). 51 | Handle(func(ctx *zero.Ctx) { 52 | chatTimeList := ctdb.getChatRank(ctx.Event.GroupID) 53 | if len(chatTimeList) == 0 { 54 | ctx.SendChain(message.Text("ERROR: 没有水群数据")) 55 | return 56 | } 57 | rankinfo := make([]*rendercard.RankInfo, len(chatTimeList)) 58 | 59 | wg := &sync.WaitGroup{} 60 | wg.Add(len(chatTimeList)) 61 | for i := 0; i < len(chatTimeList) && i < rankSize; i++ { 62 | go func(i int) { 63 | defer wg.Done() 64 | resp, err := http.Get("https://q4.qlogo.cn/g?b=qq&nk=" + strconv.FormatInt(chatTimeList[i].UserID, 10) + "&s=100") 65 | if err != nil { 66 | return 67 | } 68 | defer resp.Body.Close() 69 | img, _, err := image.Decode(resp.Body) 70 | if err != nil { 71 | return 72 | } 73 | rankinfo[i] = &rendercard.RankInfo{ 74 | TopLeftText: ctx.CardOrNickName(chatTimeList[i].UserID), 75 | BottomLeftText: "消息数: " + strconv.FormatInt(chatTimeList[i].TodayMessage, 10) + " 条", 76 | RightText: strconv.FormatInt(chatTimeList[i].TodayTime/60, 10) + "分" + strconv.FormatInt(chatTimeList[i].TodayTime%60, 10) + "秒", 77 | Avatar: img, 78 | } 79 | }(i) 80 | } 81 | wg.Wait() 82 | fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true) 83 | if err != nil { 84 | ctx.SendChain(message.Text("ERROR: ", err)) 85 | return 86 | } 87 | img, err := rendercard.DrawRankingCard(fontbyte, "今日水群排行榜", rankinfo) 88 | if err != nil { 89 | ctx.SendChain(message.Text("ERROR: ", err)) 90 | return 91 | } 92 | sendimg, err := imgfactory.ToBytes(img) 93 | if err != nil { 94 | ctx.SendChain(message.Text("ERROR: ", err)) 95 | return 96 | } 97 | if id := ctx.SendChain(message.ImageBytes(sendimg)); id.ID() == 0 { 98 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 99 | } 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /plugin/chess/db.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | var chessDB *gorm.DB 10 | 11 | // elo user elo info 12 | type elo struct { 13 | gorm.Model 14 | Uin int64 `gorm:"unique_index"` 15 | Name string 16 | Rate int 17 | } 18 | 19 | // pgn chess pgn info 20 | type pgn struct { 21 | gorm.Model 22 | Data string 23 | WhiteUin int64 24 | BlackUin int64 25 | WhiteName string 26 | BlackName string 27 | } 28 | 29 | // chessDBService 数据库服务 30 | type chessDBService struct { 31 | db *gorm.DB 32 | } 33 | 34 | // newDBService 创建数据库服务 35 | func newDBService() *chessDBService { 36 | return &chessDBService{ 37 | db: chessDB, 38 | } 39 | } 40 | 41 | // initDatabase init database 42 | func initDatabase(dbPath string) { 43 | var err error 44 | if _, err = os.Stat(dbPath); err != nil || os.IsNotExist(err) { 45 | f, err := os.Create(dbPath) 46 | if err != nil { 47 | panic(err) 48 | } 49 | defer f.Close() 50 | } 51 | chessDB, err = gorm.Open("sqlite3", dbPath) 52 | if err != nil { 53 | panic(err) 54 | } 55 | chessDB.AutoMigrate(&elo{}, &pgn{}) 56 | } 57 | 58 | // createELO 创建 ELO 59 | func (s *chessDBService) createELO(uin int64, name string, rate int) error { 60 | return s.db.Create(&elo{ 61 | Uin: uin, 62 | Name: name, 63 | Rate: rate, 64 | }).Error 65 | } 66 | 67 | // getELORateByUin 获取 ELO 等级分 68 | func (s *chessDBService) getELORateByUin(uin int64) (int, error) { 69 | var elo elo 70 | err := s.db.Select("rate").Where("uin = ?", uin).First(&elo).Error 71 | return elo.Rate, err 72 | } 73 | 74 | // getHighestRateList 获取最高的等级分列表 75 | func (s *chessDBService) getHighestRateList() ([]elo, error) { 76 | var eloList []elo 77 | err := s.db.Order("rate desc").Limit(10).Find(&eloList).Error 78 | return eloList, err 79 | } 80 | 81 | // updateELOByUin 更新 ELO 等级分 82 | func (s *chessDBService) updateELOByUin(uin int64, name string, rate int) error { 83 | return s.db.Model(&elo{}).Where("uin = ?", uin).Update("name", name).Update("rate", rate).Error 84 | } 85 | 86 | // cleanELOByUin 清空用户 ELO 等级分 87 | func (s *chessDBService) cleanELOByUin(uin int64) error { 88 | return s.db.Model(&elo{}).Where("uin = ?", uin).Update("rate", 100).Error 89 | } 90 | 91 | // createPGN 创建 PGN 92 | func (s *chessDBService) createPGN(data string, whiteUin int64, blackUin int64, whiteName string, blackName string) error { 93 | return s.db.Create(&pgn{ 94 | Data: data, 95 | WhiteUin: whiteUin, 96 | BlackUin: blackUin, 97 | WhiteName: whiteName, 98 | BlackName: blackName, 99 | }).Error 100 | } 101 | -------------------------------------------------------------------------------- /plugin/chess/elo.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // calculateNewRate calculate new rate of the player 8 | func calculateNewRate(whiteRate, blackRate int, whiteScore, blackScore float64) (int, int) { 9 | k := getKFactor(whiteRate, blackRate) 10 | exceptionWhite := calculateException(whiteRate, blackRate) 11 | exceptionBlack := calculateException(blackRate, whiteRate) 12 | whiteRate = calculateRate(whiteRate, whiteScore, exceptionWhite, k) 13 | blackRate = calculateRate(blackRate, blackScore, exceptionBlack, k) 14 | return whiteRate, blackRate 15 | } 16 | 17 | func calculateException(rate int, opponentRate int) float64 { 18 | return 1.0 / (1.0 + math.Pow(10.0, float64(opponentRate-rate)/400.0)) 19 | } 20 | 21 | func calculateRate(rate int, score float64, exception float64, k int) int { 22 | newRate := int(math.Round(float64(rate) + float64(k)*(score-exception))) 23 | if newRate < 1 { 24 | newRate = 1 25 | } 26 | return newRate 27 | } 28 | 29 | func getKFactor(rateA, rateB int) int { 30 | if rateA > 2400 && rateB > 2400 { 31 | return 16 32 | } 33 | if rateA > 2100 && rateB > 2100 { 34 | return 24 35 | } 36 | return 32 37 | } 38 | -------------------------------------------------------------------------------- /plugin/chess/elo_test.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestCalculateNewRate(t *testing.T) { 9 | type args struct { 10 | whiteRate int 11 | blackRate int 12 | whiteScore float64 13 | blackScore float64 14 | } 15 | tests := []struct { 16 | name string 17 | args args 18 | want int 19 | want1 int 20 | }{ 21 | { 22 | name: "test1", 23 | args: args{ 24 | whiteRate: 1613, 25 | blackRate: 1573, 26 | whiteScore: 0.5, 27 | blackScore: 0.5, 28 | }, 29 | want: 1611, 30 | want1: 1575, 31 | }, 32 | } 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | got, got1 := calculateNewRate(tt.args.whiteRate, tt.args.blackRate, tt.args.whiteScore, tt.args.blackScore) 36 | if got != tt.want { 37 | t.Errorf("CalculateNewRate() got = %v, want %v", got, tt.want) 38 | } 39 | if got1 != tt.want1 { 40 | t.Errorf("CalculateNewRate() got1 = %v, want %v", got1, tt.want1) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func Test_calculateException(t *testing.T) { 47 | type args struct { 48 | rate int 49 | opponentRate int 50 | } 51 | tests := []struct { 52 | name string 53 | args args 54 | want float64 55 | }{ 56 | { 57 | name: "test1", 58 | args: args{ 59 | rate: 1613, 60 | opponentRate: 1573, 61 | }, 62 | want: 0.5573116, 63 | }, 64 | { 65 | name: "test2", 66 | args: args{ 67 | rate: 1613, 68 | opponentRate: 1613, 69 | }, 70 | want: 0.5, 71 | }, 72 | } 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | if got := calculateException(tt.args.rate, tt.args.opponentRate); math.Abs(got-tt.want) > 0.0001 { 76 | t.Errorf("calculateException() = %v, want %v", got, tt.want) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /plugin/choose/choose.go: -------------------------------------------------------------------------------- 1 | // Package choose 选择困难症帮手 2 | package choose 3 | 4 | import ( 5 | "math/rand" 6 | "strconv" 7 | "strings" 8 | 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/message" 13 | ) 14 | 15 | func init() { 16 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 17 | DisableOnDefault: false, 18 | Brief: "选择困难症帮手", 19 | Help: "例: 选择可口可乐还是百事可乐\n" + 20 | "选择肯德基还是麦当劳还是必胜客", 21 | }) 22 | engine.OnPrefix("选择").SetBlock(true).Handle(handle) 23 | } 24 | func handle(ctx *zero.Ctx) { 25 | rawOptions := strings.Split(ctx.State["args"].(string), "还是") 26 | var options = make([]string, 0) 27 | for count, option := range rawOptions { 28 | options = append(options, strconv.Itoa(count+1)+", "+option) 29 | } 30 | result := rawOptions[rand.Intn(len(rawOptions))] 31 | name := ctx.Event.Sender.NickName 32 | ctx.SendChain(message.Text("> ", name, "\n", 33 | "你的选项有:", "\n", 34 | strings.Join(options, "\n"), "\n", 35 | "你最终会选: ", result, 36 | )) 37 | } 38 | -------------------------------------------------------------------------------- /plugin/chouxianghua/chouxianghua.go: -------------------------------------------------------------------------------- 1 | // Package chouxianghua 抽象话转化 2 | package chouxianghua 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | 11 | fcext "github.com/FloatTech/floatbox/ctxext" 12 | sql "github.com/FloatTech/sqlite" 13 | ctrl "github.com/FloatTech/zbpctrl" 14 | "github.com/FloatTech/zbputils/control" 15 | ) 16 | 17 | func init() { 18 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 19 | DisableOnDefault: false, 20 | Brief: "翻译为抽象话", 21 | Help: "- 抽象翻译xxx", 22 | PublicDataFolder: "ChouXiangHua", 23 | }) 24 | 25 | en.OnRegex("^抽象翻译((\\s|[\\r\\n]|[\\p{Han}\\p{P}A-Za-z0-9])+)$", 26 | fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 27 | db = sql.New(en.DataFolder() + "cxh.db") 28 | // os.RemoveAll(dbpath) 29 | _, _ = en.GetLazyData("cxh.db", true) 30 | err := db.Open(time.Hour) 31 | if err != nil { 32 | ctx.SendChain(message.Text("ERROR: ", err)) 33 | return false 34 | } 35 | err = db.Create("pinyin", &pinyin{}) 36 | if err != nil { 37 | ctx.SendChain(message.Text("ERROR: ", err)) 38 | return false 39 | } 40 | n, err := db.Count("pinyin") 41 | if err != nil { 42 | ctx.SendChain(message.Text("ERROR: ", err)) 43 | return false 44 | } 45 | logrus.Printf("[chouxianghua]读取%d条拼音", n) 46 | return true 47 | }), 48 | ).SetBlock(true). 49 | Handle(func(ctx *zero.Ctx) { 50 | r := cx(ctx.State["regex_matched"].([]string)[1]) 51 | ctx.SendChain(message.Text(r)) 52 | }) 53 | } 54 | 55 | func cx(s string) (r string) { 56 | h := []rune(s) 57 | for i := 0; i < len(h); i++ { 58 | if i < len(h)-1 { 59 | e := getEmojiByPronun(getPronunByDWord(h[i], h[i+1])) 60 | if e != "" { 61 | r += e 62 | i++ 63 | continue 64 | } 65 | } 66 | e := getEmojiByPronun(getPinyinByWord(string(h[i]))) 67 | if e != "" { 68 | r += e 69 | continue 70 | } 71 | r += string(h[i]) 72 | } 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /plugin/chouxianghua/model.go: -------------------------------------------------------------------------------- 1 | package chouxianghua 2 | 3 | import sql "github.com/FloatTech/sqlite" 4 | 5 | type pinyin struct { 6 | Word string `db:"word"` 7 | Pronun string `db:"pronunciation"` 8 | } 9 | type emoji struct { 10 | Pronun string `db:"pronunciation"` 11 | Emoji string `db:"emoji"` 12 | } 13 | 14 | var db sql.Sqlite 15 | 16 | func getPinyinByWord(word string) string { 17 | var p pinyin 18 | _ = db.Find("pinyin", &p, "WHERE word = ?", word) 19 | return p.Pronun 20 | } 21 | 22 | func getPronunByDWord(w0, w1 rune) string { 23 | return getPinyinByWord(string(w0)) + getPinyinByWord(string(w1)) 24 | } 25 | 26 | func getEmojiByPronun(pronun string) string { 27 | var e emoji 28 | _ = db.Find("emoji", &e, "WHERE pronunciation = ?", pronun) 29 | return e.Emoji 30 | } 31 | -------------------------------------------------------------------------------- /plugin/chrev/init.go: -------------------------------------------------------------------------------- 1 | // Package chrev 英文字符反转 2 | package chrev 3 | 4 | import ( 5 | "strings" 6 | 7 | ctrl "github.com/FloatTech/zbpctrl" 8 | "github.com/FloatTech/zbputils/control" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | ) 12 | 13 | func init() { 14 | // 初始化engine 15 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 16 | DisableOnDefault: false, 17 | Brief: "英文字符翻转", 18 | Help: "例: 翻转 I love you", 19 | }) 20 | // 处理字符翻转指令 21 | engine.OnRegex(`^翻转\s*([A-Za-z\s]*)$`).SetBlock(true). 22 | Handle(func(ctx *zero.Ctx) { 23 | // 获取需要翻转的字符串 24 | str := ctx.State["regex_matched"].([]string)[1] 25 | // 将字符顺序翻转 26 | tmp := strings.Builder{} 27 | for i := len(str) - 1; i >= 0; i-- { 28 | tmp.WriteRune(charMap[str[i]]) 29 | } 30 | // 发送翻转后的字符串 31 | ctx.SendChain(message.Text(tmp.String())) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /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/coser/coser.go: -------------------------------------------------------------------------------- 1 | // Package coser images 2 | package coser 3 | 4 | import ( 5 | "errors" 6 | "math/rand" 7 | "os" 8 | "time" 9 | 10 | "github.com/tidwall/gjson" 11 | 12 | zero "github.com/wdvxdr1123/ZeroBot" 13 | "github.com/wdvxdr1123/ZeroBot/message" 14 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 15 | 16 | "github.com/FloatTech/AnimeAPI/setu" 17 | "github.com/FloatTech/floatbox/file" 18 | "github.com/FloatTech/floatbox/web" 19 | ctrl "github.com/FloatTech/zbpctrl" 20 | "github.com/FloatTech/zbputils/control" 21 | "github.com/FloatTech/zbputils/ctxext" 22 | ) 23 | 24 | var ( 25 | ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36" 26 | coserURL = "https://picture.yinux.workers.dev" 27 | ) 28 | 29 | func init() { 30 | p, err := setu.NewPool(setu.DefaultPoolDir, 31 | func(s string) (string, error) { 32 | if s != "coser" { 33 | return "", errors.New("invalid call") 34 | } 35 | typ := setu.DefaultPoolDir + "/" + "coser" 36 | if file.IsNotExist(typ) { 37 | err := os.MkdirAll(typ, 0755) 38 | if err != nil { 39 | return "", err 40 | } 41 | } 42 | data, err := web.RequestDataWith(web.NewDefaultClient(), coserURL, "GET", "", ua, nil) 43 | if err != nil { 44 | return "", err 45 | } 46 | arr := gjson.Get(helper.BytesToString(data), "data.data").Array() 47 | if len(arr) == 0 { 48 | return "", errors.New("data is empty") 49 | } 50 | pic := arr[rand.Intn(len(arr))] 51 | return pic.String(), nil 52 | }, web.GetData, time.Minute) 53 | if err != nil { 54 | panic(err) 55 | } 56 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 57 | DisableOnDefault: false, 58 | Brief: "三次元coser", 59 | Help: "- coser", 60 | }).ApplySingle(ctxext.DefaultSingle).OnFullMatch("coser").SetBlock(true).Limit(ctxext.LimitByGroup). 61 | Handle(func(ctx *zero.Ctx) { 62 | pic, err := p.Roll("coser") 63 | if err != nil { 64 | ctx.SendChain(message.Text("ERROR: ", err)) 65 | return 66 | } 67 | if id := ctx.Send(message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image("file:///"+file.BOTPATH+"/"+pic))}).ID(); id == 0 { 68 | ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待")) 69 | } 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /plugin/cpstory/cpstory.go: -------------------------------------------------------------------------------- 1 | // Package cpstory cp短打 2 | package cpstory 3 | 4 | import ( 5 | "strings" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | 12 | fcext "github.com/FloatTech/floatbox/ctxext" 13 | "github.com/FloatTech/floatbox/math" 14 | sql "github.com/FloatTech/sqlite" 15 | ctrl "github.com/FloatTech/zbpctrl" 16 | "github.com/FloatTech/zbputils/control" 17 | ) 18 | 19 | func init() { 20 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 21 | DisableOnDefault: false, 22 | Brief: "cp短打", // 这里也许有更好的名字 23 | Help: "- 组cp[@xxx][@xxx]\n- 磕cp大老师 雪乃", 24 | PublicDataFolder: "CpStory", 25 | }) 26 | 27 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 28 | db = sql.New(engine.DataFolder() + "cp.db") 29 | // os.RemoveAll(dbpath) 30 | _, _ = engine.GetLazyData("cp.db", true) 31 | err := db.Open(time.Hour) 32 | if err != nil { 33 | ctx.SendChain(message.Text("ERROR: ", err)) 34 | return false 35 | } 36 | err = db.Create("cp_story", &cpstory{}) 37 | if err != nil { 38 | ctx.SendChain(message.Text("ERROR: ", err)) 39 | return false 40 | } 41 | n, err := db.Count("cp_story") 42 | if err != nil { 43 | ctx.SendChain(message.Text("ERROR: ", err)) 44 | return false 45 | } 46 | logrus.Printf("[cpstory]读取%d条故事", n) 47 | return true 48 | }) 49 | 50 | engine.OnRegex("^组cp.*?(\\d+).*?(\\d+)", zero.OnlyGroup, getdb).SetBlock(true).Handle(func(ctx *zero.Ctx) { 51 | cs := getRandomCpStory() 52 | gong := ctx.CardOrNickName(math.Str2Int64(ctx.State["regex_matched"].([]string)[1])) 53 | shou := ctx.CardOrNickName(math.Str2Int64(ctx.State["regex_matched"].([]string)[2])) 54 | text := strings.ReplaceAll(cs.Story, "<攻>", gong) 55 | text = strings.ReplaceAll(text, "<受>", shou) 56 | text = strings.ReplaceAll(text, cs.Gong, gong) 57 | text = strings.ReplaceAll(text, cs.Shou, shou) 58 | ctx.SendChain(message.Text(text)) 59 | }) 60 | engine.OnPrefix("磕cp", getdb).SetBlock(true).Handle(func(ctx *zero.Ctx) { 61 | cs := getRandomCpStory() 62 | params := strings.Split(ctx.State["args"].(string), " ") 63 | if len(params) < 2 { 64 | ctx.SendChain(message.Text(ctx.Event.MessageID), message.Text("请用空格分开两个人名")) 65 | } else { 66 | gong := params[0] 67 | shou := params[1] 68 | text := strings.ReplaceAll(cs.Story, "<攻>", gong) 69 | text = strings.ReplaceAll(text, "<受>", shou) 70 | text = strings.ReplaceAll(text, cs.Gong, gong) 71 | text = strings.ReplaceAll(text, cs.Shou, gong) 72 | ctx.SendChain(message.Text(text)) 73 | } 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /plugin/cpstory/model.go: -------------------------------------------------------------------------------- 1 | package cpstory 2 | 3 | import sql "github.com/FloatTech/sqlite" 4 | 5 | type cpstory struct { 6 | ID int64 `db:"id"` 7 | Gong string `db:"gong"` 8 | Shou string `db:"shou"` 9 | Story string `db:"story"` 10 | } 11 | 12 | var db sql.Sqlite 13 | 14 | func getRandomCpStory() (cs cpstory) { 15 | _ = db.Pick("cp_story", &cs) 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /plugin/curse/curse.go: -------------------------------------------------------------------------------- 1 | // Package curse 骂人插件(求骂,自卫) 2 | package curse 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/sirupsen/logrus" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | 11 | fcext "github.com/FloatTech/floatbox/ctxext" 12 | "github.com/FloatTech/floatbox/process" 13 | sql "github.com/FloatTech/sqlite" 14 | ctrl "github.com/FloatTech/zbpctrl" 15 | "github.com/FloatTech/zbputils/control" 16 | "github.com/FloatTech/zbputils/ctxext" 17 | ) 18 | 19 | const ( 20 | minLevel = "min" 21 | maxLevel = "max" 22 | ) 23 | 24 | func init() { 25 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 26 | DisableOnDefault: true, 27 | Brief: "骂人反击", 28 | Help: "- 骂我\n- 大力骂我", 29 | PublicDataFolder: "Curse", 30 | }) 31 | 32 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 33 | db = sql.New(engine.DataFolder() + "curse.db") 34 | _, err := engine.GetLazyData("curse.db", true) 35 | if err != nil { 36 | ctx.SendChain(message.Text("ERROR: ", err)) 37 | return false 38 | } 39 | err = db.Open(time.Hour) 40 | if err != nil { 41 | ctx.SendChain(message.Text("ERROR: ", err)) 42 | return false 43 | } 44 | err = db.Create("curse", &curse{}) 45 | if err != nil { 46 | ctx.SendChain(message.Text("ERROR: ", err)) 47 | return false 48 | } 49 | c, err := db.Count("curse") 50 | if err != nil { 51 | ctx.SendChain(message.Text("ERROR: ", err)) 52 | return false 53 | } 54 | logrus.Infoln("[curse]加载", c, "条骂人语录") 55 | return true 56 | }) 57 | 58 | engine.OnFullMatch("骂我", getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 59 | process.SleepAbout1sTo2s() 60 | text := getRandomCurseByLevel(minLevel).Text 61 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(text)) 62 | }) 63 | 64 | engine.OnFullMatch("大力骂我", getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 65 | process.SleepAbout1sTo2s() 66 | text := getRandomCurseByLevel(maxLevel).Text 67 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(text)) 68 | }) 69 | 70 | engine.OnKeywordGroup([]string{"他妈", "公交车", "你妈", "操", "屎", "去死", "快死", "我日", "逼", "尼玛", "艾滋", "癌症", "有病", "烦你", "你爹", "屮", "cnm"}, zero.OnlyToMe, getdb).SetBlock(true). 71 | Handle(func(ctx *zero.Ctx) { 72 | text := getRandomCurseByLevel(maxLevel).Text 73 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(text)) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /plugin/curse/model.go: -------------------------------------------------------------------------------- 1 | package curse 2 | 3 | import sql "github.com/FloatTech/sqlite" 4 | 5 | type curse struct { 6 | ID uint32 `db:"id"` 7 | Text string `db:"text"` 8 | Level string `db:"level"` 9 | } 10 | 11 | var db sql.Sqlite 12 | 13 | func getRandomCurseByLevel(level string) (c curse) { 14 | _ = db.Find("curse", &c, "WHERE level = ? ORDER BY RANDOM() limit 1", level) 15 | return 16 | } 17 | -------------------------------------------------------------------------------- /plugin/dailynews/dailynews.go: -------------------------------------------------------------------------------- 1 | // Package dailynews 今日早报 2 | package dailynews 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/binary" 6 | "github.com/FloatTech/floatbox/web" 7 | ctrl "github.com/FloatTech/zbpctrl" 8 | "github.com/FloatTech/zbputils/control" 9 | "github.com/tidwall/gjson" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | const api = "http://dwz.2xb.cn/zaob" 15 | 16 | func init() { 17 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 18 | DisableOnDefault: false, 19 | Brief: "今日早报", 20 | Help: "- 今日早报", 21 | PrivateDataFolder: "dailynews", 22 | }) 23 | 24 | engine.OnFullMatch(`今日早报`).SetBlock(true). 25 | Handle(func(ctx *zero.Ctx) { 26 | data, err := web.GetData(api) 27 | if err != nil { 28 | ctx.SendChain(message.Text("ERROR: ", err)) 29 | return 30 | } 31 | picURL := gjson.Get(binary.BytesToString(data), "imageUrl").String() 32 | ctx.SendChain(message.Image(picURL)) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /plugin/danbooru/main.go: -------------------------------------------------------------------------------- 1 | // Package deepdanbooru 二次元图片标签识别 2 | package deepdanbooru 3 | 4 | import ( 5 | "crypto/md5" 6 | "encoding/hex" 7 | "strings" 8 | 9 | "github.com/FloatTech/floatbox/file" 10 | "github.com/FloatTech/imgfactory" 11 | ctrl "github.com/FloatTech/zbpctrl" 12 | "github.com/FloatTech/zbputils/control" 13 | "github.com/FloatTech/zbputils/ctxext" 14 | zero "github.com/wdvxdr1123/ZeroBot" 15 | "github.com/wdvxdr1123/ZeroBot/message" 16 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 17 | ) 18 | 19 | func init() { // 插件主体 20 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 21 | DisableOnDefault: false, 22 | Brief: "二次元图片标签识别", 23 | Help: "- 鉴赏图片[图片]", 24 | PrivateDataFolder: "danbooru", 25 | }) 26 | 27 | cachefolder := engine.DataFolder() 28 | 29 | // 上传一张图进行评价 30 | engine.OnKeywordGroup([]string{"鉴赏图片"}, zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true). 31 | Handle(func(ctx *zero.Ctx) { 32 | ctx.SendChain(message.Text("少女祈祷中...")) 33 | for _, url := range ctx.State["image_url"].([]string) { 34 | t, st, err := tagurl("", url) 35 | if err != nil { 36 | ctx.SendChain(message.Text("ERROR: ", err)) 37 | return 38 | } 39 | digest := md5.Sum(helper.StringToBytes(url)) 40 | f := cachefolder + hex.EncodeToString(digest[:]) 41 | if file.IsNotExist(f) { 42 | _ = imgfactory.SavePNG2Path(f, t) 43 | } 44 | m := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image("file:///"+file.BOTPATH+"/"+f))} 45 | m = append(m, ctxext.FakeSenderForwardNode(ctx, message.Text("tags: ", strings.Join(st.tseq, ",")))) 46 | if id := ctx.Send(m).ID(); id == 0 { 47 | ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待")) 48 | } 49 | } 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /plugin/danbooru/tag.go: -------------------------------------------------------------------------------- 1 | package deepdanbooru 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "image" 9 | "net/url" 10 | "sort" 11 | 12 | "github.com/FloatTech/floatbox/file" 13 | "github.com/FloatTech/floatbox/web" 14 | "github.com/FloatTech/gg" 15 | "github.com/FloatTech/imgfactory" 16 | "github.com/FloatTech/zbputils/control" 17 | "github.com/FloatTech/zbputils/img/text" // jpg png gif 18 | _ "golang.org/x/image/webp" // webp 19 | ) 20 | 21 | const api = "https://nsfwtag.azurewebsites.net/api/tag?limit=0.5&url=" 22 | 23 | type sorttags struct { 24 | tags map[string]float64 25 | tseq []string 26 | } 27 | 28 | func newsorttags(tags map[string]float64) (s *sorttags) { 29 | t := make([]string, 0, len(tags)) 30 | for k := range tags { 31 | t = append(t, k) 32 | } 33 | return &sorttags{tags: tags, tseq: t} 34 | } 35 | 36 | func (s *sorttags) Len() int { 37 | return len(s.tags) 38 | } 39 | 40 | func (s *sorttags) Less(i, j int) bool { 41 | v1 := s.tseq[i] 42 | v2 := s.tseq[j] 43 | return s.tags[v1] >= s.tags[v2] 44 | } 45 | 46 | // Swap swaps the elements with indexes i and j. 47 | func (s *sorttags) Swap(i, j int) { 48 | s.tseq[j], s.tseq[i] = s.tseq[i], s.tseq[j] 49 | } 50 | 51 | func tagurl(name, u string) (im image.Image, st *sorttags, err error) { 52 | ch := make(chan []byte, 1) 53 | go func() { 54 | var data []byte 55 | data, err = web.GetData(u) 56 | ch <- data 57 | }() 58 | 59 | data, err := web.GetData(api + url.QueryEscape(u)) 60 | if err != nil { 61 | return 62 | } 63 | if len(data) < 4 { 64 | err = errors.New("data too short") 65 | return 66 | } 67 | tags := make(map[string]float64) 68 | err = json.Unmarshal(data[1:len(data)-1], &tags) 69 | if err != nil { 70 | return 71 | } 72 | 73 | longestlen := 0 74 | for k := range tags { 75 | if len(k) > longestlen { 76 | longestlen = len(k) 77 | } 78 | } 79 | longestlen++ 80 | 81 | st = newsorttags(tags) 82 | sort.Sort(st) 83 | 84 | boldfd, err := file.GetLazyData(text.BoldFontFile, control.Md5File, true) 85 | if err != nil { 86 | return 87 | } 88 | consfd, err := file.GetLazyData(text.ConsolasFontFile, control.Md5File, true) 89 | if err != nil { 90 | return 91 | } 92 | 93 | data = <-ch 94 | if err != nil { 95 | return 96 | } 97 | img, _, err := image.Decode(bytes.NewReader(data)) 98 | if err != nil { 99 | return 100 | } 101 | 102 | img = imgfactory.Limit(img, 1280, 720) 103 | 104 | canvas := gg.NewContext(img.Bounds().Size().X, img.Bounds().Size().Y+int(float64(img.Bounds().Size().X)*0.2)+len(tags)*img.Bounds().Size().X/25) 105 | canvas.SetRGB(1, 1, 1) 106 | canvas.Clear() 107 | canvas.DrawImage(img, 0, 0) 108 | if err = canvas.ParseFontFace(boldfd, float64(img.Bounds().Size().X)*0.1); err != nil { 109 | return 110 | } 111 | canvas.SetRGB(0, 0, 0) 112 | canvas.DrawString(name, float64(img.Bounds().Size().X)*0.02, float64(img.Bounds().Size().Y)+float64(img.Bounds().Size().X)*0.1) 113 | i := float64(img.Bounds().Size().Y) + float64(img.Bounds().Size().X)*0.2 114 | if err = canvas.ParseFontFace(consfd, float64(img.Bounds().Size().X)*0.04); err != nil { 115 | return 116 | } 117 | rate := float64(img.Bounds().Size().X) * 0.04 118 | for _, k := range st.tseq { 119 | canvas.DrawString(fmt.Sprintf("* %-*s -%.3f-", longestlen, k, tags[k]), float64(img.Bounds().Size().X)*0.04, i) 120 | i += rate 121 | } 122 | im = canvas.Image() 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /plugin/diana/bing.go: -------------------------------------------------------------------------------- 1 | // Package diana 虚拟偶像女团 A-SOUL 成员嘉然相关 2 | package diana 3 | 4 | import ( 5 | zero "github.com/wdvxdr1123/ZeroBot" 6 | "github.com/wdvxdr1123/ZeroBot/message" 7 | 8 | fcext "github.com/FloatTech/floatbox/ctxext" 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | 12 | "github.com/FloatTech/ZeroBot-Plugin/plugin/diana/data" 13 | ) 14 | 15 | var engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 16 | DisableOnDefault: false, 17 | Brief: "嘉然相关", // 也许使用常用功能当Brief更好 18 | Help: "- 小作文\n" + 19 | "- 发大病\n" + 20 | "- 教你一篇小作文[作文]", 21 | PublicDataFolder: "Diana", 22 | }) 23 | 24 | func init() { 25 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 26 | err := data.LoadText(engine.DataFolder() + "text.db") 27 | if err != nil { 28 | ctx.SendChain(message.Text("ERROR: ", err)) 29 | return false 30 | } 31 | return true 32 | }) 33 | 34 | // 随机发送一篇上面的小作文 35 | engine.OnFullMatch("小作文", getdb).SetBlock(true). 36 | Handle(func(ctx *zero.Ctx) { 37 | // 绕过第一行发病 38 | ctx.SendChain(message.Text(data.RandText())) 39 | }) 40 | // 逆天 41 | engine.OnFullMatch("发大病", getdb).SetBlock(true). 42 | Handle(func(ctx *zero.Ctx) { 43 | // 第一行是发病 44 | ctx.SendChain(message.Text(data.HentaiText())) 45 | }) 46 | // 增加小作文 47 | engine.OnRegex(`^教你一篇小作文(.*)$`, zero.AdminPermission, getdb).SetBlock(true). 48 | Handle(func(ctx *zero.Ctx) { 49 | err := data.AddText(ctx.State["regex_matched"].([]string)[1]) 50 | if err != nil { 51 | ctx.SendChain(message.Text("ERROR: ", err)) 52 | } else { 53 | ctx.SendChain(message.Text("记住啦!")) 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /plugin/diana/data/text.go: -------------------------------------------------------------------------------- 1 | // Package data 加载位于 datapath 的小作文 2 | package data 3 | 4 | import ( 5 | "crypto/md5" 6 | "encoding/binary" 7 | "time" 8 | 9 | binutils "github.com/FloatTech/floatbox/binary" 10 | "github.com/FloatTech/floatbox/file" 11 | sql "github.com/FloatTech/sqlite" 12 | "github.com/FloatTech/zbputils/control" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | var db = sql.Sqlite{} 17 | 18 | type text struct { 19 | ID int64 `db:"id"` 20 | Data string `db:"data"` 21 | } 22 | 23 | // LoadText 加载小作文 24 | func LoadText(dbfile string) error { 25 | _, err := file.GetLazyData(dbfile, control.Md5File, false) 26 | db = sql.New(dbfile) 27 | if err != nil { 28 | return err 29 | } 30 | err = db.Open(time.Hour) 31 | if err != nil { 32 | return err 33 | } 34 | err = db.Create("text", &text{}) 35 | if err != nil { 36 | return err 37 | } 38 | c, err := db.Count("text") 39 | if err != nil { 40 | return err 41 | } 42 | logrus.Printf("[Diana]读取%d条小作文", c) 43 | return nil 44 | } 45 | 46 | // AddText 添加小作文 47 | func AddText(txt string) error { 48 | s := md5.Sum(binutils.StringToBytes(txt)) 49 | i := binary.LittleEndian.Uint64(s[:8]) 50 | return db.Insert("text", &text{ID: int64(i), Data: txt}) 51 | } 52 | 53 | // RandText 随机小作文 54 | func RandText() string { 55 | var t text 56 | err := db.Pick("text", &t) 57 | if err != nil { 58 | return err.Error() 59 | } 60 | return t.Data 61 | } 62 | 63 | // HentaiText 发大病 64 | func HentaiText() string { 65 | var t text 66 | err := db.Find("text", &t, "WHERE id = -3802576048116006195") 67 | if err != nil { 68 | return err.Error() 69 | } 70 | return t.Data 71 | } 72 | -------------------------------------------------------------------------------- /plugin/dish/dish.go: -------------------------------------------------------------------------------- 1 | // Package dish 程序员做饭指南zbp版,数据来源Anduin2017/HowToCook 2 | package dish 3 | 4 | import ( 5 | "strings" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | 10 | sql "github.com/FloatTech/sqlite" 11 | ctrl "github.com/FloatTech/zbpctrl" 12 | zero "github.com/wdvxdr1123/ZeroBot" 13 | 14 | "github.com/FloatTech/zbputils/control" 15 | "github.com/FloatTech/zbputils/ctxext" 16 | "github.com/wdvxdr1123/ZeroBot/message" 17 | ) 18 | 19 | type dish struct { 20 | ID uint32 `db:"id"` 21 | Name string `db:"name"` 22 | Materials string `db:"materials"` 23 | Steps string `db:"steps"` 24 | } 25 | 26 | var ( 27 | db sql.Sqlite 28 | initialized = false 29 | ) 30 | 31 | func init() { 32 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 33 | DisableOnDefault: false, 34 | Brief: "程序员做饭指南", 35 | Help: "-怎么做[xxx]|烹饪[xxx]|随机菜谱|随便做点菜", 36 | PublicDataFolder: "Dish", 37 | }) 38 | 39 | db = sql.New(en.DataFolder() + "dishes.db") 40 | 41 | if _, err := en.GetLazyData("dishes.db", true); err != nil { 42 | logrus.Warnln("[dish]获取菜谱数据库文件失败") 43 | } else if err = db.Open(time.Hour); err != nil { 44 | logrus.Warnln("[dish]连接菜谱数据库失败") 45 | } else if err = db.Create("dish", &dish{}); err != nil { 46 | logrus.Warnln("[dish]同步菜谱数据表失败") 47 | } else if count, err := db.Count("dish"); err != nil { 48 | logrus.Warnln("[dish]统计菜谱数据失败") 49 | } else { 50 | logrus.Infoln("[dish]加载", count, "条菜谱") 51 | initialized = true 52 | } 53 | 54 | if !initialized { 55 | logrus.Warnln("[dish]插件未能成功初始化") 56 | } 57 | 58 | en.OnPrefixGroup([]string{"怎么做", "烹饪"}).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 59 | if !initialized { 60 | ctx.SendChain(message.Text("客官,本店暂未开业")) 61 | return 62 | } 63 | 64 | name := ctx.CardOrNickName(ctx.Event.UserID) 65 | dishName := ctx.State["args"].(string) 66 | 67 | if dishName == "" { 68 | return 69 | } 70 | 71 | if strings.Contains(dishName, "'") || 72 | strings.Contains(dishName, "\"") || 73 | strings.Contains(dishName, "\\") || 74 | strings.Contains(dishName, ";") { 75 | return 76 | } 77 | 78 | var d dish 79 | if err := db.Find("dish", &d, "WHERE name LIKE ?", "%"+dishName+"%"); err != nil { 80 | ctx.SendChain(message.Text("客官,本店没有" + dishName)) 81 | return 82 | } 83 | 84 | ctx.SendChain(message.Text( 85 | "已为客官", name, "找到", d.Name, "的做法辣!\n", 86 | "原材料:", d.Materials, "\n", 87 | "步骤:", d.Steps, 88 | )) 89 | }) 90 | 91 | en.OnFullMatchGroup([]string{"随机菜谱", "随便做点菜"}).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 92 | if !initialized { 93 | ctx.SendChain(message.Text("客官,本店暂未开业")) 94 | return 95 | } 96 | 97 | name := ctx.CardOrNickName(ctx.Event.UserID) 98 | var d dish 99 | if err := db.Pick("dish", &d); err != nil { 100 | ctx.SendChain(message.Text("小店好像出错了,暂时端不出菜来惹")) 101 | logrus.Warnln("[dish]随机菜谱请求出错:" + err.Error()) 102 | return 103 | } 104 | 105 | ctx.SendChain(message.Text( 106 | "已为客官", name, "送上", d.Name, "的做法:\n", 107 | "原材料:", d.Materials, "\n", 108 | "步骤:", d.Steps, 109 | )) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /plugin/emojimix/mix.go: -------------------------------------------------------------------------------- 1 | // Package emojimix 合成emoji 2 | package emojimix 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | "github.com/FloatTech/zbputils/ctxext" 12 | "github.com/sirupsen/logrus" 13 | zero "github.com/wdvxdr1123/ZeroBot" 14 | "github.com/wdvxdr1123/ZeroBot/message" 15 | ) 16 | 17 | const bed = "https://www.gstatic.com/android/keyboard/emojikitchen/%d/u%x/u%x_u%x.png" 18 | 19 | func init() { 20 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 21 | DisableOnDefault: false, 22 | Brief: "合成emoji", 23 | Help: "- [emoji][emoji]", 24 | }).OnMessage(match).SetBlock(true).Limit(ctxext.LimitByUser). 25 | Handle(func(ctx *zero.Ctx) { 26 | r := ctx.State["emojimix"].([]rune) 27 | logrus.Debugln("[emojimix] match:", r) 28 | r1, r2 := r[0], r[1] 29 | u1 := fmt.Sprintf(bed, emojis[r1], r1, r1, r2) 30 | u2 := fmt.Sprintf(bed, emojis[r2], r2, r2, r1) 31 | logrus.Debugln("[emojimix] u1:", u1) 32 | logrus.Debugln("[emojimix] u2:", u2) 33 | resp1, err := http.Head(u1) 34 | if err == nil { 35 | resp1.Body.Close() 36 | if resp1.StatusCode == http.StatusOK { 37 | ctx.SendChain(message.Image(u1)) 38 | return 39 | } 40 | } 41 | resp2, err := http.Head(u2) 42 | if err == nil { 43 | resp2.Body.Close() 44 | if resp2.StatusCode == http.StatusOK { 45 | ctx.SendChain(message.Image(u2)) 46 | return 47 | } 48 | } 49 | }) 50 | } 51 | 52 | func match(ctx *zero.Ctx) bool { 53 | logrus.Debugln("[emojimix] msg:", ctx.Event.Message) 54 | if len(ctx.Event.Message) == 2 { 55 | r1 := face2emoji(ctx.Event.Message[0]) 56 | if _, ok := emojis[r1]; !ok { 57 | return false 58 | } 59 | r2 := face2emoji(ctx.Event.Message[1]) 60 | if _, ok := emojis[r2]; !ok { 61 | return false 62 | } 63 | ctx.State["emojimix"] = []rune{r1, r2} 64 | return true 65 | } 66 | 67 | r := []rune(ctx.Event.RawMessage) 68 | logrus.Debugln("[emojimix] raw msg:", ctx.Event.RawMessage) 69 | if len(r) == 2 { 70 | if _, ok := emojis[r[0]]; !ok { 71 | return false 72 | } 73 | if _, ok := emojis[r[1]]; !ok { 74 | return false 75 | } 76 | ctx.State["emojimix"] = r 77 | return true 78 | } 79 | return false 80 | } 81 | 82 | func face2emoji(face message.Segment) rune { 83 | if face.Type == "text" { 84 | r := []rune(face.Data["text"]) 85 | if len(r) != 1 { 86 | return 0 87 | } 88 | return r[0] 89 | } 90 | if face.Type != "face" { 91 | return 0 92 | } 93 | id, err := strconv.Atoi(face.Data["id"]) 94 | if err != nil { 95 | return 0 96 | } 97 | if r, ok := message.Emoji[id]; ok { 98 | return r 99 | } 100 | return 0 101 | } 102 | -------------------------------------------------------------------------------- /plugin/event/data.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | type storage int64 4 | 5 | // 申请 6 | func (s *storage) setapply(on bool) { 7 | if on { 8 | *s |= 0b001 9 | } else { 10 | *s &= 0b110 11 | } 12 | } 13 | 14 | // 邀请 15 | func (s *storage) setinvite(on bool) { 16 | if on { 17 | *s |= 0b010 18 | } else { 19 | *s &= 0b101 20 | } 21 | } 22 | 23 | // 主人 24 | func (s *storage) setmaster(on bool) { 25 | if on { 26 | *s |= 0b100 27 | } else { 28 | *s &= 0b011 29 | } 30 | } 31 | 32 | // 申请 33 | func (s *storage) isapplyon() bool { 34 | return *s&0b001 > 0 35 | } 36 | 37 | // 邀请 38 | func (s *storage) isinviteon() bool { 39 | return *s&0b010 > 0 40 | } 41 | 42 | // 主人 43 | func (s *storage) ismasteroff() bool { 44 | return *s&0b100 > 0 45 | } 46 | -------------------------------------------------------------------------------- /plugin/font/main.go: -------------------------------------------------------------------------------- 1 | // Package font 渲染任意文字到图片 2 | package font 3 | 4 | import ( 5 | "bytes" 6 | "image" 7 | "image/color" 8 | "image/gif" 9 | "math/rand" 10 | "strings" 11 | 12 | "github.com/FloatTech/floatbox/binary" 13 | "github.com/FloatTech/floatbox/file" 14 | "github.com/FloatTech/gg" 15 | "github.com/FloatTech/imgfactory" 16 | ctrl "github.com/FloatTech/zbpctrl" 17 | "github.com/FloatTech/zbputils/control" 18 | "github.com/FloatTech/zbputils/ctxext" 19 | "github.com/FloatTech/zbputils/img/text" 20 | zero "github.com/wdvxdr1123/ZeroBot" 21 | "github.com/wdvxdr1123/ZeroBot/message" 22 | ) 23 | 24 | func init() { 25 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 26 | DisableOnDefault: false, 27 | Brief: "渲染任意文字到图片", 28 | Help: "- (用[字体])渲染(抖动)文字xxx\n可选字体: [终末体|终末变体|紫罗兰体|樱酥体|Consolas体|粗苹方体|未来荧黑体|Gugi体|八丸体|Impact体|猫啃体|苹方体]", 29 | }).OnRegex(`^(用.+)?渲染(抖动)?文字([\s\S]+)$`).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 30 | fnt := ctx.State["regex_matched"].([]string)[1] 31 | txt := ctx.State["regex_matched"].([]string)[3] 32 | switch fnt { 33 | case "用终末体": 34 | fnt = text.SyumatuFontFile 35 | case "用终末变体": 36 | fnt = text.NisiFontFile 37 | case "用紫罗兰体": 38 | fnt = text.VioletEvergardenFontFile 39 | case "用樱酥体": 40 | fnt = text.SakuraFontFile 41 | case "用Consolas体": 42 | fnt = text.ConsolasFontFile 43 | case "用粗苹方体": 44 | fnt = text.BoldFontFile 45 | case "用未来荧黑体": 46 | fnt = text.GlowSansFontFile 47 | case "用Gugi体": 48 | fnt = text.GugiRegularFontFile 49 | case "用八丸体": 50 | fnt = text.HachiMaruPopRegularFontFile 51 | case "用Impact体": 52 | fnt = text.ImpactFontFile 53 | case "用猫啃体": 54 | fnt = text.MaokenFontFile 55 | case "用苹方体": 56 | fallthrough 57 | default: 58 | fnt = text.FontFile 59 | } 60 | if ctx.State["regex_matched"].([]string)[2] == "" { 61 | b, err := text.RenderToBase64(txt, fnt, 400, 20) 62 | if err != nil { 63 | ctx.SendChain(message.Text("ERROR: ", err)) 64 | return 65 | } 66 | ctx.SendChain(message.Image("base64://" + binary.BytesToString(b))) 67 | return 68 | } 69 | nilx, nily := 1.0, 8.0 70 | s := []*image.NRGBA{} 71 | strlist := strings.Split(txt, "\n") 72 | data, err := file.GetLazyData(fnt, control.Md5File, true) 73 | if err != nil { 74 | ctx.SendChain(message.Text("ERROR: ", err)) 75 | return 76 | } 77 | // 获得画布预计 78 | testcov := gg.NewContext(1, 1) 79 | if err = testcov.ParseFontFace(data, 30); err != nil { 80 | ctx.SendChain(message.Text("ERROR: ", err)) 81 | return 82 | } 83 | // 取最长段 84 | txt = "" 85 | for _, v := range strlist { 86 | if len([]rune(v)) > len([]rune(txt)) { 87 | txt = v 88 | } 89 | } 90 | w, h := testcov.MeasureString(txt) 91 | for i := 0; i < 10; i++ { 92 | cov := gg.NewContext(int(w+float64(len([]rune(txt)))*nilx)+40, int(h+nily)*len(strlist)+30) 93 | cov.SetRGB(1, 1, 1) 94 | cov.Clear() 95 | if err = cov.ParseFontFace(data, 30); err != nil { 96 | ctx.SendChain(message.Text("ERROR: ", err)) 97 | return 98 | } 99 | cov.SetColor(color.NRGBA{R: 0, G: 0, B: 0, A: 127}) 100 | for k, v := range strlist { 101 | for kk, vv := range []rune(v) { 102 | x, y := cov.MeasureString(string([]rune(v)[:kk])) 103 | cov.DrawString(string(vv), x+float64(rand.Intn(5))+10+nilx, y+float64(rand.Intn(5))+15+float64(k)*(y+nily)) 104 | } 105 | } 106 | s = append(s, imgfactory.Size(cov.Image(), 0, 0).Image()) 107 | } 108 | var buf bytes.Buffer 109 | _ = gif.EncodeAll(&buf, imgfactory.MergeGif(5, s)) 110 | ctx.SendChain(message.ImageBytes(buf.Bytes())) 111 | }) 112 | } 113 | -------------------------------------------------------------------------------- /plugin/funny/laugh.go: -------------------------------------------------------------------------------- 1 | // Package funny 冷笑话 2 | package funny 3 | 4 | import ( 5 | "strings" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | 12 | fcext "github.com/FloatTech/floatbox/ctxext" 13 | sql "github.com/FloatTech/sqlite" 14 | ctrl "github.com/FloatTech/zbpctrl" 15 | "github.com/FloatTech/zbputils/control" 16 | "github.com/FloatTech/zbputils/ctxext" 17 | ) 18 | 19 | type joke struct { 20 | ID uint32 `db:"id"` 21 | Text string `db:"text"` 22 | } 23 | 24 | var db sql.Sqlite 25 | 26 | func init() { 27 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 28 | DisableOnDefault: false, 29 | Brief: "讲个笑话", 30 | Help: "- 讲个笑话[@xxx|qq号|人名] | 夸夸[@xxx|qq号|人名] ", 31 | PublicDataFolder: "Funny", 32 | }) 33 | 34 | en.OnPrefixGroup([]string{"讲个笑话", "夸夸"}, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 35 | db = sql.New(en.DataFolder() + "jokes.db") 36 | _, err := en.GetLazyData("jokes.db", true) 37 | if err != nil { 38 | ctx.SendChain(message.Text("ERROR: ", err)) 39 | return false 40 | } 41 | err = db.Open(time.Hour) 42 | if err != nil { 43 | ctx.SendChain(message.Text("ERROR: ", err)) 44 | return false 45 | } 46 | err = db.Create("jokes", &joke{}) 47 | if err != nil { 48 | ctx.SendChain(message.Text("ERROR: ", err)) 49 | return false 50 | } 51 | c, err := db.Count("jokes") 52 | if err != nil { 53 | ctx.SendChain(message.Text("ERROR: ", err)) 54 | return false 55 | } 56 | logrus.Infoln("[funny]加载", c, "个笑话") 57 | return true 58 | })).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 59 | // 获取名字 60 | name := ctx.NickName() 61 | var j joke 62 | err := db.Pick("jokes", &j) 63 | if err != nil { 64 | ctx.SendChain(message.Text("ERROR: ", err)) 65 | return 66 | } 67 | ctx.SendChain(message.Text(strings.ReplaceAll(j.Text, "%name", name))) 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /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/gif/README.md: -------------------------------------------------------------------------------- 1 | # ZeroBot-Plugin-Gif 2 | [ZeroBot QQ机器人](https://github.com/wdvxdr1123/ZeroBot)插件,可以制作各种沙雕gif图 3 | > 素材包地址: https://gitcode.net/anto_july/imagematerials 4 | 5 | ## 触发方式 6 | 1. [指令词]+[qq号] 如:爬123456 7 | 2. [指令词]+[图片] 如:爬[图片] 8 | 3. [指令词]+[艾特] 如:爬@小H 9 | 10 | ## 指令列表 11 | - [x] 爬 12 | - [x] 冲 13 | - [x] 摸 14 | - [x] 搓 15 | - [x] 拍 16 | - [x] 丢 17 | - [x] 敲 18 | - [x] 吃 19 | - [x] 啃 20 | - [x] 撕 21 | - [x] 蹭 22 | - [x] 灰度 23 | - [x] 上翻 24 | - [x] 下翻 25 | - [x] 左翻 26 | - [x] 右翻 27 | - [x] 反色 28 | - [x] 倒放 29 | - [x] 浮雕 30 | - [x] 打码 31 | - [x] 负片 32 | - [x] 旋转45 33 | - [x] 变形100 100 34 | - [x] 亲 35 | - [x] 娶|结婚申请|结婚登记 36 | - [x] 像只 37 | - [x] 阿尼亚喜欢 38 | - [x] 我永远喜欢|永远喜欢 39 | - [x] 像样的亲亲 40 | - [x] 国旗 41 | - [x] 不要靠近 42 | - [x] 万能表情|空白表情 43 | - [x] 采访 44 | - [x] 需要|你可能需要 45 | - [x] 这像画吗 46 | - [x] 小画家 47 | - [x] 完美 48 | - [x] 玩游戏 (应该使用透视变换) 49 | - [x] 出警 50 | - [x] 警察 51 | - [x] 舔|舔屏|prpr (应该使用透视变换) 52 | - [x] 安全感 53 | - [x] 精神支柱 54 | - [x] 想什么 55 | - [x] 墙纸 56 | - [x] 为什么at我 57 | - [x] 交个朋友 58 | - [x] 打工人|继续干活 59 | - [x] 兑换券 60 | - [ ] 捂脸 (使用了透视变换, 需要研究矩阵变换) 61 | - [x] 注意力涣散 62 | - [x] 垃圾桶|垃圾 63 | - [x] 锤 64 | - [x] 啾啾 65 | - [x] 2敲 66 | - [x] 听音乐 67 | - [ ] 群青 (需要mask) 68 | - [ ] 加载中 (需要mask) 69 | - [x] 永远爱你 (未加闪光) 70 | - [ ] 关注 (处理文字麻烦) 71 | - [x] 2拍 72 | - [x] 顶 73 | - [x] 捣 74 | - [x] 打拳 (未加闪光) 75 | - [ ] 复读 (处理文字麻烦) 76 | - [x] 滚 77 | - [x] 吸 78 | - [x] 扔 79 | - [x] 捶 80 | - [x] 紧贴 81 | - [ ] 膜拜 (使用了透视变换, 需要研究矩阵变换) 82 | - [ ] 小天使 (摆) 83 | - [ ] 一直 (摆) 84 | - [x] 转 85 | - [ ] 问问 (摆) 86 | - [ ] 典中典 (摆) 87 | - [ ] 震惊 (摆) 88 | - [ ] 哈哈镜 (摆) 89 | - [ ] 对称 (猎奇, 不整) 90 | - [x] 炖 91 | - [x] 2蹭 92 | - [x] 诶嘿 93 | - [x] 膜拜 94 | - [x] 吞 95 | - [x] 揍 96 | - [x] 给我变 97 | - [x] 玩一下 98 | - [x] 不要看 99 | - [x] 小天使 100 | - [x] 你的 101 | - [x] 我老婆 102 | - [x] 远离 103 | - [x] 抬棺 104 | -------------------------------------------------------------------------------- /plugin/gif/context.go: -------------------------------------------------------------------------------- 1 | package gif 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "sync" 7 | 8 | "github.com/FloatTech/floatbox/file" 9 | "github.com/FloatTech/floatbox/web" 10 | "github.com/FloatTech/imgfactory" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type context struct { 15 | usrdir string 16 | headimgsdir []string 17 | } 18 | 19 | func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) { 20 | defer wg.Done() 21 | target := datapath + `materials/` + name 22 | if file.IsNotExist(target) { 23 | data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/main/` + name) 24 | if err != nil { 25 | _ = os.Remove(target) 26 | exit(err) 27 | return 28 | } 29 | f, err := os.Create(target) 30 | if err != nil { 31 | exit(err) 32 | return 33 | } 34 | _, err = f.Write(data) 35 | _ = f.Close() 36 | if err != nil { 37 | _ = os.Remove(target) 38 | exit(err) 39 | return 40 | } 41 | logrus.Debugln("[gif] dl", name, "to", target, "succeeded") 42 | } else { 43 | logrus.Debugln("[gif] dl", name, "exists at", target) 44 | } 45 | *s = target 46 | } 47 | 48 | func dlblock(name string) (string, error) { 49 | target := datapath + `materials/` + name 50 | if file.IsNotExist(target) { 51 | data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/main/` + name) 52 | if err != nil { 53 | _ = os.Remove(target) 54 | return "", err 55 | } 56 | f, err := os.Create(target) 57 | if err != nil { 58 | return "", err 59 | } 60 | _, err = f.Write(data) 61 | _ = f.Close() 62 | if err != nil { 63 | _ = os.Remove(target) 64 | return "", err 65 | } 66 | logrus.Debugln("[gif] dl", name, "to", target, "succeeded") 67 | } else { 68 | logrus.Debugln("[gif] dl", name, "exists at", target) 69 | } 70 | return target, nil 71 | } 72 | 73 | func dlrange(prefix string, end int, wg *sync.WaitGroup, exit func(error)) []string { 74 | if file.IsNotExist(datapath + `materials/` + prefix) { 75 | err := os.MkdirAll(datapath+`materials/`+prefix, 0755) 76 | if err != nil { 77 | exit(err) 78 | return nil 79 | } 80 | } 81 | c := make([]string, end) 82 | for i := range c { 83 | wg.Add(1) 84 | go dlchan(prefix+"/"+strconv.Itoa(i)+".png", &c[i], wg, exit) 85 | } 86 | return c 87 | } 88 | 89 | // 新的上下文 90 | func newContext(user int64, atUser int64) *context { 91 | c := new(context) 92 | c.usrdir = datapath + "users/" + strconv.FormatInt(atUser, 10) + `/` 93 | _ = os.MkdirAll(c.usrdir, 0755) 94 | c.headimgsdir = make([]string, 2) 95 | c.headimgsdir[0] = datapath + "users/" + strconv.FormatInt(atUser, 10) + ".gif" 96 | c.headimgsdir[1] = datapath + "users/" + strconv.FormatInt(user, 10) + ".gif" 97 | return c 98 | } 99 | 100 | func loadFirstFrames(paths []string, size int) (imgs []*imgfactory.Factory, err error) { 101 | imgs = make([]*imgfactory.Factory, size) 102 | for i := range imgs { 103 | imgs[i], err = imgfactory.LoadFirstFrame(paths[i], 0, 0) 104 | if err != nil { 105 | return nil, err 106 | } 107 | } 108 | return imgs, nil 109 | } 110 | -------------------------------------------------------------------------------- /plugin/gif/logo.go: -------------------------------------------------------------------------------- 1 | package gif 2 | 3 | import ( 4 | "image" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/FloatTech/floatbox/file" 9 | "github.com/FloatTech/floatbox/process" 10 | "github.com/FloatTech/imgfactory" 11 | ) 12 | 13 | func (cc *context) prepareLogos(s ...string) error { 14 | for i, v := range s { 15 | _, err := strconv.Atoi(v) 16 | if err != nil { 17 | err = file.DownloadTo("https://gchat.qpic.cn/gchatpic_new//--"+strings.ToUpper(v)+"/0", cc.headimgsdir[i]) 18 | } else { 19 | err = file.DownloadTo("https://q4.qlogo.cn/g?b=qq&nk="+v+"&s=640", cc.headimgsdir[i]) 20 | } 21 | if err != nil { 22 | return err 23 | } 24 | process.SleepAbout1sTo2s() 25 | } 26 | return nil 27 | } 28 | 29 | func (cc *context) getLogo(w int, h int) (*image.NRGBA, error) { 30 | frame, err := imgfactory.LoadFirstFrame(cc.headimgsdir[0], w, h) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return frame.Circle(0).Image(), nil 35 | } 36 | 37 | func (cc *context) getLogo2(w int, h int) (*image.NRGBA, error) { 38 | frame, err := imgfactory.LoadFirstFrame(cc.headimgsdir[1], w, h) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return frame.Circle(0).Image(), nil 43 | } 44 | -------------------------------------------------------------------------------- /plugin/github/repo_searcher.go: -------------------------------------------------------------------------------- 1 | // Package github GitHub 仓库搜索 2 | package github 3 | 4 | import ( 5 | "net/http" 6 | "net/url" 7 | "strings" 8 | 9 | "github.com/FloatTech/floatbox/web" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/FloatTech/zbputils/control" 12 | "github.com/fumiama/terasu/http2" 13 | zero "github.com/wdvxdr1123/ZeroBot" 14 | "github.com/wdvxdr1123/ZeroBot/message" 15 | 16 | "github.com/tidwall/gjson" 17 | ) 18 | 19 | func init() { // 插件主体 20 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 21 | DisableOnDefault: false, 22 | Brief: "GitHub仓库搜索", 23 | Help: "- >github [xxx]\n" + 24 | "- >github -p [xxx]", 25 | }).OnRegex(`^>github\s(-.{1,10}? )?(.*)$`).SetBlock(true). 26 | Handle(func(ctx *zero.Ctx) { 27 | // 发送请求 28 | api, _ := url.Parse("https://api.github.com/search/repositories") 29 | api.RawQuery = url.Values{ 30 | "q": []string{ctx.State["regex_matched"].([]string)[2]}, 31 | }.Encode() 32 | body, err := web.RequestDataWithHeaders(&http2.DefaultClient, api.String(), "GET", func(r *http.Request) error { 33 | r.Header.Set("User-Agent", web.RandUA()) 34 | return nil 35 | }, nil) 36 | if err != nil { 37 | ctx.SendChain(message.Text("ERROR: ", err)) 38 | } 39 | // 解析请求 40 | info := gjson.ParseBytes(body) 41 | if info.Get("total_count").Int() == 0 { 42 | ctx.SendChain(message.Text("ERROR: 没有找到这样的仓库")) 43 | return 44 | } 45 | repo := info.Get("items.0") 46 | // 发送结果 47 | switch ctx.State["regex_matched"].([]string)[1] { 48 | case "-p ": // 图片模式 49 | ctx.SendChain( 50 | message.Image( 51 | "https://opengraph.githubassets.com/0/"+repo.Get("full_name").Str, 52 | ).Add("cache", 0), 53 | ) 54 | case "-t ": // 文字模式 55 | ctx.SendChain( 56 | message.Text( 57 | repo.Get("full_name").Str, "\n", 58 | "Description: ", 59 | repo.Get("description").Str, "\n", 60 | "Star/Fork/Issue: ", 61 | repo.Get("watchers").Int(), "/", repo.Get("forks").Int(), "/", repo.Get("open_issues").Int(), "\n", 62 | "Language: ", 63 | notnull(repo.Get("language").Str), "\n", 64 | "License: ", 65 | notnull(strings.ToUpper(repo.Get("license.key").Str)), "\n", 66 | "Last pushed: ", 67 | repo.Get("pushed_at").Str, "\n", 68 | "Jump: ", 69 | repo.Get("html_url").Str, "\n", 70 | ), 71 | ) 72 | default: // 文字模式 73 | ctx.SendChain( 74 | message.Text( 75 | repo.Get("full_name").Str, "\n", 76 | "Description: ", 77 | repo.Get("description").Str, "\n", 78 | "Star/Fork/Issue: ", 79 | repo.Get("watchers").Int(), "/", repo.Get("forks").Int(), "/", repo.Get("open_issues").Int(), "\n", 80 | "Language: ", 81 | notnull(repo.Get("language").Str), "\n", 82 | "License: ", 83 | notnull(strings.ToUpper(repo.Get("license.key").Str)), "\n", 84 | "Last pushed: ", 85 | repo.Get("pushed_at").Str, "\n", 86 | "Jump: ", 87 | repo.Get("html_url").Str, "\n", 88 | ), 89 | message.Image( 90 | "https://opengraph.githubassets.com/0/"+repo.Get("full_name").Str, 91 | ).Add("cache", 0), 92 | ) 93 | } 94 | }) 95 | } 96 | 97 | // notnull 如果传入文本为空,则返回默认值 98 | func notnull(text string) string { 99 | if text == "" { 100 | return "None" 101 | } 102 | return text 103 | } 104 | -------------------------------------------------------------------------------- /plugin/hitokoto/model.go: -------------------------------------------------------------------------------- 1 | package hitokoto 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | // hdb 表情包数据库全局变量 10 | var hdb *hitokotodb 11 | 12 | // hitokotodb 表情包数据库 13 | type hitokotodb gorm.DB 14 | 15 | // initialize 初始化 16 | func initialize(dbpath string) (db *hitokotodb, err error) { 17 | if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) { 18 | // 生成文件 19 | f, err := os.Create(dbpath) 20 | if err != nil { 21 | return nil, err 22 | } 23 | _ = f.Close() 24 | } 25 | gdb, err := gorm.Open("sqlite3", dbpath) 26 | if err != nil { 27 | return 28 | } 29 | gdb.AutoMigrate(&hitokoto{}) 30 | return (*hitokotodb)(gdb), nil 31 | } 32 | 33 | type hitokoto struct { 34 | ID int `json:"id" gorm:"column:id;primary_key"` 35 | Hitokoto string `json:"hitokoto" gorm:"column:hitokoto"` 36 | Type string `json:"type" gorm:"column:type"` 37 | From string `json:"from" gorm:"column:from"` 38 | FromWho string `json:"from_who" gorm:"column:from_who"` 39 | Creator string `json:"creator" gorm:"column:creator"` 40 | CreatorUID int `json:"creator_uid" gorm:"column:creator_uid"` 41 | Reviewer int `json:"reviewer" gorm:"column:reviewer"` 42 | UUID string `json:"uuid" gorm:"column:uuid"` 43 | CreatedAt string `json:"created_at" gorm:"column:created_at"` 44 | Category string `json:"catogory" gorm:"column:category"` 45 | } 46 | 47 | // TableName 表名 48 | func (hitokoto) TableName() string { 49 | return "hitokoto" 50 | } 51 | 52 | func (hdb *hitokotodb) getByKey(key string) (b []hitokoto, err error) { 53 | db := (*gorm.DB)(hdb) 54 | err = db.Where("hitokoto like ?", "%"+key+"%").Find(&b).Error 55 | return 56 | } 57 | 58 | type result struct { 59 | Category string 60 | Count int 61 | } 62 | 63 | func (hdb *hitokotodb) getAllCategory() (results []result, err error) { 64 | db := (*gorm.DB)(hdb) 65 | err = db.Table("hitokoto").Select("category, count(1) as count").Group("category").Scan(&results).Error 66 | return 67 | } 68 | 69 | func (hdb *hitokotodb) getByCategory(category string) (h []hitokoto, err error) { 70 | db := (*gorm.DB)(hdb) 71 | err = db.Where("category = ?", category).Find(&h).Error 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /plugin/inject/main.go: -------------------------------------------------------------------------------- 1 | // Package inject 注入指令 2 | package inject 3 | 4 | import ( 5 | ctrl "github.com/FloatTech/zbpctrl" 6 | "github.com/FloatTech/zbputils/control" 7 | zero "github.com/wdvxdr1123/ZeroBot" 8 | "github.com/wdvxdr1123/ZeroBot/message" 9 | ) 10 | 11 | func init() { 12 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 13 | DisableOnDefault: false, 14 | Brief: "注入指令", 15 | Help: "- run[CQ码]", 16 | }) 17 | // 运行 CQ 码 18 | en.OnPrefix("run", zero.SuperUserPermission).SetBlock(true). 19 | Handle(func(ctx *zero.Ctx) { 20 | // 可注入,权限为主人 21 | ctx.Send(message.UnescapeCQCodeText(ctx.State["args"].(string))) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /plugin/jandan/data.go: -------------------------------------------------------------------------------- 1 | package jandan 2 | 3 | import ( 4 | "sync" 5 | 6 | sql "github.com/FloatTech/sqlite" 7 | ) 8 | 9 | var db sql.Sqlite 10 | var mu sync.RWMutex 11 | 12 | type picture struct { 13 | ID uint64 `db:"id"` 14 | URL string `db:"url"` 15 | } 16 | 17 | func getRandomPicture() (u string, err error) { 18 | var p picture 19 | mu.RLock() 20 | err = db.Pick("picture", &p) 21 | mu.RUnlock() 22 | u = p.URL 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /plugin/jandan/jandan.go: -------------------------------------------------------------------------------- 1 | // Package jandan 煎蛋网无聊图 2 | package jandan 3 | 4 | import ( 5 | "fmt" 6 | "hash/crc64" 7 | "regexp" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/FloatTech/floatbox/binary" 12 | fcext "github.com/FloatTech/floatbox/ctxext" 13 | sql "github.com/FloatTech/sqlite" 14 | ctrl "github.com/FloatTech/zbpctrl" 15 | "github.com/FloatTech/zbputils/control" 16 | "github.com/antchfx/htmlquery" 17 | "github.com/sirupsen/logrus" 18 | zero "github.com/wdvxdr1123/ZeroBot" 19 | "github.com/wdvxdr1123/ZeroBot/message" 20 | ) 21 | 22 | const ( 23 | api = "http://jandan.net/pic" 24 | ) 25 | 26 | func init() { 27 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 28 | DisableOnDefault: false, 29 | Brief: "煎蛋网无聊图", 30 | Help: "- 来份[屌|弔|吊]图\n- 更新[屌|弔|吊]图\n", 31 | PublicDataFolder: "Jandan", 32 | }) 33 | 34 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 35 | db = sql.New(engine.DataFolder() + "pics.db") 36 | _, _ = engine.GetLazyData("pics.db", false) 37 | err := db.Open(time.Hour) 38 | if err != nil { 39 | ctx.SendChain(message.Text("ERROR: ", err)) 40 | return false 41 | } 42 | err = db.Create("picture", &picture{}) 43 | if err != nil { 44 | ctx.SendChain(message.Text("ERROR: ", err)) 45 | return false 46 | } 47 | n, err := db.Count("picture") 48 | if err != nil { 49 | ctx.SendChain(message.Text("ERROR: ", err)) 50 | return false 51 | } 52 | logrus.Printf("[jandan]读取%d张图片", n) 53 | return true 54 | }) 55 | 56 | engine.OnRegex(`来份[屌|弔|吊]图`, getdb).SetBlock(true). 57 | Handle(func(ctx *zero.Ctx) { 58 | u, err := getRandomPicture() 59 | if err != nil { 60 | ctx.SendChain(message.Text("ERROR: ", err)) 61 | return 62 | } 63 | ctx.SendChain(message.Image(u)) 64 | }) 65 | 66 | engine.OnRegex(`更新[屌|弔|吊]图`, zero.SuperUserPermission, getdb).SetBlock(true). 67 | Handle(func(ctx *zero.Ctx) { 68 | ctx.Send("少女更新中...") 69 | webpageURL := api 70 | doc, err := htmlquery.LoadURL(webpageURL) 71 | if err != nil { 72 | ctx.SendChain(message.Text("ERROR: ", err)) 73 | return 74 | } 75 | re := regexp.MustCompile(`\d+`) 76 | pageTotal, err := strconv.Atoi(re.FindString(htmlquery.FindOne(doc, "//*[@id='comments']/div[2]/div/span[@class='current-comment-page']/text()").Data)) 77 | if err != nil { 78 | ctx.SendChain(message.Text("ERROR: ", err)) 79 | return 80 | } 81 | LOOP: 82 | for i := 0; i < pageTotal; i++ { 83 | logrus.Debugln("[jandan]", fmt.Sprintf("处理第%d/%d页...", i, pageTotal)) 84 | doc, err = htmlquery.LoadURL(webpageURL) 85 | if err != nil { 86 | ctx.SendChain(message.Text("ERROR: ", err)) 87 | return 88 | } 89 | picList, err := htmlquery.QueryAll(doc, "//*[@class='view_img_link']") 90 | if err != nil { 91 | ctx.SendChain(message.Text("ERROR: ", err)) 92 | return 93 | } 94 | if len(picList) != 0 { 95 | for _, v := range picList { 96 | u := "https:" + v.Attr[0].Val 97 | i := crc64.Checksum(binary.StringToBytes(u), crc64.MakeTable(crc64.ISO)) 98 | mu.RLock() 99 | ok := db.CanFind("picture", "WHERE id = ?", i) 100 | mu.RUnlock() 101 | if !ok { 102 | mu.Lock() 103 | _ = db.Insert("picture", &picture{ID: i, URL: u}) 104 | mu.Unlock() 105 | } else { 106 | // 开始重复,说明之后都是重复 107 | break LOOP 108 | } 109 | } 110 | } 111 | if i != pageTotal-1 { 112 | webpageURL = "https:" + htmlquery.FindOne(doc, "//*[@id='comments']/div[@class='comments']/div[@class='cp-pagenavi']/a[@class='previous-comment-page']").Attr[1].Val 113 | } 114 | } 115 | ctx.Send("更新完成!") 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /plugin/jptingroom/jptingroom.go: -------------------------------------------------------------------------------- 1 | // Package jptingroom 日语听力学习材料 2 | package jptingroom 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/FloatTech/floatbox/binary" 8 | fcext "github.com/FloatTech/floatbox/ctxext" 9 | sql "github.com/FloatTech/sqlite" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/FloatTech/zbputils/control" 12 | "github.com/FloatTech/zbputils/img/text" 13 | log "github.com/sirupsen/logrus" 14 | zero "github.com/wdvxdr1123/ZeroBot" 15 | "github.com/wdvxdr1123/ZeroBot/message" 16 | ) 17 | 18 | func init() { // 插件主体 19 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 20 | DisableOnDefault: false, 21 | Brief: "日语听力学习材料", 22 | Help: "- 随机日语听力\n" + 23 | "- 随机日语歌曲\n" + 24 | "- 日语听力 xxx\n" + 25 | "- 日语歌曲 xxx\n", 26 | PublicDataFolder: "Jptingroom", 27 | }) 28 | 29 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 30 | db = sql.New(engine.DataFolder() + "item.db") 31 | _, err := engine.GetLazyData("item.db", true) 32 | if err != nil { 33 | ctx.SendChain(message.Text("ERROR: ", err)) 34 | return false 35 | } 36 | err = db.Open(time.Hour) 37 | if err != nil { 38 | ctx.SendChain(message.Text("ERROR: ", err)) 39 | return false 40 | } 41 | err = db.Create("item", &item{}) 42 | if err != nil { 43 | ctx.SendChain(message.Text("ERROR: ", err)) 44 | return false 45 | } 46 | n, err := db.Count("item") 47 | if err != nil { 48 | ctx.SendChain(message.Text("ERROR: ", err)) 49 | return false 50 | } 51 | log.Infof("[jptingroom]读取%d条日语听力材料", n) 52 | return true 53 | }) 54 | // 开启 55 | engine.OnFullMatchGroup([]string{"随机日语听力", "随机日语歌曲"}, getdb).SetBlock(true). 56 | Handle(func(ctx *zero.Ctx) { 57 | matched := ctx.State["matched"].(string) 58 | var t item 59 | switch matched { 60 | case "随机日语听力": 61 | t = getRandomAudioByCategory("tingli") 62 | case "随机日语歌曲": 63 | t = getRandomAudioByCategory("gequ") 64 | default: 65 | } 66 | if t.AudioURL == "" { 67 | ctx.SendChain(message.Text("未能找到相关材料")) 68 | return 69 | } 70 | ctx.SendChain(message.Record(t.AudioURL)) 71 | content := t.Title + "\n\n" + t.Content 72 | data, err := text.RenderToBase64(content, text.FontFile, 400, 20) 73 | if err != nil { 74 | ctx.SendChain(message.Text("ERROR: ", err)) 75 | return 76 | } 77 | if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 { 78 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 79 | } 80 | }) 81 | engine.OnRegex(`日语(听力|歌曲)\s?([一-龥A-Za-z0-9ぁ-んァ-ヶ]{1,50})$`, getdb).SetBlock(true). 82 | Handle(func(ctx *zero.Ctx) { 83 | regexMatched := ctx.State["regex_matched"].([]string) 84 | var t item 85 | switch regexMatched[1] { 86 | case "听力": 87 | t = getRandomAudioByCategoryAndKeyword("tingli", regexMatched[2]) 88 | case "歌曲": 89 | t = getRandomAudioByCategoryAndKeyword("gequ", regexMatched[2]) 90 | default: 91 | } 92 | if t.AudioURL == "" { 93 | ctx.SendChain(message.Text("未能找到相关材料")) 94 | return 95 | } 96 | content := t.Title + "\n\n" + t.Content 97 | data, err := text.RenderToBase64(content, text.FontFile, 400, 20) 98 | if err != nil { 99 | ctx.SendChain(message.Text("ERROR: ", err)) 100 | return 101 | } 102 | if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 { 103 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 104 | } 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /plugin/jptingroom/model.go: -------------------------------------------------------------------------------- 1 | package jptingroom 2 | 3 | import ( 4 | "time" 5 | 6 | sql "github.com/FloatTech/sqlite" 7 | ) 8 | 9 | type item struct { 10 | ID int64 `db:"id"` 11 | Title string `db:"title"` 12 | PageURL string `db:"page_url"` 13 | Category string `db:"category"` 14 | Intro string `db:"intro"` 15 | AudioURL string `db:"audio_url"` 16 | Content string `db:"content"` 17 | Datetime time.Time `db:"datetime"` 18 | } 19 | 20 | var db sql.Sqlite 21 | 22 | func getRandomAudioByCategory(category string) (t item) { 23 | _ = db.Find("item", &t, "WHERE category = ? ORDER BY RANDOM() limit 1", category) 24 | return 25 | } 26 | 27 | func getRandomAudioByCategoryAndKeyword(category string, keyword string) (t item) { 28 | _ = db.Find("item", &t, 29 | "WHERE category = ? and (title LIKE ? OR content LIKE ?) ORDER BY RANDOM() limit 1", 30 | category, "%"+keyword+"%", "%"+keyword+"%") 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /plugin/kfccrazythursday/kfccrazythursday.go: -------------------------------------------------------------------------------- 1 | // Package kfccrazythursday 疯狂星期四 2 | package kfccrazythursday 3 | 4 | import ( 5 | "encoding/json" 6 | 7 | "github.com/FloatTech/floatbox/web" 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | const ( 15 | crazyURL = "https://api.pearktrue.cn/api/kfc/" 16 | ) 17 | 18 | type crazyResponse struct { 19 | Code int `json:"code"` 20 | Msg string `json:"msg"` 21 | Text string `json:"text"` 22 | } 23 | 24 | func init() { 25 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 26 | DisableOnDefault: false, 27 | Brief: "疯狂星期四", 28 | Help: "疯狂星期四\n", 29 | }) 30 | engine.OnFullMatch("疯狂星期四").SetBlock(true).Handle(func(ctx *zero.Ctx) { 31 | data, err := web.GetData(crazyURL) 32 | if err != nil { 33 | ctx.SendChain(message.Text("ERROR: ", err)) 34 | return 35 | } 36 | 37 | var resp crazyResponse 38 | if err := json.Unmarshal(data, &resp); err != nil { 39 | ctx.SendChain(message.Text("JSON解析失败: ", err)) 40 | return 41 | } 42 | 43 | if resp.Code != 200 { 44 | ctx.SendChain(message.Text("API返回错误: ", resp.Msg)) 45 | return 46 | } 47 | 48 | ctx.SendChain(message.Text(resp.Text)) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /plugin/lolicon/lolicon.go: -------------------------------------------------------------------------------- 1 | // Package lolicon 基于 https://api.lolicon.app 随机图片 2 | package lolicon 3 | 4 | import ( 5 | "encoding/base64" 6 | "errors" 7 | "net/url" 8 | "strings" 9 | "time" 10 | 11 | "github.com/tidwall/gjson" 12 | zero "github.com/wdvxdr1123/ZeroBot" 13 | "github.com/wdvxdr1123/ZeroBot/message" 14 | 15 | "github.com/FloatTech/floatbox/math" 16 | "github.com/FloatTech/floatbox/process" 17 | "github.com/FloatTech/floatbox/web" 18 | ctrl "github.com/FloatTech/zbpctrl" 19 | "github.com/FloatTech/zbputils/control" 20 | "github.com/FloatTech/zbputils/ctxext" 21 | ) 22 | 23 | const ( 24 | api = "https://api.lolicon.app/setu/v2" 25 | capacity = 10 26 | ) 27 | 28 | var ( 29 | queue = make(chan string, capacity) 30 | customapi = "" 31 | ) 32 | 33 | func init() { 34 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 35 | DisableOnDefault: false, 36 | Brief: "随机图片", 37 | Help: "- 随机图片\n" + 38 | "- 随机图片 萝莉|少女\n" + 39 | "- 设置随机图片地址[http...]", 40 | }).ApplySingle(ctxext.DefaultSingle) 41 | en.OnPrefix("随机图片").Limit(ctxext.LimitByUser).SetBlock(true). 42 | Handle(func(ctx *zero.Ctx) { 43 | if imgtype := strings.TrimSpace(ctx.State["args"].(string)); imgtype != "" { 44 | imageurl, err := getimgurl(api + "?tag=" + url.QueryEscape(imgtype)) 45 | if err != nil { 46 | ctx.SendChain(message.Text("ERROR: ", err)) 47 | return 48 | } 49 | if id := ctx.Send(message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image(imageurl))}).ID(); id == 0 { 50 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 51 | } 52 | return 53 | } 54 | go func() { 55 | for i := 0; i < math.Min(cap(queue)-len(queue), 2); i++ { 56 | if customapi != "" { 57 | data, err := web.GetData(customapi) 58 | if err != nil { 59 | ctx.SendChain(message.Text("ERROR: ", err)) 60 | continue 61 | } 62 | queue <- "base64://" + base64.StdEncoding.EncodeToString(data) 63 | continue 64 | } 65 | imageurl, err := getimgurl(api) 66 | if err != nil { 67 | ctx.SendChain(message.Text("ERROR: ", err)) 68 | continue 69 | } 70 | queue <- imageurl 71 | } 72 | }() 73 | select { 74 | case <-time.After(time.Minute): 75 | ctx.SendChain(message.Text("ERROR: 等待填充,请稍后再试......")) 76 | case img := <-queue: 77 | if id := ctx.Send(message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image(img))}).ID(); id == 0 { 78 | process.SleepAbout1sTo2s() 79 | if id := ctx.Send(message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image(img).Add("cache", "0"))}).ID(); id == 0 { 80 | ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待")) 81 | } 82 | } 83 | } 84 | }) 85 | en.OnPrefix("设置随机图片地址", zero.SuperUserPermission).SetBlock(true). 86 | Handle(func(ctx *zero.Ctx) { 87 | u := strings.TrimSpace(ctx.State["args"].(string)) 88 | ctx.SendChain(message.Text("成功设置随机图片地址为", u)) 89 | customapi = u 90 | }) 91 | } 92 | 93 | func getimgurl(url string) (string, error) { 94 | data, err := web.GetData(url) 95 | if err != nil { 96 | return "", err 97 | } 98 | json := gjson.ParseBytes(data) 99 | if e := json.Get("error").Str; e != "" { 100 | return "", errors.New(e) 101 | } 102 | var imageurl string 103 | if imageurl = json.Get("data.0.urls.original").Str; imageurl == "" { 104 | return "", errors.New("未找到相关内容, 换个tag试试吧") 105 | } 106 | return imageurl, nil 107 | } 108 | -------------------------------------------------------------------------------- /plugin/magicprompt/magicprompt.go: -------------------------------------------------------------------------------- 1 | // Package magicprompt MagicPrompt-Stable-Diffusion吟唱提示 2 | package magicprompt 3 | 4 | import ( 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "strings" 9 | "time" 10 | 11 | hf "github.com/FloatTech/AnimeAPI/huggingface" 12 | ctrl "github.com/FloatTech/zbpctrl" 13 | "github.com/FloatTech/zbputils/control" 14 | "github.com/FloatTech/zbputils/ctxext" 15 | "github.com/RomiChan/websocket" 16 | "github.com/tidwall/gjson" 17 | zero "github.com/wdvxdr1123/ZeroBot" 18 | "github.com/wdvxdr1123/ZeroBot/message" 19 | ) 20 | 21 | const ( 22 | magicpromptRepo = "Gustavosta/MagicPrompt-Stable-Diffusion" 23 | ) 24 | 25 | func init() { // 插件主体 26 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 27 | DisableOnDefault: false, 28 | Brief: "MagicPrompt-Stable-Diffusion吟唱提示", 29 | Help: "- 吟唱提示 xxx", 30 | PrivateDataFolder: "magicprompt", 31 | }) 32 | 33 | // 开启 34 | engine.OnPrefixGroup([]string{`吟唱提示`, "吟唱补全"}).SetBlock(true). 35 | Handle(func(ctx *zero.Ctx) { 36 | _ctx, _cancel := context.WithTimeout(context.Background(), hf.TimeoutMax*time.Second) 37 | defer _cancel() 38 | ctx.SendChain(message.Text("少女祈祷中...")) 39 | 40 | magicpromptURL := fmt.Sprintf(hf.WssJoinPath, magicpromptRepo) 41 | args := ctx.State["args"].(string) 42 | c, _, err := websocket.DefaultDialer.Dial(magicpromptURL, nil) 43 | if err != nil { 44 | ctx.SendChain(message.Text("ERROR: ", err)) 45 | return 46 | } 47 | defer c.Close() 48 | 49 | r := hf.PushRequest{ 50 | FnIndex: 0, 51 | Data: []interface{}{args}, 52 | } 53 | b, err := json.Marshal(r) 54 | if err != nil { 55 | ctx.SendChain(message.Text("ERROR: ", err)) 56 | return 57 | } 58 | 59 | err = c.WriteMessage(websocket.TextMessage, b) 60 | if err != nil { 61 | ctx.SendChain(message.Text("ERROR: ", err)) 62 | return 63 | } 64 | t := time.NewTicker(time.Second * 1) 65 | defer t.Stop() 66 | for { 67 | select { 68 | case <-t.C: 69 | _, data, err := c.ReadMessage() 70 | if err != nil { 71 | ctx.SendChain(message.Text("ERROR: ", err)) 72 | return 73 | } 74 | j := gjson.ParseBytes(data) 75 | if j.Get("msg").String() == hf.WssCompleteStatus { 76 | m := message.Message{} 77 | for _, v := range strings.Split(j.Get("output.data.0").String(), "\n\n") { 78 | m = append(m, ctxext.FakeSenderForwardNode(ctx, message.Text(v))) 79 | } 80 | if id := ctx.Send(m).ID(); id == 0 { 81 | ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待")) 82 | } 83 | return 84 | } 85 | case <-_ctx.Done(): 86 | ctx.SendChain(message.Text("ERROR: 吟唱提示指令超时")) 87 | return 88 | } 89 | } 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /plugin/manager/gist.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/sirupsen/logrus" 11 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 12 | 13 | "github.com/FloatTech/floatbox/math" 14 | "github.com/FloatTech/floatbox/web" 15 | ) 16 | 17 | // user hash file 18 | const gistraw = "https://gist.githubusercontent.com/%s/%s/raw/%s" 19 | 20 | func checkNewUser(qq, gid int64, ghun, hash string) (bool, string) { 21 | if db.CanFind("member", "WHERE ghun = ?", ghun) { 22 | return false, "该github用户已入群" 23 | } 24 | gidsum := md5.Sum(helper.StringToBytes(strconv.FormatInt(gid, 10))) 25 | gidhex := hex.EncodeToString(gidsum[:]) 26 | u := fmt.Sprintf(gistraw, ghun, hash, gidhex) 27 | logrus.Debugln("[gist]visit url:", u) 28 | data, err := web.GetData(u) 29 | if err == nil { 30 | logrus.Debugln("[gist]get data:", helper.BytesToString(data)) 31 | st, err := strconv.ParseInt(helper.BytesToString(data), 10, 64) 32 | if err == nil { 33 | // 600s 内验证成功 34 | ok := math.Abs(int(time.Now().Unix()-st)) < 600 35 | if ok { 36 | _ = db.Insert("member", &member{QQ: qq, Ghun: ghun}) 37 | return true, "" 38 | } 39 | return false, "时间戳超时" 40 | } 41 | return false, "时间戳格式错误: " + helper.BytesToString(data) 42 | } 43 | return false, "无法连接到gist: " + err.Error() 44 | } 45 | -------------------------------------------------------------------------------- /plugin/manager/model.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | type welcome struct { 4 | GrpID int64 `db:"gid"` 5 | Msg string `db:"msg"` 6 | } 7 | 8 | type member struct { 9 | QQ int64 `db:"qq"` 10 | // github username 11 | Ghun string `db:"ghun"` 12 | } 13 | -------------------------------------------------------------------------------- /plugin/manager/slow.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/RomiChan/syncx" 7 | "github.com/fumiama/slowdo" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | ) 11 | 12 | var slowsenders = syncx.Map[int64, *syncx.Lazy[*slowdo.Job[*zero.Ctx, message.Segment]]]{} 13 | 14 | func collectsend(ctx *zero.Ctx, msgs ...message.Segment) { 15 | id := ctx.Event.GroupID 16 | if id == 0 { 17 | // only support group 18 | return 19 | } 20 | lazy, _ := slowsenders.LoadOrStore(id, &syncx.Lazy[*slowdo.Job[*zero.Ctx, message.Segment]]{ 21 | Init: func() *slowdo.Job[*zero.Ctx, message.Segment] { 22 | x, err := slowdo.NewJob(time.Second*5, ctx, func(ctx *zero.Ctx, msg []message.Segment) { 23 | if len(msg) == 1 { 24 | ctx.Send(msg) 25 | return 26 | } 27 | m := make(message.Message, len(msg)) 28 | for i, item := range msg { 29 | m[i] = message.CustomNode( 30 | zero.BotConfig.NickName[0], 31 | ctx.Event.SelfID, 32 | message.Message{item}) 33 | } 34 | ctx.SendGroupForwardMessage(id, m) 35 | }) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return x 40 | }, 41 | }) 42 | job := lazy.Get() 43 | for _, msg := range msgs { 44 | job.Add(msg) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/manager/timer/msg.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | zero "github.com/wdvxdr1123/ZeroBot" 5 | "github.com/wdvxdr1123/ZeroBot/message" 6 | ) 7 | 8 | func (t *Timer) sendmsg(grp int64, ctx *zero.Ctx) { 9 | ctx.Event = new(zero.Event) 10 | ctx.Event.GroupID = grp 11 | if t.URL == "" { 12 | ctx.SendChain(atall, message.Text(t.Alert)) 13 | } else { 14 | ctx.SendChain(atall, message.Text(t.Alert), message.Image(t.URL).Add("cache", "0")) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugin/manager/timer/timer.db.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | sql "github.com/FloatTech/sqlite" 5 | ) 6 | 7 | // Timer 计时器 8 | type Timer struct { 9 | ID uint32 `db:"id"` 10 | En1Month4Day5Week3Hour5Min6 int32 `db:"emdwhm"` 11 | SelfID int64 `db:"sid"` 12 | GrpID int64 `db:"gid"` 13 | Alert string `db:"alert"` 14 | Cron string `db:"cron"` 15 | URL string `db:"url"` 16 | } 17 | 18 | // InsertInto 插入自身 19 | func (t *Timer) InsertInto(db *sql.Sqlite) error { 20 | return db.Insert("timer", t) 21 | } 22 | 23 | /* 24 | func getTimerFrom(db *sql.Sqlite, id uint32) (t Timer, err error) { 25 | err = db.Find("timer", &t, "WHERE id = "+strconv.Itoa(int(id))) 26 | return 27 | } 28 | */ 29 | -------------------------------------------------------------------------------- /plugin/manager/timer/timer_test.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | sql "github.com/FloatTech/sqlite" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func TestNextWakeTime(t *testing.T) { 12 | logrus.SetLevel(logrus.DebugLevel) 13 | ts := &Timer{} 14 | ts.SetMonth(-1) 15 | ts.SetWeek(6) 16 | ts.SetHour(16) 17 | ts.SetMinute(30) 18 | t1 := time.Until(ts.nextWakeTime()) 19 | if t1 < 0 { 20 | t.Log(t1) 21 | t.Fail() 22 | } 23 | t.Log(t1) 24 | t.Fail() 25 | } 26 | 27 | func TestClock(t *testing.T) { 28 | db := sql.New("test.db") 29 | c := NewClock(&db) 30 | c.AddTimerIntoDB(GetFilledTimer([]string{"", "12", "-1", "12", "0", "", "test"}, 0, 0, false)) 31 | t.Log(c.ListTimers(0)) 32 | t.Fail() 33 | } 34 | -------------------------------------------------------------------------------- /plugin/manager/timer/wrap.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import "time" 4 | 5 | // En isEnabled 1bit 6 | func (t *Timer) En() (en bool) { 7 | return t.En1Month4Day5Week3Hour5Min6&0x800000 != 0 8 | } 9 | 10 | // Month 4bits 11 | func (t *Timer) Month() (mon time.Month) { 12 | mon = time.Month((t.En1Month4Day5Week3Hour5Min6 & 0x780000) >> 19) 13 | if mon == 0b1111 { 14 | mon = -1 15 | } 16 | return 17 | } 18 | 19 | // Day 5bits 20 | func (t *Timer) Day() (d int) { 21 | d = int((t.En1Month4Day5Week3Hour5Min6 & 0x07c000) >> 14) 22 | if d == 0b11111 { 23 | d = -1 24 | } 25 | return 26 | } 27 | 28 | // Week 3bits 29 | func (t *Timer) Week() (w time.Weekday) { 30 | w = time.Weekday((t.En1Month4Day5Week3Hour5Min6 & 0x003800) >> 11) 31 | if w == 0b111 { 32 | w = -1 33 | } 34 | return 35 | } 36 | 37 | // Hour 5bits 38 | func (t *Timer) Hour() (h int) { 39 | h = int((t.En1Month4Day5Week3Hour5Min6 & 0x0007c0) >> 6) 40 | if h == 0b11111 { 41 | h = -1 42 | } 43 | return 44 | } 45 | 46 | // Minute 6bits 47 | func (t *Timer) Minute() (m int) { 48 | m = int(t.En1Month4Day5Week3Hour5Min6 & 0x00003f) 49 | if m == 0b111111 { 50 | m = -1 51 | } 52 | return 53 | } 54 | 55 | // SetEn ... 56 | func (t *Timer) SetEn(en bool) { 57 | if en { 58 | t.En1Month4Day5Week3Hour5Min6 |= 0x800000 59 | } else { 60 | t.En1Month4Day5Week3Hour5Min6 &= 0x7fffff 61 | } 62 | } 63 | 64 | // SetMonth ... 65 | func (t *Timer) SetMonth(mon time.Month) { 66 | t.En1Month4Day5Week3Hour5Min6 = ((int32(mon) << 19) & 0x780000) | (t.En1Month4Day5Week3Hour5Min6 & 0x87ffff) 67 | } 68 | 69 | // SetDay ... 70 | func (t *Timer) SetDay(d int) { 71 | t.En1Month4Day5Week3Hour5Min6 = ((int32(d) << 14) & 0x07c000) | (t.En1Month4Day5Week3Hour5Min6 & 0xf83fff) 72 | } 73 | 74 | // SetWeek ... 75 | func (t *Timer) SetWeek(w time.Weekday) { 76 | t.En1Month4Day5Week3Hour5Min6 = ((int32(w) << 11) & 0x003800) | (t.En1Month4Day5Week3Hour5Min6 & 0xffc7ff) 77 | } 78 | 79 | // SetHour ... 80 | func (t *Timer) SetHour(h int) { 81 | t.En1Month4Day5Week3Hour5Min6 = ((int32(h) << 6) & 0x0007c0) | (t.En1Month4Day5Week3Hour5Min6 & 0xfff83f) 82 | } 83 | 84 | // SetMinute ... 85 | func (t *Timer) SetMinute(m int) { 86 | t.En1Month4Day5Week3Hour5Min6 = (int32(m) & 0x00003f) | (t.En1Month4Day5Week3Hour5Min6 & 0xffffc0) 87 | } 88 | -------------------------------------------------------------------------------- /plugin/minecraftobserver/ping.go: -------------------------------------------------------------------------------- 1 | package minecraftobserver 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/RomiChan/syncx" 8 | "github.com/Tnze/go-mc/bot" 9 | ) 10 | 11 | var ( 12 | // pingServerUnreachableCounter Ping服务器不可达计数器,防止bot本体网络抖动导致误报 13 | pingServerUnreachableCounter = syncx.Map[string, pingServerUnreachableCounterDef]{} 14 | // 计数器阈值 15 | pingServerUnreachableCounterThreshold = int64(3) 16 | // 时间阈值 17 | pingServerUnreachableCounterTimeThreshold = time.Minute * 30 18 | ) 19 | 20 | type pingServerUnreachableCounterDef struct { 21 | count int64 22 | firstUnreachableTime time.Time 23 | } 24 | 25 | func addPingServerUnreachableCounter(addr string, ts time.Time) (int64, time.Time) { 26 | key := addr 27 | get, ok := pingServerUnreachableCounter.Load(key) 28 | if !ok { 29 | pingServerUnreachableCounter.Store(key, pingServerUnreachableCounterDef{ 30 | count: 1, 31 | firstUnreachableTime: ts, 32 | }) 33 | return 1, ts 34 | } 35 | // 存在则更新,时间戳不变 36 | pingServerUnreachableCounter.Store(key, pingServerUnreachableCounterDef{ 37 | count: get.count + 1, 38 | firstUnreachableTime: get.firstUnreachableTime, 39 | }) 40 | return get.count + 1, get.firstUnreachableTime 41 | } 42 | 43 | func resetPingServerUnreachableCounter(addr string) { 44 | key := addr 45 | pingServerUnreachableCounter.Delete(key) 46 | } 47 | 48 | // getMinecraftServerStatus 获取Minecraft服务器状态 49 | func getMinecraftServerStatus(addr string) (*serverPingAndListResp, error) { 50 | var s serverPingAndListResp 51 | resp, delay, err := bot.PingAndListTimeout(addr, time.Second*5) 52 | if err != nil { 53 | // logrus.Errorln(logPrefix+"PingAndList error: ", err) 54 | return nil, err 55 | } 56 | err = json.Unmarshal(resp, &s) 57 | if err != nil { 58 | // logrus.Errorln(logPrefix+"Parse json response fail: ", err) 59 | return nil, err 60 | } 61 | s.Delay = delay 62 | return &s, nil 63 | } 64 | -------------------------------------------------------------------------------- /plugin/minecraftobserver/ping_test.go: -------------------------------------------------------------------------------- 1 | package minecraftobserver 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_PingListInfo(t *testing.T) { 9 | t.Run("normal", func(t *testing.T) { 10 | resp, err := getMinecraftServerStatus("cn.nekoland.top") 11 | if err != nil { 12 | t.Fatalf("getMinecraftServerStatus() error = %v", err) 13 | } 14 | msg, iconBase64 := resp.genServerSubscribeSchema("cn.nekoland.top", 123456).generateServerStatusMsg() 15 | fmt.Printf("msg: %v\n", msg) 16 | fmt.Printf("iconBase64: %v\n", iconBase64) 17 | }) 18 | t.Run("不可达", func(t *testing.T) { 19 | ss, err := getMinecraftServerStatus("dx.123213213123123.net") 20 | if err == nil { 21 | t.Fatalf("getMinecraftServerStatus() error = %v", err) 22 | } 23 | if ss != nil { 24 | t.Fatalf("getMinecraftServerStatus() got = %v, want nil", ss) 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /plugin/moyu/holiday_test.go: -------------------------------------------------------------------------------- 1 | package moyu 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | reg "github.com/fumiama/go-registry" 9 | ) 10 | 11 | var sr = reg.NewRegedit("reilia.fumiama.top:32664", "", "fumiama", "--") 12 | 13 | func TestGetHoliday(t *testing.T) { 14 | registry.Connect() 15 | h := GetHoliday("元旦") 16 | registry.Close() 17 | t.Fatal(h) 18 | } 19 | 20 | func TestSetHoliday(t *testing.T) { 21 | err := sr.Connect() 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | err = SetHoliday("元旦", 1, 2025, 1, 1) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | err = SetHoliday("春节", 7, 2025, 1, 29) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | err = SetHoliday("清明节", 1, 2025, 4, 4) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | err = SetHoliday("劳动节", 1, 2025, 5, 1) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | err = SetHoliday("端午节", 3, 2024, 6, 8) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | err = SetHoliday("中秋节", 3, 2024, 9, 15) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | err = SetHoliday("国庆节", 7, 2024, 10, 1) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | err = sr.Close() 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | } 60 | 61 | func SetHoliday(name string, dur, year int, month time.Month, day int) error { 62 | return sr.Set("holiday/"+name, fmt.Sprintf("%d_%d_%d_%d", dur, year, month, day)) 63 | } 64 | -------------------------------------------------------------------------------- /plugin/moyu/nowork.go: -------------------------------------------------------------------------------- 1 | package moyu 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | reg "github.com/fumiama/go-registry" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // Holiday 节日 13 | type Holiday struct { 14 | name string 15 | date time.Time 16 | dur time.Duration 17 | } 18 | 19 | // NewHoliday 节日名 天数 年 月 日 20 | func NewHoliday(name string, dur, year int, month time.Month, day int) *Holiday { 21 | return &Holiday{name: name, date: time.Date(year, month, day, 0, 0, 0, 0, time.Local), dur: time.Duration(dur) * time.Hour * 24} 22 | } 23 | 24 | var registry = reg.NewRegReader("reilia.fumiama.top:32664", "", "fumiama") 25 | 26 | // GetHoliday 从 reg 服务器获取节日 27 | func GetHoliday(name string) *Holiday { 28 | var dur, year int 29 | var month time.Month 30 | var day int 31 | ret, err := registry.Get("holiday/" + name) 32 | if err != nil { 33 | return NewHoliday(name+err.Error(), 0, 0, 0, 0) 34 | } 35 | _, err = fmt.Sscanf(ret, "%d_%d_%d_%d", &dur, &year, &month, &day) 36 | if err != nil { 37 | return NewHoliday(name+err.Error(), 0, 0, 0, 0) 38 | } 39 | logrus.Debugln("[moyu]获取节日:", name, dur, year, month, day) 40 | return NewHoliday(name, dur, year, month, day) 41 | } 42 | 43 | // String 获取两个时间相差 44 | func (h *Holiday) String() string { 45 | d := time.Until(h.date) 46 | switch { 47 | case d >= 0: 48 | return "距离" + h.name + "还有: " + strconv.FormatFloat(d.Hours()/24.0, 'f', 2, 64) + "天!" 49 | case d+h.dur >= 0: 50 | return "好好享受 " + h.name + " 假期吧!" 51 | default: 52 | return "今年 " + h.name + " 假期已过" 53 | } 54 | } 55 | 56 | func weekend() string { 57 | t := time.Now().Weekday() 58 | if t == time.Sunday || t == time.Saturday { 59 | return "好好享受周末吧!" 60 | } 61 | return fmt.Sprintf("距离周末还有:%d天!", 5-t) 62 | } 63 | -------------------------------------------------------------------------------- /plugin/moyu/run.go: -------------------------------------------------------------------------------- 1 | // Package moyu 摸鱼 2 | package moyu 3 | 4 | import ( 5 | "sync" 6 | "time" 7 | 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | var ( 15 | msg message.Message 16 | mu sync.Mutex 17 | lastupdate time.Time 18 | ) 19 | 20 | func init() { // 插件主体 21 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 22 | DisableOnDefault: true, 23 | Brief: "摸鱼提醒", 24 | Help: "- /启用 moyu\n" + 25 | "- /禁用 moyu\n" + 26 | "- 记录在\"0 10 * * *\"触发的指令\n" + 27 | " - 摸鱼提醒", 28 | }).OnFullMatch("摸鱼提醒").SetBlock(true). 29 | Handle(func(ctx *zero.Ctx) { 30 | mu.Lock() 31 | defer mu.Unlock() 32 | if msg == nil || time.Since(lastupdate) > time.Hour*20 { 33 | if registry.Connect() != nil { 34 | return 35 | } 36 | msg = message.Message{ 37 | message.Text(time.Now().Format("2006-01-02")), 38 | message.Text("上午好,摸鱼人!\n工作再累,一定不要忘记摸鱼哦!有事没事起身去茶水间,去厕所,去廊道走走别老在工位上坐着,钱是老板的,但命是自己的。\n"), 39 | message.Text(weekend()), 40 | message.Text("\n"), 41 | message.Text(GetHoliday("元旦")), 42 | message.Text("\n"), 43 | message.Text(GetHoliday("春节")), 44 | message.Text("\n"), 45 | message.Text(GetHoliday("清明节")), 46 | message.Text("\n"), 47 | message.Text(GetHoliday("劳动节")), 48 | message.Text("\n"), 49 | message.Text(GetHoliday("端午节")), 50 | message.Text("\n"), 51 | message.Text(GetHoliday("中秋节")), 52 | message.Text("\n"), 53 | message.Text(GetHoliday("国庆节")), 54 | message.Text("\n"), 55 | message.Text("上班是帮老板赚钱,摸鱼是赚老板的钱!最后,祝愿天下所有摸鱼人,都能愉快的渡过每一天…"), 56 | } 57 | _ = registry.Close() 58 | lastupdate = time.Now() 59 | } 60 | ctx.Send(msg) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /plugin/moyucalendar/calendar.go: -------------------------------------------------------------------------------- 1 | // Package moyucalendar 摸鱼人日历 2 | package moyucalendar 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/web" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/FloatTech/zbputils/control" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | ) 11 | 12 | func init() { 13 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 14 | DisableOnDefault: true, 15 | Brief: "摸鱼人日历", 16 | Help: "- /启用 moyucalendar\n" + 17 | "- /禁用 moyucalendar\n" + 18 | "- 记录在\"30 8 * * *\"触发的指令\n" + 19 | " - 摸鱼人日历", 20 | }).OnFullMatch("摸鱼人日历").SetBlock(true). 21 | Handle(func(ctx *zero.Ctx) { 22 | data, err := web.GetData("https://api.vvhan.com/api/moyu") 23 | if err != nil { 24 | ctx.SendChain(message.Text("ERROR: ", err)) 25 | return 26 | } 27 | ctx.SendChain(message.ImageBytes(data)) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /plugin/nativesetu/data.go: -------------------------------------------------------------------------------- 1 | package nativesetu 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "io/fs" 7 | "os" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/corona10/goimagehash" 13 | "github.com/sirupsen/logrus" 14 | _ "golang.org/x/image/webp" // import webp decoding 15 | 16 | "github.com/FloatTech/floatbox/file" 17 | sql "github.com/FloatTech/sqlite" 18 | ) 19 | 20 | // setuclass holds setus in a folder, which is the class name. 21 | type setuclass struct { 22 | ImgID int64 `db:"imgid"` // ImgID 图片唯一 id (dhash) 23 | Name string `db:"name"` // Name 图片名 24 | Path string `db:"path"` // Path 图片路径 25 | } 26 | 27 | var ns nsetu 28 | 29 | type nsetu struct { 30 | db sql.Sqlite 31 | mu sync.RWMutex 32 | } 33 | 34 | func (n *nsetu) List() (l []string) { 35 | if file.IsExist(dbpath) { 36 | var err error 37 | l, err = n.db.ListTables() 38 | if err != nil { 39 | logrus.Errorln("[nsetu]", err) 40 | } 41 | } 42 | return 43 | } 44 | 45 | func (n *nsetu) scanall(path string) error { 46 | model := &setuclass{} 47 | root := os.DirFS(path) 48 | _ = n.db.Close() 49 | _ = os.Remove(dbpath) 50 | err := n.db.Open(time.Hour) 51 | if err != nil { 52 | return err 53 | } 54 | return fs.WalkDir(root, ".", func(path string, d fs.DirEntry, err error) error { 55 | if err != nil { 56 | return err 57 | } 58 | if d.IsDir() { 59 | clsn := d.Name() 60 | if clsn != "." { 61 | n.mu.Lock() 62 | err = n.db.Create(clsn, model) 63 | n.mu.Unlock() 64 | if err == nil { 65 | err = n.scanclass(root, path, clsn) 66 | if err != nil { 67 | logrus.Errorln("[nsetu]", err) 68 | return err 69 | } 70 | } 71 | } 72 | } 73 | return nil 74 | }) 75 | } 76 | 77 | func (n *nsetu) scanclass(root fs.FS, path, clsn string) error { 78 | ds, err := fs.ReadDir(root, path) 79 | if err != nil { 80 | return err 81 | } 82 | n.mu.Lock() 83 | _ = n.db.Drop(clsn) 84 | _ = n.db.Create(clsn, &setuclass{}) 85 | n.mu.Unlock() 86 | for _, d := range ds { 87 | nm := d.Name() 88 | ln := strings.ToLower(nm) 89 | if !d.IsDir() && 90 | (strings.HasSuffix(ln, ".jpg") || strings.HasSuffix(ln, ".jpeg") || 91 | strings.HasSuffix(ln, ".png") || strings.HasSuffix(ln, ".gif") || strings.HasSuffix(ln, ".webp")) { 92 | relpath := path + "/" + nm 93 | logrus.Debugln("[nsetu] read", relpath) 94 | f, e := fs.ReadFile(root, relpath) 95 | if e != nil { 96 | return e 97 | } 98 | b := bytes.NewReader(f) 99 | img, _, e := image.Decode(b) 100 | if e != nil { 101 | return e 102 | } 103 | dh, e := goimagehash.DifferenceHash(img) 104 | if e != nil { 105 | return e 106 | } 107 | dhi := int64(dh.GetHash()) 108 | logrus.Debugln("[nsetu] insert", nm, "with id", dhi, "into", clsn) 109 | n.mu.Lock() 110 | err = n.db.Insert(clsn, &setuclass{ImgID: dhi, Name: nm, Path: relpath}) 111 | n.mu.Unlock() 112 | if err != nil { 113 | return err 114 | } 115 | } 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /plugin/nbnhhsh/nbnhhsh.go: -------------------------------------------------------------------------------- 1 | // Package nbnhhsh 能不能好好说话 2 | package nbnhhsh 3 | 4 | import ( 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "strings" 9 | 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/FloatTech/zbputils/control" 12 | "github.com/tidwall/gjson" 13 | zero "github.com/wdvxdr1123/ZeroBot" 14 | "github.com/wdvxdr1123/ZeroBot/message" 15 | ) 16 | 17 | func init() { 18 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 19 | DisableOnDefault: false, 20 | Brief: "拼音首字母释义工具", 21 | Help: "- ?? [缩写]", 22 | }).OnRegex(`^[??]{1,2} ?([a-z0-9]+)$`).SetBlock(false). 23 | Handle(func(ctx *zero.Ctx) { 24 | keyword := ctx.State["regex_matched"].([]string)[1] 25 | ctx.SendChain(message.Text(keyword + ": " + strings.Join(getValue(keyword), ", "))) 26 | }) 27 | } 28 | 29 | func getValue(text string) []string { 30 | urlValues := url.Values{} 31 | urlValues.Add("text", text) 32 | resp, err := http.PostForm("https://lab.magiconch.com/api/nbnhhsh/guess", urlValues) 33 | if err == nil { 34 | body, err := io.ReadAll(resp.Body) 35 | if err == nil { 36 | resp.Body.Close() 37 | json := gjson.ParseBytes(body) 38 | res := make([]string, 0) 39 | var jsonPath string 40 | if json.Get("0.trans").Exists() { 41 | jsonPath = "0.trans" 42 | } else { 43 | jsonPath = "0.inputting" 44 | } 45 | for _, value := range json.Get(jsonPath).Array() { 46 | res = append(res, value.String()) 47 | } 48 | return res 49 | } 50 | return []string{err.Error()} 51 | } 52 | return []string{err.Error()} 53 | } 54 | -------------------------------------------------------------------------------- /plugin/nihongo/model.go: -------------------------------------------------------------------------------- 1 | package nihongo 2 | 3 | import ( 4 | "fmt" 5 | 6 | sql "github.com/FloatTech/sqlite" 7 | ) 8 | 9 | type grammar struct { 10 | ID int `db:"id"` 11 | Tag string `db:"tag"` 12 | Name string `db:"name"` 13 | Pronunciation string `db:"pronunciation"` 14 | Usage string `db:"usage"` 15 | Meaning string `db:"meaning"` 16 | Explanation string `db:"explanation"` 17 | Example string `db:"example"` 18 | GrammarURL string `db:"grammar_url"` 19 | } 20 | 21 | func (g *grammar) string() string { 22 | return fmt.Sprintf("ID:\n%d\n\n标签:\n%s\n\n语法名:\n%s\n\n发音:\n%s\n\n用法:\n%s\n\n意思:\n%s\n\n解说:\n%s\n\n示例:\n%s", g.ID, g.Tag, g.Name, g.Pronunciation, g.Usage, g.Meaning, g.Explanation, g.Example) 23 | } 24 | 25 | var db sql.Sqlite 26 | 27 | func getRandomGrammarByTag(tag string) (g grammar) { 28 | _ = db.Find("grammar", &g, "WHERE tag LIKE ? ORDER BY RANDOM() limit 1", "%"+tag+"%") 29 | return 30 | } 31 | 32 | func getRandomGrammarByKeyword(keyword string) (g grammar) { 33 | _ = db.Find("grammar", &g, "WHERE (name LIKE ? OR pronunciation LIKE ?) ORDER BY RANDOM() limit 1", "%"+keyword+"%", "%"+keyword+"%") 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /plugin/nihongo/nihongo.go: -------------------------------------------------------------------------------- 1 | // Package nihongo 日语学习 2 | package nihongo 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/FloatTech/floatbox/binary" 8 | fcext "github.com/FloatTech/floatbox/ctxext" 9 | sql "github.com/FloatTech/sqlite" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/FloatTech/zbputils/control" 12 | "github.com/FloatTech/zbputils/img/text" 13 | log "github.com/sirupsen/logrus" 14 | zero "github.com/wdvxdr1123/ZeroBot" 15 | "github.com/wdvxdr1123/ZeroBot/message" 16 | ) 17 | 18 | func init() { 19 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 20 | DisableOnDefault: false, 21 | Brief: "日语学习", 22 | Help: "- 日语语法[xxx](使用tag随机)\n" + 23 | "搜索日语语法[xxx]", 24 | PublicDataFolder: "Nihongo", 25 | }) 26 | 27 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 28 | db = sql.New(engine.DataFolder() + "nihongo.db") 29 | _, err := engine.GetLazyData("nihongo.db", true) 30 | if err != nil { 31 | ctx.SendChain(message.Text("ERROR: ", err)) 32 | return false 33 | } 34 | err = db.Open(time.Hour) 35 | if err != nil { 36 | ctx.SendChain(message.Text("ERROR: ", err)) 37 | return false 38 | } 39 | err = db.Create("grammar", &grammar{}) 40 | if err != nil { 41 | ctx.SendChain(message.Text("ERROR: ", err)) 42 | return false 43 | } 44 | n, err := db.Count("grammar") 45 | if err != nil { 46 | ctx.SendChain(message.Text("ERROR: ", err)) 47 | return false 48 | } 49 | log.Infof("[nihongo]读取%d条语法", n) 50 | return true 51 | }) 52 | 53 | engine.OnRegex(`^日语语法\s?([0-9A-Za-zぁ-んァ-ヶ~]{1,6})$`, getdb).SetBlock(true). 54 | Handle(func(ctx *zero.Ctx) { 55 | g := getRandomGrammarByTag(ctx.State["regex_matched"].([]string)[1]) 56 | if g.ID == 0 { 57 | ctx.SendChain(message.Text("未能找到", ctx.State["regex_matched"].([]string)[1], "相关标签的语法")) 58 | return 59 | } 60 | data, err := text.RenderToBase64(g.string(), text.FontFile, 400, 20) 61 | if err != nil { 62 | ctx.SendChain(message.Text("ERROR: ", err)) 63 | return 64 | } 65 | if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 { 66 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 67 | } 68 | }) 69 | engine.OnRegex(`^搜索日语语法\s?([0-9A-Za-zぁ-んァ-ヶ~]{1,25})$`, getdb).SetBlock(true). 70 | Handle(func(ctx *zero.Ctx) { 71 | g := getRandomGrammarByKeyword(ctx.State["regex_matched"].([]string)[1]) 72 | if g.ID == 0 { 73 | ctx.SendChain(message.Text("未能找到", ctx.State["regex_matched"].([]string)[1], "相关标签的语法")) 74 | return 75 | } 76 | data, err := text.RenderToBase64(g.string(), text.FontFile, 400, 20) 77 | if err != nil { 78 | ctx.SendChain(message.Text("ERROR: ", err)) 79 | return 80 | } 81 | if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 { 82 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 83 | } 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /plugin/niuniu/draw.go: -------------------------------------------------------------------------------- 1 | package niuniu 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/png" 8 | "net/http" 9 | 10 | "github.com/FloatTech/AnimeAPI/niu" 11 | "github.com/FloatTech/floatbox/file" 12 | "github.com/FloatTech/rendercard" 13 | "github.com/FloatTech/zbputils/control" 14 | "github.com/FloatTech/zbputils/img/text" 15 | zero "github.com/wdvxdr1123/ZeroBot" 16 | ) 17 | 18 | func processRankingImg(allUsers niu.BaseInfos, ctx *zero.Ctx, t bool) ([]byte, error) { 19 | fontByte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true) 20 | if err != nil { 21 | return nil, err 22 | } 23 | s := "牛牛长度" 24 | title := "牛牛长度排行" 25 | if !t { 26 | s = "牛牛深度" 27 | title = "牛牛深度排行" 28 | } 29 | ri := make([]*rendercard.RankInfo, len(allUsers)) 30 | for i, user := range allUsers { 31 | resp, err := http.Get(fmt.Sprintf("https://q1.qlogo.cn/g?b=qq&nk=%d&s=100", user.UID)) 32 | if err != nil { 33 | return nil, err 34 | } 35 | decode, _, err := image.Decode(resp.Body) 36 | _ = resp.Body.Close() 37 | if err != nil { 38 | return nil, err 39 | } 40 | ri[i] = &rendercard.RankInfo{ 41 | Avatar: decode, 42 | TopLeftText: ctx.CardOrNickName(user.UID), 43 | BottomLeftText: fmt.Sprintf("QQ:%d", user.UID), 44 | RightText: fmt.Sprintf("%s:%.2fcm", s, user.Length), 45 | } 46 | } 47 | img, err := rendercard.DrawRankingCard(fontByte, title, ri) 48 | if err != nil { 49 | return nil, err 50 | } 51 | var buf bytes.Buffer 52 | err = png.Encode(&buf, img) 53 | return buf.Bytes(), err 54 | } 55 | -------------------------------------------------------------------------------- /plugin/nsfw/main.go: -------------------------------------------------------------------------------- 1 | // Package nsfw 图片合规性审查 2 | package nsfw 3 | 4 | import ( 5 | "github.com/FloatTech/AnimeAPI/nsfw" 6 | "github.com/FloatTech/floatbox/process" 7 | ctrl "github.com/FloatTech/zbpctrl" 8 | "github.com/FloatTech/zbputils/control" 9 | "github.com/FloatTech/zbputils/ctxext" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | const hso = "https://gchat.qpic.cn/gchatpic_new//--4234EDEC5F147A4C319A41149D7E0EA9/0" 15 | 16 | func init() { 17 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 18 | DisableOnDefault: false, 19 | Brief: "nsfw图片识别", 20 | Help: "- nsfw打分[图片]", 21 | }).ApplySingle(ctxext.DefaultSingle) 22 | // 上传一张图进行评价 23 | engine.OnKeywordGroup([]string{"nsfw打分"}, zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true). 24 | Handle(func(ctx *zero.Ctx) { 25 | url := ctx.State["image_url"].([]string) 26 | if len(url) > 0 { 27 | ctx.SendChain(message.Text("少女祈祷中...")) 28 | p, err := nsfw.Classify(url[0]) 29 | if err != nil { 30 | ctx.SendChain(message.Text("ERROR: ", err)) 31 | return 32 | } 33 | ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(judge(p)))) 34 | } 35 | }) 36 | control.Register("nsfwauto", &ctrl.Options[*zero.Ctx]{ 37 | DisableOnDefault: true, 38 | Brief: "nsfw图片自动识别", 39 | Help: "- 当图片属于非 neutral 类别时自动发送评价", 40 | }).OnMessage(zero.HasPicture).SetBlock(false). 41 | Handle(func(ctx *zero.Ctx) { 42 | url := ctx.State["image_url"].([]string) 43 | if len(url) > 0 { 44 | process.SleepAbout1sTo2s() 45 | p, err := nsfw.Classify(url[0]) 46 | if err != nil { 47 | return 48 | } 49 | process.SleepAbout1sTo2s() 50 | autojudge(ctx, p) 51 | } 52 | }) 53 | } 54 | 55 | func judge(p *nsfw.Picture) string { 56 | if p.Neutral > 0.3 { 57 | return "普通哦" 58 | } 59 | c := "" 60 | if p.Drawings > 0.3 || p.Neutral < 0.3 { 61 | c = "二次元" 62 | } else { 63 | c = "三次元" 64 | } 65 | if p.Hentai > 0.3 { 66 | c += " hentai" 67 | } 68 | if p.Porn > 0.3 { 69 | c += " porn" 70 | } 71 | if p.Sexy > 0.3 { 72 | c += " hso" 73 | } 74 | return c 75 | } 76 | 77 | func autojudge(ctx *zero.Ctx, p *nsfw.Picture) { 78 | if p.Neutral > 0.3 { 79 | return 80 | } 81 | c := "" 82 | if p.Drawings > 0.3 { 83 | c = "二次元" 84 | } else { 85 | c = "三次元" 86 | } 87 | i := 0 88 | if p.Hentai > 0.3 { 89 | c += " hentai" 90 | i++ 91 | } 92 | if p.Porn > 0.3 { 93 | c += " porn" 94 | i++ 95 | } 96 | if p.Sexy > 0.3 { 97 | c += " hso" 98 | i++ 99 | } 100 | if i > 0 { 101 | ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(c, "\n"), message.Image(hso))) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /plugin/omikuji/model.go: -------------------------------------------------------------------------------- 1 | package omikuji 2 | 3 | import ( 4 | sql "github.com/FloatTech/sqlite" 5 | ) 6 | 7 | type kuji struct { 8 | ID uint8 `db:"id"` 9 | Text string `db:"text"` 10 | } 11 | 12 | var db sql.Sqlite 13 | 14 | // 返回一个解签 15 | func getKujiByBango(id uint8) string { 16 | var s kuji 17 | err := db.Find("kuji", &s, "WHERE id = ?", id) 18 | if err != nil { 19 | return err.Error() 20 | } 21 | return s.Text 22 | } 23 | -------------------------------------------------------------------------------- /plugin/omikuji/sensou.go: -------------------------------------------------------------------------------- 1 | // Package omikuji 浅草寺求签 2 | package omikuji 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 12 | 13 | fcext "github.com/FloatTech/floatbox/ctxext" 14 | sql "github.com/FloatTech/sqlite" 15 | ctrl "github.com/FloatTech/zbpctrl" 16 | "github.com/FloatTech/zbputils/control" 17 | "github.com/FloatTech/zbputils/ctxext" 18 | "github.com/FloatTech/zbputils/img/text" 19 | ) 20 | 21 | const bed = "https://gitea.seku.su/fumiama/senso-ji-omikuji/raw/branch/main/" 22 | 23 | func init() { // 插件主体 24 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 25 | DisableOnDefault: false, 26 | Brief: "浅草寺求签", 27 | Help: "- 求签 | 占卜\n- 解签", 28 | PublicDataFolder: "Omikuji", 29 | }).ApplySingle(ctxext.DefaultSingle) 30 | 31 | engine.OnFullMatchGroup([]string{"求签", "占卜"}).SetBlock(true). 32 | Handle(func(ctx *zero.Ctx) { 33 | i := fcext.RandSenderPerDayN(ctx.Event.UserID, 100) + 1 34 | img0, err := engine.GetCustomLazyData(bed, fmt.Sprintf("%d_%d.jpg", i, 0)) 35 | if err != nil { 36 | ctx.SendChain(message.Text("ERROR: ", err)) 37 | return 38 | } 39 | img1, err := engine.GetCustomLazyData(bed, fmt.Sprintf("%d_%d.jpg", i, 1)) 40 | if err != nil { 41 | ctx.SendChain(message.Text("ERROR: ", err)) 42 | return 43 | } 44 | ctx.SendChain( 45 | message.At(ctx.Event.UserID), 46 | message.ImageBytes(img0), 47 | message.ImageBytes(img1), 48 | ) 49 | }) 50 | engine.OnFullMatch("解签", fcext.DoOnceOnSuccess( 51 | func(ctx *zero.Ctx) bool { 52 | db = sql.New(engine.DataFolder() + "kuji.db") 53 | _, err := engine.GetLazyData("kuji.db", true) 54 | if err != nil { 55 | ctx.SendChain(message.Text("ERROR: ", err)) 56 | return false 57 | } 58 | err = db.Open(time.Hour) 59 | if err != nil { 60 | ctx.SendChain(message.Text("ERROR: ", err)) 61 | return false 62 | } 63 | err = db.Create("kuji", &kuji{}) 64 | if err != nil { 65 | ctx.SendChain(message.Text("ERROR: ", err)) 66 | return false 67 | } 68 | n, err := db.Count("kuji") 69 | if err != nil { 70 | ctx.SendChain(message.Text("ERROR: ", err)) 71 | return false 72 | } 73 | logrus.Infof("[kuji]读取%d条签文", n) 74 | return true 75 | }, 76 | )).SetBlock(true). 77 | Handle(func(ctx *zero.Ctx) { 78 | kujiBytes, err := text.RenderToBase64( 79 | getKujiByBango( 80 | uint8(fcext.RandSenderPerDayN(ctx.Event.UserID, 100)+1), 81 | ), 82 | text.FontFile, 400, 20, 83 | ) 84 | if err != nil { 85 | ctx.SendChain(message.Text("ERROR: ", err)) 86 | return 87 | } 88 | if id := ctx.SendChain(message.At(ctx.Event.UserID), message.Image("base64://"+helper.BytesToString(kujiBytes))); id.ID() == 0 { 89 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 90 | } 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /plugin/poker/poker.go: -------------------------------------------------------------------------------- 1 | // Package poker 抽扑克牌 2 | package poker 3 | 4 | import ( 5 | "encoding/json" 6 | "math/rand" 7 | 8 | fcext "github.com/FloatTech/floatbox/ctxext" 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | "github.com/FloatTech/zbputils/ctxext" 12 | zero "github.com/wdvxdr1123/ZeroBot" 13 | "github.com/wdvxdr1123/ZeroBot/message" 14 | ) 15 | 16 | // 图片来源 https://www.bilibili.com/opus/834601953403076633 17 | 18 | var cardImgPathList []string 19 | 20 | func init() { 21 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 22 | DisableOnDefault: false, 23 | Brief: "抽扑克牌", 24 | Help: "- 抽扑克\n- poker", 25 | PublicDataFolder: "Poker", 26 | }).ApplySingle(ctxext.DefaultSingle) 27 | 28 | getImg := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 29 | data, err := engine.GetLazyData("imgdata.json", true) 30 | if err != nil { 31 | ctx.SendChain(message.Text("ERROR: ", err)) 32 | return false 33 | } 34 | err = json.Unmarshal(data, &cardImgPathList) 35 | if err != nil { 36 | ctx.SendChain(message.Text("ERROR: ", err)) 37 | return false 38 | } 39 | return true 40 | }) 41 | 42 | engine.OnFullMatchGroup([]string{"抽扑克", "poker"}, getImg).SetBlock(true). 43 | Handle(func(ctx *zero.Ctx) { 44 | randomIndex := rand.Intn(len(cardImgPathList)) 45 | randomImgPath := cardImgPathList[randomIndex] 46 | imgData, err := engine.GetLazyData(randomImgPath, true) 47 | if err != nil { 48 | ctx.Send("[poker]读取扑克图片失败") 49 | return 50 | } 51 | ctx.Send(message.ImageBytes(imgData)) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /plugin/qzone/README.md: -------------------------------------------------------------------------------- 1 | # qq空间表白墙 2 | 3 | ## 参考 4 | 5 | * [opq-osc/OPQBot](https://github.com/opq-osc/OPQBot) QQ空间发表说说流程 6 | * [【Ono】QQ空间协议分析----扫码登录----【1】](https://www.52pojie.cn/thread-1022123-1-1.html) QQ空间扫码登录流程 7 | 8 | ## 优化点 9 | - [ ] 匿名头像背景颜色优化 10 | - [ ] 转发消息生成图片气泡背景板 11 | - [x] 查看说说消息分页 (优先) 12 | - [ ] 加zbp水印 (优先) 13 | - [ ] 发表白墙互动优化, 监听对话 14 | - [ ] 自动审核稿 15 | - [x] 一次同意多条说说并发送 (优先) 16 | - [ ] 拒绝说说的时候可发送拒绝消息 17 | - [ ] 表白墙接入钱包 (待定) 18 | 19 | -------------------------------------------------------------------------------- /plugin/qzone/model.go: -------------------------------------------------------------------------------- 1 | package qzone 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/jinzhu/gorm" 8 | ) 9 | 10 | // qdb qq空间数据库全局变量 11 | var qdb *qzonedb 12 | 13 | // qzonedb qq空间数据库结构体 14 | type qzonedb gorm.DB 15 | 16 | // initialize 初始化 17 | func initialize(dbpath string) *qzonedb { 18 | var err error 19 | if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) { 20 | // 生成文件 21 | f, err := os.Create(dbpath) 22 | if err != nil { 23 | return nil 24 | } 25 | defer f.Close() 26 | } 27 | qdb, err := gorm.Open("sqlite3", dbpath) 28 | if err != nil { 29 | panic(err) 30 | } 31 | qdb.AutoMigrate(&qzoneConfig{}).AutoMigrate(&emotion{}) 32 | return (*qzonedb)(qdb) 33 | } 34 | 35 | // qzoneConfig qq空间初始化信息 36 | type qzoneConfig struct { 37 | ID uint `gorm:"primary_key;AUTO_INCREMENT"` 38 | QQ int64 `gorm:"column:qq;unique;not null"` 39 | Cookie string `gorm:"column:cookie;type:varchar(1024)"` 40 | } 41 | 42 | // TableName 表名 43 | func (qzoneConfig) TableName() string { 44 | return "qzone_config" 45 | } 46 | 47 | func (qdb *qzonedb) insertOrUpdate(qq int64, cookie string) (err error) { 48 | db := (*gorm.DB)(qdb) 49 | qc := qzoneConfig{ 50 | QQ: qq, 51 | Cookie: cookie, 52 | } 53 | var oqc qzoneConfig 54 | err = db.Take(&oqc, "qq = ?", qc.QQ).Error 55 | if err != nil { 56 | if gorm.IsRecordNotFoundError(err) { 57 | err = db.Create(&qc).Error 58 | } 59 | return 60 | } 61 | err = db.Model(&oqc).Updates(qc).Error 62 | return 63 | } 64 | 65 | func (qdb *qzonedb) getByUin(qq int64) (qc qzoneConfig, err error) { 66 | db := (*gorm.DB)(qdb) 67 | err = db.Take(&qc, "qq = ?", qq).Error 68 | return 69 | } 70 | 71 | // emotion 说说信息 72 | type emotion struct { 73 | gorm.Model 74 | Anonymous bool `gorm:"column:anonymous"` 75 | QQ int64 `gorm:"column:qq"` 76 | Msg string `gorm:"column:msg"` 77 | Status int `gorm:"column:status"` // 1-审核中,2-同意,3-拒绝 78 | Tag string `gorm:"column:tag"` 79 | } 80 | 81 | func (e emotion) textBrief() (t string) { 82 | t = fmt.Sprintf("序号: %v\nQQ: %v\n创建时间: %v\n", e.ID, e.QQ, e.CreatedAt.Format("2006-01-02 15:04:05")) 83 | switch e.Status { 84 | case 1: 85 | t += "状态: 审核中\n" 86 | case 2: 87 | t += "状态: 同意\n" 88 | case 3: 89 | t += "状态: 拒绝\n" 90 | } 91 | if e.Anonymous { 92 | t += "匿名: 是" 93 | } else { 94 | t += "匿名: 否" 95 | } 96 | return 97 | } 98 | 99 | // TableName 表名 100 | func (emotion) TableName() string { 101 | return "emotion" 102 | } 103 | 104 | func (qdb *qzonedb) saveEmotion(e emotion) (id int64, err error) { 105 | db := (*gorm.DB)(qdb) 106 | err = db.Create(&e).Error 107 | id = int64(e.ID) 108 | return 109 | } 110 | 111 | func (qdb *qzonedb) getEmotionByIDList(idList []int64) (el []emotion, err error) { 112 | db := (*gorm.DB)(qdb) 113 | err = db.Find(&el, "id in (?)", idList).Error 114 | return 115 | } 116 | 117 | func (qdb *qzonedb) getLoveEmotionByStatus(status int, pageNum int) (el []emotion, err error) { 118 | db := (*gorm.DB)(qdb) 119 | if status == 0 { 120 | err = db.Order("created_at desc").Limit(5).Offset(pageNum*5).Find(&el, "tag like ?", "%"+loveTag+"%").Error 121 | return 122 | } 123 | err = db.Order("created_at desc").Limit(5).Offset(pageNum*5).Find(&el, "status = ? and tag like ?", status, "%"+loveTag+"%").Error 124 | return 125 | } 126 | 127 | func (qdb *qzonedb) updateEmotionStatusByIDList(idList []int64, status int) (err error) { 128 | db := (*gorm.DB)(qdb) 129 | err = db.Model(&emotion{}).Where("id in (?)", idList).Update("status", status).Error 130 | return 131 | } 132 | -------------------------------------------------------------------------------- /plugin/realcugan/realcugan.go: -------------------------------------------------------------------------------- 1 | // Package realcugan Real-CUGAN清晰术 2 | package realcugan 3 | 4 | import ( 5 | "bytes" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | "image" 10 | "strings" 11 | 12 | hf "github.com/FloatTech/AnimeAPI/huggingface" 13 | "github.com/FloatTech/floatbox/web" 14 | ctrl "github.com/FloatTech/zbpctrl" 15 | "github.com/FloatTech/zbputils/control" 16 | "github.com/FloatTech/zbputils/ctxext" 17 | "github.com/tidwall/gjson" 18 | zero "github.com/wdvxdr1123/ZeroBot" 19 | "github.com/wdvxdr1123/ZeroBot/message" 20 | ) 21 | 22 | const ( 23 | realcuganRepo = "shichen1231/Real-CUGAN" 24 | ) 25 | 26 | func init() { // 插件主体 27 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 28 | DisableOnDefault: false, 29 | Brief: "Real-CUGAN清晰术", 30 | Help: "- 清晰术(双重吟唱|三重吟唱|四重吟唱)(强力术式|中等术式|弱术式|不变式|原式)[图片]", 31 | PrivateDataFolder: "realcugan", 32 | }) 33 | engine.OnPrefix("清晰术", zero.MustProvidePicture).SetBlock(true). 34 | Handle(func(ctx *zero.Ctx) { 35 | ctx.SendChain(message.Text("少女祈祷中...")) 36 | realcuganURL := fmt.Sprintf(hf.HTTPSPredictPath, realcuganRepo) 37 | for _, url := range ctx.State["image_url"].([]string) { 38 | imgdata, err := web.GetData(url) 39 | if err != nil { 40 | ctx.SendChain(message.Text("ERROR: ", err)) 41 | return 42 | } 43 | img, _, err := image.Decode(bytes.NewReader(imgdata)) 44 | if err != nil { 45 | ctx.SendChain(message.Text("ERROR: ", err)) 46 | return 47 | } 48 | // 初始化参数 49 | var ( 50 | fashu = ctx.Event.Message.ExtractPlainText() 51 | scale = 2 52 | con = "conservative" 53 | ) 54 | switch { 55 | case strings.Contains(fashu, "双重吟唱"): 56 | scale = 2 57 | case strings.Contains(fashu, "三重吟唱") && img.Bounds().Dx()*img.Bounds().Dy() < 400000: 58 | scale = 3 59 | case strings.Contains(fashu, "四重吟唱") && img.Bounds().Dx()*img.Bounds().Dy() < 400000: 60 | scale = 4 61 | } 62 | switch { 63 | case strings.Contains(fashu, "强力术式"): 64 | con = "denoise3x" 65 | case strings.Contains(fashu, "中等术式"): 66 | con = "no-denoise" 67 | if scale == 2 { 68 | con = "denoise2x" 69 | } 70 | case strings.Contains(fashu, "弱术式"): 71 | con = "no-denoise" 72 | if scale == 2 { 73 | con = "denoise1x" 74 | } 75 | case strings.Contains(fashu, "不变式"): 76 | con = "no-denoise" 77 | case strings.Contains(fashu, "原式"): 78 | con = "conservative" 79 | } 80 | modelname := fmt.Sprintf("up%vx-latest-%v.pth", scale, con) 81 | encodeStr := base64.StdEncoding.EncodeToString(imgdata) 82 | encodeStr = "data:image/jpeg;base64," + encodeStr 83 | pr := hf.PushRequest{ 84 | Data: []interface{}{encodeStr, modelname, 2}, 85 | } 86 | buf := bytes.NewBuffer([]byte{}) 87 | err = json.NewEncoder(buf).Encode(pr) 88 | if err != nil { 89 | ctx.SendChain(message.Text("ERROR: ", err)) 90 | return 91 | } 92 | data, err := web.PostData(realcuganURL, "application/json", buf) 93 | if err != nil { 94 | ctx.SendChain(message.Text("ERROR: ", err)) 95 | return 96 | } 97 | imgStr := gjson.ParseBytes(data).Get("data.0").String() 98 | m := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Text(scale, "重唱", con, "分支大清晰术!")), 99 | ctxext.FakeSenderForwardNode(ctx, message.Image("base64://"+strings.TrimPrefix(imgStr, "data:image/png;base64,")))} 100 | if id := ctx.Send(m).ID(); id == 0 { 101 | ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待")) 102 | } 103 | } 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /plugin/reborn/born.go: -------------------------------------------------------------------------------- 1 | package reborn 2 | 3 | import ( 4 | wr "github.com/mroth/weightedrand" 5 | ) 6 | 7 | type rate []struct { 8 | Name string `json:"name"` 9 | Weight float64 `json:"weight"` 10 | } 11 | 12 | var ( 13 | areac *wr.Chooser 14 | gender, _ = wr.NewChooser( 15 | wr.Choice{Item: "男孩子", Weight: 50707}, 16 | wr.Choice{Item: "女孩子", Weight: 48292}, 17 | wr.Choice{Item: "雌雄同体", Weight: 1001}, 18 | ) 19 | ) 20 | 21 | func randcoun() string { 22 | return areac.Pick().(string) 23 | } 24 | 25 | func randgen() string { 26 | return gender.Pick().(string) 27 | } 28 | -------------------------------------------------------------------------------- /plugin/reborn/load.go: -------------------------------------------------------------------------------- 1 | package reborn 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/FloatTech/floatbox/file" 7 | "github.com/FloatTech/zbputils/control" 8 | ) 9 | 10 | // load 加载rate数据 11 | func load(area *rate, jsonfile string) error { 12 | data, err := file.GetLazyData(jsonfile, control.Md5File, true) 13 | if err != nil { 14 | return err 15 | } 16 | return json.Unmarshal(data, area) 17 | } 18 | -------------------------------------------------------------------------------- /plugin/reborn/main.go: -------------------------------------------------------------------------------- 1 | // Package reborn 投胎 来自 https://github.com/YukariChiba/tgbot/blob/main/modules/Reborn.py 2 | package reborn 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | 8 | fcext "github.com/FloatTech/floatbox/ctxext" 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | wr "github.com/mroth/weightedrand" 12 | "github.com/sirupsen/logrus" 13 | zero "github.com/wdvxdr1123/ZeroBot" 14 | "github.com/wdvxdr1123/ZeroBot/message" 15 | ) 16 | 17 | func init() { 18 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 19 | DisableOnDefault: false, 20 | Brief: "投胎模拟器", 21 | Help: "- reborn", 22 | PublicDataFolder: "Reborn", 23 | }) 24 | 25 | en.OnFullMatch("reborn", fcext.DoOnceOnSuccess( 26 | func(ctx *zero.Ctx) bool { 27 | datapath := en.DataFolder() 28 | jsonfile := datapath + "rate.json" 29 | area := make(rate, 226) 30 | err := load(&area, jsonfile) 31 | if err != nil { 32 | ctx.SendChain(message.Text("ERROR: ", err)) 33 | return false 34 | } 35 | choices := make([]wr.Choice, len(area)) 36 | for i, a := range area { 37 | choices[i].Item = a.Name 38 | choices[i].Weight = uint(a.Weight * 1e9) 39 | } 40 | areac, err = wr.NewChooser(choices...) 41 | if err != nil { 42 | ctx.SendChain(message.Text("ERROR: ", err)) 43 | return false 44 | } 45 | logrus.Printf("[Reborn]读取%d个国家/地区", len(area)) 46 | return true 47 | }, 48 | )).SetBlock(true). 49 | Handle(func(ctx *zero.Ctx) { 50 | if rand.Int31() > 1<<27 { 51 | ctx.SendChain(message.At(ctx.Event.UserID), message.Text(fmt.Sprintf("投胎成功!\n您出生在 %s, 是 %s。", randcoun(), randgen()))) 52 | } else { 53 | ctx.SendChain(message.At(ctx.Event.UserID), message.Text("投胎失败!\n您没能活到出生,祝您下次好运!")) 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /plugin/runcode/code_runner.go: -------------------------------------------------------------------------------- 1 | // Package runcode 基于 https://tool.runoob.com 的在线运行代码 2 | package runcode 3 | 4 | import ( 5 | "strings" 6 | 7 | "github.com/FloatTech/AnimeAPI/runoob" 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | "github.com/FloatTech/zbputils/ctxext" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/message" 13 | ) 14 | 15 | var ro = runoob.NewRunOOB("066417defb80d038228de76ec581a50a") 16 | 17 | func init() { 18 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 19 | DisableOnDefault: false, 20 | Brief: "在线代码运行", 21 | Help: ">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).OnRegex(`^>runcode(raw)?\s(.+?)\s([\s\S]+)$`).SetBlock(true).Limit(ctxext.LimitByUser). 30 | Handle(func(ctx *zero.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 | ctx.SendChain( 37 | message.Text("> ", ctx.Event.Sender.NickName, "\n"), 38 | message.Text("语言不是受支持的编程语种呢~"), 39 | ) 40 | } else { 41 | // 执行运行 42 | block := message.UnescapeCQText(ctx.State["regex_matched"].([]string)[3]) 43 | switch block { 44 | case "help": 45 | ctx.SendChain( 46 | message.Text("> ", ctx.Event.Sender.NickName, " ", language, "-template:\n"), 47 | message.Text( 48 | ">runcode ", language, "\n", 49 | runoob.Templates[language], 50 | ), 51 | ) 52 | default: 53 | if output, err := ro.Run(block, language, ""); err != nil { 54 | // 运行失败 55 | ctx.SendChain( 56 | message.Text("> ", ctx.Event.Sender.NickName, "\n"), 57 | message.Text("ERROR: ", err), 58 | ) 59 | } else { 60 | // 运行成功 61 | output = cutTooLong(strings.Trim(output, "\n")) 62 | if israw && zero.AdminPermission(ctx) { 63 | ctx.SendChain(message.Text(output)) 64 | } else { 65 | ctx.SendChain( 66 | message.Text("> ", ctx.Event.Sender.NickName, "\n"), 67 | message.Text(output), 68 | ) 69 | } 70 | } 71 | } 72 | } 73 | }) 74 | } 75 | 76 | // 截断过长文本 77 | func cutTooLong(text string) string { 78 | temp := []rune(text) 79 | count := 0 80 | for i := range temp { 81 | switch { 82 | case temp[i] == 13 && i < len(temp)-1 && temp[i+1] == 10: 83 | // 匹配 \r\n 跳过,等 \n 自己加 84 | case temp[i] == 10: 85 | count++ 86 | case temp[i] == 13: 87 | count++ 88 | } 89 | if count > 30 || i > 1000 { 90 | temp = append(temp[:i-1], []rune("\n............\n............")...) 91 | break 92 | } 93 | } 94 | return string(temp) 95 | } 96 | -------------------------------------------------------------------------------- /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 scdata struct { 127 | drawedfile string 128 | picfile string 129 | uid int64 130 | nickname string 131 | inc int // 增加币 132 | score int // 钱包 133 | level int 134 | rank int 135 | } 136 | -------------------------------------------------------------------------------- /plugin/shadiao/ergofabulous.go: -------------------------------------------------------------------------------- 1 | package shadiao 2 | 3 | import ( 4 | "github.com/FloatTech/zbputils/ctxext" 5 | "github.com/antchfx/htmlquery" 6 | zero "github.com/wdvxdr1123/ZeroBot" 7 | "github.com/wdvxdr1123/ZeroBot/message" 8 | ) 9 | 10 | func init() { 11 | engine.OnFullMatch("马丁路德骂我").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 12 | doc, err := htmlquery.LoadURL(ergofabulousURL) 13 | if err != nil { 14 | ctx.SendChain(message.Text("ERROR: ", err)) 15 | return 16 | } 17 | node, err := htmlquery.Query(doc, "//main[@role=\"main\"]/p[@class=\"larger\"]/text()") 18 | if err != nil { 19 | ctx.SendChain(message.Text("ERROR: ", err)) 20 | return 21 | } 22 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(node.Data)) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /plugin/shadiao/shadiao.go: -------------------------------------------------------------------------------- 1 | // Package shadiao 来源于 https://shadiao.app/# 的接口 2 | package shadiao 3 | 4 | import ( 5 | "github.com/FloatTech/floatbox/web" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/FloatTech/zbputils/control" 8 | "github.com/FloatTech/zbputils/ctxext" 9 | "github.com/tidwall/gjson" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | const ( 15 | shadiaoURL = "https://api.shadiao.pro" 16 | chpURL = shadiaoURL + "/chp" 17 | duURL = shadiaoURL + "/du" 18 | pyqURL = shadiaoURL + "/pyq" 19 | yduanziURL = "http://www.yduanzi.com/duanzi/getduanzi" 20 | chayiURL = "https://api.lovelive.tools/api/SweetNothings/Web/0" 21 | ganhaiURL = "https://api.lovelive.tools/api/SweetNothings/Web/1" 22 | ergofabulousURL = "https://ergofabulous.org/luther/?" 23 | ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" 24 | sdReferer = shadiaoURL 25 | yduanziReferer = "http://www.yduanzi.com/?utm_source=shadiao.app" 26 | loveliveReferer = "https://lovelive.tools/" 27 | ) 28 | 29 | var ( 30 | engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 31 | DisableOnDefault: false, 32 | Brief: "沙雕app", // 也许可以更好 33 | Help: "- 哄我\n- 渣我\n- 来碗绿茶\n- 发个朋友圈\n- 来碗毒鸡汤\n- 讲个段子\n- 马丁路德骂我\n", 34 | }) 35 | sdMap = map[string]string{"哄我": chpURL, "来碗毒鸡汤": duURL, "发个朋友圈": pyqURL} 36 | ) 37 | 38 | func init() { 39 | engine.OnFullMatchGroup([]string{"哄我", "来碗毒鸡汤", "发个朋友圈"}).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 40 | requestURL := sdMap[ctx.State["matched"].(string)] 41 | data, err := web.RequestDataWith(web.NewDefaultClient(), requestURL, "GET", sdReferer, ua, nil) 42 | if err != nil { 43 | ctx.SendChain(message.Text("ERROR: ", err)) 44 | return 45 | } 46 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(gjson.GetBytes(data, "data.text").String())) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /plugin/shadiao/sweetnothings.go: -------------------------------------------------------------------------------- 1 | package shadiao 2 | 3 | import ( 4 | "github.com/tidwall/gjson" 5 | zero "github.com/wdvxdr1123/ZeroBot" 6 | "github.com/wdvxdr1123/ZeroBot/message" 7 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 8 | 9 | "github.com/FloatTech/floatbox/web" 10 | "github.com/FloatTech/zbputils/ctxext" 11 | ) 12 | 13 | func init() { 14 | engine.OnFullMatch("来碗绿茶").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 15 | data, err := web.RequestDataWith(web.NewDefaultClient(), chayiURL, "GET", loveliveReferer, ua, nil) 16 | if err != nil { 17 | ctx.SendChain(message.Text("ERROR: ", err)) 18 | return 19 | } 20 | text := gjson.Get(helper.BytesToString(data), "returnObj.content").String() 21 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(text)) 22 | }) 23 | 24 | engine.OnFullMatch("渣我").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 25 | data, err := web.RequestDataWith(web.NewDefaultClient(), ganhaiURL, "GET", loveliveReferer, ua, nil) 26 | if err != nil { 27 | ctx.SendChain(message.Text("ERROR: ", err)) 28 | return 29 | } 30 | text := gjson.Get(helper.BytesToString(data), "returnObj.content").String() 31 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(text)) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /plugin/shadiao/yduanzi.go: -------------------------------------------------------------------------------- 1 | package shadiao 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/tidwall/gjson" 7 | zero "github.com/wdvxdr1123/ZeroBot" 8 | "github.com/wdvxdr1123/ZeroBot/message" 9 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 10 | 11 | "github.com/FloatTech/floatbox/web" 12 | "github.com/FloatTech/zbputils/ctxext" 13 | ) 14 | 15 | func init() { 16 | engine.OnFullMatch("讲个段子").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 17 | data, err := web.RequestDataWith(web.NewDefaultClient(), yduanziURL, "POST", yduanziReferer, ua, nil) 18 | if err != nil { 19 | ctx.SendChain(message.Text("ERROR: ", err)) 20 | return 21 | } 22 | text := gjson.Get(helper.BytesToString(data), "duanzi").String() 23 | text = strings.ReplaceAll(text, "
", "\n") 24 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(text)) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /plugin/shindan/shindan.go: -------------------------------------------------------------------------------- 1 | // Package shindan 基于 https://shindanmaker.com 的测定小功能 2 | package shindan 3 | 4 | import ( 5 | "github.com/FloatTech/AnimeAPI/shindanmaker" 6 | zero "github.com/wdvxdr1123/ZeroBot" 7 | "github.com/wdvxdr1123/ZeroBot/message" 8 | "github.com/wdvxdr1123/ZeroBot/utils/helper" 9 | 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/FloatTech/zbputils/control" 12 | "github.com/FloatTech/zbputils/ctxext" 13 | "github.com/FloatTech/zbputils/img/text" 14 | ) 15 | 16 | func init() { 17 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 18 | DisableOnDefault: false, 19 | Brief: "shindan测定", 20 | Help: "- 今天是什么少女[@xxx]\n" + 21 | "- 异世界转生[@xxx]\n" + 22 | "- 卖萌[@xxx]\n" + 23 | "- 今日老婆[@xxx]\n" + 24 | "- 黄油角色[@xxx]", 25 | }) 26 | engine.OnPrefix("异世界转生", number(587874)).SetBlock(true).Limit(ctxext.LimitByUser).Handle(handlepic) 27 | engine.OnPrefix("今天是什么少女", number(162207)).SetBlock(true).Limit(ctxext.LimitByUser).Handle(handlepic) 28 | engine.OnPrefix("卖萌", number(360578)).SetBlock(true).Limit(ctxext.LimitByUser).Handle(handletxt) 29 | engine.OnPrefix("今日老婆", number(1075116)).SetBlock(true).Limit(ctxext.LimitByUser).Handle(handlecq) 30 | engine.OnPrefix("黄油角色", number(1115465)).SetBlock(true).Limit(ctxext.LimitByUser).Handle(handlepic) 31 | } 32 | 33 | func handletxt(ctx *zero.Ctx) { 34 | // 获取名字 35 | name := ctx.NickName() 36 | // 调用接口 37 | txt, err := shindanmaker.Shindanmaker(ctx.State["id"].(int64), name) 38 | if err != nil { 39 | ctx.SendChain(message.Text("ERROR: ", err)) 40 | return 41 | } 42 | ctx.SendChain(message.Text(txt)) 43 | } 44 | 45 | func handlecq(ctx *zero.Ctx) { 46 | // 获取名字 47 | name := ctx.NickName() 48 | // 调用接口 49 | txt, err := shindanmaker.Shindanmaker(ctx.State["id"].(int64), name) 50 | if err != nil { 51 | ctx.SendChain(message.Text("ERROR: ", err)) 52 | return 53 | } 54 | ctx.Send(txt) 55 | } 56 | 57 | func handlepic(ctx *zero.Ctx) { 58 | // 获取名字 59 | name := ctx.NickName() 60 | // 调用接口 61 | txt, err := shindanmaker.Shindanmaker(ctx.State["id"].(int64), name) 62 | if err != nil { 63 | ctx.SendChain(message.Text("ERROR: ", err)) 64 | return 65 | } 66 | data, err := text.RenderToBase64(txt, text.FontFile, 400, 20) 67 | if err != nil { 68 | ctx.SendChain(message.Text("ERROR: ", err)) 69 | return 70 | } 71 | if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(data))); id.ID() == 0 { 72 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 73 | } 74 | } 75 | 76 | // 传入 shindanmaker id 77 | func number(id int64) func(ctx *zero.Ctx) bool { 78 | return func(ctx *zero.Ctx) bool { 79 | ctx.State["id"] = id 80 | return true 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /plugin/sleepmanage/model.go: -------------------------------------------------------------------------------- 1 | package sleepmanage 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/jinzhu/gorm" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // sdb 睡眠数据库全局变量 12 | var sdb *sleepdb 13 | 14 | // sleepdb 睡眠数据库结构体 15 | type sleepdb gorm.DB 16 | 17 | // initialize 初始化 18 | func initialize(dbpath string) *sleepdb { 19 | var err error 20 | if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) { 21 | // 生成文件 22 | f, err := os.Create(dbpath) 23 | if err != nil { 24 | return nil 25 | } 26 | defer f.Close() 27 | } 28 | gdb, err := gorm.Open("sqlite3", dbpath) 29 | if err != nil { 30 | panic(err) 31 | } 32 | gdb.AutoMigrate(&SleepManage{}) 33 | return (*sleepdb)(gdb) 34 | } 35 | 36 | // Close 关闭 37 | func (sdb *sleepdb) Close() error { 38 | db := (*gorm.DB)(sdb) 39 | return db.Close() 40 | } 41 | 42 | // SleepManage 睡眠信息 43 | type SleepManage struct { 44 | ID uint `gorm:"primary_key"` 45 | GroupID int64 `gorm:"column:group_id"` 46 | UserID int64 `gorm:"column:user_id"` 47 | SleepTime time.Time `gorm:"column:sleep_time"` 48 | } 49 | 50 | // TableName 表名 51 | func (SleepManage) TableName() string { 52 | return "sleep_manage" 53 | } 54 | 55 | // sleep 更新睡眠时间 56 | func (sdb *sleepdb) sleep(gid, uid int64) (position int, awakeTime time.Duration) { 57 | db := (*gorm.DB)(sdb) 58 | now := time.Now() 59 | var today time.Time 60 | if now.Hour() >= 21 { 61 | today = now.Add(-time.Hour*time.Duration(-21+now.Hour()) - time.Minute*time.Duration(now.Minute()) - time.Second*time.Duration(now.Second())) 62 | } else if now.Hour() <= 3 { 63 | today = now.Add(-time.Hour*time.Duration(3+now.Hour()) - time.Minute*time.Duration(now.Minute()) - time.Second*time.Duration(now.Second())) 64 | } 65 | st := SleepManage{ 66 | GroupID: gid, 67 | UserID: uid, 68 | SleepTime: now, 69 | } 70 | if err := db.Model(&SleepManage{}).Where("group_id = ? and user_id = ?", gid, uid).First(&st).Error; err != nil { 71 | // error handling... 72 | if gorm.IsRecordNotFoundError(err) { 73 | db.Model(&SleepManage{}).Create(&st) // newUser not user 74 | } 75 | } else { 76 | log.Debugln("sleeptime为", st) 77 | awakeTime = now.Sub(st.SleepTime) 78 | db.Model(&SleepManage{}).Where("group_id = ? and user_id = ?", gid, uid).Update( 79 | map[string]any{ 80 | "sleep_time": now, 81 | }) 82 | } 83 | db.Model(&SleepManage{}).Where("group_id = ? and sleep_time <= ? and sleep_time >= ?", gid, now, today).Count(&position) 84 | return position, awakeTime 85 | } 86 | 87 | // getUp 更新起床时间 88 | func (sdb *sleepdb) getUp(gid, uid int64) (position int, sleepTime time.Duration) { 89 | db := (*gorm.DB)(sdb) 90 | now := time.Now() 91 | today := now.Add(-time.Hour*time.Duration(-6+now.Hour()) - time.Minute*time.Duration(now.Minute()) - time.Second*time.Duration(now.Second())) 92 | st := SleepManage{ 93 | GroupID: gid, 94 | UserID: uid, 95 | SleepTime: now, 96 | } 97 | if err := db.Model(&SleepManage{}).Where("group_id = ? and user_id = ?", gid, uid).First(&st).Error; err != nil { 98 | // error handling... 99 | if gorm.IsRecordNotFoundError(err) { 100 | db.Model(&SleepManage{}).Create(&st) // newUser not user 101 | } 102 | } else { 103 | log.Debugln("sleeptime为", st) 104 | sleepTime = now.Sub(st.SleepTime) 105 | db.Model(&SleepManage{}).Where("group_id = ? and user_id = ?", gid, uid).Update( 106 | map[string]any{ 107 | "sleep_time": now, 108 | }) 109 | } 110 | db.Model(&SleepManage{}).Where("group_id = ? and sleep_time <= ? and sleep_time >= ?", gid, now, today).Count(&position) 111 | return position, sleepTime 112 | } 113 | -------------------------------------------------------------------------------- /plugin/sleepmanage/sleep_manage.go: -------------------------------------------------------------------------------- 1 | // Package sleepmanage 睡眠管理 2 | package sleepmanage 3 | 4 | import ( 5 | "fmt" 6 | "time" 7 | 8 | log "github.com/sirupsen/logrus" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | 12 | ctrl "github.com/FloatTech/zbpctrl" 13 | "github.com/FloatTech/zbputils/control" 14 | ) 15 | 16 | func init() { 17 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 18 | DisableOnDefault: false, 19 | Brief: "睡眠小助手", 20 | Help: "- 早安\n- 晚安", 21 | PrivateDataFolder: "sleep", 22 | }) 23 | go func() { 24 | sdb = initialize(engine.DataFolder() + "manage.db") 25 | }() 26 | engine.OnFullMatch("早安", isMorning, zero.OnlyGroup).SetBlock(true). 27 | Handle(func(ctx *zero.Ctx) { 28 | position, getUpTime := sdb.getUp(ctx.Event.GroupID, ctx.Event.UserID) 29 | log.Debugln(position, getUpTime) 30 | hour, minute, second := timeDuration(getUpTime) 31 | if (hour == 0 && minute == 0 && second == 0) || hour >= 24 { 32 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("早安成功!你是今天第%d个起床的", position))) 33 | } else { 34 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("早安成功!你的睡眠时长为%d时%d分%d秒,你是今天第%d个起床的", hour, minute, second, position))) 35 | } 36 | }) 37 | engine.OnFullMatch("晚安", isEvening, zero.OnlyGroup).SetBlock(true). 38 | Handle(func(ctx *zero.Ctx) { 39 | position, sleepTime := sdb.sleep(ctx.Event.GroupID, ctx.Event.UserID) 40 | log.Debugln(position, sleepTime) 41 | hour, minute, second := timeDuration(sleepTime) 42 | if (hour == 0 && minute == 0 && second == 0) || hour >= 24 { 43 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("晚安成功!你是今天第%d个睡觉的", position))) 44 | } else { 45 | ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("晚安成功!你的清醒时长为%d时%d分%d秒,你是今天第%d个睡觉的", hour, minute, second, position))) 46 | } 47 | }) 48 | } 49 | 50 | func timeDuration(time time.Duration) (hour, minute, second int64) { 51 | hour = int64(time) / (1000 * 1000 * 1000 * 60 * 60) 52 | minute = (int64(time) - hour*(1000*1000*1000*60*60)) / (1000 * 1000 * 1000 * 60) 53 | second = (int64(time) - hour*(1000*1000*1000*60*60) - minute*(1000*1000*1000*60)) / (1000 * 1000 * 1000) 54 | return hour, minute, second 55 | } 56 | 57 | // 只统计6点到12点的早安 58 | func isMorning(*zero.Ctx) bool { 59 | now := time.Now().Hour() 60 | return now >= 6 && now <= 12 61 | } 62 | 63 | // 只统计21点到凌晨3点的晚安 64 | func isEvening(*zero.Ctx) bool { 65 | now := time.Now().Hour() 66 | return now >= 21 || now <= 3 67 | } 68 | -------------------------------------------------------------------------------- /plugin/steam/store.go: -------------------------------------------------------------------------------- 1 | package steam 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | fcext "github.com/FloatTech/floatbox/ctxext" 8 | sql "github.com/FloatTech/sqlite" 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | zero "github.com/wdvxdr1123/ZeroBot" 11 | "github.com/wdvxdr1123/ZeroBot/message" 12 | ) 13 | 14 | var ( 15 | database streamDB 16 | // 开启并检查数据库链接 17 | getDB = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 18 | database.db = sql.New(engine.DataFolder() + "steam.db") 19 | err := database.db.Open(time.Hour) 20 | if err != nil { 21 | ctx.SendChain(message.Text("[steam] ERROR: ", err)) 22 | return false 23 | } 24 | if err = database.db.Create(tableListenPlayer, &player{}); err != nil { 25 | ctx.SendChain(message.Text("[steam] ERROR: ", err)) 26 | return false 27 | } 28 | // 校验密钥是否初始化 29 | m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) 30 | apiKeyMu.Lock() 31 | defer apiKeyMu.Unlock() 32 | _ = m.GetExtra(&apiKey) 33 | if apiKey == "" { 34 | ctx.SendChain(message.Text("ERROR: 未设置steam apikey")) 35 | return false 36 | } 37 | return true 38 | }) 39 | ) 40 | 41 | // streamDB 继承方法的存储结构 42 | type streamDB struct { 43 | sync.RWMutex 44 | db sql.Sqlite 45 | } 46 | 47 | const ( 48 | // tableListenPlayer 存储查询用户信息 49 | tableListenPlayer = "listen_player" 50 | ) 51 | 52 | // player 用户状态存储结构体 53 | type player struct { 54 | SteamID int64 `json:"steam_id"` // 绑定用户标识ID 55 | PersonaName string `json:"persona_name"` // 用户昵称 56 | Target string `json:"target"` // 信息推送群组 57 | GameID int64 `json:"game_id"` // 游戏ID 58 | GameExtraInfo string `json:"game_extra_info"` // 游戏信息 59 | LastUpdate int64 `json:"last_update"` // 更新时间 60 | } 61 | 62 | // update 如果主键不存在则插入一条新的数据,如果主键存在直接复写 63 | func (sdb *streamDB) update(dbInfo *player) error { 64 | sdb.Lock() 65 | defer sdb.Unlock() 66 | return sdb.db.Insert(tableListenPlayer, dbInfo) 67 | } 68 | 69 | // find 根据主键查信息 70 | func (sdb *streamDB) find(steamID int64) (dbInfo player, err error) { 71 | sdb.Lock() 72 | defer sdb.Unlock() 73 | err = sdb.db.Find(tableListenPlayer, &dbInfo, "WHERE steam_id = ?", steamID) 74 | if err == sql.ErrNullResult { // 规避没有该用户数据的报错 75 | err = nil 76 | } 77 | return 78 | } 79 | 80 | // findWithGroupID 根据用户steamID和groupID查询信息 81 | func (sdb *streamDB) findWithGroupID(steamID int64, groupID string) (dbInfo player, err error) { 82 | sdb.Lock() 83 | defer sdb.Unlock() 84 | err = sdb.db.Find(tableListenPlayer, &dbInfo, "WHERE steam_id = ? AND target LIKE ?", steamID, "%"+groupID+"%") 85 | if err == sql.ErrNullResult { // 规避没有该用户数据的报错 86 | err = nil 87 | } 88 | return 89 | } 90 | 91 | // findAll 查询所有库信息 92 | func (sdb *streamDB) findAll() (dbInfos []*player, err error) { 93 | sdb.Lock() 94 | defer sdb.Unlock() 95 | return sql.FindAll[player](&sdb.db, tableListenPlayer, "") 96 | } 97 | 98 | // del 删除指定数据 99 | func (sdb *streamDB) del(steamID int64) error { 100 | sdb.Lock() 101 | defer sdb.Unlock() 102 | return sdb.db.Del(tableListenPlayer, "WHERE steam_id = ?", steamID) 103 | } 104 | -------------------------------------------------------------------------------- /plugin/tarot/README.md: -------------------------------------------------------------------------------- 1 | # ZeroBot-Plugin-Tarot 2 | 3 | [ZeroBot QQ机器人](https://github.com/wdvxdr1123/ZeroBot)插件,玄学占卜抽塔罗牌! 4 | 5 | ## 触发方式 6 | 7 | - [x] 抽[塔罗牌|大阿卡纳|小阿卡纳] 8 | - [x] 抽n张[塔罗牌|大阿卡纳|小阿卡纳] 9 | - [x] 解塔罗牌[牌名] 10 | - [x] [塔罗|大阿卡纳|小阿卡纳|混合]牌阵[圣三角|时间之流|四要素|五牌阵|吉普赛十字|马蹄|六芒星] 11 | 12 | ## 致谢 13 | 14 | 解牌来自[MinatoAquaCrews/nonebot_plugin_tarot](https://github.com/MinatoAquaCrews/nonebot_plugin_tarot),感谢[KafCoppelia](https://github.com/KafCoppelia)的收集与整理! 15 | -------------------------------------------------------------------------------- /plugin/tiangou/tiangou.go: -------------------------------------------------------------------------------- 1 | // Package tiangou 舔狗日记 2 | package tiangou 3 | 4 | import ( 5 | "time" 6 | 7 | fcext "github.com/FloatTech/floatbox/ctxext" 8 | sql "github.com/FloatTech/sqlite" 9 | ctrl "github.com/FloatTech/zbpctrl" 10 | "github.com/FloatTech/zbputils/control" 11 | "github.com/FloatTech/zbputils/ctxext" 12 | "github.com/sirupsen/logrus" 13 | zero "github.com/wdvxdr1123/ZeroBot" 14 | "github.com/wdvxdr1123/ZeroBot/message" 15 | ) 16 | 17 | type tiangou struct { 18 | ID uint32 `db:"id"` 19 | Text string `db:"text"` 20 | } 21 | 22 | var db sql.Sqlite 23 | 24 | func init() { 25 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 26 | DisableOnDefault: false, 27 | Brief: "舔狗日记", 28 | Help: "- 舔狗日记", 29 | PublicDataFolder: "Tiangou", 30 | }) 31 | 32 | en.OnFullMatch("舔狗日记", fcext.DoOnceOnSuccess( 33 | func(ctx *zero.Ctx) bool { 34 | db = sql.New(en.DataFolder() + "tiangou.db") 35 | _, err := en.GetLazyData("tiangou.db", true) 36 | if err != nil { 37 | ctx.SendChain(message.Text("ERROR: ", err)) 38 | return false 39 | } 40 | err = db.Open(time.Hour) 41 | if err != nil { 42 | ctx.SendChain(message.Text("ERROR: ", err)) 43 | return false 44 | } 45 | err = db.Create("tiangou", &tiangou{}) 46 | if err != nil { 47 | ctx.SendChain(message.Text("ERROR: ", err)) 48 | return false 49 | } 50 | c, err := db.Count("tiangou") 51 | if err != nil { 52 | ctx.SendChain(message.Text("ERROR: ", err)) 53 | return false 54 | } 55 | logrus.Infoln("[tiangou]加载", c, "条舔狗日记") 56 | return true 57 | }, 58 | )).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { 59 | var t tiangou 60 | err := db.Pick("tiangou", &t) 61 | if err != nil { 62 | ctx.SendChain(message.Text("ERROR: ", err)) 63 | return 64 | } 65 | ctx.SendChain(message.Text(t.Text)) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /plugin/tracemoe/moe.go: -------------------------------------------------------------------------------- 1 | // Package tracemoe 搜番 2 | package tracemoe 3 | 4 | import ( 5 | ctrl "github.com/FloatTech/zbpctrl" 6 | "github.com/FloatTech/zbputils/control" 7 | trmoe "github.com/fumiama/gotracemoe" 8 | zero "github.com/wdvxdr1123/ZeroBot" 9 | "github.com/wdvxdr1123/ZeroBot/message" 10 | ) 11 | 12 | var ( 13 | moe = trmoe.NewMoe("") 14 | ) 15 | 16 | func init() { // 插件主体 17 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 18 | DisableOnDefault: false, 19 | Brief: "以图搜番", 20 | Help: "- 以图搜番 | 搜番 | 搜索番剧[图片]", 21 | }) 22 | // 以图搜图 23 | engine.OnKeywordGroup([]string{"以图搜番", "搜番", "搜索番剧"}, zero.MustProvidePicture).SetBlock(true). 24 | Handle(func(ctx *zero.Ctx) { 25 | // 开始搜索图片 26 | ctx.SendChain(message.Text("少女祈祷中......")) 27 | for _, pic := range ctx.State["image_url"].([]string) { 28 | if result, err := moe.Search(pic, true, true); err != nil { 29 | ctx.SendChain(message.Text("ERROR: ", err)) 30 | } else if len(result.Result) > 0 { 31 | r := result.Result[0] 32 | hint := "我有把握是这个!" 33 | if r.Similarity < 80 { 34 | hint = "大概是这个?" 35 | } 36 | mf := int(r.From / 60) 37 | mt := int(r.To / 60) 38 | sf := r.From - float32(mf*60) 39 | st := r.To - float32(mt*60) 40 | ctx.SendChain( 41 | message.Text(hint), 42 | message.Image(r.Image), 43 | message.Text( 44 | "\n", 45 | "番剧名:", r.Anilist.Title.Native, "\n", 46 | "话数:", r.Episode, "\n", 47 | "时间:", mf, ":", sf, "-", mt, ":", st, 48 | ), 49 | ) 50 | } 51 | } 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /plugin/translation/tl.go: -------------------------------------------------------------------------------- 1 | // Package translation 翻译 2 | package translation 3 | 4 | import ( 5 | "github.com/FloatTech/AnimeAPI/tl" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/FloatTech/zbputils/control" 8 | "github.com/FloatTech/zbputils/ctxext" 9 | zero "github.com/wdvxdr1123/ZeroBot" 10 | "github.com/wdvxdr1123/ZeroBot/message" 11 | ) 12 | 13 | func init() { 14 | control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 15 | DisableOnDefault: false, 16 | Brief: "单词翻译", 17 | Help: ">TL [好|good]", 18 | }).OnRegex(`^>TL\s(-.{1,10}? )?(.*)$`).SetBlock(true).Limit(ctxext.LimitByUser). 19 | Handle(func(ctx *zero.Ctx) { 20 | msg := []string{ctx.State["regex_matched"].([]string)[2]} 21 | data, err := tl.Translate(msg[0]) 22 | if err != nil { 23 | ctx.SendChain(message.Text("ERROR: ", data)) 24 | return 25 | } 26 | ctx.SendChain(message.Text(data)) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /plugin/warframeapi/api.go: -------------------------------------------------------------------------------- 1 | package warframeapi 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "net/http" 9 | "sort" 10 | 11 | "github.com/RomiChan/syncx" 12 | "github.com/sirupsen/logrus" 13 | 14 | "github.com/FloatTech/floatbox/web" 15 | ) 16 | 17 | const wfapiurl = "https://api.warframestat.us/pc" // 星际战甲API 18 | const wfitemurl = "https://api.warframe.market/v1/items" // 星际战甲游戏品信息列表URL 19 | 20 | // 从WFapi获取数据 21 | func newwfapi() (w wfapi, err error) { 22 | var data []byte 23 | data, err = web.GetData(wfapiurl) 24 | if err != nil { 25 | return 26 | } 27 | err = json.Unmarshal(data, &w) 28 | return 29 | } 30 | 31 | // 获取Warframe市场的售价表,并进行排序,cn_name为物品中文名称,onlyMaxRank表示只取最高等级的物品,返回物品售价表,物品信息,物品英文 32 | func getitemsorder(cnName string, onlyMaxRank bool) (od orders, it *itemsInSet, n string, err error) { 33 | var wfapiio wfAPIItemsOrders 34 | data, err := web.RequestDataWithHeaders(&http.Client{Transport: &http.Transport{ 35 | TLSClientConfig: &tls.Config{ 36 | MinVersion: tls.VersionTLS12, 37 | }, 38 | }}, fmt.Sprintf("https://api.warframe.market/v1/items/%s/orders?include=item", cnName), "GET", func(request *http.Request) error { 39 | request.Header.Add("Accept", "application/json") 40 | request.Header.Add("Platform", "pc") 41 | return nil 42 | }, nil) 43 | if err != nil { 44 | return 45 | } 46 | err = json.Unmarshal(data, &wfapiio) 47 | if len(wfapiio.Payload.Orders) == 0 { 48 | err = errors.New("no such name") 49 | } 50 | od = make(orders, 0, len(wfapiio.Payload.Orders)) 51 | // 遍历市场物品列表 52 | for _, v := range wfapiio.Payload.Orders { 53 | // 取其中类型为售卖,且去掉不在线的玩家 54 | if v.OrderType == "sell" && v.User.Status != "offline" { 55 | if !onlyMaxRank { 56 | od = append(od, v) 57 | continue 58 | } 59 | if v.ModRank == wfapiio.Include.Item.ItemsInSet[0].ModMaxRank { 60 | od = append(od, v) 61 | } 62 | } 63 | } 64 | // 对报价表进行排序,由低到高 65 | sort.Sort(od) 66 | // 获取物品信息 67 | for i, v := range wfapiio.Include.Item.ItemsInSet { 68 | if v.URLName == cnName { 69 | it = &wfapiio.Include.Item.ItemsInSet[i] 70 | n = v.En.ItemName 71 | return 72 | } 73 | } 74 | it = &wfapiio.Include.Item.ItemsInSet[0] 75 | n = wfapiio.Include.Item.ItemsInSet[0].En.ItemName 76 | return 77 | } 78 | 79 | type wmdata struct { 80 | wmitems map[string]items 81 | itemNames []string 82 | } 83 | 84 | var ( 85 | wderr error 86 | wd = syncx.Lazy[*wmdata]{Init: func() (d *wmdata) { 87 | d, wderr = newwm() 88 | return 89 | }} 90 | ) 91 | 92 | func newwm() (*wmdata, error) { 93 | var itemapi wfAPIItem // WarFrame市场的数据实例 94 | var wd wmdata 95 | data, err := web.RequestDataWithHeaders(&http.Client{Transport: &http.Transport{ 96 | TLSClientConfig: &tls.Config{ 97 | MinVersion: tls.VersionTLS12, 98 | }, 99 | }}, wfitemurl, "GET", func(request *http.Request) error { 100 | request.Header.Add("Accept", "application/json") 101 | request.Header.Add("Language", "zh-hans") 102 | return nil 103 | }, nil) 104 | if err != nil { 105 | return &wd, err 106 | } 107 | err = json.Unmarshal(data, &itemapi) 108 | if err != nil { 109 | return &wd, err 110 | } 111 | wd.wmitems = make(map[string]items, len(itemapi.Payload.Items)*4) 112 | wd.itemNames = make([]string, len(itemapi.Payload.Items)) 113 | for i, v := range itemapi.Payload.Items { 114 | wd.wmitems[v.ItemName] = v 115 | wd.itemNames[i] = v.ItemName 116 | } 117 | logrus.Infoln("[wfapi] 获取", len(wd.itemNames), "项内容") 118 | return &wd, nil 119 | } 120 | -------------------------------------------------------------------------------- /plugin/warframeapi/world.go: -------------------------------------------------------------------------------- 1 | package warframeapi 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | 9 | "github.com/davidscholberg/go-durationfmt" 10 | ) 11 | 12 | // 游戏时间模拟 13 | type timezone struct { 14 | sync.RWMutex `json:"-"` 15 | Name string `json:"name"` // 时间名称 16 | NextTime time.Time `json:"time"` // 下次更新时间 17 | IsDay bool `json:"status"` // 状态 18 | DayDesc string `json:"true_des"` // 状态说明 19 | NightDesc string `json:"false_des"` // 状态说明 20 | DayLen int `json:"day"` // 白天时长 21 | NightLen int `json:"night"` // 夜间时长 22 | } 23 | 24 | type world struct { 25 | w [3]*timezone 26 | hassync uintptr 27 | } 28 | 29 | var gameWorld = newworld() 30 | 31 | // String 根据传入的世界编号,获取对应的游戏时间文本 32 | func (t *timezone) String() string { 33 | t.RLock() 34 | defer t.RUnlock() 35 | sb := strings.Builder{} 36 | sb.WriteString("平原时间: ") 37 | if t.IsDay { 38 | sb.WriteString(t.DayDesc) 39 | } else { 40 | sb.WriteString(t.NightDesc) 41 | } 42 | sb.WriteString(", ") 43 | sb.WriteString("下次更新: ") 44 | d := time.Until(t.NextTime) 45 | durStr, _ := durationfmt.Format(d, "%m分%s秒后") 46 | sb.WriteString(durStr) 47 | return sb.String() 48 | } 49 | 50 | func newworld() (w world) { 51 | w.w = [3]*timezone{ 52 | {Name: "地球平原", DayDesc: "白天", NightDesc: "夜晚", DayLen: 100 * 60, NightLen: 50 * 60}, 53 | {Name: "金星平原", DayDesc: "温暖", NightDesc: "寒冷", DayLen: 400, NightLen: 20 * 60}, 54 | {Name: "火卫二平原", DayDesc: "fass", NightDesc: "vome", DayLen: 100 * 60, NightLen: 50 * 60}, 55 | } 56 | return 57 | } 58 | 59 | func (w *world) hasSync() bool { 60 | return atomic.LoadUintptr(&w.hassync) != 0 61 | } 62 | 63 | func (w *world) setsync() bool { 64 | return atomic.CompareAndSwapUintptr(&w.hassync, 0, 1) 65 | } 66 | 67 | func (w *world) resetsync() bool { 68 | return atomic.CompareAndSwapUintptr(&w.hassync, 1, 0) 69 | } 70 | 71 | // 根据API返回内容修正游戏时间 72 | func (w *world) refresh(api *wfapi) { 73 | for _, t := range w.w { 74 | t.Lock() 75 | } 76 | w.w[0].NextTime = api.CetusCycle.Expiry.Local() 77 | w.w[0].IsDay = api.CetusCycle.IsDay 78 | 79 | w.w[1].NextTime = api.VallisCycle.Expiry.Local() 80 | w.w[1].IsDay = api.VallisCycle.IsWarm 81 | 82 | w.w[2].NextTime = api.CambionCycle.Expiry.Local() 83 | w.w[2].IsDay = api.CambionCycle.Active == "fass" 84 | for _, t := range w.w { 85 | t.Unlock() 86 | } 87 | } 88 | 89 | // 游戏时间更新 90 | func (w *world) update() { 91 | if !w.hasSync() { 92 | return 93 | } 94 | for _, t := range w.w { 95 | t.Lock() 96 | // 当前时间对比下一次游戏状态更新时间,看看还剩多少秒 97 | nt := time.Until(t.NextTime).Seconds() 98 | // 已经过了游戏时间状态更新时间 99 | if nt < 0 { 100 | // 更新游戏状态,如果是白天就切换到晚上,反之亦然 101 | if t.IsDay { 102 | // 计算下次的晚上更新时间 103 | t.NextTime = t.NextTime.Add(time.Duration(t.NightLen) * time.Second) 104 | } else { 105 | // 计算下次的白天更新时间 106 | t.NextTime = t.NextTime.Add(time.Duration(t.DayLen) * time.Second) 107 | } 108 | } 109 | t.Unlock() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /plugin/wife/main.go: -------------------------------------------------------------------------------- 1 | // Package wife 抽老婆 2 | package wife 3 | 4 | import ( 5 | "encoding/json" 6 | "os" 7 | "strings" 8 | 9 | fcext "github.com/FloatTech/floatbox/ctxext" 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/FloatTech/zbputils/control" 12 | "github.com/FloatTech/zbputils/ctxext" 13 | "github.com/sirupsen/logrus" 14 | zero "github.com/wdvxdr1123/ZeroBot" 15 | "github.com/wdvxdr1123/ZeroBot/message" 16 | ) 17 | 18 | func init() { 19 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 20 | DisableOnDefault: false, 21 | Help: "- 抽老婆", 22 | Brief: "从老婆库抽每日老婆", 23 | PublicDataFolder: "Wife", 24 | }).ApplySingle(ctxext.DefaultSingle) 25 | _ = os.MkdirAll(engine.DataFolder()+"wives", 0755) 26 | cards := []string{} 27 | engine.OnFullMatch("抽老婆", fcext.DoOnceOnSuccess( 28 | func(ctx *zero.Ctx) bool { 29 | data, err := engine.GetLazyData("wife.json", true) 30 | if err != nil { 31 | ctx.SendChain(message.Text("ERROR: ", err)) 32 | return false 33 | } 34 | err = json.Unmarshal(data, &cards) 35 | if err != nil { 36 | ctx.SendChain(message.Text("ERROR: ", err)) 37 | return false 38 | } 39 | logrus.Infof("[wife]加载%d个老婆", len(cards)) 40 | return true 41 | }, 42 | )).SetBlock(true). 43 | Handle(func(ctx *zero.Ctx) { 44 | card := cards[fcext.RandSenderPerDayN(ctx.Event.UserID, len(cards))] 45 | data, err := engine.GetLazyData("wives/"+card, true) 46 | card, _, _ = strings.Cut(card, ".") 47 | if err != nil { 48 | ctx.SendChain( 49 | message.At(ctx.Event.UserID), 50 | message.Text("今天的二次元老婆是~【", card, "】哒\n【图片下载失败: ", err, "】"), 51 | ) 52 | return 53 | } 54 | if id := ctx.SendChain( 55 | message.At(ctx.Event.UserID), 56 | message.Text("今天的二次元老婆是~【", card, "】哒"), 57 | message.ImageBytes(data), 58 | ); id.ID() == 0 { 59 | ctx.SendChain( 60 | message.At(ctx.Event.UserID), 61 | message.Text("今天的二次元老婆是~【", card, "】哒\n【图片发送失败, 请联系维护者】"), 62 | ) 63 | } 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /plugin/wtf/main.go: -------------------------------------------------------------------------------- 1 | // Package wtf 鬼东西 2 | package wtf 3 | 4 | import ( 5 | "fmt" 6 | "strconv" 7 | 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | "github.com/FloatTech/zbputils/ctxext" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/message" 13 | ) 14 | 15 | func init() { 16 | en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 17 | DisableOnDefault: false, 18 | Brief: "鬼东西", 19 | Help: "- 鬼东西列表\n- 查询鬼东西[序号][@xxx]", 20 | }) 21 | en.OnFullMatch("鬼东西列表").SetBlock(true). 22 | Handle(func(ctx *zero.Ctx) { 23 | s := "" 24 | for i, w := range table { 25 | s += fmt.Sprintf("%02d. %s\n", i, w.name) 26 | } 27 | ctx.SendChain(message.Text(s)) 28 | }) 29 | en.OnRegex(`^查询鬼东西(\d*)`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByUser). 30 | Handle(func(ctx *zero.Ctx) { 31 | // 调用接口 32 | i, err := strconv.Atoi(ctx.State["regex_matched"].([]string)[1]) 33 | if err != nil { 34 | ctx.SendChain(message.Text("ERROR: ", err)) 35 | return 36 | } 37 | w := newWtf(i) 38 | if w == nil { 39 | ctx.SendChain(message.Text("没有这项内容!")) 40 | return 41 | } 42 | // 获取名字 43 | var name string 44 | var secondname string 45 | if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" { 46 | qq, _ := strconv.ParseInt(ctx.Event.Message[1].Data["qq"], 10, 64) 47 | secondname = ctx.GetThisGroupMemberInfo(qq, false).Get("nickname").Str 48 | } 49 | name = ctx.Event.Sender.NickName 50 | var text string 51 | if secondname != "" { 52 | text, err = w.predict(name, secondname) 53 | } else { 54 | text, err = w.predict(name) 55 | } 56 | if err != nil { 57 | ctx.SendChain(message.Text("ERROR: ", err)) 58 | return 59 | } 60 | // TODO: 可注入 61 | ctx.Send(text) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /plugin/ymgal/ymgal.go: -------------------------------------------------------------------------------- 1 | // Package ymgal 月幕galgame 2 | package ymgal 3 | 4 | import ( 5 | "strings" 6 | 7 | fcext "github.com/FloatTech/floatbox/ctxext" 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/FloatTech/zbputils/control" 10 | "github.com/FloatTech/zbputils/ctxext" 11 | zero "github.com/wdvxdr1123/ZeroBot" 12 | "github.com/wdvxdr1123/ZeroBot/message" 13 | ) 14 | 15 | func init() { 16 | engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ 17 | DisableOnDefault: false, 18 | Brief: "月慕galgame相关", 19 | Help: "- 随机galCG\n- 随机gal表情包\n- galCG[xxx]\n- gal表情包[xxx]\n- 更新gal", 20 | PublicDataFolder: "Ymgal", 21 | }) 22 | getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 23 | dbfile := engine.DataFolder() + "ymgal.db" 24 | _, err := engine.GetLazyData("ymgal.db", false) 25 | if err != nil { 26 | ctx.SendChain(message.Text("ERROR: ", err)) 27 | return false 28 | } 29 | gdb, err = initialize(dbfile) 30 | if err != nil { 31 | ctx.SendChain(message.Text("ERROR: ", err)) 32 | return false 33 | } 34 | return true 35 | }) 36 | engine.OnRegex("^随机gal(CG|表情包)$", getdb).Limit(ctxext.LimitByUser).SetBlock(true). 37 | Handle(func(ctx *zero.Ctx) { 38 | ctx.Send("少女祈祷中......") 39 | pictureType := ctx.State["regex_matched"].([]string)[1] 40 | var y ymgal 41 | if pictureType == "表情包" { 42 | y = gdb.randomYmgal(emoticonType) 43 | } else { 44 | y = gdb.randomYmgal(cgType) 45 | } 46 | sendYmgal(y, ctx) 47 | }) 48 | engine.OnRegex("^gal(CG|表情包)([一-龥ぁ-んァ-ヶA-Za-z0-9]{1,25})$", getdb).Limit(ctxext.LimitByUser).SetBlock(true). 49 | Handle(func(ctx *zero.Ctx) { 50 | ctx.Send("少女祈祷中......") 51 | pictureType := ctx.State["regex_matched"].([]string)[1] 52 | key := ctx.State["regex_matched"].([]string)[2] 53 | var y ymgal 54 | if pictureType == "CG" { 55 | y = gdb.getYmgalByKey(cgType, key) 56 | } else { 57 | y = gdb.getYmgalByKey(emoticonType, key) 58 | } 59 | sendYmgal(y, ctx) 60 | }) 61 | engine.OnFullMatch("更新gal", zero.SuperUserPermission, getdb).SetBlock(true).Handle( 62 | func(ctx *zero.Ctx) { 63 | ctx.Send("少女祈祷中......") 64 | err := updatePic() 65 | if err != nil { 66 | ctx.SendChain(message.Text("ERROR: ", err)) 67 | return 68 | } 69 | ctx.Send("ymgal数据库已更新") 70 | }) 71 | } 72 | 73 | func sendYmgal(y ymgal, ctx *zero.Ctx) { 74 | if y.PictureList == "" { 75 | ctx.SendChain(message.Text(zero.BotConfig.NickName[0] + "暂时没有这样的图呢")) 76 | return 77 | } 78 | m := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Text(y.Title))} 79 | if y.PictureDescription != "" { 80 | m = append(m, ctxext.FakeSenderForwardNode(ctx, message.Text(y.PictureDescription))) 81 | } 82 | for _, v := range strings.Split(y.PictureList, ",") { 83 | m = append(m, ctxext.FakeSenderForwardNode(ctx, message.Image(v))) 84 | } 85 | if id := ctx.Send(m).ID(); id == 0 { 86 | ctx.SendChain(message.Text("ERROR: 可能被风控了")) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | go version 2 | go env -w GOPROXY=https://goproxy.cn,direct 3 | go env -w GO111MODULE=auto 4 | go mod tidy 5 | ::go build -ldflags="-s -w" -o ZeroBot-Plugin.exe 6 | go run main.go 7 | pause 8 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | go version 2 | go env -w GOPROXY=https://goproxy.cn,direct 3 | go env -w GO111MODULE=auto 4 | go mod tidy 5 | #go build -ldflags="-s -w" -o ZeroBot-Plugin 6 | go run main.go 7 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? ( 3 | let 4 | inherit (builtins) fetchTree fromJSON readFile; 5 | inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; 6 | in 7 | import (fetchTree nixpkgs.locked) { 8 | overlays = [ 9 | (import "${fetchTree gomod2nix.locked}/overlay.nix") 10 | ]; 11 | } 12 | ), 13 | mkGoEnv ? pkgs.mkGoEnv, 14 | gomod2nix ? pkgs.gomod2nix, 15 | }: let 16 | goEnv = mkGoEnv { pwd = ./.; go = pkgs.go_1_20; }; 17 | in 18 | pkgs.mkShell { 19 | packages = [ 20 | goEnv 21 | gomod2nix 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /winres/gen/json.go: -------------------------------------------------------------------------------- 1 | // Package main generates winres.json 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "time" 10 | 11 | "github.com/FloatTech/ZeroBot-Plugin/kanban/banner" 12 | ) 13 | 14 | const js = `{ 15 | "RT_GROUP_ICON": { 16 | "APP": { 17 | "0000": [ 18 | "icon.png", 19 | "icon16.png" 20 | ] 21 | } 22 | }, 23 | "RT_MANIFEST": { 24 | "#1": { 25 | "0409": { 26 | "identity": { 27 | "name": "ZeroBot-Plugin", 28 | "version": "%s" 29 | }, 30 | "description": "", 31 | "minimum-os": "vista", 32 | "execution-level": "as invoker", 33 | "ui-access": false, 34 | "auto-elevate": false, 35 | "dpi-awareness": "system", 36 | "disable-theming": false, 37 | "disable-window-filtering": false, 38 | "high-resolution-scrolling-aware": false, 39 | "ultra-high-resolution-scrolling-aware": false, 40 | "long-path-aware": false, 41 | "printer-driver-isolation": false, 42 | "gdi-scaling": false, 43 | "segment-heap": false, 44 | "use-common-controls-v6": false 45 | } 46 | } 47 | }, 48 | "RT_VERSION": { 49 | "#1": { 50 | "0000": { 51 | "fixed": { 52 | "file_version": "%s", 53 | "product_version": "%s", 54 | "timestamp": "%s" 55 | }, 56 | "info": { 57 | "0409": { 58 | "Comments": "OneBot plugins based on ZeroBot", 59 | "CompanyName": "FloatTech", 60 | "FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin", 61 | "FileVersion": "%s", 62 | "InternalName": "", 63 | "LegalCopyright": "%s", 64 | "LegalTrademarks": "", 65 | "OriginalFilename": "ZBP.EXE", 66 | "PrivateBuild": "", 67 | "ProductName": "ZeroBot-Plugin", 68 | "ProductVersion": "%s", 69 | "SpecialBuild": "" 70 | } 71 | } 72 | } 73 | } 74 | } 75 | }` 76 | 77 | const timeformat = `2006-01-02T15:04:05+08:00` 78 | 79 | func main() { 80 | f, err := os.Create("winres.json") 81 | if err != nil { 82 | panic(err) 83 | } 84 | defer f.Close() 85 | i := strings.LastIndex(banner.Version, "-") 86 | if i <= 0 { 87 | i = len(banner.Version) 88 | } 89 | commitcnt := strings.Builder{} 90 | commitcnt.WriteString(banner.Version[1:i]) 91 | commitcnt.WriteByte('.') 92 | commitcntcmd := exec.Command("git", "rev-list", "--count", "HEAD") 93 | commitcntcmd.Stdout = &commitcnt 94 | err = commitcntcmd.Run() 95 | if err != nil { 96 | panic(err) 97 | } 98 | fv := commitcnt.String()[:commitcnt.Len()-1] 99 | _, err = fmt.Fprintf(f, js, fv, fv, banner.Version, time.Now().Format(timeformat), fv, banner.Copyright+". All Rights Reserved.", banner.Version) 100 | if err != nil { 101 | panic(err) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /winres/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloatTech/ZeroBot-Plugin/43b45ce6c5f9bccf8489b122de8c8453550733f1/winres/icon.png -------------------------------------------------------------------------------- /winres/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FloatTech/ZeroBot-Plugin/43b45ce6c5f9bccf8489b122de8c8453550733f1/winres/icon16.png -------------------------------------------------------------------------------- /winres/init.go: -------------------------------------------------------------------------------- 1 | // Package winres 生成windows资源 2 | package winres 3 | 4 | //go:generate go run github.com/FloatTech/ZeroBot-Plugin/winres/gen 5 | -------------------------------------------------------------------------------- /winres/winres.json: -------------------------------------------------------------------------------- 1 | { 2 | "RT_GROUP_ICON": { 3 | "APP": { 4 | "0000": [ 5 | "icon.png", 6 | "icon16.png" 7 | ] 8 | } 9 | }, 10 | "RT_MANIFEST": { 11 | "#1": { 12 | "0409": { 13 | "identity": { 14 | "name": "ZeroBot-Plugin", 15 | "version": "1.9.8.2223" 16 | }, 17 | "description": "", 18 | "minimum-os": "vista", 19 | "execution-level": "as invoker", 20 | "ui-access": false, 21 | "auto-elevate": false, 22 | "dpi-awareness": "system", 23 | "disable-theming": false, 24 | "disable-window-filtering": false, 25 | "high-resolution-scrolling-aware": false, 26 | "ultra-high-resolution-scrolling-aware": false, 27 | "long-path-aware": false, 28 | "printer-driver-isolation": false, 29 | "gdi-scaling": false, 30 | "segment-heap": false, 31 | "use-common-controls-v6": false 32 | } 33 | } 34 | }, 35 | "RT_VERSION": { 36 | "#1": { 37 | "0000": { 38 | "fixed": { 39 | "file_version": "1.9.8.2223", 40 | "product_version": "v1.9.8", 41 | "timestamp": "2025-06-01T18:52:57+08:00" 42 | }, 43 | "info": { 44 | "0409": { 45 | "Comments": "OneBot plugins based on ZeroBot", 46 | "CompanyName": "FloatTech", 47 | "FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin", 48 | "FileVersion": "1.9.8.2223", 49 | "InternalName": "", 50 | "LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.", 51 | "LegalTrademarks": "", 52 | "OriginalFilename": "ZBP.EXE", 53 | "PrivateBuild": "", 54 | "ProductName": "ZeroBot-Plugin", 55 | "ProductVersion": "v1.9.8", 56 | "SpecialBuild": "" 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } --------------------------------------------------------------------------------