├── .github └── workflows │ ├── nightly.yml │ ├── pull.yml │ ├── push.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── kanban └── banner.go ├── main.go ├── plugin ├── action │ └── main.go ├── chat │ └── main.go ├── chun │ ├── main.go │ └── struct.go ├── fortune │ └── main.go ├── lolicon │ └── lolicon.go ├── mai │ ├── alias.go │ ├── lxnsHandler.go │ ├── main.go │ ├── req.go │ ├── sql.go │ └── struct.go ├── phigros │ ├── database.go │ ├── function.go │ └── main.go ├── reborn │ └── main.go ├── score │ ├── coin.go │ └── main.go ├── simai │ └── main.go ├── slash │ └── main.go ├── stickers │ └── main.go ├── tools │ └── main.go ├── tracemoe │ └── moe.go ├── what2eat │ └── main.go └── wife │ ├── main.go │ ├── sqlite.go │ └── struct.go ├── qodana.yaml └── utils ├── bilibili └── fix.go ├── coins └── main.go ├── ctxext └── speed.go ├── toolchain └── toolchain.go ├── transform └── main.go ├── userlist └── main.go └── userpackage └── main.go /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: 最新版 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | BINARY_PREFIX: "rbp_" 7 | BINARY_SUFFIX: "" 8 | PR_PROMPT: "::warning:: Build artifact will not be uploaded due to the workflow is trigged by pull request." 9 | LD_FLAGS: "-w -s" 10 | 11 | jobs: 12 | build: 13 | name: Build binary CI 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | # build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/amd64, darwin/arm64 18 | goos: [linux, windows] 19 | goarch: ["386", amd64, arm, arm64] 20 | exclude: 21 | - goos: windows 22 | goarch: arm 23 | - goos: windows 24 | goarch: arm64 25 | fail-fast: true 26 | steps: 27 | - uses: actions/checkout@master 28 | - name: Setup Go environment 29 | uses: actions/setup-go@master 30 | with: 31 | go-version: 1.19 32 | - name: Cache downloaded module 33 | uses: actions/cache@master 34 | with: 35 | path: | 36 | ~/.cache/go-build 37 | ~/go/pkg/mod 38 | key: ${{ runner.os }}-go-${{ matrix.goos }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }} 39 | - name: Build binary file 40 | env: 41 | GOOS: ${{ matrix.goos }} 42 | GOARCH: ${{ matrix.goarch }} 43 | IS_PR: ${{ !!github.head_ref }} 44 | run: | 45 | if [ $GOOS = "windows" ]; then export BINARY_SUFFIX="$BINARY_SUFFIX.exe"; fi 46 | if $IS_PR ; then echo $PR_PROMPT; fi 47 | export BINARY_NAME="$BINARY_PREFIX$GOOS_$GOARCH$BINARY_SUFFIX" 48 | export CGO_ENABLED=0 49 | go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" . 50 | - name: Upload artifact 51 | uses: actions/upload-artifact@master 52 | if: ${{ !github.head_ref }} 53 | with: 54 | name: ${{ matrix.goos }}_${{ matrix.goarch }} 55 | path: output/ -------------------------------------------------------------------------------- /.github/workflows/pull.yml: -------------------------------------------------------------------------------- 1 | name: PullLint 2 | on: [ pull_request ] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 9 | uses: actions/setup-go@master 10 | with: 11 | go-version: 1.19 12 | 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@master 15 | 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@master 18 | with: 19 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 20 | version: latest 21 | 22 | # Optional: working directory, useful for monorepos 23 | # working-directory: somedir 24 | 25 | # Optional: golangci-lint command line arguments. 26 | # args: --issues-exit-code=0 27 | 28 | # Optional: show only new issues if it's a pull request. The default value is `false`. 29 | # only-new-issues: true 30 | 31 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 32 | # skip-pkg-cache: true 33 | 34 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 35 | # skip-build-cache: true 36 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: PushLint 2 | on: [ push ] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Set up Go 9 | uses: actions/setup-go@master 10 | with: 11 | go-version: 1.19 12 | 13 | - name: Check out code into the Go module directory 14 | uses: actions/checkout@master 15 | 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@master 18 | with: 19 | version: latest 20 | args: --issues-exit-code=0 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 发行版 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.19' 20 | 21 | - name: Run GoReleaser 22 | uses: goreleaser/goreleaser-action@master 23 | with: 24 | version: latest 25 | args: release --rm-dist 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | .vscode 9 | # Test binary, built with `go test -c` 10 | *.test 11 | /plugin/mai/handler.go 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | /data 20 | .idea 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "telegram-bot-api"] 2 | path = telegram-bot-api 3 | url = git@github.com:OvyFlash/telegram-bot-api.git 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | errcheck: 3 | ignore: fmt:.* 4 | ignoretests: true 5 | 6 | goimports: 7 | local-prefixes: github.com/MoYoez/lucy_reibot 8 | 9 | forbidigo: 10 | # Forbid the following identifiers 11 | forbid: 12 | - ^fmt\.Errorf$ # consider errors.Errorf in github.com/pkg/errors 13 | 14 | linters: 15 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 16 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 17 | disable-all: true 18 | fast: false 19 | enable: 20 | - bodyclose 21 | - deadcode 22 | - depguard 23 | - dogsled 24 | - errcheck 25 | - exportloopref 26 | - exhaustive 27 | #- funlen 28 | #- goconst 29 | - gocritic 30 | #- gocyclo 31 | - gofmt 32 | - goimports 33 | - goprintffuncname 34 | #- gosec 35 | - gosimple 36 | - govet 37 | - ineffassign 38 | #- misspell 39 | - nolintlint 40 | - rowserrcheck 41 | - staticcheck 42 | - structcheck 43 | - stylecheck 44 | - typecheck 45 | - unconvert 46 | - unparam 47 | - unused 48 | - varcheck 49 | - whitespace 50 | - prealloc 51 | - predeclared 52 | - asciicheck 53 | - revive 54 | - forbidigo 55 | - makezero 56 | 57 | run: 58 | # default concurrency is a available CPU number. 59 | # concurrency: 4 # explicitly omit this value to fully utilize available resources. 60 | deadline: 5m 61 | issues-exit-code: 1 62 | tests: false 63 | skip-dirs: 64 | - order 65 | go: '1.19 ' 66 | 67 | # output configuration options 68 | output: 69 | format: "colored-line-number" 70 | print-issued-lines: true 71 | print-linter-name: true 72 | uniq-by-line: true 73 | 74 | issues: 75 | # Fix found issues (if it's supported by the linter) 76 | fix: true 77 | exclude-use-default: false 78 | exclude: 79 | - "Error return value of .((os.)?std(out|err)..*|.*Close|.*Seek|.*Flush|os.Remove(All)?|.*print(f|ln)?|os.(Un)?Setenv). is not check" 80 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: rbp 2 | env: 3 | - GO111MODULE=on 4 | before: 5 | hooks: 6 | - go mod tidy 7 | builds: 8 | - id: nowin 9 | env: 10 | - CGO_ENABLED=0 11 | - GO111MODULE=on 12 | goos: 13 | - linux 14 | goarch: 15 | - 386 16 | - amd64 17 | - arm 18 | - arm64 19 | goarm: 20 | - 6 21 | - 7 22 | mod_timestamp: "{{ .CommitTimestamp }}" 23 | flags: 24 | - -trimpath 25 | ldflags: 26 | - -s -w 27 | - id: win 28 | env: 29 | - CGO_ENABLED=0 30 | - GO111MODULE=on 31 | goos: 32 | - windows 33 | goarch: 34 | - 386 35 | - amd64 36 | mod_timestamp: "{{ .CommitTimestamp }}" 37 | flags: 38 | - -trimpath 39 | ldflags: 40 | - -s -w 41 | 42 | checksum: 43 | name_template: "rbp_checksums.txt" 44 | changelog: 45 | sort: asc 46 | filters: 47 | exclude: 48 | - "^docs:" 49 | - "^test:" 50 | - fix typo 51 | - Merge pull request 52 | - Merge branch 53 | - Merge remote-tracking 54 | - go mod tidy 55 | 56 | archives: 57 | - id: nowin 58 | builds: 59 | - nowin 60 | - win 61 | name_template: "rbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 62 | format_overrides: 63 | - goos: windows 64 | format: zip 65 | 66 | nfpms: 67 | - license: GPL 3.0 68 | homepage: https://github.com/MoYoez/Lucy_reibot 69 | file_name_template: "rbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" 70 | formats: 71 | - deb 72 | - rpm 73 | maintainer: FloatTech 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lucy_ReiBot 2 | 3 | Lucy | Ver.2.0 (BETAAAAAAAAA 2.0 ver1) 4 | 5 | 基于 [ReiBot-Plugin](https://github.com/floattech/reibot-plugin) **的 三方 Bot 计划 Lucy** 6 | 7 | > [Lucy's Doc.](https://side-lucy.lemonkoi.one) 8 | 9 | **Made By MoeMagicMango with ❤** 10 | 11 | **Copyright © 2021-2023 FloatTech. All Rights Reserved.** 12 | 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/MoYoez/Lucy_reibot 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/FloatTech/floatbox v0.0.0-20230331064925-9af336a84944 7 | github.com/FloatTech/gg v1.1.2 8 | github.com/FloatTech/imgfactory v0.2.1 9 | github.com/FloatTech/sqlite v1.6.2 10 | github.com/FloatTech/zbpctrl v1.5.3-0.20230130095145-714ad318cd52 11 | github.com/disintegration/imaging v1.6.2 12 | github.com/fogleman/gg v1.3.0 13 | github.com/fumiama/ReiBot v0.0.0-00010101000000-000000000000 14 | github.com/fumiama/go-base16384 v1.7.0 15 | github.com/fumiama/gotracemoe v0.0.3 16 | github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 17 | github.com/jinzhu/gorm v1.9.16 18 | github.com/joho/godotenv v1.5.1 19 | github.com/mroth/weightedrand v1.0.0 20 | github.com/shirou/gopsutil v3.21.11+incompatible 21 | github.com/sirupsen/logrus v1.9.0 22 | github.com/tidwall/gjson v1.14.4 23 | github.com/wdvxdr1123/ZeroBot v1.6.11 24 | github.com/xuri/excelize/v2 v2.8.1 25 | golang.org/x/image v0.14.0 26 | golang.org/x/text v0.14.0 27 | gopkg.in/yaml.v3 v3.0.1 28 | ) 29 | 30 | require ( 31 | github.com/FloatTech/AnimeAPI v1.7.0 // indirect 32 | github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b // indirect 33 | github.com/MoYoez/LucyBaseRouter-Toolchain v0.0.0-20240513105735-fa25f6949091 // indirect 34 | github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e // indirect 35 | github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect 36 | github.com/fumiama/cron v1.3.0 // indirect 37 | github.com/fumiama/go-registry v0.2.6 // indirect 38 | github.com/fumiama/go-simple-protobuf v0.1.0 // indirect 39 | github.com/fumiama/gofastTEA v0.0.10 // indirect 40 | github.com/fumiama/imgsz v0.0.2 // indirect 41 | github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 // indirect 42 | github.com/go-ole/go-ole v1.2.6 // indirect 43 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 44 | github.com/google/uuid v1.3.0 // indirect 45 | github.com/jinzhu/inflection v1.0.0 // indirect 46 | github.com/mattn/go-isatty v0.0.16 // indirect 47 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 48 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect 49 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 50 | github.com/richardlehane/mscfb v1.0.4 // indirect 51 | github.com/richardlehane/msoleps v1.0.3 // indirect 52 | github.com/tidwall/match v1.1.1 // indirect 53 | github.com/tidwall/pretty v1.2.0 // indirect 54 | github.com/tklauser/go-sysconf v0.3.12 // indirect 55 | github.com/tklauser/numcpus v0.6.1 // indirect 56 | github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect 57 | github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect 58 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 59 | golang.org/x/crypto v0.21.0 // indirect 60 | golang.org/x/net v0.22.0 // indirect 61 | golang.org/x/sys v0.18.0 // indirect 62 | modernc.org/libc v1.21.5 // indirect 63 | modernc.org/mathutil v1.5.0 // indirect 64 | modernc.org/memory v1.4.0 // indirect 65 | modernc.org/sqlite v1.20.0 // indirect 66 | ) 67 | 68 | replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.20.0-with-win386 69 | 70 | replace github.com/remyoudompheng/bigfft => github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b 71 | 72 | replace github.com/fumiama/ReiBot => github.com/MoYoez/ReiBot v0.0.0-20240311142840-76873b41aa4a 73 | 74 | replace github.com/go-telegram-bot-api/telegram-bot-api/v5 => ./telegram-bot-api/ 75 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/FloatTech/AnimeAPI v1.7.0 h1:CX1bTjHbVZTYlvppzrWFyLzIFzRVrIDxJmtbWo/Ew6Y= 2 | github.com/FloatTech/AnimeAPI v1.7.0/go.mod h1:6vYu7bW5gPQsBnXB+I6yk+eJQaaAwusoQ/I/wQMwOAI= 3 | github.com/FloatTech/floatbox v0.0.0-20230207080446-026a2f086c74 h1:H6TvOGSq0IydBQDuGqFsRZgMg28XWgI26W+lmBu9jpk= 4 | github.com/FloatTech/floatbox v0.0.0-20230207080446-026a2f086c74/go.mod h1:88i7PKBQuMbMvrQFsfOYC3NprLytdVKnTkHxBLeZk78= 5 | github.com/FloatTech/floatbox v0.0.0-20230331064925-9af336a84944 h1:/eQoMa6Aj3coF5F7yhzZe1+SzX6SItul7MW8//pl18o= 6 | github.com/FloatTech/floatbox v0.0.0-20230331064925-9af336a84944/go.mod h1:FwQm6wk+b4wuW54KCKn3zccMX47Q5apnHD/Yakzv0fI= 7 | github.com/FloatTech/gg v1.1.2 h1:YolgOYg3uDHc1+g0bLtt6QuRA/pvLn+b9IBCIhOOX88= 8 | github.com/FloatTech/gg v1.1.2/go.mod h1:uzPzAeT35egARdRuu+1oyjU3CmTwCceoq3Vvje7LpcI= 9 | github.com/FloatTech/imgfactory v0.2.1 h1:XoVwy0Xu0AvTRtzlhv5teZcuZlAcHrYjeQ8MynJ/zlk= 10 | github.com/FloatTech/imgfactory v0.2.1/go.mod h1:QBJKHbzpE+x/9Wn7mXebWap/K/xUJSjgiaelAElwU9Q= 11 | github.com/FloatTech/sqlite v1.5.7 h1:Bvo4LSojcZ6dVtbHrkqvt6z4v8e+sj0G5PSUIvdawsk= 12 | github.com/FloatTech/sqlite v1.5.7/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY= 13 | github.com/FloatTech/sqlite v1.6.2 h1:FytbExjpvYalZxxITtmSenHiPGLPUvlz47LY/P0SCCw= 14 | github.com/FloatTech/sqlite v1.6.2/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY= 15 | github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJGLDNIdRX3BI546D3O7k7vrVueZw= 16 | github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= 17 | github.com/FloatTech/zbpctrl v1.5.3-0.20230130095145-714ad318cd52 h1:BrStRXeosWh8L0iA/EjPd8w6xNexDkqki39ITZko/9Q= 18 | github.com/FloatTech/zbpctrl v1.5.3-0.20230130095145-714ad318cd52/go.mod h1:qqMLUwR7tKpqnAqsgI7aZbn0hbs2FEVF4ylMXqIpBdY= 19 | github.com/MoYoez/LucyBaseRouter-Toolchain v0.0.0-20240513105735-fa25f6949091 h1:S/bbzRiSkKRu/IJOu0WAPfvs0nJ2cMr2f6frUizIS7Y= 20 | github.com/MoYoez/LucyBaseRouter-Toolchain v0.0.0-20240513105735-fa25f6949091/go.mod h1:cPlGyxReSzPHtl31iX2bYj0VzAXugUqLAImnyJcT3m4= 21 | github.com/MoYoez/ReiBot v0.0.0-20240311142840-76873b41aa4a h1:hVdKlkVTX2ta8BFo2xcH7wBuQVsVzzRNKUbZklyaeNI= 22 | github.com/MoYoez/ReiBot v0.0.0-20240311142840-76873b41aa4a/go.mod h1:d8YGk5jIN2VyMMnMJg6edBt7I7yDhpTXSUELGuLxCCQ= 23 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 24 | github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA= 25 | github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= 26 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 27 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 29 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= 31 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 32 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= 33 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 34 | github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 h1:BBade+JlV/f7JstZ4pitd4tHhpN+w+6I+LyOS7B4fyU= 35 | github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4/go.mod h1:H7chHJglrhPPzetLdzBleF8d22WYOv7UM/lEKYiwlKM= 36 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 37 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 38 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 39 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 40 | github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b h1:Zt3pFQditAdWTHCOVkiloc9ZauBoWrb37guFV4iIRvE= 41 | github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 42 | github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo= 43 | github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY= 44 | github.com/fumiama/go-base16384 v1.6.4 h1:rYDRwD/th2cG4U7QLokpzmST1cCxZGXtHmolOUePt5o= 45 | github.com/fumiama/go-base16384 v1.6.4/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= 46 | github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= 47 | github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= 48 | github.com/fumiama/go-registry v0.2.5 h1:Y6tnHnTThQPv7E4JPM2vBprU+4EQw/LEDO33HCmxgI4= 49 | github.com/fumiama/go-registry v0.2.5/go.mod h1:GP45kejHuDLFxcWdksrt75r5rHBqYwtfeUl3JzGWxfQ= 50 | github.com/fumiama/go-registry v0.2.6 h1:+vEeBUwa1+GC87ujW3Km42fi8O/H7QcpVJWu1iuGNh0= 51 | github.com/fumiama/go-registry v0.2.6/go.mod h1:HjYagPZXzR2xCCxaSQerqX7JRzC0yiv2kslDdBiTq/g= 52 | github.com/fumiama/go-simple-protobuf v0.1.0 h1:rLzJgNqB6LHNDVMl81yyNt6ZKziWtVfu+ioF0edlEVw= 53 | github.com/fumiama/go-simple-protobuf v0.1.0/go.mod h1:5yYNapXq1tQMOZg9bOIVhQlZk9pQqpuFIO4DZLbsdy4= 54 | github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoGhhQ= 55 | github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk= 56 | github.com/fumiama/gotracemoe v0.0.3 h1:iI5EbE9A3UUbfukG6+/soYPjp1S31eCNYf4tw7s6/Jc= 57 | github.com/fumiama/gotracemoe v0.0.3/go.mod h1:tyqahdUzHf0bQIAVY/GYmDWvYYe5ik1ZbhnGYh+zl40= 58 | github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= 59 | github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= 60 | github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhiKdEg+Ig354TaqgCQRxd/0KgIOQ= 61 | github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565/go.mod h1:UUEvyLTJ7yoOA/viKG4wEis4ERydM7+Ny6gZUWgkS80= 62 | github.com/fumiama/sqlite3 v1.20.0-with-win386 h1:ZR1AXGBEtkfq9GAXehOVcwn+aaCG8itrkgEsz4ggx5k= 63 | github.com/fumiama/sqlite3 v1.20.0-with-win386/go.mod h1:Os58MHwYCcYZCy2PGChBrQtBAw5/LS1ZZOkfc+C/I7s= 64 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 65 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 66 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 67 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 68 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 69 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 70 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 71 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 72 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 73 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 74 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= 75 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= 76 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 77 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 78 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 79 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 80 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 81 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 82 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 83 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 84 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 85 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 86 | github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= 87 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 88 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 89 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 90 | github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E= 91 | github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE= 92 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= 93 | github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= 94 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 95 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 | github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= 97 | github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= 98 | github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= 99 | github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= 100 | github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= 101 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 102 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 103 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 104 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 105 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 106 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 107 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 108 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 109 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 110 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 111 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 112 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 113 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 114 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 115 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 116 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 117 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 118 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 119 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 120 | github.com/wdvxdr1123/ZeroBot v1.6.9 h1:vaFqtIXpyeU60xEddg4EsN6cX6cpB3MKhLbe/MQ5OVw= 121 | github.com/wdvxdr1123/ZeroBot v1.6.9/go.mod h1:T5kD5vLi/YxL/fyDOCOaawi96LRBdJjcXh2CIjDyFfg= 122 | github.com/wdvxdr1123/ZeroBot v1.6.11 h1:44Wr6CsCtWlFensK5IhuVCWkosdRw0rA8SygVD8DgoI= 123 | github.com/wdvxdr1123/ZeroBot v1.6.11/go.mod h1:y29UIOy0RD3P+0meDNIWRhcJF3jtWPN9xP9hgt/AJAU= 124 | github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= 125 | github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= 126 | github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= 127 | github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= 128 | github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= 129 | github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= 130 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 131 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 132 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 133 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 134 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 135 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 136 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 137 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 138 | golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= 139 | golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= 140 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 141 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 142 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 143 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 144 | golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= 145 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 146 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 147 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 151 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 152 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 153 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 154 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 155 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 156 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 157 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 158 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 159 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 160 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 161 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 162 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 163 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 164 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 165 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 166 | modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI= 167 | modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= 168 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 169 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 170 | modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= 171 | modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 172 | -------------------------------------------------------------------------------- /kanban/banner.go: -------------------------------------------------------------------------------- 1 | package kanban 2 | 3 | // Banner ... 4 | var Banner = "* Telegram + ReiBot \n" + 5 | "* Unofficial Fork of Lucy (HiMoYo Bot.)\n" + 6 | "* Copyright © 2020 - 2024 FloatTech. All Rights Reserved.\n" + 7 | "* Project: https://github.com/MoYoez/lucy_reibot\n" + 8 | "* ManualBook : https://side-lucy.lemonkoi.one" 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/MoYoez/Lucy_reibot/kanban" 8 | rei "github.com/fumiama/ReiBot" 9 | 10 | _ "github.com/MoYoez/Lucy_reibot/plugin/chat" 11 | _ "github.com/MoYoez/Lucy_reibot/plugin/chun" 12 | _ "github.com/MoYoez/Lucy_reibot/plugin/fortune" 13 | _ "github.com/MoYoez/Lucy_reibot/plugin/lolicon" 14 | _ "github.com/MoYoez/Lucy_reibot/plugin/mai" 15 | _ "github.com/MoYoez/Lucy_reibot/plugin/phigros" 16 | _ "github.com/MoYoez/Lucy_reibot/plugin/reborn" 17 | _ "github.com/MoYoez/Lucy_reibot/plugin/score" 18 | _ "github.com/MoYoez/Lucy_reibot/plugin/tools" 19 | _ "github.com/MoYoez/Lucy_reibot/plugin/tracemoe" 20 | _ "github.com/MoYoez/Lucy_reibot/plugin/what2eat" 21 | _ "github.com/MoYoez/Lucy_reibot/plugin/wife" 22 | 23 | _ "github.com/MoYoez/Lucy_reibot/plugin/action" 24 | _ "github.com/MoYoez/Lucy_reibot/plugin/simai" 25 | _ "github.com/MoYoez/Lucy_reibot/plugin/slash" // slash should be the last 26 | _ "github.com/MoYoez/Lucy_reibot/plugin/stickers" 27 | 28 | "os" 29 | "strconv" 30 | 31 | "github.com/joho/godotenv" 32 | 33 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 34 | "github.com/sirupsen/logrus" 35 | ) 36 | 37 | func main() { 38 | _ = godotenv.Load() 39 | token := flag.String("t", os.Getenv("tgbot"), "telegram api token") 40 | buffer := flag.Int("b", 256, "message sequence length") 41 | debug := flag.Bool("d", false, "enable debug-level log output") 42 | offset := flag.Int("o", 0, "the last Update ID to include") 43 | timeout := flag.Int("T", 60, "timeout") 44 | help := flag.Bool("h", false, "print this help") 45 | flag.Parse() 46 | 47 | if *help { 48 | fmt.Println("Usage:") 49 | flag.PrintDefaults() 50 | os.Exit(0) 51 | } 52 | 53 | if *debug { 54 | logrus.SetLevel(logrus.DebugLevel) 55 | } 56 | 57 | sus := make([]int64, 0, 16) 58 | for _, s := range flag.Args() { 59 | i, err := strconv.ParseInt(s, 10, 64) 60 | if err != nil { 61 | continue 62 | } 63 | sus = append(sus, i) 64 | } 65 | 66 | rei.OnMessageCommand("help").SetBlock(true). 67 | Handle(func(ctx *rei.Ctx) { 68 | ctx.SendPlainMessage(true, kanban.Banner) 69 | }) 70 | rei.Run(rei.Bot{ 71 | Token: *token, 72 | Botname: "Lucy", 73 | Buffer: *buffer, 74 | UpdateConfig: tgba.UpdateConfig{ 75 | Offset: *offset, 76 | Limit: 0, 77 | Timeout: *timeout, 78 | // AllowedUpdates: []string{"message", "edited_message", "message_reaction", "message_reaction_count", "inline_query", "chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer", "my_chat_member", "chat_member", "chat_join_request", "chat_boost", "removed_chat_boost"}, 79 | AllowedUpdates: []string{"message", "edited_message", "inline_query", "chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer", "my_chat_member", "chat_member", "chat_join_request", "chat_boost", "removed_chat_boost"}, 80 | }, 81 | SuperUsers: sus, 82 | Debug: *debug, 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /plugin/action/main.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | ctrl "github.com/FloatTech/zbpctrl" 8 | "github.com/MoYoez/Lucy_reibot/utils/ctxext" 9 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 10 | rei "github.com/fumiama/ReiBot" 11 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 12 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 13 | ) 14 | 15 | var ( 16 | limit = rate.NewManager[int64](time.Minute*10, 15) 17 | LucyImg = "/root/Lucy_Project/memes/" // LucyImg for Lucy的meme表情包地址 18 | ) 19 | 20 | func init() { 21 | engine := rei.Register("action", &ctrl.Options[*rei.Ctx]{ 22 | DisableOnDefault: false, 23 | Help: "Lucy容易被动触发语言\n", 24 | }) 25 | engine.OnMessageFullMatchGroup([]string{"喵", "喵喵", "喵喵喵"}).SetBlock(true).Handle(func(ctx *rei.Ctx) { 26 | if !limit.Load(toolchain.GetThisGroupID(ctx)).Acquire() { 27 | return 28 | } 29 | switch rand.Intn(6) { 30 | case 2, 3: 31 | ctx.SendPhoto(tgba.FilePath(RandImage("6152277811454.jpg", "meow.jpg", "file_3491851.jpg", "file_3492320.jpg")), true, "") 32 | case 4, 5: 33 | ctx.SendPlainMessage(true, []string{"喵喵~", "喵w~"}[rand.Intn(2)]) 34 | } 35 | }) 36 | engine.OnMessageFullMatch("咕咕").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *rei.Ctx) { 37 | if !limit.Load(toolchain.GetThisGroupID(ctx)).Acquire() { 38 | return 39 | } 40 | ctx.SendPlainMessage(true, []string{"炖了~鸽子都要恰掉w", "咕咕咕", "不许咕咕咕"}[rand.Intn(3)]) 41 | }) 42 | engine.OnMessageKeyword("小情侣").SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *rei.Ctx) { 43 | if !limit.Load(toolchain.GetThisGroupID(ctx)).Acquire() { 44 | return 45 | } 46 | if rand.Intn(2) == 1 { 47 | ctx.SendPlainMessage(true, "唉,小情侣") 48 | } 49 | }) 50 | } 51 | 52 | func RandImage(file ...string) string { 53 | return LucyImg + file[rand.Intn(len(file))] 54 | } 55 | -------------------------------------------------------------------------------- /plugin/chat/main.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | ctrl "github.com/FloatTech/zbpctrl" 8 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 9 | rei "github.com/fumiama/ReiBot" 10 | ) 11 | 12 | var ( 13 | engine = rei.Register("chat", &ctrl.Options[*rei.Ctx]{ 14 | DisableOnDefault: false, 15 | Help: "chat", 16 | }) 17 | ) 18 | 19 | func init() { 20 | engine.OnMessage(rei.OnlyToMe).SetBlock(false).Handle(func(ctx *rei.Ctx) { 21 | nickname := "Lucy" // hardcoded is a good choice ( I will fix it later.( 22 | if ctx.Message.Text != "" { 23 | return 24 | } 25 | time.Sleep(time.Second * 1) 26 | toolchain.FastSendRandMuiltText(ctx, "这里是"+nickname+"(っ●ω●)っ", nickname+"不在呢~", "哼!"+nickname+"不想理你~") 27 | }) 28 | engine.OnMessageCommand("callname").SetBlock(true).Handle(func(ctx *rei.Ctx) { 29 | _, results := toolchain.SplitCommandTo(ctx.Message.Text, 2) 30 | if len(results) <= 1 { 31 | return 32 | } 33 | texts := results[1] 34 | if texts == "" { 35 | return 36 | } 37 | if toolchain.StringInArray(texts, []string{"Lucy", "笨蛋", "老公", "猪", "夹子", "主人"}) { 38 | ctx.SendPlainMessage(true, "这些名字可不好哦(敲)") 39 | return 40 | } 41 | getID, _ := toolchain.GetChatUserInfoID(ctx) 42 | userID := strconv.FormatInt(getID, 10) 43 | err := toolchain.StoreUserNickname(userID, texts) 44 | if err != nil { 45 | ctx.SendPlainMessage(true, "发生了一些不可预料的问题 请稍后再试, ERR: ", err) 46 | return 47 | } 48 | ctx.SendPlainMessage(true, "好哦~ ", texts, " ちゃん~~~") 49 | 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /plugin/chun/main.go: -------------------------------------------------------------------------------- 1 | package chun 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/FloatTech/gg" 6 | ctrl "github.com/FloatTech/zbpctrl" 7 | "github.com/MoYoez/Lucy_reibot/plugin/mai" 8 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 9 | rei "github.com/fumiama/ReiBot" 10 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 11 | "strconv" 12 | ) 13 | 14 | var engine = rei.Register("chun", &ctrl.Options[*rei.Ctx]{ 15 | DisableOnDefault: false, 16 | Help: "chun for Lucy", 17 | PrivateDataFolder: "chun", 18 | }) 19 | 20 | func init() { 21 | engine.OnMessageCommand("chun").SetBlock(true).Handle(func(ctx *rei.Ctx) { 22 | getSplitLength, GetSplitInfo := toolchain.SplitCommandTo(ctx.Message.Text, 3) 23 | if getSplitLength >= 2 { 24 | switch { 25 | case GetSplitInfo[1] == "raw": 26 | // baserender 27 | ChunRender(ctx, true) 28 | case GetSplitInfo[1] == "bind": 29 | if getSplitLength < 3 { 30 | ctx.SendPlainMessage(true, "w? 绑定错了啊 是 /chun bind ") 31 | return 32 | } 33 | getUsername := GetSplitInfo[2] 34 | mai.BindUserToMaimai(ctx, getUsername) 35 | return 36 | } 37 | 38 | } else { 39 | ChunRender(ctx, false) 40 | } 41 | 42 | }) 43 | } 44 | 45 | func ChunRender(ctx *rei.Ctx, israw bool) { 46 | // check the user using. 47 | getUserID, _ := toolchain.GetChatUserInfoID(ctx) 48 | getUsername := mai.GetUserInfoNameFromDatabase(getUserID) 49 | if getUsername == "" { 50 | ctx.SendPlainMessage(true, "你还没有绑定呢!使用/chun bind 以绑定") 51 | return 52 | } 53 | getUserData, err := QueryChunDataFromUserName(getUsername) 54 | if err != nil { 55 | ctx.SendPlainMessage(true, err) 56 | return 57 | } 58 | var data ChunData 59 | _ = json.Unmarshal(getUserData, &data) 60 | renderImg := BaseRender(data, ctx) 61 | _ = gg.NewContextForImage(renderImg).SaveJPG(engine.DataFolder()+"save/"+strconv.Itoa(int(getUserID))+".png", 80) 62 | 63 | if israw { 64 | getDocumentType := &tgba.DocumentConfig{ 65 | BaseFile: tgba.BaseFile{BaseChat: tgba.BaseChat{ 66 | ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}, 67 | }, 68 | File: tgba.FilePath(engine.DataFolder() + "save/" + strconv.Itoa(int(getUserID)) + ".png")}, 69 | Caption: "", 70 | CaptionEntities: nil, 71 | } 72 | ctx.Send(true, getDocumentType) 73 | } else { 74 | ctx.SendPhoto(tgba.FilePath(engine.DataFolder()+"save/"+strconv.Itoa(int(getUserID))+".png"), true, "") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /plugin/chun/struct.go: -------------------------------------------------------------------------------- 1 | package chun 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "github.com/FloatTech/gg" 8 | "github.com/FloatTech/imgfactory" 9 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 10 | rei "github.com/fumiama/ReiBot" 11 | "golang.org/x/image/font" 12 | "golang.org/x/image/font/opentype" 13 | "golang.org/x/text/width" 14 | "image" 15 | "image/color" 16 | "image/png" 17 | "io" 18 | "log" 19 | "net/http" 20 | "os" 21 | "strconv" 22 | "unicode/utf8" 23 | ) 24 | 25 | // ChunData Struct. 26 | type ChunData struct { 27 | Nickname string `json:"nickname"` 28 | Rating float64 `json:"rating"` 29 | Records struct { 30 | B30 []struct { 31 | Cid int `json:"cid"` 32 | Ds float64 `json:"ds"` 33 | Fc string `json:"fc"` 34 | Level string `json:"level"` 35 | LevelIndex int `json:"level_index"` 36 | LevelLabel string `json:"level_label"` 37 | Mid int `json:"mid"` 38 | Ra float64 `json:"ra"` 39 | Score int `json:"score"` 40 | Title string `json:"title"` 41 | } `json:"b30"` 42 | R10 []struct { 43 | Cid int `json:"cid"` 44 | Ds float64 `json:"ds"` 45 | Fc string `json:"fc"` 46 | Level string `json:"level"` 47 | LevelIndex int `json:"level_index"` 48 | LevelLabel string `json:"level_label"` 49 | Mid int `json:"mid"` 50 | Ra float64 `json:"ra"` 51 | Score int `json:"score"` 52 | Title string `json:"title"` 53 | } `json:"r10"` 54 | } `json:"records"` 55 | Username string `json:"username"` 56 | } 57 | 58 | // UserDataInner CardBase 59 | type UserDataInner []struct { 60 | Cid int `json:"cid"` 61 | Ds float64 `json:"ds"` 62 | Fc string `json:"fc"` 63 | Level string `json:"level"` 64 | LevelIndex int `json:"level_index"` 65 | LevelLabel string `json:"level_label"` 66 | Mid int `json:"mid"` 67 | Ra float64 `json:"ra"` 68 | Score int `json:"score"` 69 | Title string `json:"title"` 70 | } 71 | 72 | type DivingFishB50 struct { 73 | Username string `json:"username"` 74 | B50 bool `json:"b50"` 75 | } 76 | 77 | var ( 78 | Root = engine.DataFolder() 79 | Texture = engine.DataFolder() + "texture/" 80 | ) 81 | 82 | // QueryChunDataFromUserName Query Chun Data. 83 | func QueryChunDataFromUserName(userName string) (playerdata []byte, err error) { 84 | // packed json and sent. 85 | jsonStruct := DivingFishB50{Username: userName, B50: true} 86 | jsonStructData, err := json.Marshal(jsonStruct) 87 | if err != nil { 88 | return nil, err 89 | } 90 | req, err := http.NewRequest("POST", "https://www.diving-fish.com/api/chunithmprober/query/player", bytes.NewBuffer(jsonStructData)) 91 | req.Header.Set("Content-Type", "application/json") 92 | if err != nil { 93 | panic(err) 94 | } 95 | client := &http.Client{} 96 | resp, err := client.Do(req) 97 | if err != nil { 98 | panic(err) 99 | } 100 | defer resp.Body.Close() 101 | if resp.StatusCode == 400 { 102 | return nil, errors.New("- 未找到用户或者用户数据丢失\n\n - 请检查您是否在 水鱼查分器 上 上传过成绩并且有绑定QQ号") 103 | } 104 | if resp.StatusCode == 403 { 105 | return nil, errors.New("- 该用户设置禁止查分\n\n - 请检查您是否在 水鱼查分器 上 是否关闭了允许他人查分功能") 106 | } 107 | playerData, err := io.ReadAll(resp.Body) 108 | return playerData, err 109 | } 110 | 111 | func RenderCardChun(data UserDataInner, renderCount int) image.Image { 112 | // get pic 113 | onloadPic, _ := GetCover(strconv.Itoa(data[renderCount].Mid)) 114 | loadTable, _ := gg.LoadImage(Texture + LevelIndexCount(data[renderCount].LevelIndex) + "_table.png") 115 | getPic := gg.NewContextForImage(loadTable) 116 | getPic.DrawImage(onloadPic, 250, 10) 117 | getPic.Fill() 118 | // draw Name 119 | getTitleLoader := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Bold.otf", 25) 120 | getPic.SetFontFace(getTitleLoader) 121 | getPic.SetColor(color.Black) 122 | getPic.DrawStringAnchored(BreakWords(data[renderCount].Title, 15), 15, 58, 0, 0) 123 | getPic.Fill() 124 | // draw FC/AJ if possible. 125 | var returnFCAJLink string 126 | if data[renderCount].Fc != "" { 127 | returnFCAJLink = Texture + "icon_" + "fullcombo" + ".png" 128 | } else { 129 | returnFCAJLink = Texture + "icon_" + "clear" + ".png" 130 | } 131 | getLink, _ := gg.LoadImage(returnFCAJLink) 132 | getPic.DrawImage(getLink, 30, 85) 133 | getPic.Fill() 134 | // draw line 135 | getPic.DrawLine(115, 80, 115, 110) 136 | getPic.Stroke() 137 | // draw Upper 138 | getTitleLoaderS := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Bold.otf", 18) 139 | getPic.SetFontFace(getTitleLoaderS) 140 | getPic.DrawStringAnchored(strconv.FormatFloat(data[renderCount].Ds, 'f', 1, 64), 125, 105, 0, 0) 141 | getPic.DrawStringAnchored(">", 165, 105, 0, 0) 142 | // draw num 143 | DrawBorderString(getPic, "# "+strconv.Itoa(renderCount+1), 3, 10, 20, 0, 0, color.White, color.Black) 144 | getPic.SetColor(color.White) 145 | getPic.DrawString("# "+strconv.Itoa(renderCount+1), 10, 20) 146 | getPic.SetColor(color.Black) 147 | getTitleLoaderHeader := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Bold.otf", 24) 148 | getPic.SetFontFace(getTitleLoaderHeader) 149 | getPic.DrawStringAnchored(strconv.FormatFloat(data[renderCount].Ra, 'f', 1, 64), 180, 105, 0, 0) 150 | getTitleLoaderScore := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Bold.otf", 40) 151 | getPic.SetFontFace(getTitleLoaderScore) 152 | getPic.DrawStringAnchored(formatNumber(data[renderCount].Score), 120, 130, 0.5, 0.5) 153 | return getPic.Image() 154 | } 155 | 156 | // Resize Image width height 157 | func Resize(image image.Image, w int, h int) image.Image { 158 | return imgfactory.Size(image, w, h).Image() 159 | } 160 | 161 | func LevelIndexCount(count int) string { 162 | switch { 163 | case count == 0: 164 | return "basic" 165 | case count == 1: 166 | return "advance" 167 | case count == 2: 168 | return "expert" 169 | case count == 3: 170 | return "master" 171 | case count == 4: 172 | return "ultra" 173 | case count == 5: 174 | return "worldend" 175 | } 176 | return "" 177 | } 178 | 179 | // Used For format Chunithm Score Game 180 | func formatNumber(number int) string { 181 | numStr := strconv.Itoa(number) 182 | length := len(numStr) 183 | zeroCount := 7 - length 184 | if zeroCount < 0 { 185 | zeroCount = 0 186 | } 187 | for i := 0; i < zeroCount; i++ { 188 | numStr = "0" + numStr 189 | } 190 | formattedStr := "" 191 | for i, char := range numStr { 192 | if i > 0 && (length-i)%3 == 0 { 193 | formattedStr += "," 194 | } 195 | formattedStr += string(char) 196 | } 197 | 198 | return formattedStr 199 | } 200 | 201 | func getColorByRating(value float64) color.Color { 202 | switch { 203 | case value >= 0.00 && value <= 3.99: 204 | return color.NRGBA{G: 255, A: 255} 205 | case value >= 4.00 && value <= 6.99: 206 | return color.NRGBA{R: 255, G: 102, A: 255} 207 | case value >= 7.00 && value <= 9.99: 208 | return color.NRGBA{R: 255, A: 255} 209 | case value >= 10.00 && value <= 11.99: 210 | return color.NRGBA{R: 255, B: 255, A: 255} 211 | case value >= 12.00 && value <= 13.24: 212 | return color.NRGBA{R: 153, G: 51, A: 255} 213 | case value >= 13.25 && value <= 14.49: 214 | return color.NRGBA{R: 128, G: 128, B: 128, A: 255} 215 | case value >= 14.50 && value <= 15.24: 216 | return color.NRGBA{R: 255, G: 204, A: 255} 217 | case value >= 15.25 && value <= 15.99: 218 | return color.NRGBA{R: 255, G: 255, A: 255} 219 | default: 220 | return color.NRGBA{R: 204, G: 153, B: 255, A: 255} 221 | } 222 | } 223 | 224 | func BaseRender(JsonResultData ChunData, ctx *rei.Ctx) image.Image { 225 | bgMain, err := gg.LoadImage(Texture + "Background_SUN.png") 226 | if err != nil { 227 | panic(err) 228 | } 229 | getContent := gg.NewContextForImage(bgMain) 230 | startCountWidth := 700 231 | StartCountHeight := 800 232 | baseCount := 0 233 | // render B30 + B15 234 | var sumUserB30 float64 235 | var SumUserR10 float64 236 | for renderCount := range JsonResultData.Records.B30 { 237 | sumUserB30 += JsonResultData.Records.B30[renderCount].Ra 238 | getRender := RenderCardChun(JsonResultData.Records.B30, renderCount) 239 | getContent.DrawImage(getRender, startCountWidth, StartCountHeight) 240 | startCountWidth += 550 241 | baseCount += 1 242 | if baseCount == 5 { 243 | baseCount = 0 244 | startCountWidth = 700 245 | StartCountHeight += 230 246 | } 247 | } 248 | sumUserB30Result := sumUserB30 / float64(len(JsonResultData.Records.B30)) 249 | startCountWidthRecent := 3840 250 | StartCountHeightRecent := 915 251 | baseCountRecent := 0 252 | for renderCountBase := range JsonResultData.Records.R10 { 253 | SumUserR10 += JsonResultData.Records.R10[renderCountBase].Ra 254 | getRender := RenderCardChun(JsonResultData.Records.R10, renderCountBase) 255 | getContent.DrawImage(getRender, startCountWidthRecent, StartCountHeightRecent) 256 | startCountWidthRecent += 440 257 | baseCountRecent += 1 258 | if baseCountRecent == 2 { 259 | baseCountRecent = 0 260 | startCountWidthRecent = 3840 261 | StartCountHeightRecent += 230 262 | } 263 | } 264 | sumUserR10Result := SumUserR10 / float64(len(JsonResultData.Records.R10)) 265 | // RENDER USER COUNT 266 | getRecentUserCount := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Bold.otf", 60) 267 | getContent.SetFontFace(getRecentUserCount) 268 | getContent.SetColor(color.White) 269 | getContent.DrawStringAnchored("BEST 30: "+strconv.FormatFloat(sumUserB30Result, 'f', 2, 64), 1500, 730, 0, 0) 270 | getContent.DrawStringAnchored("RECENT 10: "+strconv.FormatFloat(sumUserR10Result, 'f', 2, 64), 2100, 730, 0, 0) 271 | getContent.Fill() 272 | // Draw USERTABLE 273 | getUserNameFontTitle := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Regular.otf", 50) 274 | getContent.SetFontFace(getUserNameFontTitle) 275 | getContent.SetColor(color.Black) 276 | getContent.DrawStringAnchored(width.Widen.String(JsonResultData.Nickname), 630, 330, 0, 0) 277 | getContent.Fill() 278 | // Rating 279 | getUserNameFontTitleSmaller := LoadFontFace(Texture+"fonts/"+"SourceHanSansCN-Regular.otf", 35) 280 | getContent.SetFontFace(getUserNameFontTitleSmaller) 281 | DrawBorderString(getContent, "RATING ", 3, 490, 420, 0, 0, getColorByRating(JsonResultData.Rating), color.Black) 282 | getContent.SetFontFace(getUserNameFontTitle) 283 | DrawBorderString(getContent, width.Widen.String(strconv.FormatFloat(JsonResultData.Rating, 'f', 2, 64)), 3, 630, 420, 0, 0, getColorByRating(JsonResultData.Rating), color.Black) 284 | getContent.Fill() 285 | // draw Avatar 286 | if toolchain.GetTargetAvatar(ctx) != nil { 287 | getContent.DrawImage(Resize(toolchain.GetTargetAvatar(ctx), 179, 179), 1030, 258) 288 | getContent.Fill() 289 | } 290 | return getContent.Image() 291 | } 292 | 293 | // GetCover Careful The nil data 294 | func GetCover(id string) (image.Image, error) { 295 | fileName := id + ".png" 296 | filePath := Root + "cover/" + fileName 297 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 298 | // Auto download cover from diving fish's site 299 | downloadURL := "https://lx-rec-reproxy.lemonkoi.one/chunithm/jacket/" + id + ".png" 300 | cover, err := DownloadImage(downloadURL) 301 | if err != nil { 302 | return nil, err 303 | } 304 | SaveImage(cover, filePath) 305 | } 306 | imageFile, err := os.Open(filePath) 307 | if err != nil { 308 | return nil, err 309 | } 310 | defer func(imageFile *os.File) { 311 | err := imageFile.Close() 312 | if err != nil { 313 | return 314 | } 315 | }(imageFile) 316 | img, _, err := image.Decode(imageFile) 317 | if err != nil { 318 | return nil, err 319 | } 320 | return Resize(img, 155, 154), nil 321 | } 322 | 323 | // LoadFontFace load font face once before running, to work it quickly and save memory. 324 | func LoadFontFace(filePath string, size float64) font.Face { 325 | fontFile, _ := os.ReadFile(filePath) 326 | fontFileParse, _ := opentype.Parse(fontFile) 327 | fontFace, _ := opentype.NewFace(fontFileParse, &opentype.FaceOptions{Size: size, DPI: 72, Hinting: font.HintingFull}) 328 | return fontFace 329 | } 330 | 331 | // BreakWords Reduce the length of strings, if out of range, use ".." instead. 332 | func BreakWords(getSongName string, breakerCount float64) string { 333 | charCount := 0.0 334 | setBreaker := false 335 | var truncated string 336 | var charFloatNum float64 337 | for _, runeValue := range getSongName { 338 | charWidth := utf8.RuneLen(runeValue) 339 | if charWidth == 3 { 340 | charFloatNum = 2 341 | } else { 342 | charFloatNum = float64(charWidth) 343 | } 344 | if charCount+charFloatNum > breakerCount { 345 | setBreaker = true 346 | break 347 | } 348 | truncated += string(runeValue) 349 | charCount += charFloatNum 350 | } 351 | if setBreaker { 352 | getSongName = truncated + ".." 353 | } else { 354 | getSongName = truncated 355 | } 356 | return getSongName 357 | } 358 | 359 | // DrawBorderString GG Package Not support The string render, so I write this (^^) 360 | func DrawBorderString(page *gg.Context, s string, size int, x float64, y float64, ax float64, ay float64, inlineRGB color.Color, outlineRGB color.Color) { 361 | page.SetColor(outlineRGB) 362 | n := size 363 | for dy := -n; dy <= n; dy++ { 364 | for dx := -n; dx <= n; dx++ { 365 | if dx*dx+dy*dy >= n*n { 366 | continue 367 | } 368 | renderX := x + float64(dx) 369 | renderY := y + float64(dy) 370 | page.DrawStringAnchored(s, renderX, renderY, ax, ay) 371 | } 372 | } 373 | page.SetColor(inlineRGB) 374 | page.DrawStringAnchored(s, x, y, ax, ay) 375 | } 376 | 377 | // SaveImage Save Cover Chun | Maimai 378 | func SaveImage(img image.Image, path string) { 379 | files, err := os.Create(path) 380 | if err != nil { 381 | log.Fatal(err) 382 | } 383 | defer func(files *os.File) { 384 | err := files.Close() 385 | if err != nil { 386 | return 387 | } 388 | }(files) 389 | err = png.Encode(files, img) 390 | if err != nil { 391 | log.Fatal(err) 392 | } 393 | } 394 | 395 | // DownloadImage Simple Downloader. 396 | func DownloadImage(url string) (image.Image, error) { 397 | response, err := http.Get(url) 398 | if err != nil { 399 | return nil, err 400 | } 401 | defer func(Body io.ReadCloser) { 402 | err := Body.Close() 403 | if err != nil { 404 | return 405 | } 406 | }(response.Body) 407 | img, _, err := image.Decode(response.Body) 408 | if err != nil { 409 | return nil, err 410 | } 411 | return img, nil 412 | } 413 | -------------------------------------------------------------------------------- /plugin/fortune/main.go: -------------------------------------------------------------------------------- 1 | // Package fortune 2 | package fortune 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "hash/crc64" 8 | "image" 9 | "image/color" 10 | "math" 11 | "math/rand" 12 | "os" 13 | "regexp" 14 | "strconv" 15 | "time" 16 | "unicode/utf8" 17 | "unsafe" 18 | 19 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 20 | "github.com/FloatTech/floatbox/binary" 21 | "github.com/FloatTech/imgfactory" 22 | "github.com/fogleman/gg" 23 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 24 | "golang.org/x/image/font" 25 | "golang.org/x/image/font/opentype" 26 | 27 | "github.com/MoYoez/Lucy_reibot/utils/transform" 28 | ctrl "github.com/FloatTech/zbpctrl" 29 | rei "github.com/fumiama/ReiBot" 30 | ) 31 | 32 | type card struct { 33 | Name string `json:"name"` 34 | Info struct { 35 | Description string `json:"description"` 36 | ReverseDescription string `json:"reverseDescription"` 37 | ImgURL string `json:"imgUrl"` 38 | } `json:"info"` 39 | } 40 | 41 | type cardset = map[string]card 42 | 43 | var ( 44 | info string 45 | cardMap = make(cardset, 256) 46 | position = []string{"正位", "逆位"} 47 | result map[int64]int 48 | signTF map[string]int 49 | ) 50 | 51 | func init() { 52 | engine := rei.Register("fortune", &ctrl.Options[*rei.Ctx]{ 53 | DisableOnDefault: false, 54 | Help: "Hi NekoPachi!\n说明书: https://lucy-sider.lemonkoi.one", 55 | PrivateDataFolder: "fortune", 56 | }) 57 | signTF = make(map[string]int) 58 | result = make(map[int64]int) 59 | // onload fortune mapset. 60 | data, err := os.ReadFile(transform.ReturnLucyMainDataIndex("funwork") + "tarots.json") 61 | if err != nil { 62 | return 63 | } 64 | _ = json.Unmarshal(data, &cardMap) 65 | picDir, err := os.ReadDir(transform.ReturnLucyMainDataIndex("funwork") + "randpic") 66 | if err != nil { 67 | return 68 | } 69 | picDirNum := len(picDir) 70 | reg := regexp.MustCompile(`[^.]+`) 71 | engine.OnMessageCommand("fortune").SetBlock(true).Handle(func(ctx *rei.Ctx) { 72 | getUserID, getUserName := toolchain.GetChatUserInfoID(ctx) 73 | userPic := strconv.FormatInt(getUserID, 10) + time.Now().Format("20060102") + ".png" 74 | usersRandPic := RandSenderPerDayN(getUserID, picDirNum) 75 | picDirName := picDir[usersRandPic].Name() 76 | list := reg.FindAllString(picDirName, -1) 77 | p := rand.Intn(2) 78 | is := rand.Intn(77) 79 | i := is + 1 80 | card := cardMap[(strconv.Itoa(i))] 81 | if p == 0 { 82 | info = card.Info.Description 83 | } else { 84 | info = card.Info.ReverseDescription 85 | } 86 | userS := strconv.FormatInt(getUserID, 10) 87 | now := time.Now().Format("20060102") 88 | // modify this possibility to 40-100, don't be to low. 89 | randEveryone := RandSenderPerDayN(getUserID, 50) 90 | var si = now + userS // use map to store. 91 | loadNotoSans := transform.ReturnLucyMainDataIndex("funwork") + "NotoSansCJKsc-Regular.otf" 92 | if signTF[si] == 0 { 93 | result[getUserID] = randEveryone + 50 94 | // background 95 | img, err := gg.LoadImage(transform.ReturnLucyMainDataIndex("funwork") + "randpic" + "/" + list[0] + ".png") 96 | if err != nil { 97 | return 98 | } 99 | bgFormat := imgfactory.Limit(img, 1920, 1080) 100 | getBackGroundMainColorR, getBackGroundMainColorG, getBackGroundMainColorB := GetAverageColorAndMakeAdjust(bgFormat) 101 | mainContext := gg.NewContext(bgFormat.Bounds().Dx(), bgFormat.Bounds().Dy()) 102 | mainContextWidth := mainContext.Width() 103 | mainContextHight := mainContext.Height() 104 | mainContext.DrawImage(bgFormat, 0, 0) 105 | // draw Round rectangle 106 | mainContext.SetFontFace(LoadFontFace(loadNotoSans, 50)) 107 | if err != nil { 108 | _, _ = ctx.SendPlainMessage(false, "Something wrong while rendering pic? font") 109 | return 110 | } 111 | // shade mode || not bugs( 112 | mainContext.SetLineWidth(4) 113 | mainContext.SetRGBA255(255, 255, 255, 255) 114 | mainContext.DrawRoundedRectangle(0, float64(mainContextHight-150), float64(mainContextWidth), 150, 16) 115 | mainContext.Stroke() 116 | mainContext.SetRGBA255(255, 224, 216, 215) 117 | mainContext.DrawRoundedRectangle(0, float64(mainContextHight-150), float64(mainContextWidth), 150, 16) 118 | mainContext.Fill() 119 | // avatar,name,desc 120 | // draw third round rectangle 121 | mainContext.SetRGBA255(91, 57, 83, 255) 122 | mainContext.SetFontFace(LoadFontFace(loadNotoSans, 25)) 123 | charCount := 0.0 124 | setBreaker := false 125 | emojiRegex := regexp.MustCompile(`[\x{1F600}-\x{1F64F}|[\x{1F300}-\x{1F5FF}]|[\x{1F680}-\x{1F6FF}]|[\x{1F700}-\x{1F77F}]|[\x{1F780}-\x{1F7FF}]|[\x{1F800}-\x{1F8FF}]|[\x{1F900}-\x{1F9FF}]|[\x{1FA00}-\x{1FA6F}]|[\x{1FA70}-\x{1FAFF}]|[\x{1FB00}-\x{1FBFF}]|[\x{1F170}-\x{1F251}]|[\x{1F300}-\x{1F5FF}]|[\x{1F600}-\x{1F64F}]|[\x{1FC00}-\x{1FCFF}]|[\x{1F004}-\x{1F0CF}]|[\x{1F170}-\x{1F251}]]+`) 126 | getUserName = emojiRegex.ReplaceAllString(getUserName, "") 127 | var truncated string 128 | var UserFloatNum float64 129 | // set rune count 130 | for _, runeValue := range getUserName { 131 | charWidth := utf8.RuneLen(runeValue) 132 | if charWidth == 3 { 133 | UserFloatNum = 1.5 134 | } else { 135 | UserFloatNum = float64(charWidth) 136 | } 137 | if charCount+UserFloatNum > 24 { 138 | setBreaker = true 139 | break 140 | } 141 | truncated += string(runeValue) 142 | charCount += UserFloatNum 143 | } 144 | if setBreaker { 145 | getUserName = truncated + "..." 146 | } else { 147 | getUserName = truncated 148 | } 149 | nameLength, _ := mainContext.MeasureString(getUserName) 150 | var renderLength float64 151 | renderLength = nameLength + 160 152 | if nameLength+160 <= 450 { 153 | renderLength = 450 154 | } 155 | mainContext.DrawRoundedRectangle(50, float64(mainContextHight-175), renderLength, 250, 20) 156 | mainContext.Fill() 157 | // avatar draw end. 158 | avatarFormatRaw := toolchain.GetTargetAvatar(ctx) 159 | if avatarFormatRaw != nil { 160 | mainContext.DrawImage(imgfactory.Size(avatarFormatRaw, 100, 100).Circle(0).Image(), 60, int(float64(mainContextHight-150)+25)) 161 | } 162 | mainContext.SetRGBA255(255, 255, 255, 255) 163 | mainContext.DrawString("User Info", 60, float64(mainContextHight-150)+10) // basic ui 164 | mainContext.SetRGBA255(155, 121, 147, 255) 165 | mainContext.DrawString(getUserName, 180, float64(mainContextHight-150)+50) 166 | mainContext.DrawString(fmt.Sprintf("今日人品值: %d", randEveryone+50), 180, float64(mainContextHight-150)+100) 167 | mainContext.Fill() 168 | // AOSP time and date 169 | setInlineColor := color.NRGBA{R: uint8(getBackGroundMainColorR), G: uint8(getBackGroundMainColorG), B: uint8(getBackGroundMainColorB), A: 255} 170 | if err != nil { 171 | _, _ = ctx.SendPlainMessage(false, "Something wrong while rendering pic?") 172 | return 173 | } 174 | formatTimeDate := time.Now().Format("2006 / 01 / 02") 175 | formatTimeCurrent := time.Now().Format("15 : 04 : 05") 176 | formatTimeWeek := time.Now().Weekday().String() 177 | mainContext.SetFontFace(LoadFontFace(loadNotoSans, 35)) 178 | setOutlineColor := color.White 179 | DrawBorderString(mainContext, formatTimeCurrent, 5, float64(mainContextWidth-80), 50, 1, 0.5, setInlineColor, setOutlineColor) 180 | DrawBorderString(mainContext, formatTimeDate, 5, float64(mainContextWidth-80), 100, 1, 0.5, setInlineColor, setOutlineColor) 181 | DrawBorderString(mainContext, formatTimeWeek, 5, float64(mainContextWidth-80), 150, 1, 0.5, setInlineColor, setOutlineColor) 182 | mainContext.FillPreserve() 183 | if err != nil { 184 | return 185 | } 186 | mainContext.SetFontFace(LoadFontFace(loadNotoSans, 140)) 187 | DrawBorderString(mainContext, "|", 5, float64(mainContextWidth-30), 65, 1, 0.5, setInlineColor, setOutlineColor) 188 | // throw tarot card 189 | mainContext.SetFontFace(LoadFontFace(loadNotoSans, 20)) 190 | if err != nil { 191 | _, _ = ctx.SendPlainMessage(false, "Something wrong while rendering pic?") 192 | return 193 | } 194 | mainContext.SetRGBA255(91, 57, 83, 255) 195 | mainContext.DrawRoundedRectangle(float64(mainContextWidth-300), float64(mainContextHight-350), 450, 300, 20) 196 | mainContext.Fill() 197 | mainContext.SetRGBA255(255, 255, 255, 255) 198 | mainContext.SetLineWidth(3) 199 | mainContext.DrawString("今日塔罗牌", float64(mainContextWidth-300)+10, float64(mainContextHight-350)+30) 200 | mainContext.SetRGBA255(155, 121, 147, 255) 201 | mainContext.DrawString(card.Name, float64(mainContextWidth-300)+10, float64(mainContextHight-350)+60) 202 | mainContext.DrawString(fmt.Sprintf("- %s", position[p]), float64(mainContextWidth-300)+10, float64(mainContextHight-350)+280) 203 | placedList := SplitChineseString(info, 44) 204 | for ist, v := range placedList { 205 | mainContext.DrawString(v, float64(mainContextWidth-300)+10, float64(mainContextHight-350)+90+float64(ist*30)) 206 | } 207 | // output 208 | mainContext.SetFontFace(LoadFontFace(loadNotoSans, 20)) 209 | mainContext.SetRGBA255(186, 163, 157, 255) 210 | mainContext.DrawStringAnchored("Generated By Lucy (HiMoYo), Design By MoeMagicMango", float64(mainContextWidth-15), float64(mainContextHight-30), 1, 1) 211 | mainContext.Fill() 212 | _ = mainContext.SavePNG(engine.DataFolder() + "jrrp/" + userPic) 213 | _, _ = ctx.SendPhoto(tgba.FilePath(engine.DataFolder()+"jrrp/"+userPic), true, "") 214 | signTF[si] = 1 215 | } else { 216 | _, _ = ctx.SendPhoto(tgba.FilePath(engine.DataFolder()+"jrrp/"+userPic), true, "今天已经测试过了哦w") 217 | } 218 | }) 219 | } 220 | 221 | // SplitChineseString Split Chinese type chart. 222 | func SplitChineseString(s string, length int) []string { 223 | results := make([]string, 0) 224 | runes := []rune(s) 225 | start := 0 226 | for i := 0; i < len(runes); i++ { 227 | size := utf8.RuneLen(runes[i]) 228 | if start+size > length { 229 | results = append(results, string(runes[0:i])) 230 | runes = runes[i:] 231 | i, start = 0, 0 232 | } 233 | start += size 234 | } 235 | if len(runes) > 0 { 236 | results = append(results, string(runes)) 237 | } 238 | return results 239 | } 240 | 241 | // LoadFontFace load font face once before running, to work it quickly and save memory. 242 | func LoadFontFace(filePath string, size float64) font.Face { 243 | fontFile, _ := os.ReadFile(filePath) 244 | fontFileParse, _ := opentype.Parse(fontFile) 245 | fontFace, _ := opentype.NewFace(fontFileParse, &opentype.FaceOptions{Size: size, DPI: 72, Hinting: font.HintingFull}) 246 | return fontFace 247 | } 248 | 249 | // GetAverageColorAndMakeAdjust different from k-means algorithm,it uses origin plugin's algorithm.(Reduce the cost of averge color usage.) 250 | func GetAverageColorAndMakeAdjust(image image.Image) (int, int, int) { 251 | var RList []int 252 | var GList []int 253 | var BList []int 254 | width, height := image.Bounds().Size().X, image.Bounds().Size().Y 255 | // use the center of the bg, to make it more quickly and save memory and usage. 256 | for x := int(math.Round(float64(width) / 1.5)); x < int(math.Round(float64(width))); x++ { 257 | for y := height / 10; y < height/2; y++ { 258 | r, g, b, _ := image.At(x, y).RGBA() 259 | RList = append(RList, int(r>>8)) 260 | GList = append(GList, int(g>>8)) 261 | BList = append(BList, int(b>>8)) 262 | } 263 | } 264 | RAverage := int(Average(RList)) 265 | GAverage := int(Average(GList)) 266 | BAverage := int(Average(BList)) 267 | return RAverage, GAverage, BAverage 268 | } 269 | 270 | // Average sum all the numbers and divide by the length of the list. 271 | func Average(numbers []int) float64 { 272 | var sum float64 273 | for _, num := range numbers { 274 | sum += float64(num) 275 | } 276 | return math.Round(sum / float64(len(numbers))) 277 | } 278 | 279 | // DrawBorderString GG Package Not support The string render, so I write this (^^) 280 | func DrawBorderString(page *gg.Context, s string, size int, x float64, y float64, ax float64, ay float64, inlineRGB color.Color, outlineRGB color.Color) { 281 | page.SetColor(outlineRGB) 282 | n := size 283 | for dy := -n; dy <= n; dy++ { 284 | for dx := -n; dx <= n; dx++ { 285 | if dx*dx+dy*dy >= n*n { 286 | continue 287 | } 288 | renderX := x + float64(dx) 289 | renderY := y + float64(dy) 290 | page.DrawStringAnchored(s, renderX, renderY, ax, ay) 291 | } 292 | } 293 | page.SetColor(inlineRGB) 294 | page.DrawStringAnchored(s, x, y, ax, ay) 295 | } 296 | 297 | // RandSenderPerDayN 每个用户每天随机数 298 | func RandSenderPerDayN(uid int64, n int) int { 299 | sum := crc64.New(crc64.MakeTable(crc64.ISO)) 300 | _, _ = sum.Write(binary.StringToBytes(time.Now().Format("20060102"))) 301 | _, _ = sum.Write((*[8]byte)(unsafe.Pointer(&uid))[:]) 302 | r := rand.New(rand.NewSource(int64(sum.Sum64()))) 303 | return r.Intn(n) 304 | } 305 | -------------------------------------------------------------------------------- /plugin/lolicon/lolicon.go: -------------------------------------------------------------------------------- 1 | // Package lolicon 基于 https://api.lolicon.app 随机图片 2 | package lolicon 3 | 4 | import ( 5 | "encoding/json" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | rei "github.com/fumiama/ReiBot" 11 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 12 | 13 | base14 "github.com/fumiama/go-base16384" 14 | 15 | "github.com/FloatTech/floatbox/binary" 16 | "github.com/FloatTech/floatbox/math" 17 | "github.com/FloatTech/floatbox/web" 18 | ctrl "github.com/FloatTech/zbpctrl" 19 | ) 20 | 21 | const ( 22 | api = "https://api.lolicon.app/setu/v2?r18=2&proxy=i.pixiv.cat" 23 | capacity = 10 24 | ) 25 | 26 | type lolire struct { 27 | Error string `json:"error"` 28 | Data []struct { 29 | Pid int `json:"pid"` 30 | P int `json:"p"` 31 | UID int `json:"uid"` 32 | Title string `json:"title"` 33 | Author string `json:"author"` 34 | R18 bool `json:"r18"` 35 | Width int `json:"width"` 36 | Height int `json:"height"` 37 | Tags []string `json:"tags"` 38 | Ext string `json:"ext"` 39 | UploadDate int64 `json:"uploadDate"` 40 | Urls struct { 41 | Original string `json:"original"` 42 | } `json:"urls"` 43 | } `json:"data"` 44 | } 45 | 46 | var ( 47 | queue = make(chan *tgba.PhotoConfig, capacity) 48 | ) 49 | 50 | func init() { 51 | en := rei.Register("lolicon", &ctrl.Options[*rei.Ctx]{ 52 | DisableOnDefault: false, 53 | Help: "lolicon\n" + 54 | "- 来份萝莉", 55 | }).ApplySingle(rei.NewSingle( 56 | rei.WithKeyFn(func(ctx *rei.Ctx) int64 { 57 | switch msg := ctx.Value.(type) { 58 | case *tgba.Message: 59 | return msg.Chat.ID 60 | case *tgba.CallbackQuery: 61 | if msg.Message != nil { 62 | return msg.Message.Chat.ID 63 | } 64 | return msg.From.ID 65 | } 66 | return 0 67 | }), 68 | rei.WithPostFn[int64](func(ctx *rei.Ctx) { 69 | _, _ = ctx.SendPlainMessage(false, "有其他操作正在执行中, 不要着急哦") 70 | }))) 71 | en.OnMessageCommand("lolicon").SetBlock(true). 72 | Handle(func(ctx *rei.Ctx) { 73 | go func() { 74 | for i := 0; i < math.Min(cap(queue)-len(queue), 2); i++ { 75 | data, err := web.GetData(api) 76 | if err != nil { 77 | continue 78 | } 79 | var r lolire 80 | err = json.Unmarshal(data, &r) 81 | if err != nil { 82 | continue 83 | } 84 | if r.Error != "" { 85 | continue 86 | } 87 | caption := strings.Builder{} 88 | caption.WriteString(r.Data[0].Title) 89 | caption.WriteString(" @") 90 | caption.WriteString(r.Data[0].Author) 91 | caption.WriteByte('\n') 92 | for _, t := range r.Data[0].Tags { 93 | caption.WriteByte(' ') 94 | caption.WriteString(t) 95 | } 96 | uidlink := "https://pixiv.net/u/" + strconv.Itoa(r.Data[0].UID) 97 | pidlink := "https://pixiv.net/i/" + strconv.Itoa(r.Data[0].Pid) 98 | title16, err := base14.UTF82UTF16BE(binary.StringToBytes(r.Data[0].Title)) 99 | if err != nil { 100 | continue 101 | } 102 | auth16, err := base14.UTF82UTF16BE(binary.StringToBytes(r.Data[0].Author)) 103 | if err != nil { 104 | continue 105 | } 106 | _, imgcallbackdata, _ := strings.Cut(r.Data[0].Urls.Original, "/img-original/img/") 107 | queue <- &tgba.PhotoConfig{ 108 | BaseFile: tgba.BaseFile{ 109 | BaseChat: tgba.BaseChat{ 110 | ReplyMarkup: tgba.NewInlineKeyboardMarkup( 111 | tgba.NewInlineKeyboardRow( 112 | tgba.NewInlineKeyboardButtonURL( 113 | "UID "+strconv.Itoa(r.Data[0].UID), 114 | uidlink, 115 | ), 116 | tgba.NewInlineKeyboardButtonURL( 117 | "PID "+strconv.Itoa(r.Data[0].Pid), 118 | pidlink, 119 | ), 120 | ), 121 | tgba.NewInlineKeyboardRow( 122 | tgba.NewInlineKeyboardButtonData( 123 | "发送原图", 124 | imgcallbackdata, 125 | ), 126 | ), 127 | ), 128 | }, 129 | File: tgba.FileURL(r.Data[0].Urls.Original), 130 | }, 131 | Caption: caption.String(), 132 | CaptionEntities: []tgba.MessageEntity{ 133 | { 134 | Type: "bold", 135 | Offset: 0, 136 | Length: len(title16) / 2, 137 | }, 138 | { 139 | Type: "underline", 140 | Offset: len(title16)/2 + 1, 141 | Length: len(auth16)/2 + 1, 142 | }, 143 | }, 144 | } 145 | } 146 | }() 147 | select { 148 | case <-time.After(time.Minute): 149 | _, _ = ctx.SendPlainMessage(false, "ERROR: 等待填充,请稍后再试...") 150 | case img := <-queue: 151 | _, err := ctx.Send(false, img) 152 | if err != nil { 153 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 154 | return 155 | } 156 | } 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /plugin/mai/alias.go: -------------------------------------------------------------------------------- 1 | package mai 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | 12 | "github.com/xuri/excelize/v2" 13 | ) 14 | 15 | type MappedListStruct struct { 16 | DingFishId int `json:"dv_id"` 17 | SongName string `json:"song_name"` 18 | SongId []int `json:"song_id"` 19 | Aliases []string `json:"aliases"` 20 | } 21 | 22 | type LxnsSongListInfo struct { 23 | Songs []struct { 24 | Id int `json:"id"` 25 | Title string `json:"title"` 26 | Artist string `json:"artist"` 27 | Genre string `json:"genre"` 28 | Bpm int `json:"bpm"` 29 | Version int `json:"version"` 30 | Difficulties struct { 31 | Standard []struct { 32 | Type string `json:"type"` 33 | Difficulty int `json:"difficulty"` 34 | Level string `json:"level"` 35 | LevelValue float64 `json:"level_value"` 36 | NoteDesigner string `json:"note_designer"` 37 | Version int `json:"version"` 38 | } `json:"standard"` 39 | Dx []struct { 40 | Type string `json:"type"` 41 | Difficulty int `json:"difficulty"` 42 | Level string `json:"level"` 43 | LevelValue float64 `json:"level_value"` 44 | NoteDesigner string `json:"note_designer"` 45 | Version int `json:"version"` 46 | } `json:"dx"` 47 | } `json:"difficulties"` 48 | } `json:"songs"` 49 | Genres []struct { 50 | Id int `json:"id"` 51 | Title string `json:"title"` 52 | Genre string `json:"genre"` 53 | } `json:"genres"` 54 | Versions []struct { 55 | Id int `json:"id"` 56 | Title string `json:"title"` 57 | Version int `json:"version"` 58 | } `json:"versions"` 59 | } 60 | 61 | type LxnsAliases struct { 62 | Aliases []struct { 63 | SongId int `json:"song_id"` 64 | Aliases []string `json:"aliases"` 65 | } `json:"aliases"` 66 | } 67 | 68 | type AliasesReturnValue struct { 69 | Aliases []struct { 70 | DvId int `json:"dv_id"` 71 | SongName string `json:"song_name"` 72 | SongId []int `json:"song_id"` 73 | Aliases []string `json:"aliases"` 74 | } `json:"aliases"` 75 | } 76 | 77 | // only support LXNS because => if DivingFish then need Token. 78 | 79 | // QueryReferSong use LocalStorageData. 80 | func QueryReferSong(Alias string, isLxnet bool) (status bool, id []int, needAcc bool, accInfoList [][]int) { 81 | // unpackedData 82 | getData, err := os.ReadFile(engine.DataFolder() + "alias.json") 83 | if err != nil { 84 | panic(err) 85 | } 86 | var DataHandler AliasesReturnValue 87 | json.Unmarshal(getData, &DataHandler) 88 | var onloadList [][]int 89 | for _, dataSearcher := range DataHandler.Aliases { 90 | for _, aliasSearcher := range dataSearcher.Aliases { 91 | if aliasSearcher == Alias { 92 | onloadList = append(onloadList, dataSearcher.SongId) // write in memory 93 | } 94 | } 95 | } 96 | // if list is 2,query them is from the same song? | if above 2(3 or more ,means this song need acc.) 97 | switch { 98 | case len(onloadList) == 1: // only one query. 99 | if isLxnet { 100 | for _, listhere := range onloadList[0] { 101 | if listhere < 10000 { 102 | return true, []int{listhere}, false, nil 103 | } 104 | } 105 | } else { 106 | return true, onloadList[0], false, nil 107 | } 108 | // query length is 2,it means this maybe same name but diff id ==> (E.G: Oshama Scramble!) 109 | case len(onloadList) == 2: 110 | for _, listHere := range onloadList[0] { 111 | for _, listAliasTwo := range onloadList[1] { 112 | if listHere == listAliasTwo { 113 | // same list here. 114 | var returnIntList []int 115 | returnIntList = append(returnIntList, onloadList[0]...) 116 | returnIntList = append(returnIntList, onloadList[1]...) 117 | returnIntList = removeIntDuplicates(returnIntList) 118 | if isLxnet { 119 | for _, listhere := range returnIntList { 120 | if listhere < 10000 { 121 | return true, []int{listhere}, false, nil 122 | } 123 | } 124 | } else { 125 | return true, returnIntList, false, nil 126 | } 127 | } 128 | } 129 | } 130 | // if query is none, means it need moreacc 131 | return true, nil, true, onloadList 132 | case len(onloadList) >= 3: 133 | return true, nil, true, onloadList 134 | } 135 | // no found. 136 | return false, nil, false, nil 137 | } 138 | 139 | // UpdateAliasPackage Use simple action to update alias. 140 | func UpdateAliasPackage() { 141 | // get Lxns Data 142 | respls, _ := http.Get("https://maimai.lxns.net/api/v0/maimai/alias/list") 143 | getData, _ := io.ReadAll(respls.Body) 144 | var lxnsAliasData LxnsAliases 145 | json.Unmarshal(getData, &lxnsAliasData) 146 | 147 | // get Lxns Data SongListInfo 148 | resplsSongList, _ := http.Get("https://maimai.lxns.net/api/v0/maimai/song/list") 149 | getDataSongList, _ := io.ReadAll(resplsSongList.Body) 150 | var lxnsSongListData LxnsSongListInfo 151 | json.Unmarshal(getDataSongList, &lxnsSongListData) 152 | 153 | // get AkiraBot AliasData 154 | url := "https://docs.google.com/spreadsheets/d/e/2PACX-1vRwHptWLUyMG9ASCgk9MhI693jmAA1_CJrPfTxjX9J8f3wGHlR09Ja_h5i3InPbFhK1BjJp5cO_kugM/pub?output=xlsx" 155 | response, err := http.Get(url) 156 | if err != nil { 157 | fmt.Println("Error fetching URL:", err) 158 | return 159 | } 160 | defer response.Body.Close() 161 | body, _ := io.ReadAll(response.Body) 162 | reader := bytes.NewReader(body) 163 | f, err := excelize.OpenReader(reader) 164 | if err != nil { 165 | fmt.Println(err) 166 | return 167 | } 168 | defer func() { 169 | if err := f.Close(); err != nil { 170 | fmt.Println(err) 171 | } 172 | }() 173 | savedListMap := map[string][]string{} 174 | getRows, _ := f.GetRows("主表") 175 | var titleStart bool 176 | for _, rows := range getRows { 177 | if rows[0] == "ID" { 178 | titleStart = true 179 | continue 180 | } 181 | if titleStart { 182 | var mappedList []string 183 | for _, rowList := range rows { 184 | mappedList = append(mappedList, rowList) 185 | } 186 | savedListMap[mappedList[0]] = mappedList[1:] 187 | } 188 | } 189 | // generate a json file here. 190 | var tempList []interface{} 191 | for i, listData := range savedListMap { 192 | var vartiesList []int 193 | getInt, _ := strconv.Atoi(i) 194 | vartiesList = append(vartiesList, getInt) 195 | var referListData []string 196 | // check this alias in lxns network pattern, it maybe slowly( 197 | for _, listLxns := range lxnsSongListData.Songs { 198 | if listLxns.Title == listData[0] && listLxns.Id != getInt { 199 | vartiesList = append(vartiesList, listLxns.Id) 200 | } 201 | } 202 | // due to AkihaBot use two packed id, so make the id together. 203 | referListData = append(referListData, listData[1:]...) 204 | // add same alias to lxns 205 | for _, lxnsAliasRefer := range lxnsAliasData.Aliases { 206 | for _, listLocation := range vartiesList { 207 | if listLocation == lxnsAliasRefer.SongId { 208 | // prefix, add alias to it. 209 | referListData = append(referListData, lxnsAliasRefer.Aliases...) 210 | referListData = removeDuplicates(referListData) 211 | } 212 | } 213 | } 214 | 215 | tempList = append(tempList, &MappedListStruct{DingFishId: getInt, SongName: listData[0], Aliases: referListData, SongId: vartiesList}) 216 | } 217 | GeneratedList := map[string]interface{}{ 218 | "aliases": tempList, 219 | } 220 | getBytes, err := json.Marshal(GeneratedList) 221 | if err != nil { 222 | panic(err) 223 | } 224 | os.WriteFile(engine.DataFolder()+"alias.json", getBytes, 0777) 225 | GeneratedList = nil // revoke 226 | } 227 | 228 | func removeDuplicates(list []string) []string { 229 | seen := make(map[string]bool) 230 | var result []string 231 | for _, item := range list { 232 | if !seen[item] { 233 | seen[item] = true 234 | result = append(result, item) 235 | } 236 | } 237 | return result 238 | } 239 | 240 | func removeIntDuplicates(list []int) []int { 241 | seen := make(map[int]bool) 242 | var result []int 243 | for _, item := range list { 244 | if !seen[item] { 245 | seen[item] = true 246 | result = append(result, item) 247 | } 248 | } 249 | return result 250 | } 251 | -------------------------------------------------------------------------------- /plugin/mai/req.go: -------------------------------------------------------------------------------- 1 | package mai 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "github.com/FloatTech/floatbox/web" 8 | "io" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | type DivingFishB50UserName struct { 14 | Username string `json:"username"` 15 | B50 bool `json:"b50"` 16 | } 17 | 18 | type DivingFishDevFullDataRecords struct { 19 | AdditionalRating int `json:"additional_rating"` 20 | Nickname string `json:"nickname"` 21 | Plate string `json:"plate"` 22 | Rating int `json:"rating"` 23 | Records []struct { 24 | Achievements float64 `json:"achievements"` 25 | Ds float64 `json:"ds"` 26 | DxScore int `json:"dxScore"` 27 | Fc string `json:"fc"` 28 | Fs string `json:"fs"` 29 | Level string `json:"level"` 30 | LevelIndex int `json:"level_index"` 31 | LevelLabel string `json:"level_label"` 32 | Ra int `json:"ra"` 33 | Rate string `json:"rate"` 34 | SongId int `json:"song_id"` 35 | Title string `json:"title"` 36 | Type string `json:"type"` 37 | } `json:"records"` 38 | Username string `json:"username"` 39 | } 40 | 41 | func QueryMaiBotDataFromUserName(username string) (playerdata []byte, err error) { 42 | // packed json and sent. 43 | jsonStruct := DivingFishB50UserName{Username: username, B50: true} 44 | jsonStructData, err := json.Marshal(jsonStruct) 45 | if err != nil { 46 | return nil, err 47 | } 48 | req, err := http.NewRequest("POST", "https://www.diving-fish.com/api/maimaidxprober/query/player", bytes.NewBuffer(jsonStructData)) 49 | req.Header.Set("Content-Type", "application/json") 50 | if err != nil { 51 | return 52 | } 53 | client := &http.Client{} 54 | resp, err := client.Do(req) 55 | if err != nil { 56 | return 57 | } 58 | defer resp.Body.Close() 59 | if resp.StatusCode == 400 { 60 | return nil, errors.New("- 未找到用户或者用户数据丢失\n\n - 请检查您是否在 https://www.diving-fish.com/maimaidx/prober/ 上 上传过成绩") 61 | } 62 | if resp.StatusCode == 403 { 63 | return nil, errors.New("- 该用户设置禁止查分\n\n - 请检查您是否在 https://www.diving-fish.com/maimaidx/prober/ 上 是否关闭了允许他人查分功能") 64 | } 65 | playerDataByte, err := io.ReadAll(resp.Body) 66 | return playerDataByte, err 67 | } 68 | 69 | func QueryDevDataFromDivingFish(username string) DivingFishDevFullDataRecords { 70 | getData, err := web.RequestDataWithHeaders(web.NewDefaultClient(), "https://www.diving-fish.com/api/maimaidxprober/dev/player/records?username="+username, "GET", func(request *http.Request) error { 71 | request.Header.Add("Developer-Token", os.Getenv("dvkey")) 72 | return nil 73 | }, nil) 74 | if err != nil { 75 | return DivingFishDevFullDataRecords{} 76 | } 77 | var handlerData DivingFishDevFullDataRecords 78 | json.Unmarshal(getData, &handlerData) 79 | return handlerData 80 | } 81 | -------------------------------------------------------------------------------- /plugin/mai/sql.go: -------------------------------------------------------------------------------- 1 | package mai 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "time" 7 | 8 | sql "github.com/FloatTech/sqlite" 9 | ) 10 | 11 | type DataHostSQL struct { 12 | TelegramId int64 `db:"telegramid"` // telegramid 13 | Username string `db:"username"` // maimai user id ,query from this one. 14 | Plate string `db:"plate"` // plate 15 | Background string `db:"bg"` // bg 16 | } 17 | 18 | type UserSwitcherService struct { 19 | TGId int64 `db:"tgid"` 20 | IsUsed bool `db:"isused"` // true == lxns service \ false == Diving Fish. 21 | } 22 | 23 | type UserIDToMaimaiFriendCode struct { 24 | TelegramId int64 `db:"telegramid"` 25 | MaimaiID int64 `db:"friendid"` 26 | } 27 | 28 | var ( 29 | maiDatabase = &sql.Sqlite{} 30 | maiLocker = sync.Mutex{} 31 | ) 32 | 33 | func init() { 34 | maiDatabase.DBPath = engine.DataFolder() + "maisql.db" 35 | err := maiDatabase.Open(time.Hour * 24) 36 | if err != nil { 37 | return 38 | } 39 | _ = InitDataBase() 40 | } 41 | 42 | func FormatUserDataBase(tgid int64, plate string, bg string, username string) *DataHostSQL { 43 | return &DataHostSQL{TelegramId: tgid, Plate: plate, Background: bg, Username: username} 44 | } 45 | 46 | func FormatUserSwitcher(tgid int64, isSwitcher bool) *UserSwitcherService { 47 | return &UserSwitcherService{TGId: tgid, IsUsed: isSwitcher} 48 | } 49 | 50 | func FormatMaimaiFriendCode(friendCode int64, tgid int64) *UserIDToMaimaiFriendCode { 51 | return &UserIDToMaimaiFriendCode{TelegramId: tgid, MaimaiID: friendCode} 52 | } 53 | 54 | func InitDataBase() error { 55 | maiLocker.Lock() 56 | defer maiLocker.Unlock() 57 | maiDatabase.Create("userinfo", &DataHostSQL{}) 58 | 59 | maiDatabase.Create("userswitchinfo", &UserSwitcherService{}) 60 | maiDatabase.Create("usermaifriendinfo", &UserIDToMaimaiFriendCode{}) 61 | 62 | return nil 63 | } 64 | 65 | func GetUserMaiFriendID(userid int64) UserIDToMaimaiFriendCode { 66 | maiLocker.Lock() 67 | defer maiLocker.Unlock() 68 | var infosql UserIDToMaimaiFriendCode 69 | userIDStr := strconv.FormatInt(userid, 10) 70 | _ = maiDatabase.Find("usermaifriendinfo", &infosql, "where telegramid is "+userIDStr) 71 | return infosql 72 | } 73 | 74 | func GetUserSwitcherInfoFromDatabase(userid int64) bool { 75 | maiLocker.Lock() 76 | defer maiLocker.Unlock() 77 | var info UserSwitcherService 78 | userIDStr := strconv.FormatInt(userid, 10) 79 | err := maiDatabase.Find("userswitchinfo", &info, "where tgid is "+userIDStr) 80 | if err != nil { 81 | return false 82 | } 83 | return info.IsUsed 84 | } 85 | 86 | func (info *UserSwitcherService) ChangeUserSwitchInfoFromDataBase() error { 87 | maiLocker.Lock() 88 | defer maiLocker.Unlock() 89 | return maiDatabase.Insert("userswitchinfo", info) 90 | } 91 | 92 | func (info *UserIDToMaimaiFriendCode) BindUserFriendCode() error { 93 | maiLocker.Lock() 94 | defer maiLocker.Unlock() 95 | return maiDatabase.Insert("usermaifriendinfo", info) 96 | } 97 | 98 | // maimai origin render base. 99 | 100 | // GetUserPlateInfoFromDatabase Get plate data 101 | func GetUserPlateInfoFromDatabase(userID int64) string { 102 | maiLocker.Lock() 103 | defer maiLocker.Unlock() 104 | var infosql DataHostSQL 105 | userIDStr := strconv.FormatInt(userID, 10) 106 | _ = maiDatabase.Find("userinfo", &infosql, "where telegramid is "+userIDStr) 107 | return infosql.Plate 108 | } 109 | 110 | // GetUserInfoNameFromDatabase GetUserName 111 | func GetUserInfoNameFromDatabase(userID int64) string { 112 | maiLocker.Lock() 113 | defer maiLocker.Unlock() 114 | var infosql DataHostSQL 115 | userIDStr := strconv.FormatInt(userID, 10) 116 | _ = maiDatabase.Find("userinfo", &infosql, "where telegramid is "+userIDStr) 117 | if infosql.Username == "" { 118 | return "" 119 | } 120 | return infosql.Username 121 | } 122 | 123 | // GetUserDefaultBackgroundDataFromDatabase Get Default Background. 124 | func GetUserDefaultBackgroundDataFromDatabase(userID int64) string { 125 | maiLocker.Lock() 126 | defer maiLocker.Unlock() 127 | var infosql DataHostSQL 128 | userIDStr := strconv.FormatInt(userID, 10) 129 | _ = maiDatabase.Find("userinfo", &infosql, "where telegramid is "+userIDStr) 130 | return infosql.Background 131 | } 132 | 133 | // BindUserDataBase Bind Database only for DataHost Inline code. 134 | func (info *DataHostSQL) BindUserDataBase() error { 135 | maiLocker.Lock() 136 | defer maiLocker.Unlock() 137 | return maiDatabase.Insert("userinfo", info) 138 | } 139 | -------------------------------------------------------------------------------- /plugin/mai/struct.go: -------------------------------------------------------------------------------- 1 | package mai 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/png" 8 | "io" 9 | "log" 10 | "net/http" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | "unicode/utf8" 16 | 17 | "github.com/FloatTech/gg" 18 | "github.com/FloatTech/imgfactory" 19 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 20 | "github.com/MoYoez/Lucy_reibot/utils/transform" 21 | rei "github.com/fumiama/ReiBot" 22 | "golang.org/x/image/font" 23 | "golang.org/x/image/font/opentype" 24 | "golang.org/x/text/width" 25 | ) 26 | 27 | type player struct { 28 | AdditionalRating int `json:"additional_rating"` 29 | Charts struct { 30 | Dx []struct { 31 | Achievements float64 `json:"achievements"` 32 | Ds float64 `json:"ds"` 33 | DxScore int `json:"dxScore"` 34 | Fc string `json:"fc"` 35 | Fs string `json:"fs"` 36 | Level string `json:"level"` 37 | LevelIndex int `json:"level_index"` 38 | LevelLabel string `json:"level_label"` 39 | Ra int `json:"ra"` 40 | Rate string `json:"rate"` 41 | SongId int `json:"song_id"` 42 | Title string `json:"title"` 43 | Type string `json:"type"` 44 | } `json:"dx"` 45 | Sd []struct { 46 | Achievements float64 `json:"achievements"` 47 | Ds float64 `json:"ds"` 48 | DxScore int `json:"dxScore"` 49 | Fc string `json:"fc"` 50 | Fs string `json:"fs"` 51 | Level string `json:"level"` 52 | LevelIndex int `json:"level_index"` 53 | LevelLabel string `json:"level_label"` 54 | Ra int `json:"ra"` 55 | Rate string `json:"rate"` 56 | SongId int `json:"song_id"` 57 | Title string `json:"title"` 58 | Type string `json:"type"` 59 | } `json:"sd"` 60 | } `json:"charts"` 61 | Nickname string `json:"nickname"` 62 | Plate string `json:"plate"` 63 | Rating int `json:"rating"` 64 | UserData interface{} `json:"user_data"` 65 | UserId interface{} `json:"user_id"` 66 | Username string `json:"username"` 67 | } 68 | 69 | type playerData struct { 70 | Achievements float64 `json:"achievements"` 71 | Ds float64 `json:"ds"` 72 | DxScore int `json:"dxScore"` 73 | Fc string `json:"fc"` 74 | Fs string `json:"fs"` 75 | Level string `json:"level"` 76 | LevelIndex int `json:"level_index"` 77 | LevelLabel string `json:"level_label"` 78 | Ra int `json:"ra"` 79 | Rate string `json:"rate"` 80 | SongId int `json:"song_id"` 81 | Title string `json:"title"` 82 | Type string `json:"type"` 83 | } 84 | 85 | type LxnsUploaderStruct struct { 86 | Score []LxnsScoreUploader `json:"scores"` 87 | } 88 | 89 | type LxnsScoreUploader struct { 90 | Id int `json:"id"` 91 | Type string `json:"type"` 92 | LevelIndex int `json:"level_index"` 93 | Achievements float64 `json:"achievements"` 94 | Fc interface{} `json:"fc"` 95 | Fs interface{} `json:"fs"` 96 | DxScore int `json:"dx_score"` 97 | PlayTime string `json:"play_time"` 98 | } 99 | 100 | var ( 101 | loadMaiPic = Root + "pic/" 102 | defaultCoverLink = Root + "default_cover.png" 103 | typeImageDX = loadMaiPic + "chart_type_dx.png" 104 | typeImageSD = loadMaiPic + "chart_type_sd.png" 105 | titleFontPath = maifont + "NotoSansSC-Bold.otf" 106 | UniFontPath = maifont + "Montserrat-Bold.ttf" 107 | nameFont = maifont + "NotoSansSC-Regular.otf" 108 | maifont = Root + "font/" 109 | b50bgOriginal = loadMaiPic + "b50_bg.png" 110 | b50bg = loadMaiPic + "b50_bg.png" 111 | b50Custom = loadMaiPic + "b50_bg_custom.png" 112 | Root = transform.ReturnLucyMainDataIndex("maidx") + "resources/maimai/" 113 | userPlate = engine.DataFolder() + "user/" 114 | titleFont font.Face 115 | scoreFont font.Face 116 | rankFont font.Face 117 | levelFont font.Face 118 | ratingFont font.Face 119 | nameTypeFont font.Face 120 | diffColor []color.RGBA 121 | ratingBgFilenames = []string{ 122 | "rating_white.png", 123 | "rating_blue.png", 124 | "rating_green.png", 125 | "rating_yellow.png", 126 | "rating_red.png", 127 | "rating_purple.png", 128 | "rating_copper.png", 129 | "rating_silver.png", 130 | "rating_gold.png", 131 | "rating_rainbow.png", 132 | } 133 | ) 134 | 135 | func init() { 136 | if _, err := os.Stat(userPlate); os.IsNotExist(err) { 137 | err := os.MkdirAll(userPlate, 0777) 138 | if err != nil { 139 | return 140 | } 141 | } 142 | nameTypeFont = LoadFontFace(nameFont, 36) 143 | titleFont = LoadFontFace(titleFontPath, 20) 144 | scoreFont = LoadFontFace(UniFontPath, 32) 145 | rankFont = LoadFontFace(UniFontPath, 24) 146 | levelFont = LoadFontFace(UniFontPath, 20) 147 | ratingFont = LoadFontFace(UniFontPath, 24) 148 | diffColor = []color.RGBA{ 149 | {69, 193, 36, 255}, 150 | {255, 186, 1, 255}, 151 | {255, 90, 102, 255}, 152 | {134, 49, 200, 255}, 153 | {207, 144, 240, 255}, 154 | } 155 | 156 | } 157 | 158 | // FullPageRender Render Full Page 159 | func FullPageRender(data player, ctx *rei.Ctx) (raw image.Image) { 160 | // muilt-threading. 161 | getUserID, _ := toolchain.GetChatUserInfoID(ctx) 162 | var avatarHandler sync.WaitGroup 163 | avatarHandler.Add(1) 164 | var getAvatarFormat *gg.Context 165 | // avatar handler. 166 | go func() { 167 | // avatar Round Style 168 | defer avatarHandler.Done() 169 | getAvatar := toolchain.GetTargetAvatar(ctx) 170 | if getAvatar != nil { 171 | avatarFormat := imgfactory.Size(getAvatar, 180, 180) 172 | getAvatarFormat = gg.NewContext(180, 180) 173 | getAvatarFormat.DrawRoundedRectangle(0, 0, 178, 178, 20) 174 | getAvatarFormat.Clip() 175 | getAvatarFormat.DrawImage(avatarFormat.Image(), 0, 0) 176 | getAvatarFormat.Fill() 177 | } 178 | }() 179 | userPlatedCustom := GetUserDefaultBackgroundDataFromDatabase(getUserID) 180 | // render Header. 181 | b50Render := gg.NewContext(2090, 1660) 182 | rawPlateData, errs := gg.LoadImage(userPlate + strconv.Itoa(int(getUserID)) + ".png") 183 | if errs == nil { 184 | b50bg = b50Custom 185 | b50Render.DrawImage(rawPlateData, 595, 30) 186 | b50Render.Fill() 187 | } else { 188 | if userPlatedCustom != "" { 189 | b50bg = b50Custom 190 | images, _ := GetDefaultPlate(userPlatedCustom) 191 | b50Render.DrawImage(images, 595, 30) 192 | b50Render.Fill() 193 | } else { 194 | // show nil 195 | b50bg = b50bgOriginal 196 | } 197 | } 198 | getContent, _ := gg.LoadImage(b50bg) 199 | b50Render.DrawImage(getContent, 0, 0) 200 | b50Render.Fill() 201 | // render user info 202 | avatarHandler.Wait() 203 | if getAvatarFormat != nil { 204 | b50Render.DrawImage(getAvatarFormat.Image(), 610, 50) 205 | b50Render.Fill() 206 | } 207 | // render Userinfo 208 | b50Render.SetFontFace(nameTypeFont) 209 | b50Render.SetColor(color.Black) 210 | b50Render.DrawStringAnchored(width.Widen.String(data.Nickname), 825, 160, 0, 0) 211 | b50Render.Fill() 212 | b50Render.SetFontFace(titleFont) 213 | setPlateLocalStatus := GetUserPlateInfoFromDatabase(getUserID) 214 | if setPlateLocalStatus != "" { 215 | data.Plate = setPlateLocalStatus 216 | } 217 | b50Render.DrawStringAnchored(strings.Join(strings.Split(data.Plate, ""), " "), 1050, 207, 0.5, 0.5) 218 | b50Render.Fill() 219 | getRating := getRatingBg(data.Rating) 220 | getRatingBG, err := gg.LoadImage(loadMaiPic + getRating) 221 | if err != nil { 222 | return 223 | } 224 | b50Render.DrawImage(getRatingBG, 800, 40) 225 | b50Render.Fill() 226 | // render Rank 227 | imgs, err := GetRankPicRaw(data.AdditionalRating) 228 | if err != nil { 229 | return 230 | } 231 | b50Render.DrawImage(imgs, 1080, 50) 232 | b50Render.Fill() 233 | // draw number 234 | b50Render.SetFontFace(scoreFont) 235 | b50Render.SetRGBA255(236, 219, 113, 255) 236 | b50Render.DrawStringAnchored(strconv.Itoa(data.Rating), 1056, 60, 1, 1) 237 | b50Render.Fill() 238 | // Render Card Type 239 | getSDLength := len(data.Charts.Sd) 240 | getDXLength := len(data.Charts.Dx) 241 | getDXinitX := 45 242 | getDXinitY := 1225 243 | getInitX := 45 244 | getInitY := 285 245 | var i int 246 | for i = 0; i < getSDLength; i++ { 247 | b50Render.DrawImage(RenderCard(data.Charts.Sd[i], i+1, false), getInitX, getInitY) 248 | getInitX += 400 249 | if getInitX == 2045 { 250 | getInitX = 45 251 | getInitY += 125 252 | } 253 | } 254 | 255 | for dx := 0; dx < getDXLength; dx++ { 256 | b50Render.DrawImage(RenderCard(data.Charts.Dx[dx], dx+1, false), getDXinitX, getDXinitY) 257 | getDXinitX += 400 258 | if getDXinitX == 2045 { 259 | getDXinitX = 45 260 | getDXinitY += 125 261 | } 262 | } 263 | return b50Render.Image() 264 | } 265 | 266 | // RenderCard Main Lucy Render Page , if isSimpleRender == true, then render count will not show here. 267 | func RenderCard(data playerData, num int, isSimpleRender bool) image.Image { 268 | getType := data.Type 269 | var CardBackGround string 270 | var multiTypeRender sync.WaitGroup 271 | var CoverDownloader sync.WaitGroup 272 | CoverDownloader.Add(1) 273 | multiTypeRender.Add(1) 274 | // choose Type. 275 | if getType == "SD" { 276 | CardBackGround = typeImageSD 277 | } else { 278 | CardBackGround = typeImageDX 279 | } 280 | charCount := 0.0 281 | setBreaker := false 282 | var truncated string 283 | var charFloatNum float64 284 | getSongName := data.Title 285 | var getSongId string 286 | switch { 287 | case data.SongId < 1000: 288 | getSongId = fmt.Sprintf("%05d", data.SongId) 289 | case data.SongId < 10000: 290 | getSongId = fmt.Sprintf("1%d", data.SongId) 291 | default: 292 | getSongId = strconv.Itoa(data.SongId) 293 | } 294 | var Image image.Image 295 | go func() { 296 | defer CoverDownloader.Done() 297 | Image, _ = GetCover(getSongId) 298 | }() 299 | // set rune count 300 | go func() { 301 | defer multiTypeRender.Done() 302 | for _, runeValue := range getSongName { 303 | charWidth := utf8.RuneLen(runeValue) 304 | if charWidth == 3 { 305 | charFloatNum = 1.5 306 | } else { 307 | charFloatNum = float64(charWidth) 308 | } 309 | if charCount+charFloatNum > 19 { 310 | setBreaker = true 311 | break 312 | } 313 | truncated += string(runeValue) 314 | charCount += charFloatNum 315 | } 316 | if setBreaker { 317 | getSongName = truncated + ".." 318 | } else { 319 | getSongName = truncated 320 | } 321 | }() 322 | loadSongType, _ := gg.LoadImage(CardBackGround) 323 | // draw pic 324 | drawBackGround := gg.NewContextForImage(GetChartType(data.LevelLabel)) 325 | // draw song pic 326 | CoverDownloader.Wait() 327 | drawBackGround.DrawImage(Image, 25, 25) 328 | // draw name 329 | drawBackGround.SetColor(color.White) 330 | drawBackGround.SetFontFace(titleFont) 331 | multiTypeRender.Wait() 332 | drawBackGround.DrawStringAnchored(getSongName, 130, 32.5, 0, 0.5) 333 | drawBackGround.Fill() 334 | // draw acc 335 | drawBackGround.SetFontFace(scoreFont) 336 | drawBackGround.DrawStringAnchored(strconv.FormatFloat(data.Achievements, 'f', 4, 64)+"%", 129, 62.5, 0, 0.5) 337 | // draw rate 338 | drawBackGround.DrawImage(GetRateStatusAndRenderToImage(data.Rate), 305, 45) 339 | drawBackGround.Fill() 340 | drawBackGround.SetFontFace(rankFont) 341 | drawBackGround.SetColor(diffColor[data.LevelIndex]) 342 | if !isSimpleRender { 343 | drawBackGround.DrawString("#"+strconv.Itoa(num), 130, 111) 344 | } 345 | drawBackGround.FillPreserve() 346 | // draw rest of card. 347 | drawBackGround.SetFontFace(levelFont) 348 | drawBackGround.DrawString(strconv.FormatFloat(data.Ds, 'f', 1, 64), 195, 111) 349 | drawBackGround.FillPreserve() 350 | drawBackGround.SetFontFace(ratingFont) 351 | drawBackGround.DrawString("▶", 235, 111) 352 | drawBackGround.FillPreserve() 353 | drawBackGround.SetFontFace(ratingFont) 354 | drawBackGround.DrawString(strconv.Itoa(data.Ra), 250, 111) 355 | drawBackGround.FillPreserve() 356 | if data.Fc != "" { 357 | drawBackGround.DrawImage(LoadComboImage(data.Fc), 290, 84) 358 | } 359 | if data.Fs != "" { 360 | drawBackGround.DrawImage(LoadSyncImage(data.Fs), 325, 84) 361 | } 362 | drawBackGround.DrawImage(loadSongType, 68, 88) 363 | return drawBackGround.Image() 364 | } 365 | 366 | func GetRankPicRaw(id int) (image.Image, error) { 367 | log.Println(id) 368 | var idStr string 369 | if id < 10 { 370 | idStr = "0" + strconv.FormatInt(int64(id), 10) 371 | } else { 372 | idStr = strconv.FormatInt(int64(id), 10) 373 | } 374 | if id == 22 { 375 | idStr = "21" 376 | } 377 | data := Root + "rank/UI_CMN_DaniPlate_" + idStr + ".png" 378 | imgRaw, err := gg.LoadImage(data) 379 | if err != nil { 380 | return nil, err 381 | } 382 | return imgRaw, nil 383 | } 384 | 385 | func GetDefaultPlate(id string) (image.Image, error) { 386 | data := Root + "plate/plate_" + id + ".png" 387 | imgRaw, err := gg.LoadImage(data) 388 | if err != nil { 389 | return nil, err 390 | } 391 | return imgRaw, nil 392 | } 393 | 394 | // GetCover Careful The nil data 395 | func GetCover(id string) (image.Image, error) { 396 | fileName := id + ".png" 397 | filePath := Root + "cover/" + fileName 398 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 399 | // Auto download cover from diving fish's site 400 | downloadURL := "https://www.diving-fish.com/covers/" + fileName 401 | cover, err := downloadImage(downloadURL) 402 | if err != nil { 403 | // try with lxns service 404 | getConvert, _ := strconv.Atoi(id) 405 | switch { 406 | case getConvert >= 11000: 407 | id = id[1:] 408 | } 409 | if getConvert > 10000 && getConvert < 11000 { 410 | id = id[2:] 411 | } 412 | downloadFromLxns := "https://lx-rec-reproxy.lemonkoi.one/maimai/jacket/" + id + ".png" 413 | coverNewer, err := downloadImage(downloadFromLxns) 414 | if err != nil { 415 | return LoadPictureWithResize(defaultCoverLink, 90, 90), nil 416 | } 417 | cover = coverNewer 418 | } 419 | saveImage(cover, filePath) 420 | } 421 | imageFile, err := os.Open(filePath) 422 | if err != nil { 423 | return LoadPictureWithResize(defaultCoverLink, 90, 90), nil 424 | } 425 | defer func(imageFile *os.File) { 426 | err := imageFile.Close() 427 | if err != nil { 428 | return 429 | } 430 | }(imageFile) 431 | img, _, err := image.Decode(imageFile) 432 | if err != nil { 433 | return LoadPictureWithResize(defaultCoverLink, 90, 90), nil 434 | } 435 | return Resize(img, 90, 90), nil 436 | } 437 | 438 | // Resize Image width height 439 | func Resize(image image.Image, w int, h int) image.Image { 440 | return imgfactory.Size(image, w, h).Image() 441 | } 442 | 443 | // LoadPictureWithResize Load Picture 444 | func LoadPictureWithResize(link string, w int, h int) image.Image { 445 | getImage, err := gg.LoadImage(link) 446 | if err != nil { 447 | return nil 448 | } 449 | return Resize(getImage, w, h) 450 | } 451 | 452 | // GetRateStatusAndRenderToImage Get Rate 453 | func GetRateStatusAndRenderToImage(rank string) image.Image { 454 | // Load rank images 455 | return LoadPictureWithResize(loadMaiPic+"rate_"+rank+".png", 80, 40) 456 | } 457 | 458 | // GetChartType Get Chart Type 459 | func GetChartType(chart string) image.Image { 460 | data, _ := gg.LoadImage(loadMaiPic + "chart_" + NoHeadLineCase(chart) + ".png") 461 | return data 462 | } 463 | 464 | // LoadComboImage Load combo images 465 | func LoadComboImage(imageName string) image.Image { 466 | link := loadMaiPic + "combo_" + imageName + ".png" 467 | return LoadPictureWithResize(link, 60, 40) 468 | } 469 | 470 | // LoadSyncImage Load sync images 471 | func LoadSyncImage(imageName string) image.Image { 472 | var link string 473 | if imageName == "sync" { 474 | link = loadMaiPic + "sync_fs.png" 475 | } else { 476 | link = loadMaiPic + "sync_" + imageName + ".png" 477 | } 478 | return LoadPictureWithResize(link, 60, 40) 479 | } 480 | 481 | // NoHeadLineCase No HeadLine. 482 | func NoHeadLineCase(word string) string { 483 | text := strings.ToLower(word) 484 | textNewer := strings.ReplaceAll(text, ":", "") 485 | return textNewer 486 | } 487 | 488 | // LoadFontFace load font face once before running, to work it quickly and save memory. 489 | func LoadFontFace(filePath string, size float64) font.Face { 490 | fontFile, _ := os.ReadFile(filePath) 491 | fontFileParse, _ := opentype.Parse(fontFile) 492 | fontFace, _ := opentype.NewFace(fontFileParse, &opentype.FaceOptions{Size: size, DPI: 70, Hinting: font.HintingFull}) 493 | return fontFace 494 | } 495 | 496 | // Inline Code. 497 | func saveImage(img image.Image, path string) { 498 | files, err := os.Create(path) 499 | if err != nil { 500 | log.Fatal(err) 501 | } 502 | defer func(files *os.File) { 503 | err := files.Close() 504 | if err != nil { 505 | return 506 | } 507 | }(files) 508 | err = png.Encode(files, img) 509 | if err != nil { 510 | log.Fatal(err) 511 | } 512 | } 513 | 514 | func downloadImage(url string) (image.Image, error) { 515 | response, err := http.Get(url) 516 | if err != nil { 517 | return nil, err 518 | } 519 | defer func(Body io.ReadCloser) { 520 | err := Body.Close() 521 | if err != nil { 522 | return 523 | } 524 | }(response.Body) 525 | img, _, err := image.Decode(response.Body) 526 | if err != nil { 527 | return nil, err 528 | } 529 | return img, nil 530 | } 531 | 532 | func getRatingBg(rating int) string { 533 | index := 0 534 | switch { 535 | case rating >= 15000: 536 | index++ 537 | fallthrough 538 | case rating >= 14000: 539 | index++ 540 | fallthrough 541 | case rating >= 13000: 542 | index++ 543 | fallthrough 544 | case rating >= 12000: 545 | index++ 546 | fallthrough 547 | case rating >= 10000: 548 | index++ 549 | fallthrough 550 | case rating >= 7000: 551 | index++ 552 | fallthrough 553 | case rating >= 4000: 554 | index++ 555 | fallthrough 556 | case rating >= 2000: 557 | index++ 558 | fallthrough 559 | case rating >= 1000: 560 | index++ 561 | } 562 | return ratingBgFilenames[index] 563 | } 564 | -------------------------------------------------------------------------------- /plugin/phigros/database.go: -------------------------------------------------------------------------------- 1 | package phigros 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "time" 7 | 8 | sql "github.com/FloatTech/sqlite" 9 | ) 10 | 11 | type PhigrosSQL struct { 12 | Id int64 `db:"user_tgid"` // tgid 13 | PhiSession string `db:"session"` // pgr session 14 | Time int64 `db:"time"` // time. 15 | } 16 | 17 | var ( 18 | pgrDatabase = &sql.Sqlite{} 19 | pgrLocker = sync.Mutex{} 20 | ) 21 | 22 | func init() { 23 | pgrDatabase.DBPath = engine.DataFolder() + "pgrsql.db" 24 | err := pgrDatabase.Open(time.Hour * 24) 25 | if err != nil { 26 | return 27 | } 28 | _ = InitDataBase() 29 | } 30 | 31 | func FormatUserDataBase(tgid int64, session string, Time int64) *PhigrosSQL { 32 | return &PhigrosSQL{Id: tgid, PhiSession: session, Time: Time} 33 | } 34 | 35 | func InitDataBase() error { 36 | pgrLocker.Lock() 37 | defer pgrLocker.Unlock() 38 | return pgrDatabase.Create("userinfo", &PhigrosSQL{}) 39 | } 40 | 41 | func GetUserInfoFromDatabase(userID int64) *PhigrosSQL { 42 | pgrLocker.Lock() 43 | defer pgrLocker.Unlock() 44 | var infosql PhigrosSQL 45 | userIDStr := strconv.FormatInt(userID, 10) 46 | _ = pgrDatabase.Find("userinfo", &infosql, "where user_tgid is "+userIDStr) 47 | return &infosql 48 | } 49 | 50 | func GetUserInfoTimeFromDatabase(userID int64) int64 { 51 | pgrLocker.Lock() 52 | defer pgrLocker.Unlock() 53 | var infosql PhigrosSQL 54 | userIDStr := strconv.FormatInt(userID, 10) 55 | _ = pgrDatabase.Find("userinfo", &infosql, "where user_tgid is "+userIDStr) 56 | return infosql.Time 57 | } 58 | 59 | func (info *PhigrosSQL) BindUserDataBase() error { 60 | pgrLocker.Lock() 61 | defer pgrLocker.Unlock() 62 | return pgrDatabase.Insert("userinfo", info) 63 | } 64 | -------------------------------------------------------------------------------- /plugin/phigros/function.go: -------------------------------------------------------------------------------- 1 | package phigros 2 | 3 | import ( 4 | "encoding/json" 5 | "image" 6 | "image/color" 7 | "math" 8 | "strconv" 9 | 10 | "github.com/FloatTech/gg" 11 | "github.com/MoYoez/Lucy_reibot/utils/transform" 12 | ) 13 | 14 | var ( 15 | phigrosB19 PhigrosStruct 16 | font = transform.ReturnLucyMainDataIndex("phi") + "rec/font.ttf" 17 | background = transform.ReturnLucyMainDataIndex("phi") + "rec/illustrationLowRes/" // .png 18 | backgroundRender = transform.ReturnLucyMainDataIndex("phi") + "rec/background.png" 19 | rank = transform.ReturnLucyMainDataIndex("phi") + "rec/rank/" 20 | ChanllengeMode = transform.ReturnLucyMainDataIndex("phi") + "rec/challengemode/" 21 | icon = transform.ReturnLucyMainDataIndex("phi") + "rec/icon.png" 22 | a float64 = 75 23 | ) 24 | 25 | type PhigrosStruct struct { 26 | Status bool `json:"status"` 27 | Message string `json:"message"` 28 | Content struct { 29 | Phi bool `json:"phi"` 30 | BestList []struct { 31 | Score int `json:"score"` 32 | Acc float64 `json:"acc"` 33 | Level string `json:"level"` 34 | Fc bool `json:"fc"` 35 | SongId string `json:"songId"` 36 | Songname string `json:"songname"` 37 | Difficulty float64 `json:"difficulty"` 38 | Rks float64 `json:"rks"` 39 | } `json:"bests"` 40 | PlayerID string `json:"PlayerID"` 41 | ChallengeModeRank int `json:"ChallengeModeRank"` 42 | RankingScore float64 `json:"RankingScore"` 43 | } `json:"content"` 44 | } 45 | 46 | // TODO: Use Original Phigros Index Path 47 | 48 | // 绘制平行四边形 angle 角度 x, y 坐标 w 宽度 l 斜边长 (github.com/Jiang-red/go-phigros-b19 function.) 49 | func drawTriAngle(canvas *gg.Context, angle, x, y, w, l float64) { 50 | // 左上角为原点 51 | x0, y0 := x, y 52 | // 右上角 53 | x1, y1 := x+w, y 54 | // 右下角 55 | x2 := x1 - (l * (math.Cos(angle * math.Pi / 180.0))) 56 | y2 := y1 + (l * (math.Sin(angle * math.Pi / 180.0))) 57 | // 左下角 58 | x3, y3 := x2-w, y2 59 | canvas.NewSubPath() 60 | canvas.MoveTo(x0, y0) 61 | canvas.LineTo(x1, y1) 62 | canvas.LineTo(x2, y2) 63 | canvas.LineTo(x3, y3) 64 | canvas.ClosePath() 65 | } 66 | 67 | // CardRender Render By Original Image,so it's single work and do slowly. 68 | func CardRender(canvas *gg.Context, dataOrigin []byte) *gg.Context { 69 | var referceLength = 0 70 | var referceWidth = 300 // + 1280 71 | var isRight = false 72 | var i, renderPath int 73 | // background render path. 74 | _ = json.Unmarshal(dataOrigin, &phigrosB19) 75 | i = 0 76 | renderPath = 0 77 | isPhi := phigrosB19.Content.Phi 78 | if isPhi { // while render the first set this change To Phi 79 | renderImage := phigrosB19.Content.BestList[i].SongId 80 | getRenderImage := background + renderImage + ".png" 81 | getImage, _ := gg.LoadImage(getRenderImage) 82 | // get background 83 | cardBackGround := DrawParallelogram(getImage) 84 | canvas.DrawImage(cardBackGround, referceWidth, 800+referceLength) 85 | // draw score path 86 | canvas.SetRGBA255(0, 0, 0, 160) 87 | drawTriAngle(canvas, 77, float64(referceWidth+500), float64(referceLength+850), 500, 210) 88 | canvas.Fill() 89 | // draw white line. 90 | canvas.SetColor(color.White) 91 | drawTriAngle(canvas, 77, float64(referceWidth+1000), float64(referceLength+850), 6, 210) 92 | canvas.Fill() 93 | // draw number format 94 | canvas.SetRGBA255(255, 255, 255, 245) 95 | drawTriAngle(canvas, 77, float64(referceWidth-26), float64(referceLength+800), 90, 55) 96 | canvas.Fill() 97 | // draw number path 98 | canvas.SetColor(color.Black) 99 | _ = canvas.LoadFontFace(font, 35) 100 | canvas.DrawString("Phi", float64(referceWidth-20), float64(referceLength+840)) 101 | canvas.Fill() 102 | // render Diff. 103 | getDiff := phigrosB19.Content.BestList[i].Level 104 | SetDiffColor(getDiff, canvas) 105 | drawTriAngle(canvas, 77, float64(referceWidth-65), float64(referceLength+992), 150, 80) 106 | canvas.Fill() 107 | // render Text 108 | getRKS := strconv.FormatFloat(phigrosB19.Content.BestList[i].Difficulty, 'f', 1, 64) 109 | getRating := strconv.FormatFloat(phigrosB19.Content.BestList[i].Rks, 'f', 2, 64) 110 | canvas.SetColor(color.White) 111 | _ = canvas.LoadFontFace(font, 30) 112 | canvas.DrawString(getDiff+" "+getRKS, float64(referceWidth-50), float64(referceLength+1019)) 113 | canvas.Fill() 114 | canvas.SetColor(color.White) 115 | _ = canvas.LoadFontFace(font, 45) 116 | canvas.DrawString(getRating, float64(referceWidth-60), float64(referceLength+1062)) 117 | canvas.Fill() 118 | // render info (acc,score,name) 119 | getRankLink := GetRank(phigrosB19.Content.BestList[i].Score, phigrosB19.Content.BestList[i].Fc) 120 | loadRankImage, _ := gg.LoadImage(rank + getRankLink + ".png") 121 | canvas.DrawImage(loadRankImage, referceWidth+500, referceLength+920) 122 | canvas.Fill() 123 | getName := phigrosB19.Content.BestList[i].Songname 124 | if len(getName) > 26 { 125 | getName = getName[:26] + ".." 126 | } 127 | _ = canvas.LoadFontFace(font, 34) 128 | canvas.DrawStringAnchored(getName, float64(referceWidth+740), float64(referceLength+890), 0.5, 0.5) 129 | canvas.SetColor(color.White) 130 | canvas.Fill() 131 | getScore := strconv.Itoa(phigrosB19.Content.BestList[i].Score) 132 | _ = canvas.LoadFontFace(font, 50) 133 | canvas.DrawStringAnchored(getScore, float64(referceWidth+740), float64(referceLength+950), 0.5, 0.5) 134 | canvas.Fill() 135 | canvas.SetColor(color.White) 136 | canvas.SetLineWidth(4) 137 | canvas.DrawLine(float64(referceWidth+630), float64(referceLength+990), float64(referceWidth+890), float64(referceLength+990)) 138 | canvas.Stroke() 139 | _ = canvas.LoadFontFace(font, 40) 140 | getAcc := strconv.FormatFloat(phigrosB19.Content.BestList[i].Acc, 'f', 2, 64) + "%" 141 | canvas.DrawStringAnchored(getAcc, float64(referceWidth+760), float64(referceLength+1020), 0.5, 0.5) 142 | canvas.Fill() 143 | // width = referceWidth | height = 800+ referenceLength 144 | referceWidth = referceWidth + 1280 145 | referceLength += 75 146 | isRight = true 147 | i = i + 1 148 | } 149 | for ; i < len(phigrosB19.Content.BestList); i++ { 150 | renderImage := phigrosB19.Content.BestList[i].SongId 151 | getRenderImage := background + renderImage + ".png" 152 | getImage, _ := gg.LoadImage(getRenderImage) 153 | // get background 154 | cardBackGround := DrawParallelogram(getImage) 155 | canvas.DrawImage(cardBackGround, referceWidth, 800+referceLength) 156 | // draw score path 157 | canvas.SetRGBA255(0, 0, 0, 160) 158 | drawTriAngle(canvas, 77, float64(referceWidth+500), float64(referceLength+850), 500, 210) 159 | canvas.Fill() 160 | // draw white line. 161 | canvas.SetColor(color.White) 162 | drawTriAngle(canvas, 77, float64(referceWidth+1000), float64(referceLength+850), 6, 210) 163 | canvas.Fill() 164 | // draw number format 165 | canvas.SetRGBA255(255, 255, 255, 245) 166 | drawTriAngle(canvas, 77, float64(referceWidth-26), float64(referceLength+800), 90, 55) 167 | canvas.Fill() 168 | // draw number path 169 | canvas.SetColor(color.Black) 170 | _ = canvas.LoadFontFace(font, 35) 171 | canvas.DrawString("#"+strconv.Itoa(renderPath+1), float64(referceWidth-20), float64(referceLength+840)) 172 | canvas.Fill() 173 | renderPath = renderPath + 1 174 | // render Diff. 175 | getDiff := phigrosB19.Content.BestList[i].Level 176 | SetDiffColor(getDiff, canvas) 177 | drawTriAngle(canvas, 77, float64(referceWidth-65), float64(referceLength+992), 150, 80) 178 | canvas.Fill() 179 | // render Text 180 | getRKS := strconv.FormatFloat(phigrosB19.Content.BestList[i].Difficulty, 'f', 1, 64) 181 | getRating := strconv.FormatFloat(phigrosB19.Content.BestList[i].Rks, 'f', 2, 64) 182 | canvas.SetColor(color.White) 183 | _ = canvas.LoadFontFace(font, 30) 184 | canvas.DrawString(getDiff+" "+getRKS, float64(referceWidth-50), float64(referceLength+1019)) 185 | canvas.Fill() 186 | canvas.SetColor(color.White) 187 | _ = canvas.LoadFontFace(font, 45) 188 | canvas.DrawString(getRating, float64(referceWidth-60), float64(referceLength+1062)) 189 | canvas.Fill() 190 | // render info (acc,score,name) 191 | getRankLink := GetRank(phigrosB19.Content.BestList[i].Score, phigrosB19.Content.BestList[i].Fc) 192 | loadRankImage, _ := gg.LoadImage(rank + getRankLink + ".png") 193 | canvas.DrawImage(loadRankImage, referceWidth+500, referceLength+920) 194 | canvas.Fill() 195 | getName := phigrosB19.Content.BestList[i].Songname 196 | if len(getName) > 26 { 197 | getName = getName[:26] + ".." 198 | } 199 | _ = canvas.LoadFontFace(font, 34) 200 | canvas.DrawStringAnchored(getName, float64(referceWidth+740), float64(referceLength+890), 0.5, 0.5) 201 | canvas.SetColor(color.White) 202 | canvas.Fill() 203 | getScore := strconv.Itoa(phigrosB19.Content.BestList[i].Score) 204 | _ = canvas.LoadFontFace(font, 50) 205 | canvas.DrawStringAnchored(getScore, float64(referceWidth+740), float64(referceLength+950), 0.5, 0.5) 206 | canvas.Fill() 207 | canvas.SetColor(color.White) 208 | canvas.SetLineWidth(4) 209 | canvas.DrawLine(float64(referceWidth+630), float64(referceLength+990), float64(referceWidth+890), float64(referceLength+990)) 210 | canvas.Stroke() 211 | _ = canvas.LoadFontFace(font, 40) 212 | getAcc := strconv.FormatFloat(phigrosB19.Content.BestList[i].Acc, 'f', 2, 64) + "%" 213 | canvas.DrawStringAnchored(getAcc, float64(referceWidth+760), float64(referceLength+1020), 0.5, 0.5) 214 | canvas.Fill() 215 | // width = referceWidth | height = 800+ referenceLength 216 | if !isRight { 217 | referceWidth = referceWidth + 1280 218 | referceLength += 75 219 | isRight = true 220 | } else { 221 | referceWidth = referceWidth - 1280 222 | referceLength -= 75 223 | isRight = false 224 | referceLength += 400 225 | } 226 | } 227 | canvas.Fill() 228 | return canvas 229 | } 230 | 231 | // GetUserChallengeMode Challenge Mode Type Reply 232 | func GetUserChallengeMode(num int) (challenge string, link string) { 233 | var colors string 234 | for i := 1; i < 7; i++ { 235 | if i*100 > num { 236 | getCurrentRankLevel := i - 1 237 | switch { 238 | case getCurrentRankLevel == 1: 239 | colors = "Green" 240 | link = strconv.Itoa(num - (i-1)*100) 241 | case getCurrentRankLevel == 2: 242 | colors = "Blue" 243 | link = strconv.Itoa(num - (i-1)*100) 244 | case getCurrentRankLevel == 3: 245 | colors = "Red" 246 | link = strconv.Itoa(num - (i-1)*100) 247 | case getCurrentRankLevel == 4: 248 | colors = "Gold" 249 | link = strconv.Itoa(num - (i-1)*100) 250 | case getCurrentRankLevel == 5: 251 | colors = "Rainbow" 252 | link = strconv.Itoa(num - (i-1)*100) 253 | default: 254 | colors = "" 255 | link = "无记录" 256 | } 257 | break 258 | } 259 | } 260 | return colors, link 261 | } 262 | 263 | // DrawParallelogram Draw Card TriAnglePath 264 | func DrawParallelogram(img image.Image) image.Image { 265 | length := 690.0 266 | dc := gg.NewContext(img.Bounds().Dx(), img.Bounds().Dy()) 267 | picLengthWidth := img.Bounds().Dx() 268 | picLengthHeight := img.Bounds().Dy() 269 | getClipWidth := float64(picLengthWidth) - (length * 0.65) // get start point 270 | dc.NewSubPath() 271 | dc.MoveTo(getClipWidth, 0) 272 | dc.LineTo(float64(picLengthWidth), 0) 273 | dc.LineTo(length*0.65, float64(picLengthHeight)) 274 | dc.LineTo(0, float64(picLengthHeight)) 275 | dc.ClosePath() 276 | dc.Clip() 277 | dc.DrawImage(img, 0, 0) 278 | //getResizeImage := imaging.Resize(dc.Image(), 350, 270, imaging.Lanczos) 279 | getResizeImage := dc.Image() 280 | return getResizeImage 281 | } 282 | 283 | // GetRank get this rank. 284 | func GetRank(num int, isFC bool) string { 285 | var rankPicName string 286 | 287 | switch { 288 | case num == 1000000: 289 | rankPicName = "phi" 290 | case num >= 960000: 291 | rankPicName = "v" 292 | case num >= 920000: 293 | rankPicName = "s" 294 | case num >= 880000: 295 | rankPicName = "a" 296 | case num >= 820000: 297 | rankPicName = "b" 298 | case num >= 700000: 299 | rankPicName = "c" 300 | default: 301 | rankPicName = "f" 302 | } 303 | if isFC && num != 1000000 { 304 | rankPicName = "fc" 305 | } 306 | return rankPicName 307 | } 308 | 309 | // SetDiffColor Set Diff Color. 310 | func SetDiffColor(diff string, canvas *gg.Context) { 311 | switch { 312 | case diff == "IN": 313 | canvas.SetRGB255(189, 45, 36) 314 | case diff == "HD": 315 | canvas.SetRGB255(50, 115, 179) 316 | case diff == "AT": 317 | canvas.SetRGB255(56, 56, 56) 318 | case diff == "EZ": 319 | canvas.SetRGB255(79, 200, 134) 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /plugin/phigros/main.go: -------------------------------------------------------------------------------- 1 | package phigros 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | "unicode/utf8" 7 | 8 | "image/color" 9 | 10 | "strconv" 11 | "sync" 12 | 13 | "github.com/FloatTech/floatbox/web" 14 | "github.com/FloatTech/gg" 15 | ctrl "github.com/FloatTech/zbpctrl" 16 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 17 | "github.com/disintegration/imaging" 18 | rei "github.com/fumiama/ReiBot" 19 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 20 | ) 21 | 22 | var ( 23 | engine = rei.Register("phigros", &ctrl.Options[*rei.Ctx]{ 24 | DisableOnDefault: false, 25 | Help: "phigros - | generate phigros b19 | roll ", 26 | PrivateDataFolder: "phigros", 27 | }) 28 | router = "http://221.131.165.69:51602/" 29 | ) 30 | 31 | func init() { 32 | engine.OnMessageCommand("pgr").SetBlock(true).Handle(func(ctx *rei.Ctx) { 33 | getMsg := ctx.Message.Text 34 | getSplitLength, getSplitStringList := toolchain.SplitCommandTo(getMsg, 3) 35 | if getSplitLength >= 2 { 36 | switch { 37 | case getSplitStringList[1] == "bind": 38 | if getSplitLength < 3 { 39 | ctx.SendPlainMessage(true, "参数提供不足") 40 | return 41 | } 42 | PhiBind(ctx, getSplitStringList[2]) 43 | case getSplitStringList[1] == "roll": 44 | if getSplitLength < 3 { 45 | ctx.SendPlainMessage(true, "参数提供不足") 46 | return 47 | } 48 | RollToRenderPhigros(ctx, getSplitStringList[2]) 49 | default: 50 | ctx.SendPlainMessage(true, "未知的指令或者指令出现错误~") 51 | } 52 | } else { 53 | RenderPhi(ctx) 54 | } 55 | }) 56 | 57 | } 58 | 59 | func RenderPhi(ctx *rei.Ctx) { 60 | getUserId, _ := toolchain.GetChatUserInfoID(ctx) 61 | data := GetUserInfoFromDatabase(getUserId) 62 | getDataSession := data.PhiSession 63 | if getDataSession == "" { 64 | _, _ = ctx.SendPlainMessage(true, "请前往 https://phi.lemonkoi.one 获取绑定码进行绑定 ") 65 | return 66 | } 67 | userData := GetUserInfoFromDatabase(getUserId) 68 | ctx.SendPlainMessage(true, "好哦~正在帮你请求,请稍等一下啦w~大约需要1-2分钟") 69 | var dataWaiter sync.WaitGroup 70 | var AvatarWaiter sync.WaitGroup 71 | var getAvatarFormat *gg.Context 72 | var phidata []byte 73 | var setGlobalStat = true 74 | AvatarWaiter.Add(1) 75 | dataWaiter.Add(1) 76 | go func() { 77 | defer dataWaiter.Done() 78 | getFullLink := router + "/api/phi/bests?session=" + userData.PhiSession + "&overflow=2" 79 | phidata, _ = web.GetData(getFullLink) 80 | if phidata == nil { 81 | ctx.SendPlainMessage(true, "目前 Unoffical Phigros API 暂时无法工作 请过一段时候尝试") 82 | setGlobalStat = false 83 | return 84 | } 85 | err := json.Unmarshal(phidata, &phigrosB19) 86 | if !phigrosB19.Status || err != nil { 87 | ctx.SendPlainMessage(true, "w? 貌似出现了一些问题x") 88 | return 89 | } 90 | }() 91 | go func() { 92 | defer AvatarWaiter.Done() 93 | avatarByteUni := toolchain.GetTargetAvatar(ctx) 94 | if avatarByteUni == nil { 95 | return 96 | } 97 | // avatar 98 | showUserAvatar := imaging.Resize(avatarByteUni, 250, 250, imaging.Lanczos) 99 | getAvatarFormat = gg.NewContext(250, 250) 100 | getAvatarFormat.DrawRoundedRectangle(0, 0, 248, 248, 20) 101 | getAvatarFormat.Clip() 102 | getAvatarFormat.DrawImage(showUserAvatar, 0, 0) 103 | getAvatarFormat.Fill() 104 | }() 105 | getRawBackground, _ := gg.LoadImage(backgroundRender) 106 | getMainBgRender := gg.NewContextForImage(imaging.Resize(getRawBackground, 2750, 5500, imaging.Lanczos)) 107 | _ = getMainBgRender.LoadFontFace(font, 30) 108 | // header background 109 | drawTriAngle(getMainBgRender, a, 0, 166, 1324, 410) 110 | getMainBgRender.SetRGBA255(0, 0, 0, 160) 111 | getMainBgRender.Fill() 112 | drawTriAngle(getMainBgRender, a, 1318, 192, 1600, 350) 113 | getMainBgRender.SetRGBA255(0, 0, 0, 160) 114 | getMainBgRender.Fill() 115 | drawTriAngle(getMainBgRender, a, 1320, 164, 6, 414) 116 | getMainBgRender.SetColor(color.White) 117 | getMainBgRender.Fill() 118 | // header background end. 119 | // load icon with other userinfo. 120 | getMainBgRender.SetColor(color.White) 121 | logo, _ := gg.LoadPNG(icon) 122 | getImageLogo := imaging.Resize(logo, 290, 290, imaging.Lanczos) 123 | getMainBgRender.DrawImage(getImageLogo, 50, 216) 124 | fontface, _ := gg.LoadFontFace(font, 90) 125 | getMainBgRender.SetFontFace(fontface) 126 | getMainBgRender.DrawString("Phigros", 422, 336) 127 | getMainBgRender.DrawString("RankingScore查询", 422, 462) 128 | // draw userinfo path 129 | renderHeaderText, _ := gg.LoadFontFace(font, 54) 130 | getMainBgRender.SetFontFace(renderHeaderText) 131 | dataWaiter.Wait() 132 | if !setGlobalStat { 133 | return 134 | } 135 | getMainBgRender.DrawString("Player: "+phigrosB19.Content.PlayerID, 1490, 300) 136 | getMainBgRender.DrawString("RankingScore: "+strconv.FormatFloat(phigrosB19.Content.RankingScore, 'f', 3, 64), 1490, 380) 137 | getMainBgRender.DrawString("ChanllengeMode: ", 1490, 460) // +56 138 | getColor, getLink := GetUserChallengeMode(phigrosB19.Content.ChallengeModeRank) 139 | if getColor != "" { 140 | getColorLink := ChanllengeMode + getColor + ".png" 141 | getColorImage, _ := gg.LoadImage(getColorLink) 142 | getMainBgRender.DrawImage(imaging.Resize(getColorImage, 238, 130, imaging.Lanczos), 1912, 390) 143 | } 144 | renderHeaderTextNumber, _ := gg.LoadFontFace(font, 65) 145 | getMainBgRender.SetFontFace(renderHeaderTextNumber) 146 | // white glow render 147 | getMainBgRender.SetRGB(1, 1, 1) 148 | getMainBgRender.DrawStringAnchored(getLink, 2021, 430, 0.4, 0.4) 149 | // avatar 150 | AvatarWaiter.Wait() 151 | if getAvatarFormat != nil { 152 | getMainBgRender.DrawImage(getAvatarFormat.Image(), 2321, 230) 153 | getMainBgRender.Fill() 154 | } 155 | // render 156 | CardRender(getMainBgRender, phidata) 157 | // draw bottom 158 | _ = getMainBgRender.LoadFontFace(font, 40) 159 | getMainBgRender.SetColor(color.White) 160 | getMainBgRender.Fill() 161 | getMainBgRender.DrawString("Generated By Lucy (HiMoYoBOT) | Designed By Eastown | Data From Phigros Library Project", 10, 5480) 162 | _ = getMainBgRender.SavePNG(engine.DataFolder() + "save/" + strconv.Itoa(int(getUserId)) + ".png") 163 | ctx.SendPhoto(tgba.FilePath(engine.DataFolder()+"save/"+strconv.Itoa(int(getUserId))+".png"), true, "") 164 | } 165 | 166 | func RollToRenderPhigros(ctx *rei.Ctx, num string) { 167 | var wg sync.WaitGroup 168 | var avatarWaitGroup sync.WaitGroup 169 | var dataWaiter sync.WaitGroup 170 | var getMainBgRender *gg.Context 171 | var getAvatarFormat *gg.Context 172 | var setGlobalStat = true 173 | var phidata []byte 174 | wg.Add(1) 175 | avatarWaitGroup.Add(1) 176 | dataWaiter.Add(1) 177 | // get Session From Database. 178 | userid, _ := toolchain.GetChatUserInfoID(ctx) 179 | data := GetUserInfoFromDatabase(userid) 180 | getDataSession := data.PhiSession 181 | if getDataSession == "" { 182 | ctx.SendPlainMessage(true, "由于Session特殊性,请前往 https://phi.lemonkoi.one 获取绑定码进行绑定") 183 | return 184 | } 185 | userData := GetUserInfoFromDatabase(userid) 186 | getRoll := num 187 | getRollInt, err := strconv.ParseInt(getRoll, 10, 64) 188 | if err != nil { 189 | ctx.SendPlainMessage(true, "请求roll不合法") 190 | return 191 | } 192 | if getRollInt > 40 { 193 | ctx.SendPlainMessage(true, "限制查询数为小于40") 194 | return 195 | } 196 | getOverFlowNumber := getRollInt - 19 197 | if getOverFlowNumber <= 0 { 198 | getOverFlowNumber = 0 199 | } 200 | ctx.SendPlainMessage(true, "好哦~正在帮你请求,请稍等一下啦w~大约需要1-2分钟") 201 | // data handling. 202 | go func() { 203 | defer dataWaiter.Done() 204 | getFullLink := router + "/api/phi/bests?session=" + userData.PhiSession + "&overflow=" + strconv.Itoa(int(getOverFlowNumber)) 205 | phidata, _ = web.GetData(getFullLink) 206 | if phidata == nil { 207 | ctx.SendPlainMessage(true, "目前 Unoffical Phigros Library 暂时无法工作 请过一段时候尝试") 208 | setGlobalStat = false 209 | return 210 | } 211 | err = json.Unmarshal(phidata, &phigrosB19) 212 | if err != nil { 213 | ctx.SendPlainMessage(true, "发生解析错误: \n", err) 214 | setGlobalStat = false 215 | return 216 | } 217 | if !phigrosB19.Status { 218 | ctx.SendPlainMessage(true, "w? 貌似出现了一些问题x\n", phigrosB19.Message) 219 | setGlobalStat = false 220 | return 221 | } 222 | }() 223 | go func() { 224 | defer wg.Done() 225 | getRawBackground, _ := gg.LoadImage(backgroundRender) 226 | getMainBgRender = gg.NewContextForImage(imaging.Resize(getRawBackground, 2750, int(5250+getOverFlowNumber*200), imaging.Lanczos)) 227 | }() 228 | go func() { 229 | defer avatarWaitGroup.Done() 230 | avatarByteUni := toolchain.GetTargetAvatar(ctx) 231 | if avatarByteUni == nil { 232 | return 233 | } 234 | // avatar 235 | showUserAvatar := imaging.Resize(avatarByteUni, 250, 250, imaging.Lanczos) 236 | getAvatarFormat = gg.NewContext(250, 250) 237 | getAvatarFormat.DrawRoundedRectangle(0, 0, 248, 248, 20) 238 | getAvatarFormat.Clip() 239 | getAvatarFormat.DrawImage(showUserAvatar, 0, 0) 240 | getAvatarFormat.Fill() 241 | }() 242 | wg.Wait() 243 | _ = getMainBgRender.LoadFontFace(font, 30) 244 | // header background 245 | drawTriAngle(getMainBgRender, a, 0, 166, 1324, 410) 246 | getMainBgRender.SetRGBA255(0, 0, 0, 160) 247 | getMainBgRender.Fill() 248 | drawTriAngle(getMainBgRender, a, 1318, 192, 1600, 350) 249 | getMainBgRender.SetRGBA255(0, 0, 0, 160) 250 | getMainBgRender.Fill() 251 | drawTriAngle(getMainBgRender, a, 1320, 164, 6, 414) 252 | getMainBgRender.SetColor(color.White) 253 | getMainBgRender.Fill() 254 | // header background end. 255 | // load icon with other userinfo. 256 | getMainBgRender.SetColor(color.White) 257 | logo, _ := gg.LoadPNG(icon) 258 | getImageLogo := imaging.Resize(logo, 290, 290, imaging.Lanczos) 259 | getMainBgRender.DrawImage(getImageLogo, 50, 216) 260 | fontface, _ := gg.LoadFontFace(font, 90) 261 | getMainBgRender.SetFontFace(fontface) 262 | getMainBgRender.DrawString("Phigros", 422, 336) 263 | getMainBgRender.DrawString("RankingScore查询", 422, 462) 264 | dataWaiter.Wait() 265 | if !setGlobalStat { 266 | return 267 | } 268 | // draw userinfo path 269 | renderHeaderText, _ := gg.LoadFontFace(font, 54) 270 | getMainBgRender.SetFontFace(renderHeaderText) 271 | // wait data until fine. 272 | getMainBgRender.DrawString("Player: "+phigrosB19.Content.PlayerID, 1490, 300) 273 | getMainBgRender.DrawString("RankingScore: "+strconv.FormatFloat(phigrosB19.Content.RankingScore, 'f', 3, 64), 1490, 380) 274 | getMainBgRender.DrawString("ChanllengeMode: ", 1490, 460) // +56 275 | getColor, getLink := GetUserChallengeMode(phigrosB19.Content.ChallengeModeRank) 276 | if getColor != "" { 277 | getColorLink := ChanllengeMode + getColor + ".png" 278 | getColorImage, _ := gg.LoadImage(getColorLink) 279 | getMainBgRender.DrawImage(imaging.Resize(getColorImage, 238, 130, imaging.Lanczos), 1912, 390) 280 | } 281 | renderHeaderTextNumber, _ := gg.LoadFontFace(font, 65) 282 | getMainBgRender.SetFontFace(renderHeaderTextNumber) 283 | // white glow render 284 | getMainBgRender.SetRGB(1, 1, 1) 285 | getMainBgRender.DrawStringAnchored(getLink, 2021, 430, 0.4, 0.4) 286 | avatarWaitGroup.Wait() 287 | getMainBgRender.DrawImage(getAvatarFormat.Image(), 2321, 230) 288 | getMainBgRender.Fill() 289 | // render 290 | CardRender(getMainBgRender, phidata) 291 | // draw bottom 292 | _ = getMainBgRender.LoadFontFace(font, 40) 293 | getMainBgRender.SetColor(color.White) 294 | getMainBgRender.Fill() 295 | getMainBgRender.DrawString("Generated By Lucy (HiMoYoBOT) | Designed By Eastown | Data From Phigros Library Project", 10, float64(5110+getOverFlowNumber*200+100)) 296 | _ = getMainBgRender.SavePNG(engine.DataFolder() + "save/" + "roll" + strconv.Itoa(int(userid)) + ".png") 297 | ctx.SendPhoto(tgba.FilePath(engine.DataFolder()+"save/"+"roll"+strconv.Itoa(int(userid))+".png"), true, "") 298 | 299 | } 300 | 301 | func PhiBind(ctx *rei.Ctx, bindAcc string) { 302 | hash := bindAcc 303 | getUserId, _ := toolchain.GetChatUserInfoID(ctx) 304 | userInfo := GetUserInfoTimeFromDatabase(getUserId) 305 | if userInfo+(12*60*60) > time.Now().Unix() { 306 | ctx.SendPlainMessage(true, "12小时内仅允许绑定一次哦") 307 | return 308 | } 309 | // get session. 310 | if hash == "" { 311 | ctx.SendPlainMessage(true, "请前往 https://phi.lemonkoi.one 获取绑定码进行绑定") 312 | return 313 | } 314 | if utf8.RuneCountInString(hash) != 25 { 315 | ctx.SendPlainMessage(true, "Session 传入数值出现错误,请重新绑定") 316 | return 317 | } 318 | _ = FormatUserDataBase(ctx.Message.From.ID, hash, time.Now().Unix()).BindUserDataBase() 319 | ctx.SendPlainMessage(true, "绑定成功~") 320 | } 321 | -------------------------------------------------------------------------------- /plugin/reborn/main.go: -------------------------------------------------------------------------------- 1 | package reborn 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "time" 9 | 10 | ctrl "github.com/FloatTech/zbpctrl" 11 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 12 | "github.com/MoYoez/Lucy_reibot/utils/transform" 13 | rei "github.com/fumiama/ReiBot" 14 | wr "github.com/mroth/weightedrand" 15 | "github.com/sirupsen/logrus" 16 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 17 | ) 18 | 19 | var ( 20 | areac *wr.Chooser 21 | gender, _ = wr.NewChooser( 22 | wr.Choice{Item: "男孩子", Weight: 23707}, 23 | wr.Choice{Item: "女孩子", Weight: 49292}, 24 | wr.Choice{Item: "雌雄同体", Weight: 1001}, 25 | wr.Choice{Item: "猫猫!", Weight: 15000}, 26 | wr.Choice{Item: "狗狗!", Weight: 5000}, 27 | wr.Choice{Item: "龙猫~", Weight: 6000}, 28 | ) 29 | rebornTimerManager = rate.NewManager[int64](time.Minute*2, 5) 30 | ) 31 | 32 | type ratego []struct { 33 | Name string `json:"name"` 34 | Weight float64 `json:"weight"` 35 | } 36 | 37 | func init() { 38 | engine := rei.Register("reborn", &ctrl.Options[*rei.Ctx]{ 39 | DisableOnDefault: false, 40 | Help: "reborn", 41 | }) 42 | go func() { 43 | datapath := transform.ReturnLucyMainDataIndex("funwork") 44 | jsonfile := datapath + "ratego.json" 45 | area := make(ratego, 226) 46 | err := load(&area, jsonfile) 47 | if err != nil { 48 | return 49 | } 50 | choices := make([]wr.Choice, len(area)) 51 | for i, a := range area { 52 | choices[i].Item = a.Name 53 | choices[i].Weight = uint(a.Weight * 1e9) 54 | } 55 | areac, err = wr.NewChooser(choices...) 56 | if err != nil { 57 | return 58 | } 59 | logrus.Printf("[Reborn]读取%d个国家/地区", len(area)) 60 | }() 61 | engine.OnMessageCommand("reborn").SetBlock(true).Handle(func(ctx *rei.Ctx) { 62 | if !rebornTimerManager.Load(toolchain.GetThisGroupID(ctx)).Acquire() { 63 | ctx.SendPlainMessage(true, "太快了哦,麻烦慢一点~") 64 | return 65 | } 66 | if rand.Int31() > 1<<27 { 67 | ctx.SendPlainMessage(true, fmt.Sprintf("投胎成功!\n您出生在 %s, 是 %s。", randcoun(), randgen())) 68 | } else { 69 | ctx.SendPlainMessage(true, "投胎失败!\n您没能活到出生,希望下次运气好一点呢~!") 70 | } 71 | 72 | }) 73 | } 74 | 75 | // load 加载rate数据 76 | func load(area *ratego, jsonfile string) error { 77 | data, err := os.ReadFile(jsonfile) 78 | if err != nil { 79 | return err 80 | } 81 | return json.Unmarshal(data, area) 82 | } 83 | 84 | func randcoun() string { 85 | return areac.Pick().(string) 86 | } 87 | 88 | func randgen() string { 89 | return gender.Pick().(string) 90 | } 91 | -------------------------------------------------------------------------------- /plugin/score/main.go: -------------------------------------------------------------------------------- 1 | package score 2 | 3 | import ( 4 | "github.com/FloatTech/floatbox/math" 5 | "math/rand" 6 | "regexp" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/FloatTech/floatbox/file" 11 | "github.com/FloatTech/gg" 12 | ctrl "github.com/FloatTech/zbpctrl" 13 | coins "github.com/MoYoez/Lucy_reibot/utils/coins" 14 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 15 | "github.com/MoYoez/Lucy_reibot/utils/transform" 16 | rei "github.com/fumiama/ReiBot" 17 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 18 | ) 19 | 20 | var ( 21 | engine = rei.Register("score", &ctrl.Options[*rei.Ctx]{ 22 | DisableOnDefault: false, 23 | Help: "Hi NekoPachi!\n说明书: https://lucy-sider.lemonkoi.one", 24 | PrivateDataFolder: "score", 25 | }) 26 | ) 27 | 28 | func init() { 29 | cachePath := engine.DataFolder() + "scorecache/" 30 | sdb := coins.Initialize("./data/score/score.db") 31 | engine.OnMessageCommand("sign").SetBlock(true).Handle(func(ctx *rei.Ctx) { 32 | uid, username := toolchain.GetChatUserInfoID(ctx) 33 | // remove emoji. 34 | emojiRegex := regexp.MustCompile(`[\x{1F600}-\x{1F64F}|[\x{1F300}-\x{1F5FF}]|[\x{1F680}-\x{1F6FF}]|[\x{1F700}-\x{1F77F}]|[\x{1F780}-\x{1F7FF}]|[\x{1F800}-\x{1F8FF}]|[\x{1F900}-\x{1F9FF}]|[\x{1FA00}-\x{1FA6F}]|[\x{1FA70}-\x{1FAFF}]|[\x{1FB00}-\x{1FBFF}]|[\x{1F170}-\x{1F251}]|[\x{1F300}-\x{1F5FF}]|[\x{1F600}-\x{1F64F}]|[\x{1FC00}-\x{1FCFF}]|[\x{1F004}-\x{1F0CF}]|[\x{1F170}-\x{1F251}]]+`) 35 | username = emojiRegex.ReplaceAllString(username, "") 36 | // save time data by add 30mins (database save it, not to handle it when it gets ready.) 37 | // just handle data time when it on,make sure to decrease 30 mins when render the page( 38 | 39 | // not sure what happened 40 | getNowUnixFormatElevenThirten := time.Now().Add(time.Minute * 30).Format("20060102") 41 | getStatus, _ := coins.GetUserIsSignInToday(sdb, uid) 42 | si := coins.GetSignInByUID(sdb, uid) 43 | drawedFile := cachePath + strconv.FormatInt(uid, 10) + getNowUnixFormatElevenThirten + "signin.png" 44 | if getStatus && si.Count != 0 { // test pattern 45 | ctx.SendPlainMessage(true, "w~ 你今天已经签到过了哦w") 46 | if file.IsExist(drawedFile) { 47 | ctx.SendPhoto(tgba.FilePath(drawedFile), true, "~") 48 | } 49 | return 50 | } 51 | 52 | coinsGet := 300 + rand.Intn(200) 53 | coins.UpdateUserSignInValue(sdb, uid) 54 | coins.InsertUserCoins(sdb, uid, si.Coins+coinsGet) 55 | coins.InsertOrUpdateSignInCountByUID(sdb, uid, si.Count+1) // 柠檬片获取 56 | score := coins.GetScoreByUID(sdb, uid).Score 57 | score++ 58 | coins.InsertOrUpdateScoreByUID(sdb, uid, score) 59 | CurrentCountTable := coins.GetCurrentCount(sdb, getNowUnixFormatElevenThirten) 60 | handledTodayNum := CurrentCountTable.Counttime + 1 61 | coins.UpdateUserTime(sdb, handledTodayNum, getNowUnixFormatElevenThirten) 62 | 63 | if time.Now().Hour() > 6 && time.Now().Hour() < 19 { 64 | // package for test draw. 65 | getTimeReplyMsg := coins.GetHourWord(time.Now()) // get time and msg 66 | currentTime := time.Now().Format("2006-01-02 15:04:05") 67 | // time day. 68 | dayTimeImg, _ := gg.LoadImage(transform.ReturnLucyMainDataIndex("score") + "BetaScoreDay.png") 69 | dayGround := gg.NewContext(1920, 1080) 70 | dayGround.DrawImage(dayTimeImg, 0, 0) 71 | _ = dayGround.LoadFontFace(transform.ReturnLucyMainDataIndex("score")+"dyh.ttf", 60) 72 | dayGround.SetRGB(0, 0, 0) 73 | // draw something with cautions Only ( 74 | dayGround.DrawString(currentTime, 1270, 950) // draw time 75 | dayGround.DrawString(getTimeReplyMsg, 50, 930) // draw text. 76 | dayGround.DrawString(username, 310, 110) // draw name :p why I should do this??? 77 | _ = dayGround.LoadFontFace(transform.ReturnLucyMainDataIndex("score")+"dyh.ttf", 60) 78 | dayGround.DrawStringWrapped(strconv.Itoa(handledTodayNum), 350, 255, 1, 1, 0, 1.3, gg.AlignCenter) // draw first part 79 | dayGround.DrawStringWrapped(strconv.Itoa(si.Count+1), 1000, 255, 1, 1, 0, 1.3, gg.AlignCenter) // draw second part 80 | dayGround.DrawStringWrapped(strconv.Itoa(coinsGet), 220, 370, 1, 1, 0, 1.3, gg.AlignCenter) // draw third part 81 | dayGround.DrawStringWrapped(strconv.Itoa(si.Coins+coinsGet), 720, 370, 1, 1, 0, 1.3, gg.AlignCenter) // draw forth part 82 | // level array with rectangle work. 83 | rankNum := coins.GetLevel(score) 84 | RankGoal := rankNum + 1 85 | achieveNextGoal := coins.LevelArray[RankGoal] 86 | achievedGoal := coins.LevelArray[rankNum] 87 | currentNextGoalMeasure := achieveNextGoal - score // measure rest of the num. like 20 - currentLink(TestRank 15) 88 | measureGoalsLens := math.Abs(achievedGoal - achieveNextGoal) // like 20 - 10 89 | currentResult := float64(measureGoalsLens-currentNextGoalMeasure) / float64(measureGoalsLens) 90 | // draw this part 91 | dayGround.SetRGB255(180, 255, 254) // aqua color 92 | dayGround.DrawRectangle(70, 570, 600, 50) // draw rectangle part1 93 | dayGround.Fill() 94 | dayGround.SetRGB255(130, 255, 254) 95 | dayGround.DrawRectangle(70, 570, 600*currentResult, 50) // draw rectangle part2 96 | dayGround.Fill() 97 | dayGround.SetRGB255(0, 0, 0) 98 | dayGround.DrawString("Lv. "+strconv.Itoa(rankNum)+" 签到天数 + 1", 80, 490) 99 | _ = dayGround.LoadFontFace(transform.ReturnLucyMainDataIndex("score")+"dyh.ttf", 40) 100 | dayGround.DrawString(strconv.Itoa(measureGoalsLens-currentNextGoalMeasure)+"/"+strconv.Itoa(measureGoalsLens), 710, 610) 101 | _ = dayGround.SavePNG(drawedFile) 102 | ctx.SendPhoto(tgba.FilePath(drawedFile), true, "[sign]签到完毕~") 103 | } else { 104 | // nightVision 105 | // package for test draw. 106 | getTimeReplyMsg := coins.GetHourWord(time.Now()) // get time and msg 107 | currentTime := time.Now().Format("2006-01-02 15:04:05") 108 | nightTimeImg, _ := gg.LoadImage(transform.ReturnLucyMainDataIndex("score") + "BetaScoreNight.png") 109 | nightGround := gg.NewContext(1886, 1060) 110 | nightGround.DrawImage(nightTimeImg, 0, 0) 111 | _ = nightGround.LoadFontFace(transform.ReturnLucyMainDataIndex("score")+"dyh.ttf", 60) 112 | nightGround.SetRGB255(255, 255, 255) 113 | // draw something with cautions Only ( 114 | nightGround.DrawString(currentTime, 1360, 910) // draw time 115 | nightGround.DrawString(getTimeReplyMsg, 60, 930) // draw text. 116 | nightGround.DrawString(username, 350, 140) // draw name :p why I should do this??? 117 | _ = nightGround.LoadFontFace(transform.ReturnLucyMainDataIndex("score")+"dyh.ttf", 60) 118 | nightGround.DrawStringWrapped(strconv.Itoa(handledTodayNum), 345, 275, 1, 1, 0, 1.3, gg.AlignCenter) // draw first part 119 | nightGround.DrawStringWrapped(strconv.Itoa(si.Count+1), 990, 275, 1, 1, 0, 1.3, gg.AlignCenter) // draw second part 120 | nightGround.DrawStringWrapped(strconv.Itoa(coinsGet), 225, 360, 1, 1, 0, 1.3, gg.AlignCenter) // draw third part 121 | nightGround.DrawStringWrapped(strconv.Itoa(si.Coins+coinsGet), 720, 360, 1, 1, 0, 1.3, gg.AlignCenter) // draw forth part 122 | 123 | // level array with rectangle work. 124 | rankNum := coins.GetLevel(score) 125 | RankGoal := rankNum + 1 126 | achieveNextGoal := coins.LevelArray[RankGoal] 127 | 128 | achievedGoal := coins.LevelArray[rankNum] 129 | 130 | currentNextGoalMeasure := achieveNextGoal - score // measure rest of the num. like 20 - currentLink(TestRank 15) 131 | measureGoalsLens := math.Abs(achievedGoal - achieveNextGoal) // like 20 - 10 132 | 133 | currentResult := float64(measureGoalsLens-currentNextGoalMeasure) / float64(measureGoalsLens) 134 | // draw this part 135 | nightGround.SetRGB255(49, 86, 157) 136 | nightGround.DrawRectangle(70, 570, 600, 50) // draw rectangle part1 137 | nightGround.Fill() 138 | nightGround.SetRGB255(255, 255, 255) 139 | nightGround.DrawRectangle(70, 570, 600*currentResult, 50) // draw rectangle part2 140 | nightGround.Fill() 141 | nightGround.SetRGB255(255, 255, 255) 142 | nightGround.DrawString("Lv. "+strconv.Itoa(rankNum)+" 签到天数 + 1", 80, 490) 143 | _ = nightGround.LoadFontFace(transform.ReturnLucyMainDataIndex("score")+"dyh.ttf", 40) 144 | nightGround.DrawString(strconv.Itoa(measureGoalsLens-currentNextGoalMeasure)+"/"+strconv.Itoa(measureGoalsLens), 710, 610) 145 | _ = nightGround.SavePNG(drawedFile) 146 | ctx.SendPhoto(tgba.FilePath(drawedFile), true, "[sign]签到完成~") 147 | } 148 | }) 149 | 150 | } 151 | -------------------------------------------------------------------------------- /plugin/simai/main.go: -------------------------------------------------------------------------------- 1 | // Package simai refactory From Lucy For Onebot. (origin github.com/FloatTech/Zerobot-Plugin) 2 | package simai 3 | 4 | import ( 5 | "math/rand" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | ctrl "github.com/FloatTech/zbpctrl" 12 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 13 | "github.com/MoYoez/Lucy_reibot/utils/transform" 14 | rei "github.com/fumiama/ReiBot" 15 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 16 | "gopkg.in/yaml.v3" 17 | ) 18 | 19 | // SimPackData simai Data 20 | type SimPackData struct { 21 | Proud map[string][]string `yaml:"傲娇"` 22 | Kawaii map[string][]string `yaml:"可爱"` 23 | } 24 | 25 | var limit = rate.NewManager[int64](time.Minute*3, 28) // 回复限制 26 | 27 | func init() { 28 | engine := rei.Register("simai", &ctrl.Options[*rei.Ctx]{ 29 | DisableOnDefault: false, 30 | PrivateDataFolder: "simai", 31 | Help: "simai - Use simia pre-render dict to make it more clever", 32 | }) 33 | // onload simai dict 34 | dictLoaderLocation := transform.ReturnLucyMainDataIndex("simai") + "simai.yml" 35 | dictLoader, err := os.ReadFile(dictLoaderLocation) 36 | if err != nil { 37 | return 38 | } 39 | var data SimPackData 40 | _ = yaml.Unmarshal(dictLoader, &data) 41 | engine.OnMessage(rei.OnlyToMeOrToReply).SetBlock(false).Handle(func(ctx *rei.Ctx) { 42 | msg := ctx.Message.Text 43 | var getChartReply []string 44 | if GetTiredToken(ctx) < 4 { 45 | getChartReply = data.Proud[msg] 46 | // if no data 47 | if getChartReply == nil { 48 | getChartReply = data.Kawaii[msg] 49 | if getChartReply == nil { 50 | // no reply 51 | return 52 | } 53 | } 54 | } else { 55 | getChartReply = data.Kawaii[msg] 56 | // if no data 57 | if getChartReply == nil { 58 | getChartReply = data.Proud[msg] 59 | if getChartReply == nil { 60 | // no reply 61 | return 62 | } 63 | } 64 | } 65 | if GetTiredToken(ctx) < 4 { 66 | ctx.SendPlainMessage(true, "咱不想说话 好累awww") 67 | return 68 | } 69 | GetCostTiredToken(ctx) 70 | 71 | getReply := getChartReply[rand.Intn(len(getChartReply))] 72 | getLucyName := []string{"Lucy", "Lucy酱"}[rand.Intn(2)] 73 | getReply = strings.ReplaceAll(getReply, "{segment}", " ") 74 | // get name 75 | getUserID, getUserName := toolchain.GetChatUserInfoID(ctx) 76 | getName := toolchain.LoadUserNickname(strconv.FormatInt(getUserID, 10)) 77 | if getName == "你" { 78 | getName = getUserName 79 | } 80 | getReply = strings.ReplaceAll(getReply, "{name}", getName) 81 | getReply = strings.ReplaceAll(getReply, "{me}", getLucyName) 82 | ctx.SendPlainMessage(true, getReply) 83 | }) 84 | } 85 | 86 | func GetTiredToken(ctx *rei.Ctx) float64 { 87 | getID, _ := toolchain.GetChatUserInfoID(ctx) 88 | return limit.Load(getID).Tokens() 89 | } 90 | 91 | func GetCostTiredToken(ctx *rei.Ctx) bool { 92 | getID, _ := toolchain.GetChatUserInfoID(ctx) 93 | return limit.Load(getID).AcquireN(3) 94 | } 95 | -------------------------------------------------------------------------------- /plugin/slash/main.go: -------------------------------------------------------------------------------- 1 | package slash 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode" 7 | 8 | ctrl "github.com/FloatTech/zbpctrl" 9 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 10 | rei "github.com/fumiama/ReiBot" 11 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 12 | ) 13 | 14 | func init() { 15 | engine := rei.Register("slash", &ctrl.Options[*rei.Ctx]{ 16 | DisableOnDefault: true, 17 | Help: "slash - use / pattern to make it well", 18 | PrivateDataFolder: "slash", 19 | }) 20 | 21 | engine.OnMessage().SetBlock(false).Handle(func(ctx *rei.Ctx) { 22 | getReply := QuoteReply(ctx) 23 | if getReply == "" { 24 | return 25 | } 26 | ctx.Caller.Send(&tgba.MessageConfig{BaseChat: tgba.BaseChat{ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}}, Text: getReply, ParseMode: "MarkdownV2", LinkPreviewOptions: tgba.LinkPreviewOptions{IsDisabled: true}}) 27 | }) 28 | 29 | } 30 | 31 | func QuoteReply(message *rei.Ctx) (replyMsg string) { 32 | if len(message.Message.Text) < 2 { 33 | return "" 34 | } 35 | if !strings.HasPrefix(message.Message.Text, "/") || (isASCII(message.Message.Text[:2]) && !strings.HasPrefix(message.Message.Text, "/$")) { 36 | return "" 37 | } 38 | keywords := strings.SplitN(tgba.EscapeText(tgba.ModeMarkdownV2, strings.Replace(message.Message.Text, "$", "", 1)[1:]), " ", 2) 39 | if len(keywords) == 0 { 40 | return "" 41 | } 42 | senderName := tgba.EscapeText(tgba.ModeMarkdownV2, message.Message.From.FirstName+" "+message.Message.From.LastName) 43 | senderURI := fmt.Sprintf("tg://user?id=%d", message.Message.From.ID) 44 | replyToName := "" 45 | replyToURI := "" 46 | if message.Message.SenderChat != nil { 47 | senderName = tgba.EscapeText(tgba.ModeMarkdownV2, message.Message.SenderChat.Title) 48 | senderURI = fmt.Sprintf("https://t.me/%s", message.Message.SenderChat.UserName) 49 | } 50 | if message.Message.ReplyToMessage != nil { 51 | replyToName = tgba.EscapeText(tgba.ModeMarkdownV2, message.Message.ReplyToMessage.From.FirstName+" "+message.Message.ReplyToMessage.From.LastName) 52 | replyToURI = fmt.Sprintf("tg://user?id=%d", message.Message.ReplyToMessage.From.ID) 53 | 54 | if message.Message.ReplyToMessage.From.IsBot && len(message.Message.ReplyToMessage.Entities) != 0 { 55 | if message.Message.ReplyToMessage.Entities[0].Type == "text_mention" { 56 | replyToName = tgba.EscapeText(tgba.ModeMarkdownV2, message.Message.ReplyToMessage.Entities[0].User.FirstName+" "+message.Message.ReplyToMessage.Entities[0].User.LastName) 57 | replyToURI = fmt.Sprintf("tg://user?id=%d", message.Message.ReplyToMessage.Entities[0].User.ID) 58 | } 59 | } 60 | 61 | if message.Message.ReplyToMessage.SenderChat != nil { 62 | replyToName = tgba.EscapeText(tgba.ModeMarkdownV2, message.Message.ReplyToMessage.SenderChat.Title) 63 | replyToURI = fmt.Sprintf("https://t.me/%s", message.Message.ReplyToMessage.SenderChat.UserName) 64 | } 65 | 66 | } else { 67 | textNoCommand := strings.TrimPrefix(strings.TrimPrefix(keywords[0], "/"), "$") 68 | if text := strings.Split(textNoCommand, "@"); len(text) > 1 { 69 | name := toolchain.GetNickNameFromUsername(text[1]) 70 | if name != "" { 71 | keywords[0] = text[0] 72 | replyToName = tgba.EscapeText(tgba.ModeMarkdownV2, name) 73 | replyToURI = fmt.Sprintf("https://t.me/%s", text[1]) 74 | } 75 | } 76 | if replyToName == "" { 77 | replyToName = "自己" 78 | replyToURI = senderURI 79 | } 80 | } 81 | least := tgba.EscapeText(tgba.ModeMarkdownV2, "~") 82 | if len(keywords) < 2 { 83 | return fmt.Sprintf("[%s](%s) %s了 [%s](%s) %s ", senderName, senderURI, keywords[0], replyToName, replyToURI, least) 84 | } 85 | return fmt.Sprintf("[%s](%s) %s [%s](%s) %s %s", senderName, senderURI, keywords[0], replyToName, replyToURI, keywords[1], least) 86 | 87 | } 88 | 89 | func isASCII(s string) bool { 90 | for _, r := range s { 91 | if r > unicode.MaxASCII { 92 | return false 93 | } 94 | } 95 | return true 96 | } 97 | -------------------------------------------------------------------------------- /plugin/stickers/main.go: -------------------------------------------------------------------------------- 1 | package stickers 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | ctrl "github.com/FloatTech/zbpctrl" 8 | rei "github.com/fumiama/ReiBot" 9 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 10 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 11 | ) 12 | 13 | var ( 14 | limitedManager = rate.NewManager[int64](time.Minute*10, 8) 15 | engine = rei.Register("stickers", &ctrl.Options[*rei.Ctx]{ 16 | DisableOnDefault: true, 17 | Help: "stickers", 18 | }) 19 | ) 20 | 21 | func init() { 22 | engine.OnMessage().SetBlock(false).Handle(func(ctx *rei.Ctx) { 23 | if rand.Intn(10) > 1 { 24 | return 25 | } 26 | if !limitedManager.Load(ctx.Message.Chat.ID).Acquire() { 27 | return 28 | } 29 | if ctx.Message.Sticker != nil { 30 | getStickerPack, err := ctx.Caller.GetStickerSet(tgba.GetStickerSetConfig{Name: ctx.Message.Sticker.SetName}) 31 | if err != nil { 32 | return 33 | } 34 | ctx.Caller.Request(tgba.StickerConfig{BaseFile: tgba.BaseFile{BaseChat: tgba.BaseChat{ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}}, File: tgba.FileID(getStickerPack.Stickers[rand.Intn(len(getStickerPack.Stickers))].FileID)}}) 35 | } 36 | 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /plugin/tools/main.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "github.com/MoYoez/Lucy_reibot/utils/bilibili" 6 | "math" 7 | "math/rand" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/FloatTech/floatbox/web" 13 | ctrl "github.com/FloatTech/zbpctrl" 14 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 15 | CoreFactory "github.com/MoYoez/Lucy_reibot/utils/userpackage" 16 | rei "github.com/fumiama/ReiBot" 17 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 18 | "github.com/shirou/gopsutil/cpu" 19 | "github.com/shirou/gopsutil/disk" 20 | "github.com/shirou/gopsutil/mem" 21 | ) 22 | 23 | var engine = rei.Register("tools", &ctrl.Options[*rei.Ctx]{ 24 | DisableOnDefault: false, 25 | Help: "tools for Lucy", 26 | }) 27 | 28 | func init() { 29 | engine.OnMessageCommand("leave", rei.SuperUserPermission).SetBlock(true).Handle(func(ctx *rei.Ctx) { 30 | arg := strings.TrimSpace(ctx.State["args"].(string)) 31 | var gid int64 32 | var err error 33 | if arg != "" { 34 | gid, err = strconv.ParseInt(arg, 10, 64) 35 | if err != nil { 36 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 37 | return 38 | } 39 | } else { 40 | gid = ctx.Message.Chat.ID 41 | } 42 | _, _ = ctx.Caller.Send(&tgba.LeaveChatConfig{ChatConfig: tgba.ChatConfig{ChatID: gid}}) 43 | }) 44 | engine.OnMessageCommand("status").SetBlock(true).Handle(func(ctx *rei.Ctx) { 45 | ctx.SendPlainMessage(false, "* Hosted On Azure JP Cloud.\n", 46 | "* CPU Usage: ", cpuPercent(), "%\n", 47 | "* RAM Usage: ", memPercent(), "%\n", 48 | "* DiskInfo Usage Check: ", diskPercent(), "\n", 49 | " Lucyは、高性能ですから!") 50 | }) 51 | engine.OnMessageCommand("dataupdate").SetBlock(true).Handle(func(ctx *rei.Ctx) { 52 | if !toolchain.GetTheTargetIsNormalUser(ctx) { 53 | return 54 | } 55 | getUserName := ctx.Message.From.UserName 56 | getUserID := ctx.Message.From.ID 57 | newUserName := CoreFactory.GetUserSampleUserinfobyid(getUserID).UserName 58 | if newUserName == getUserName { 59 | ctx.SendPlainMessage(true, "不需要更新的~用户名为最新w") 60 | return 61 | } 62 | CoreFactory.StoreUserDataBase(getUserID, newUserName) 63 | }) 64 | engine.OnMessage().SetBlock(false).Handle(func(ctx *rei.Ctx) { 65 | toolchain.FastSaveUserStatus(ctx) 66 | 67 | }) 68 | engine.OnMessage().SetBlock(false).Handle(func(ctx *rei.Ctx) { 69 | toolchain.FastSaveUserGroupList(ctx) // error 70 | }) 71 | engine.OnMessageCommand("runpanic", rei.SuperUserPermission).Handle(func(ctx *rei.Ctx) { 72 | ctx.SendPlainMessage(true, "run panic , check debug.") 73 | panic("Test Value ERR") 74 | }) 75 | 76 | engine.OnMessageCommand("qpic").SetBlock(true).Handle(func(ctx *rei.Ctx) { 77 | getLength, List := rei.SplitCommandTo(ctx.Message.Text, 2) 78 | if getLength == 2 { 79 | getDataRaw, err := web.GetData("https://gchat.qpic.cn/gchatpic_new/0/0-0-" + List[1] + "/0") 80 | if err != nil { 81 | ctx.SendPlainMessage(true, "获取对应图片错误,或许是图片已过期") 82 | return 83 | } 84 | ctx.SendPhoto(tgba.FileBytes{Name: List[1], Bytes: getDataRaw}, true, "Link: "+"https://gchat.qpic.cn/gchatpic_new/0/0-0-"+List[1]+"/0") 85 | } else { 86 | ctx.SendPlainMessage(true, "缺少参数/ 应当是 /qpic ") 87 | } 88 | }) 89 | engine.OnMessageRegex(`(?:是不是|有没有|好不好|尊嘟假嘟)`).SetBlock(true).Handle(func(ctx *rei.Ctx) { 90 | rawRegexPattern := ctx.State["regex_matched"].([]string)[0] 91 | randPattern := rand.Intn(2) 92 | switch { 93 | case rawRegexPattern == "是不是": 94 | randPattern += 0 95 | case rawRegexPattern == "有没有": 96 | randPattern += 2 97 | case rawRegexPattern == "好不好": 98 | randPattern += 4 99 | case rawRegexPattern == "尊嘟假嘟": 100 | randPattern += 6 101 | } 102 | ctx.SendPlainMessage(true, []string{"是", "不是", "有", "没有", "好", "不好", "尊嘟", "假嘟"}[randPattern]) 103 | }) 104 | 105 | engine.OnMessageRegex(`((b23|acg).tv|bili2233.cn)/[0-9a-zA-Z]+`).SetBlock(true).Handle(func(ctx *rei.Ctx) { 106 | rawRegexPattern := ctx.State["regex_matched"].([]string)[0] 107 | getLink := bilibili.BilibiliFixedLink(rawRegexPattern) 108 | ctx.Send(true, &tgba.MessageConfig{ 109 | BaseChat: tgba.BaseChat{ 110 | ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}, 111 | }, 112 | Text: "https://" + getLink, 113 | LinkPreviewOptions: tgba.LinkPreviewOptions{ 114 | IsDisabled: false, 115 | URL: "https://" + getLink, 116 | PreferSmallMedia: false, 117 | PreferLargeMedia: true, 118 | ShowAboveText: true, 119 | }, 120 | }) 121 | }) 122 | 123 | engine.OnMessageCommand("title").SetBlock(true).Handle(func(ctx *rei.Ctx) { 124 | getCutterLength, cutterTypeList := rei.SplitCommandTo(ctx.Message.Text, 2) 125 | // Check if Lucy has Permission to modify 126 | // get user permissions. 127 | _, err := ctx.Caller.Request(tgba.PromoteChatMemberConfig{ 128 | ChatMemberConfig: tgba.ChatMemberConfig{UserID: ctx.Message.From.ID, ChatConfig: tgba.ChatConfig{ 129 | ChatID: ctx.Message.Chat.ID, 130 | }}, 131 | CanManageChat: true, 132 | }) 133 | if err != nil { 134 | ctx.SendPlainMessage(true, " 发生了一点错误: 将对方提升管理员失效 , Err: ", err) 135 | return 136 | } 137 | 138 | if getCutterLength == 1 { 139 | getendpoint, errs := ctx.Caller.Request(tgba.SetChatAdministratorCustomTitle{ 140 | ChatMemberConfig: tgba.ChatMemberConfig{ 141 | ChatConfig: tgba.ChatConfig{ 142 | ChatID: ctx.Message.Chat.ID, 143 | }, 144 | UserID: ctx.Message.From.ID, 145 | }, 146 | CustomTitle: ctx.Message.From.UserName, 147 | }) 148 | if getendpoint.Ok { 149 | ctx.SendPlainMessage(true, "是个不错的头衔呢w~") 150 | } else { 151 | ctx.SendPlainMessage(true, "貌似出错了( | ", errs) 152 | } 153 | return 154 | } 155 | 156 | getendpoint, err := ctx.Caller.Request(tgba.SetChatAdministratorCustomTitle{ 157 | ChatMemberConfig: tgba.ChatMemberConfig{ 158 | ChatConfig: tgba.ChatConfig{ 159 | ChatID: ctx.Message.Chat.ID, 160 | }, 161 | UserID: ctx.Message.From.ID, 162 | }, 163 | CustomTitle: cutterTypeList[1], 164 | }) 165 | 166 | if getendpoint.Ok { 167 | ctx.SendPlainMessage(true, "返回正常, 帮你贴上去了w 现在的头衔是 ", cutterTypeList[1], " 了") 168 | } else { 169 | ctx.SendPlainMessage(true, "貌似出错了( | ", err) 170 | } 171 | 172 | }) 173 | 174 | } 175 | 176 | func cpuPercent() float64 { 177 | percent, err := cpu.Percent(time.Second, false) 178 | if err != nil { 179 | return -1 180 | } 181 | return math.Round(percent[0]) 182 | } 183 | 184 | func memPercent() float64 { 185 | memInfo, err := mem.VirtualMemory() 186 | if err != nil { 187 | return -1 188 | } 189 | return math.Round(memInfo.UsedPercent) 190 | } 191 | 192 | func diskPercent() string { 193 | parts, err := disk.Partitions(true) 194 | if err != nil { 195 | return err.Error() 196 | } 197 | msg := "" 198 | for _, p := range parts { 199 | diskInfo, err := disk.Usage(p.Mountpoint) 200 | if err != nil { 201 | msg += "\n - " + err.Error() 202 | continue 203 | } 204 | pc := uint(math.Round(diskInfo.UsedPercent)) 205 | if pc > 0 { 206 | msg += fmt.Sprintf("\n - %s(%dM) %d%%", p.Mountpoint, diskInfo.Total/1024/1024, pc) 207 | } 208 | } 209 | return msg 210 | } 211 | -------------------------------------------------------------------------------- /plugin/tracemoe/moe.go: -------------------------------------------------------------------------------- 1 | // Package tracemoe 搜番 2 | package tracemoe 3 | 4 | import ( 5 | "fmt" 6 | "strconv" 7 | 8 | rei "github.com/fumiama/ReiBot" 9 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 10 | 11 | trmoe "github.com/fumiama/gotracemoe" 12 | 13 | "github.com/FloatTech/floatbox/binary" 14 | ctrl "github.com/FloatTech/zbpctrl" 15 | ) 16 | 17 | var ( 18 | moe = trmoe.NewMoe("") 19 | ) 20 | 21 | func init() { // 插件主体 22 | engine := rei.Register("tracemoe", &ctrl.Options[*rei.Ctx]{ 23 | DisableOnDefault: false, 24 | Help: "tracemoe\n- 搜番 | 搜索番剧[图片]", 25 | }) 26 | // 以图搜图 27 | engine.OnMessageCommand("tracemoe", rei.MustProvidePhoto("请发送一张图片", "获取图片失败!")).SetBlock(true). 28 | Handle(func(ctx *rei.Ctx) { 29 | // 开始搜索图片 30 | _, _ = ctx.SendPlainMessage(false, "少女祈祷中...") 31 | ps := ctx.State["photos"].([]tgba.PhotoSize) 32 | pic := ps[len(ps)-1] 33 | picu, err := ctx.Caller.GetFileDirectURL(pic.FileID) 34 | if err != nil { 35 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 36 | return 37 | } 38 | if result, err := moe.Search(picu, true, true); err != nil { 39 | _, _ = ctx.SendPlainMessage(false, "ERROR: ", err) 40 | } else if len(result.Result) > 0 { 41 | r := result.Result[0] 42 | hint := "我有把握是这个!" 43 | if r.Similarity < 80 { 44 | hint = "大概是这个?" 45 | } 46 | mf := int(r.From / 60) 47 | mt := int(r.To / 60) 48 | sf := r.From - float32(mf*60) 49 | st := r.To - float32(mt*60) 50 | _, _ = ctx.Caller.Send(&tgba.PhotoConfig{ 51 | BaseFile: tgba.BaseFile{ 52 | BaseChat: tgba.BaseChat{ 53 | ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}, 54 | ReplyParameters: tgba.ReplyParameters{MessageID: ctx.Event.Value.(*tgba.Message).MessageID}, 55 | }, 56 | File: tgba.FileURL(r.Image), 57 | }, 58 | Caption: binary.BytesToString(binary.NewWriterF(func(m *binary.Writer) { 59 | m.WriteString(hint) 60 | _ = m.WriteByte('\n') 61 | m.WriteString("番剧名: ") 62 | m.WriteString(r.Anilist.Title.Native) 63 | _ = m.WriteByte('\n') 64 | m.WriteString("话数: ") 65 | m.WriteString(strconv.Itoa(r.Episode)) 66 | _ = m.WriteByte('\n') 67 | m.WriteString(fmt.Sprint("时间:", mf, ":", sf, "-", mt, ":", st)) 68 | })), 69 | }) 70 | } 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /plugin/what2eat/main.go: -------------------------------------------------------------------------------- 1 | // Package what2eat Waht 2 Eat Package for group. 2 | package what2eat 3 | 4 | import ( 5 | ctrl "github.com/FloatTech/zbpctrl" 6 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 7 | "github.com/MoYoez/Lucy_reibot/utils/userlist" 8 | rei "github.com/fumiama/ReiBot" 9 | "strconv" 10 | ) 11 | 12 | var engine = rei.Register("what2eat", &ctrl.Options[*rei.Ctx]{ 13 | DisableOnDefault: false, 14 | Help: "今天吃什么群友", 15 | PrivateDataFolder: "what2eat", 16 | }) 17 | 18 | func init() { 19 | engine.OnMessageFullMatch("今天吃什么群友").SetBlock(true).Handle(func(ctx *rei.Ctx) { 20 | getInt := GetUserListAndChooseOne(ctx) 21 | if getInt != 0 && toolchain.GetUserNickNameByIDInGroup(ctx, getInt) != "" { 22 | ctx.SendPlainMessage(true, "决定了, 今天吃"+toolchain.GetUserNickNameByIDInGroup(ctx, getInt)) 23 | } else { 24 | ctx.SendPlainMessage(true, "Lucy 正在确认群里的人数, 过段时间试试吧w") 25 | } 26 | }) 27 | } 28 | 29 | // GetUserListAndChooseOne choose people. 30 | func GetUserListAndChooseOne(ctx *rei.Ctx) int64 { 31 | toint64, _ := strconv.ParseInt(userlist.PickUserOnGroup(strconv.FormatInt(ctx.Message.Chat.ID, 10)), 10, 64) 32 | if !toolchain.CheckIfthisUserInThisGroup(toint64, ctx) { 33 | userlist.RemoveUserOnList(strconv.FormatInt(toint64, 10), strconv.FormatInt(ctx.Message.Chat.ID, 10)) 34 | TrackerCallFuncGetUserListAndChooseOne(ctx) 35 | } 36 | return toint64 37 | } 38 | 39 | func TrackerCallFuncGetUserListAndChooseOne(ctx *rei.Ctx) int64 { 40 | var toint64 int64 41 | for i := 0; i < 3; i++ { 42 | toint64, _ = strconv.ParseInt(userlist.PickUserOnGroup(strconv.FormatInt(ctx.Message.Chat.ID, 10)), 10, 64) 43 | if toolchain.CheckIfthisUserInThisGroup(toint64, ctx) { 44 | break 45 | } 46 | } 47 | return toint64 48 | } 49 | -------------------------------------------------------------------------------- /plugin/wife/sqlite.go: -------------------------------------------------------------------------------- 1 | package wife 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "time" 7 | 8 | sql "github.com/FloatTech/sqlite" 9 | ) 10 | 11 | // BlackListStruct (Example: blacklist_1292581422) 12 | type BlackListStruct struct { 13 | BlackList int64 `db:"blacklist"` 14 | } 15 | 16 | // DisabledListStruct (Example: disabled_1292581422) 17 | type DisabledListStruct struct { 18 | DisabledList int64 `db:"disabledlist"` 19 | } 20 | 21 | // OrderListStruct (Example: orderlist_1145141919180) 22 | type OrderListStruct struct { 23 | OrderPerson int64 `db:"order"` 24 | TargerPerson int64 `db:"target"` 25 | Time string `db:"time"` 26 | } 27 | 28 | // GlobalDataStruct (Example: grouplist_1292581422 ) 29 | type GlobalDataStruct struct { 30 | PairKey string `db:"pairkey"` 31 | UserID int64 `db:"userid"` 32 | TargetID int64 `db:"targetid"` 33 | Time string `db:"time"` 34 | } 35 | 36 | // PairKeyStruct pairkey is used to check the list. 37 | type PairKeyStruct struct { 38 | PairKey string `db:"pairkey"` 39 | StatusID int64 `db:"statusid"` 40 | } 41 | 42 | // ChosenList User Refer To Theirs username and save it by using the quick additon (table: chosenList) be careful that it's global. 43 | type ChosenList struct { 44 | IdentifyID string `db:"id"` 45 | UserID int64 `db:"userid"` 46 | TargetName string `db:"targetname"` 47 | TargetID int64 `db:"targetid"` 48 | } 49 | 50 | var ( 51 | marryList = &sql.Sqlite{} 52 | marryLocker = sync.Mutex{} 53 | ) 54 | 55 | func init() { 56 | // init UserDataBase 57 | marryList.DBPath = engine.DataFolder() + "data.db" 58 | err := marryList.Open(time.Hour * 24) 59 | if err != nil { 60 | panic(err) 61 | } 62 | } 63 | 64 | /* 65 | SQL HANDLER 66 | */ 67 | 68 | // FormatInsertUserGlobalMarryList Format Insert 69 | func FormatInsertUserGlobalMarryList(UserID int64, targetID int64, PairKeyRaw string) *GlobalDataStruct { 70 | return &GlobalDataStruct{PairKey: PairKeyRaw, UserID: UserID, TargetID: targetID, Time: strconv.FormatInt(time.Now().Unix(), 10)} 71 | } 72 | 73 | // FormatPairKey Format PairKey 74 | func FormatPairKey(PairKeyRaw string, statusID int64) *PairKeyStruct { 75 | return &PairKeyStruct{PairKey: PairKeyRaw, StatusID: statusID} 76 | } 77 | 78 | // FormatBlackList Format BlackList 79 | func FormatBlackList(blockID int64) *BlackListStruct { 80 | return &BlackListStruct{BlackList: blockID} 81 | } 82 | 83 | // FormatDisabledList Format DisabledList 84 | func FormatDisabledList(disabledID int64) *DisabledListStruct { 85 | return &DisabledListStruct{DisabledList: disabledID} 86 | } 87 | 88 | // FormatOrderList Format OrderList 89 | func FormatOrderList(orderPersonal int64, targetPersonal int64, time string) *OrderListStruct { 90 | return &OrderListStruct{OrderPerson: orderPersonal, TargerPerson: targetPersonal, Time: time} 91 | } 92 | 93 | // InsertUserGlobalMarryList no check,use it with caution // Only insert and do nothing. 94 | func InsertUserGlobalMarryList(db *sql.Sqlite, groupID int64, UserID int64, targetID int64, StatusID int64, PairKeyRaw string) error { 95 | marryLocker.Lock() 96 | defer marryLocker.Unlock() 97 | formatList := FormatInsertUserGlobalMarryList(UserID, targetID, PairKeyRaw) 98 | err := db.Insert("grouplist_"+strconv.FormatInt(groupID, 10), formatList) 99 | if err != nil { 100 | _ = db.Create("grouplist_"+strconv.FormatInt(groupID, 10), &GlobalDataStruct{}) 101 | _ = db.Insert("grouplist_"+strconv.FormatInt(groupID, 10), formatList) 102 | } 103 | // throw key 104 | err = db.Insert("pairkey_"+strconv.FormatInt(groupID, 10), FormatPairKey(PairKeyRaw, StatusID)) 105 | if err != nil { 106 | _ = db.Create("pairkey_"+strconv.FormatInt(groupID, 10), &PairKeyStruct{}) 107 | _ = db.Insert("pairkey_"+strconv.FormatInt(groupID, 10), FormatPairKey(PairKeyRaw, StatusID)) 108 | } 109 | return err 110 | } 111 | 112 | // RemoveUserGlobalMarryList just remove it,it will persist the key and change it to Type4. 113 | func RemoveUserGlobalMarryList(db *sql.Sqlite, pairKey string, groupID int64) bool { 114 | marryLocker.Lock() 115 | defer marryLocker.Unlock() 116 | // first find the key list. 117 | var pairKeyNeed PairKeyStruct 118 | err := db.Find("pairkey_"+strconv.FormatInt(groupID, 10), &pairKeyNeed, "where pairkey is '"+pairKey+"'") 119 | if err != nil { 120 | // cannnot find, don't need to remove. 121 | return false 122 | } 123 | // if found. 124 | getThisKey := pairKeyNeed.PairKey 125 | err = db.Del("pairkey_"+strconv.FormatInt(groupID, 10), "where pairkey is '"+getThisKey+"'") 126 | if err != nil { 127 | // cannnot find, don't need to remove. 128 | return false 129 | } 130 | err = db.Del("grouplist_"+strconv.FormatInt(groupID, 10), "where pairkey is '"+getThisKey+"'") 131 | if err != nil { 132 | // cannnot find, don't need to remove. 133 | return false 134 | } 135 | err = db.Insert("pairkey_"+strconv.FormatInt(groupID, 10), FormatPairKey(pairKey, 4)) 136 | return err == nil 137 | // store? || persist this key and check the next Time. 138 | } 139 | 140 | // CustomRemoveUserGlobalMarryList just remove it,it will persist the key (referKeyStatus) 141 | func CustomRemoveUserGlobalMarryList(db *sql.Sqlite, pairKey string, groupID int64, statusID int64) bool { 142 | marryLocker.Lock() 143 | defer marryLocker.Unlock() 144 | // first find the key list. 145 | var pairKeyNeed PairKeyStruct 146 | err := db.Find("pairkey_"+strconv.FormatInt(groupID, 10), &pairKeyNeed, "where pairkey is '"+pairKey+"'") 147 | if err != nil { 148 | // cannnot find, don't need to remove. 149 | return false 150 | } 151 | // if found. 152 | getThisKey := pairKeyNeed.PairKey 153 | _ = db.Del("pairkey_"+strconv.FormatInt(groupID, 10), "where pairkey is '"+getThisKey+"'") 154 | _ = db.Del("grouplist_"+strconv.FormatInt(groupID, 10), "where pairkey is '"+getThisKey+"'") 155 | // store? || persist this key and check the next Time. 156 | _ = db.Insert("pairkey_"+strconv.FormatInt(groupID, 10), FormatPairKey(pairKey, statusID)) 157 | return err == nil 158 | } 159 | 160 | /* 161 | // CheckThisKeyStatus check this key status. 162 | func CheckThisKeyStatus(db *sql.Sqlite, pairKey string, groupID int64) int64 { 163 | marryLocker.Lock() 164 | defer marryLocker.Unlock() 165 | // first find the key list. 166 | var pairKeyNeed PairKeyStruct 167 | err := db.Find("pairkey_"+strconv.FormatInt(groupID, 10), &pairKeyNeed, "where pairkey is "+pairKey) 168 | if err != nil { 169 | // cannnot find, don't need to remove. 170 | return -1 171 | } 172 | return pairKeyNeed.StatusID 173 | } 174 | 175 | */ 176 | 177 | // AddBlackList add blacklist 178 | func AddBlackList(db *sql.Sqlite, userID int64, targetID int64) error { 179 | marryLocker.Lock() 180 | defer marryLocker.Unlock() 181 | var blackListNeed BlackListStruct 182 | err := db.Find("blacklist_"+strconv.FormatInt(userID, 10), &blackListNeed, "where blacklist is '"+strconv.FormatInt(targetID, 10)+"'") 183 | if err != nil { 184 | // add it, not sure then init this and add. 185 | _ = db.Create("blacklist_"+strconv.FormatInt(userID, 10), &BlackListStruct{}) 186 | err = db.Insert("blacklist_"+strconv.FormatInt(userID, 10), FormatBlackList(targetID)) 187 | return err 188 | } 189 | // find this so don't do it. 190 | return err 191 | } 192 | 193 | // DeleteBlackList delete blacklist. 194 | func DeleteBlackList(db *sql.Sqlite, userID int64, targetID int64) error { 195 | marryLocker.Lock() 196 | defer marryLocker.Unlock() 197 | var blackListNeed BlackListStruct 198 | err := db.Find("blacklist_"+strconv.FormatInt(userID, 10), &blackListNeed, "where blacklist is '"+strconv.FormatInt(targetID, 10)+"'") 199 | if err != nil { 200 | // not init or didn't find. 201 | return err 202 | } 203 | _ = db.Del("blacklist_"+strconv.FormatInt(userID, 10), "where blacklist is '"+strconv.FormatInt(targetID, 10)+"'") 204 | return err 205 | } 206 | 207 | // CheckTheBlackListIsExistedToThisPerson check the person is blocked. 208 | func CheckTheBlackListIsExistedToThisPerson(db *sql.Sqlite, userID int64, targetID int64) bool { 209 | marryLocker.Lock() 210 | defer marryLocker.Unlock() 211 | var blackListNeed BlackListStruct 212 | err := db.Find("blacklist_"+strconv.FormatInt(userID, 10), &blackListNeed, "where blacklist is '"+strconv.FormatInt(targetID, 10)+"'") 213 | if err != nil { 214 | return true 215 | } 216 | if blackListNeed.BlackList == targetID { 217 | return false 218 | } 219 | return true 220 | } 221 | 222 | // AddDisabledList add disabledList 223 | func AddDisabledList(db *sql.Sqlite, userID int64, groupID int64) error { 224 | marryLocker.Lock() 225 | defer marryLocker.Unlock() 226 | var disabledListNeed DisabledListStruct 227 | err := db.Find("disabled_"+strconv.FormatInt(userID, 10), &disabledListNeed, "where disabledlist is '"+strconv.FormatInt(groupID, 10)+"'") 228 | if err != nil { 229 | // add it, not sure then init this and add. 230 | _ = db.Create("disabled_"+strconv.FormatInt(userID, 10), &DisabledListStruct{}) 231 | _ = db.Insert("disabled_"+strconv.FormatInt(userID, 10), FormatDisabledList(groupID)) 232 | return err 233 | } 234 | // find this so don't do it. 235 | return err 236 | } 237 | 238 | // DeleteDisabledList delete disabledlist. 239 | func DeleteDisabledList(db *sql.Sqlite, userID int64, groupID int64) error { 240 | marryLocker.Lock() 241 | defer marryLocker.Unlock() 242 | var disabledListNeed DisabledListStruct 243 | err := db.Find("disabled_"+strconv.FormatInt(userID, 10), &disabledListNeed, "where disabledlist is '"+strconv.FormatInt(groupID, 10)+"'") 244 | if err != nil { 245 | // not init or didn't find. 246 | return err 247 | } 248 | _ = db.Del("disabled_"+strconv.FormatInt(userID, 10), "where disabledlist is '"+strconv.FormatInt(groupID, 10)+"'") 249 | return err 250 | } 251 | 252 | // CheckDisabledListIsExistedInThisGroup Check this group. 253 | func CheckDisabledListIsExistedInThisGroup(db *sql.Sqlite, userID int64, groupID int64) bool { 254 | marryLocker.Lock() 255 | defer marryLocker.Unlock() 256 | var disabledListNeed DisabledListStruct 257 | err := db.Find("disabled_"+strconv.FormatInt(userID, 10), &disabledListNeed, "where disabledlist is '"+strconv.FormatInt(groupID, 10)+"'") 258 | if err != nil { 259 | // not init or didn't find. 260 | return true 261 | } 262 | if disabledListNeed.DisabledList == groupID { 263 | return false 264 | } 265 | return true 266 | } 267 | 268 | // AddOrderToList add order. 269 | func AddOrderToList(db *sql.Sqlite, userID int64, targetID int64, time string, groupID int64) error { 270 | marryLocker.Lock() 271 | defer marryLocker.Unlock() 272 | var addOrderListNeed OrderListStruct 273 | err := db.Find("orderlist_"+strconv.FormatInt(groupID, 10), &addOrderListNeed, "where order is '"+strconv.FormatInt(userID, 10)+"'") 274 | if err != nil { 275 | // create and insert. 276 | _ = db.Create("orderlist_"+strconv.FormatInt(groupID, 10), &OrderListStruct{}) 277 | _ = db.Insert("orderlist_"+strconv.FormatInt(groupID, 10), FormatOrderList(userID, targetID, time)) 278 | return err 279 | } 280 | // find it. 281 | return err 282 | } 283 | 284 | // RemoveOrderToList remove it. 285 | func RemoveOrderToList(db *sql.Sqlite, userID int64, groupID int64) error { 286 | marryLocker.Lock() 287 | defer marryLocker.Unlock() 288 | var addOrderListNeed OrderListStruct 289 | err := db.Find("orderlist_"+strconv.FormatInt(groupID, 10), &addOrderListNeed, "where order is '"+strconv.FormatInt(userID, 10)+"'") 290 | if err != nil { 291 | return err 292 | } 293 | err = db.Del("orderlist_"+strconv.FormatInt(groupID, 10), "where order is '"+strconv.FormatInt(userID, 10)+"'") 294 | return err 295 | } 296 | 297 | /* 298 | // CheckThisOrderList getList 299 | func CheckThisOrderList(db *sql.Sqlite, userID int64, groupID int64) (OrderUser int64, TargetUSer int64, time string) { 300 | marryLocker.Lock() 301 | defer marryLocker.Unlock() 302 | var addOrderListNeed OrderListStruct 303 | err := db.Find("orderlist_"+strconv.FormatInt(groupID, 10), &addOrderListNeed, "where order is "+strconv.FormatInt(userID, 10)) 304 | if err != nil { 305 | return -1, -1, "" 306 | } 307 | OrderUser = addOrderListNeed.OrderPerson 308 | TargetUSer = addOrderListNeed.OrderPerson 309 | time = addOrderListNeed.Time 310 | return OrderUser, TargetUSer, time 311 | } 312 | 313 | */ 314 | 315 | // GetTheGroupList Get this group. 316 | func GetTheGroupList(gid int64) (list [][2]string, num int) { 317 | marryLocker.Lock() 318 | defer marryLocker.Unlock() 319 | gidStr := strconv.FormatInt(gid, 10) 320 | getNum, _ := marryList.Count("grouplist_" + gidStr) 321 | if getNum == 0 { 322 | return nil, 0 323 | } 324 | var info GlobalDataStruct 325 | list = make([][2]string, 0, num) 326 | _ = marryList.FindFor("grouplist_"+gidStr, &info, "GROUP BY userid", func() error { 327 | getInfoSlice := [2]string{ 328 | strconv.FormatInt(info.UserID, 10), 329 | strconv.FormatInt(info.TargetID, 10), 330 | } 331 | list = append(list, getInfoSlice) 332 | return nil 333 | }) 334 | return list, getNum 335 | } 336 | -------------------------------------------------------------------------------- /plugin/wife/struct.go: -------------------------------------------------------------------------------- 1 | package wife 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "io" 8 | "math/rand" 9 | "net/http" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | fcext "github.com/FloatTech/floatbox/ctxext" 15 | sql "github.com/FloatTech/sqlite" 16 | "github.com/MoYoez/Lucy_reibot/utils/toolchain" 17 | "github.com/MoYoez/Lucy_reibot/utils/userlist" 18 | rei "github.com/fumiama/ReiBot" 19 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 20 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 21 | ) 22 | 23 | var GlobalTimeManager = rate.NewManager[int64](time.Hour*12, 6) 24 | var LeaveTimeManager = rate.NewManager[int64](time.Hour*12, 4) 25 | 26 | func init() { 27 | timer := time.NewTimer(time.Until(getNextExecutionTime())) 28 | go func() { 29 | for range timer.C { 30 | ResetToInitalizeMode() 31 | timer.Reset(time.Until(getNextExecutionTime())) 32 | } 33 | }() 34 | } 35 | 36 | // GetUserListAndChooseOne choose people. 37 | func GetUserListAndChooseOne(ctx *rei.Ctx) int64 { 38 | toint64, _ := strconv.ParseInt(userlist.PickUserOnGroup(strconv.FormatInt(ctx.Message.Chat.ID, 10)), 10, 64) 39 | if !toolchain.CheckIfthisUserInThisGroup(toint64, ctx) { 40 | userlist.RemoveUserOnList(strconv.FormatInt(toint64, 10), strconv.FormatInt(ctx.Message.Chat.ID, 10)) 41 | TrackerCallFuncGetUserListAndChooseOne(ctx) 42 | } 43 | return toint64 44 | } 45 | 46 | func TrackerCallFuncGetUserListAndChooseOne(ctx *rei.Ctx) int64 { 47 | var toint64 int64 48 | for i := 0; i < 3; i++ { 49 | toint64, _ = strconv.ParseInt(userlist.PickUserOnGroup(strconv.FormatInt(ctx.Message.Chat.ID, 10)), 10, 64) 50 | if toolchain.CheckIfthisUserInThisGroup(toint64, ctx) { 51 | break 52 | } 53 | } 54 | return toint64 55 | } 56 | 57 | // GlobalCDModelCost cd timeManager 58 | func GlobalCDModelCost(ctx *rei.Ctx) bool { 59 | // 12h 6times. 60 | UserKeyTag := ctx.Message.From.ID + ctx.Message.Chat.ID 61 | 62 | return GlobalTimeManager.Load(UserKeyTag).Acquire() 63 | } 64 | 65 | // GlobalCDModelCostLeastReply Get the existed Token. 66 | func GlobalCDModelCostLeastReply(ctx *rei.Ctx) int { 67 | UserKeyTag := ctx.Message.From.ID + ctx.Message.Chat.ID 68 | return int(GlobalTimeManager.Load(UserKeyTag).Tokens()) 69 | } 70 | 71 | // LeaveCDModelCost cd timeManager 72 | func LeaveCDModelCost(ctx *rei.Ctx) bool { 73 | // 12h 6times. 74 | UserKeyTag := ctx.Message.From.ID + ctx.Message.Chat.ID 75 | return LeaveTimeManager.Load(UserKeyTag).Acquire() 76 | } 77 | 78 | // LeaveCDModelCostLeastReply Get the existed Token. 79 | func LeaveCDModelCostLeastReply(ctx *rei.Ctx) int { 80 | UserKeyTag := ctx.Message.From.ID + ctx.Message.Chat.ID 81 | return int(LeaveTimeManager.Load(UserKeyTag).Tokens()) 82 | } 83 | 84 | // CheckTheUserIsTargetOrUser check the status. 85 | func CheckTheUserIsTargetOrUser(db *sql.Sqlite, ctx *rei.Ctx, userID int64) (statuscode int64, targetID int64) { 86 | // -1 --> not found | 0 --> Target | 1 --> User 87 | marryLocker.Lock() 88 | defer marryLocker.Unlock() 89 | groupID := ctx.Message.Chat.ID 90 | var globalDataStructList GlobalDataStruct 91 | err := db.Find("grouplist_"+strconv.FormatInt(groupID, 10), &globalDataStructList, "where userid is '"+strconv.FormatInt(userID, 10)+"'") 92 | if err != nil { 93 | err = db.Find("grouplist_"+strconv.FormatInt(groupID, 10), &globalDataStructList, "where targetid is '"+strconv.FormatInt(userID, 10)+"'") 94 | if err != nil { 95 | return -1, -1 96 | } 97 | return 0, globalDataStructList.UserID 98 | } 99 | if globalDataStructList.TargetID == globalDataStructList.UserID { 100 | return 10, globalDataStructList.UserID 101 | } 102 | return 1, globalDataStructList.TargetID 103 | } 104 | 105 | // CheckTheUserIsInBlackListOrGroupList Check this user is in list? 106 | func CheckTheUserIsInBlackListOrGroupList(userID int64, targetID int64, groupID int64) bool { 107 | /* -1 --> both is null 108 | 1 --> the user random the person that he don't want (Other is in his blocklist) | or in blocklist(others) 109 | */ 110 | // first check the blocklist 111 | if !CheckTheBlackListIsExistedToThisPerson(marryList, userID, targetID) || !CheckTheBlackListIsExistedToThisPerson(marryList, targetID, userID) { 112 | return true 113 | } 114 | // check the target is disabled this group 115 | if !CheckDisabledListIsExistedInThisGroup(marryList, userID, groupID) { 116 | return true 117 | } 118 | return false 119 | } 120 | 121 | // GetSomeRanDomChoiceProps get some props chances to make it random. 122 | func GetSomeRanDomChoiceProps(ctx *rei.Ctx) int64 { 123 | // get Random numbers. 124 | randNum := rand.Intn(90) + fcext.RandSenderPerDayN(ctx.Message.From.ID, 30) 125 | if randNum > 110 { 126 | getOtherRand := rand.Intn(9) 127 | switch { 128 | case getOtherRand < 3: 129 | return 2 130 | case getOtherRand > 3 && getOtherRand < 6: 131 | return 3 132 | case getOtherRand > 6: 133 | return 6 134 | } 135 | } 136 | return 1 137 | } 138 | 139 | // ReplyMeantMode format the reply and clear. 140 | func ReplyMeantMode(header string, referTarget int64, statusCodeToPerson int64, ctx *rei.Ctx) { 141 | msg := header 142 | var replyTarget string 143 | if statusCodeToPerson == 1 { 144 | replyTarget = "老婆" 145 | } else { 146 | replyTarget = "老公" 147 | } 148 | aheader := msg + "\n今天你的群" + replyTarget + "是\n" 149 | formatAvatar := GenerateUserImageLink(ctx, referTarget) 150 | userNickName := toolchain.GetUserNickNameByIDInGroup(ctx, referTarget) 151 | senderURI := fmt.Sprintf("tg://user?id=%d", referTarget) 152 | userNickName = tgba.EscapeText(tgba.ModeMarkdownV2, userNickName) 153 | aheader = aheader + "[" + userNickName + "](" + senderURI + ")" + "哦w~" 154 | datas, err := http.Get(formatAvatar) 155 | // avatar 156 | // aheader+formatReply 157 | if err != nil { 158 | ctx.Caller.Send(&tgba.MessageConfig{ 159 | BaseChat: tgba.BaseChat{ 160 | ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}, 161 | }, 162 | Text: aheader, 163 | ParseMode: tgba.ModeMarkdownV2, 164 | }) 165 | return 166 | } 167 | data, _ := io.ReadAll(datas.Body) 168 | ctx.Caller.Send(&tgba.PhotoConfig{BaseFile: tgba.BaseFile{BaseChat: tgba.BaseChat{ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}}, File: tgba.FileBytes{Bytes: data, Name: "IMAGE.png"}}, Caption: aheader, ParseMode: tgba.ModeMarkdownV2}) 169 | } 170 | 171 | // GenerateMD5 Generate MD5 172 | func GenerateMD5(userID int64, targetID int64, groupID int64) string { 173 | input := strconv.FormatInt(userID+targetID+groupID, 10) 174 | hash := md5.New() 175 | hash.Write([]byte(input)) 176 | hashValue := hash.Sum(nil) 177 | hashString := hex.EncodeToString(hashValue) 178 | return hashString 179 | } 180 | 181 | // CheckTheUserStatusAndDoRepeat If ture, means it no others (Only Refer to current user.) 182 | func CheckTheUserStatusAndDoRepeat(ctx *rei.Ctx) bool { 183 | getStatusCode, getOtherUserData := CheckTheUserIsTargetOrUser(marryList, ctx, ctx.Message.From.ID) // 判断这个user是否已经和别人在一起了,同时判断Type3 184 | switch { 185 | case getStatusCode == 0: 186 | // case target mode (0) 187 | ReplyMeantMode("貌似你今天已经有了哦~", getOtherUserData, 0, ctx) 188 | return false 189 | case getStatusCode == 1: 190 | ReplyMeantMode("貌似你今天已经有了哦~", getOtherUserData, 1, ctx) 191 | // case user mode (1) 192 | return false 193 | case getStatusCode == 10: 194 | ctx.SendPlainMessage(true, "啾啾~今天的对象是你自己哦w") 195 | return false 196 | } 197 | return true 198 | } 199 | 200 | // CheckTheTargetUserStatusAndDoRepeat Check the target status and do repeats. 201 | func CheckTheTargetUserStatusAndDoRepeat(ctx *rei.Ctx, ChooseAPerson int64) bool { 202 | getTargetStatusCode, _ := CheckTheUserIsTargetOrUser(marryList, ctx, ChooseAPerson) // 判断这个target是否已经和别人在一起了,同时判断Type3 203 | switch { 204 | case getTargetStatusCode == 1 || getTargetStatusCode == 0: 205 | ctx.SendPlainMessage(true, "aw~ 对方已经有人了哦w~算是运气不好的一次呢,Lucy多给一次机会呢w") 206 | return false 207 | case getTargetStatusCode == 10: 208 | ctx.SendPlainMessage(true, "啾啾~今天的对方是单身贵族哦(笑~ Lucy再给你一次机会哦w") 209 | return false 210 | } 211 | return true 212 | } 213 | 214 | // ResuitTheReferUserAndMakeIt Result For Married || be married, 215 | func ResuitTheReferUserAndMakeIt(ctx *rei.Ctx, dict map[string][]string, EventUser int64, TargetUser int64) { 216 | // get failed possibility. 217 | props := rand.Intn(100) 218 | if props < 50 { 219 | // failed,lost chance. 220 | getFailedMsg := dict["failed"][rand.Intn(len(dict["failed"]))] 221 | ctx.SendPlainMessage(true, getFailedMsg) 222 | return 223 | } 224 | returnNumber := GetSomeRanDomChoiceProps(ctx) 225 | switch { 226 | case returnNumber == 1: 227 | GlobalCDModelCost(ctx) 228 | getSuccessMsg := dict["success"][rand.Intn(len(dict["success"]))] 229 | // normal mode. nothing happened. 230 | ReplyMeantMode(getSuccessMsg, TargetUser, 1, ctx) 231 | generatePairKey := GenerateMD5(EventUser, TargetUser, ctx.Message.Chat.ID) 232 | err := InsertUserGlobalMarryList(marryList, ctx.Message.Chat.ID, EventUser, TargetUser, 1, generatePairKey) 233 | if err != nil { 234 | fmt.Print(err) 235 | return 236 | } 237 | case returnNumber == 2: 238 | GlobalCDModelCost(ctx) 239 | ReplyMeantMode("貌似很奇怪哦~因为某种奇怪的原因~1变成了0,0变成了1", TargetUser, 0, ctx) 240 | generatePairKey := GenerateMD5(TargetUser, EventUser, ctx.Message.Chat.ID) 241 | err := InsertUserGlobalMarryList(marryList, ctx.Message.Chat.ID, TargetUser, EventUser, 2, generatePairKey) 242 | if err != nil { 243 | panic(err) 244 | } 245 | // reverse Target Mode 246 | case returnNumber == 3: 247 | GlobalCDModelCost(ctx) 248 | // drop target pls. 249 | // status 3 turns to be case 1 ,for it cannot check it again. (With 2 same person, so it can be panic.) 250 | getSuccessMsg := dict["success"][rand.Intn(len(dict["success"]))] 251 | // normal mode. nothing happened. 252 | ReplyMeantMode(getSuccessMsg, TargetUser, 1, ctx) 253 | generatePairKey := GenerateMD5(EventUser, TargetUser, ctx.Message.Chat.ID) 254 | err := InsertUserGlobalMarryList(marryList, ctx.Message.Chat.ID, EventUser, TargetUser, 1, generatePairKey) 255 | if err != nil { 256 | fmt.Print(err) 257 | return 258 | } 259 | // you became your own target 260 | case returnNumber == 6: 261 | GlobalCDModelCost(ctx) 262 | // now no wife mode. 263 | getHideMsg := dict["hide_mode"][rand.Intn(len(dict["hide_mode"]))] 264 | ctx.SendPlainMessage(true, getHideMsg, "\n貌似没有任何反馈~") 265 | generatePairKey := GenerateMD5(EventUser, TargetUser, ctx.Message.Chat.ID) 266 | err := InsertUserGlobalMarryList(marryList, ctx.Message.Chat.ID, EventUser, TargetUser, 6, generatePairKey) 267 | if err != nil { 268 | panic(err) 269 | } 270 | } 271 | } 272 | 273 | // CheckThePairKey Check this pair key 274 | func CheckThePairKey(db *sql.Sqlite, uid int64, groupID int64) string { 275 | marryLocker.Lock() 276 | defer marryLocker.Unlock() 277 | var globalDataStructList GlobalDataStruct 278 | err := db.Find("grouplist_"+strconv.FormatInt(groupID, 10), &globalDataStructList, "where userid is '"+strconv.FormatInt(uid, 10)+"'") 279 | if err != nil { 280 | err = db.Find("grouplist_"+strconv.FormatInt(groupID, 10), &globalDataStructList, "where targetid is '"+strconv.FormatInt(uid, 10)+"'") 281 | if err != nil { 282 | return "" 283 | } 284 | return globalDataStructList.PairKey 285 | } 286 | return globalDataStructList.PairKey 287 | } 288 | 289 | // GenerateUserImageLink Generate Format Link. 290 | func GenerateUserImageLink(ctx *rei.Ctx, uid int64) string { 291 | return toolchain.GetReferTargetAvatar(ctx, uid) 292 | } 293 | 294 | // ResetToInitalizeMode delete marrylist (pairkey | grouplist) 295 | func ResetToInitalizeMode() { 296 | marryLocker.Lock() 297 | defer marryLocker.Unlock() 298 | getFullList, err := marryList.ListTables() 299 | if err != nil { 300 | panic(err) 301 | } 302 | // find all the list named: grouplist | pairkey 303 | getFilteredList := FindStrings(getFullList, "grouplist") 304 | getPairKeyFilteredList := FindStrings(getFullList, "pairkey") 305 | getFullFilteredList := append(getFilteredList, getPairKeyFilteredList...) 306 | getLength := len(getFullFilteredList) 307 | if getLength == 0 { 308 | return 309 | } 310 | for i := 0; i < getLength; i++ { 311 | err := marryList.Drop(getFullFilteredList[i]) 312 | if err != nil { 313 | panic(err) 314 | } 315 | } 316 | } 317 | 318 | // CheckTheOrderListAndBackDetailed Check this and reply some details 319 | func CheckTheOrderListAndBackDetailed(userID int64, groupID int64) (chance int, target int64, time string) { 320 | var orderListStructFinder OrderListStruct 321 | err := marryList.Find("orderlist_"+strconv.FormatInt(groupID, 10), &orderListStructFinder, "where order is '"+strconv.FormatInt(userID, 10)+"'") 322 | if err != nil { 323 | return 0, 0, "" 324 | } 325 | getTheTarget := orderListStructFinder.TargerPerson 326 | getTheTimer := orderListStructFinder.Time 327 | getRandomMoreChance := rand.Intn(30) 328 | return getRandomMoreChance, getTheTarget, getTheTimer 329 | } 330 | 331 | // FindStrings find the strings in list. 332 | func FindStrings(list []string, searchString string) []string { 333 | result := make([]string, 0) 334 | for _, item := range list { 335 | if strings.Contains(item, searchString) { 336 | result = append(result, item) 337 | } 338 | } 339 | return result 340 | } 341 | 342 | // getNextExecutionTime to this 23:00 343 | func getNextExecutionTime() time.Time { 344 | now := time.Now() 345 | nextExecutionTime := time.Date(now.Year(), now.Month(), now.Day(), 23, 0, 0, 0, now.Location()) 346 | if nextExecutionTime.Before(now) { 347 | nextExecutionTime = nextExecutionTime.Add(24 * time.Hour) 348 | } 349 | return nextExecutionTime 350 | } 351 | -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------# 2 | # Qodana analysis is configured by qodana.yaml file # 3 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html # 4 | #-------------------------------------------------------------------------------# 5 | version: "1.0" 6 | 7 | #Specify inspection profile for code analysis 8 | profile: 9 | name: qodana.starter 10 | 11 | #Enable inspections 12 | #include: 13 | # - name: 14 | 15 | #Disable inspections 16 | #exclude: 17 | # - name: 18 | # paths: 19 | # - 20 | 21 | #Execute shell command before Qodana execution (Applied in CI/CD pipeline) 22 | #bootstrap: sh ./prepare-qodana.sh 23 | 24 | #Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) 25 | #plugins: 26 | # - id: #(plugin id can be found at https://plugins.jetbrains.com) 27 | 28 | #Specify Qodana linter for analysis (Applied in CI/CD pipeline) 29 | linter: jetbrains/qodana-go:latest 30 | -------------------------------------------------------------------------------- /utils/bilibili/fix.go: -------------------------------------------------------------------------------- 1 | package bilibili 2 | 3 | import ( 4 | "github.com/FloatTech/AnimeAPI/bilibili" 5 | "regexp" 6 | ) 7 | 8 | func BilibiliFixedLink(link string) string { 9 | // query for the last link here. 10 | getRealLink, err := bilibili.GetRealURL("https://" + link) 11 | if err != nil { 12 | return "" 13 | } 14 | 15 | getReq, err := regexp.Compile("bilibili.com\\\\?/video\\\\?/(?:av(\\d+)|([bB][vV][0-9a-zA-Z]+))") 16 | if err != nil { 17 | return "" 18 | } 19 | return getReq.FindAllString(getRealLink, 1)[0] 20 | } 21 | -------------------------------------------------------------------------------- /utils/coins/main.go: -------------------------------------------------------------------------------- 1 | // Package Coins 2 | package Coins 3 | 4 | import ( 5 | "os" 6 | "time" 7 | 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | var ( 12 | LevelArray = [...]int{0, 1, 2, 5, 10, 20, 35, 55, 75, 100, 120, 180, 260, 360, 480, 600} 13 | ) 14 | 15 | // Scoredb 分数数据库 16 | type Scoredb gorm.DB 17 | 18 | // Scoretable 分数结构体 19 | type Scoretable struct { 20 | UID int64 `gorm:"column:uid;primary_key"` 21 | Score int `gorm:"column:score;default:0"` 22 | } 23 | 24 | // Signintable 签到结构体 25 | type Signintable struct { 26 | UID int64 `gorm:"column:uid;primary_key"` 27 | Count int `gorm:"column:count;default:0"` 28 | Coins int `gorm:"column:coins;default:0"` 29 | SignUpdated int64 `gorm:"column:sign;default:0"` // fix : score shown error. 30 | UpdatedAt time.Time 31 | } 32 | 33 | // Globaltable 总体结构体 34 | type Globaltable struct { 35 | Counttime int `gorm:"column:counttime;default:0"` 36 | Times string 37 | } 38 | 39 | // WagerTable wager Table Struct 40 | type WagerTable struct { 41 | Wagercount int `gorm:"column:wagercount;default:0"` 42 | Expected int `gorm:"column:expected;default:0"` 43 | Winner int `gorm:"column:winner;default:0"` 44 | } 45 | 46 | // WagerUserInputTable Get Wager Table User Input In Times 47 | type WagerUserInputTable struct { 48 | UID int64 `gorm:"column:uid;primary_key"` 49 | InputCountNumber int64 `gorm:"column:coin;default:0"` 50 | UserExistedStoppedTime int64 `gorm:"column:time;default:0"` 51 | } 52 | 53 | // ProtectModeIndex Protect User From taking part in games, user can only change their mode in 24h. 54 | type ProtectModeIndex struct { 55 | UID int64 `gorm:"column:uid;primary_key"` 56 | Time int64 `gorm:"column:time;default:0"` 57 | Status int64 `gorm:"column:status;default:0"` // 1 mean enabled || else none. 58 | } 59 | 60 | func (ProtectModeIndex) TableName() string { 61 | return "protectmode" 62 | } 63 | 64 | func (WagerTable) TableName() string { 65 | return "wagertable" 66 | } 67 | 68 | // TableName ... 69 | func (Globaltable) TableName() string { 70 | return "global" 71 | } 72 | 73 | // TableName ... 74 | func (Scoretable) TableName() string { 75 | return "score" 76 | } 77 | 78 | // TableName ... 79 | func (Signintable) TableName() string { 80 | return "sign_in" 81 | } 82 | 83 | func (WagerUserInputTable) TableName() string { 84 | return "wager_user" 85 | } 86 | 87 | // Initialize 初始化ScoreDB数据库 88 | func Initialize(dbpath string) *Scoredb { 89 | var err error 90 | if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) { 91 | // 生成文件 92 | f, err := os.Create(dbpath) 93 | if err != nil { 94 | return nil 95 | } 96 | defer func(f *os.File) { 97 | err := f.Close() 98 | if err != nil { 99 | return 100 | } 101 | }(f) 102 | } 103 | gdb, err := gorm.Open("sqlite3", dbpath) 104 | if err != nil { 105 | return nil 106 | } 107 | gdb.AutoMigrate(&Scoretable{}).AutoMigrate(&Signintable{}).AutoMigrate(&Globaltable{}).AutoMigrate(&WagerTable{}).AutoMigrate(&WagerUserInputTable{}).AutoMigrate(&ProtectModeIndex{}) 108 | return (*Scoredb)(gdb) 109 | } 110 | 111 | // GetScoreByUID 取得分数 112 | func GetScoreByUID(sdb *Scoredb, uid int64) (s Scoretable) { 113 | db := (*gorm.DB)(sdb) 114 | db.Debug().Model(&Scoretable{}).FirstOrCreate(&s, "uid = ? ", uid) 115 | return s 116 | } 117 | 118 | // GetProtectModeStatus Get Status 119 | func GetProtectModeStatus(sdb *Scoredb, uid int64) (s ProtectModeIndex) { 120 | db := (*gorm.DB)(sdb) 121 | db.Debug().Model(&ProtectModeIndex{}).FirstOrCreate(&s, "uid = ? ", uid) 122 | return s 123 | } 124 | 125 | func ChangeProtectStatus(sdb *Scoredb, uid int64, statusID int64) (err error) { 126 | db := (*gorm.DB)(sdb) 127 | s := ProtectModeIndex{UID: uid, Status: statusID, Time: time.Now().Unix()} 128 | if err = db.Debug().Model(&ProtectModeIndex{}).First(&s, "uid = ? ", uid).Error; err != nil { 129 | if gorm.IsRecordNotFoundError(err) { 130 | err = db.Debug().Model(&ProtectModeIndex{}).Create(&s).Error // newUser not user 131 | } 132 | } else { 133 | err = db.Debug().Model(&ProtectModeIndex{}).Where("uid = ? ", uid).Update( 134 | map[string]interface{}{ 135 | "uid": uid, 136 | "status": statusID, 137 | "time": time.Now().Unix(), 138 | }).Error 139 | } 140 | 141 | return 142 | } 143 | 144 | // InsertOrUpdateScoreByUID 插入或更新打卡累计 初始化更新临时保存 145 | func InsertOrUpdateScoreByUID(sdb *Scoredb, uid int64, score int) (err error) { 146 | db := (*gorm.DB)(sdb) 147 | s := Scoretable{ 148 | UID: uid, 149 | Score: score, 150 | } 151 | if err = db.Debug().Model(&Scoretable{}).First(&s, "uid = ? ", uid).Error; err != nil { 152 | // error handling... 153 | if gorm.IsRecordNotFoundError(err) { 154 | err = db.Debug().Model(&Scoretable{}).Create(&s).Error // newUser not user 155 | } 156 | } else { 157 | err = db.Debug().Model(&Scoretable{}).Where("uid = ? ", uid).Update( 158 | map[string]interface{}{ 159 | "score": score, 160 | }).Error 161 | } 162 | return 163 | } 164 | 165 | // GetSignInByUID 取得签到次数 166 | func GetSignInByUID(sdb *Scoredb, uid int64) (si Signintable) { 167 | db := (*gorm.DB)(sdb) 168 | db.Debug().Model(&Signintable{}).FirstOrCreate(&si, "uid = ? ", uid) 169 | return si 170 | } 171 | 172 | // GetCurrentCount 取得现在的人数 173 | func GetCurrentCount(sdb *Scoredb, times string) (si Globaltable) { 174 | db := (*gorm.DB)(sdb) 175 | db.Debug().Model(&Globaltable{}).FirstOrCreate(&si, "times = ? ", times) 176 | return si 177 | } 178 | 179 | // GetWagerStatus Get Status (total) 180 | func GetWagerStatus(sdb *Scoredb) (si WagerTable) { 181 | db := (*gorm.DB)(sdb) 182 | db.Debug().Model(&WagerTable{}).FirstOrCreate(&si, "winner = ? ", 0) 183 | return si 184 | } 185 | 186 | // GetUserIsSignInToday Check user is signin today. 187 | func GetUserIsSignInToday(sdb *Scoredb, uid int64) (getStat bool, si Signintable) { 188 | db := (*gorm.DB)(sdb) 189 | db.Debug().Model(&Signintable{}).FirstOrCreate(&si, "uid = ? ", uid) 190 | getLocation, _ := time.LoadLocation("Asia/Shanghai") 191 | getDatabaseTime := time.Unix(si.SignUpdated, 0).In(getLocation) 192 | getNow := time.Unix(time.Now().Unix(), 0).In(getLocation).Add(time.Minute * 30) 193 | return getDatabaseTime.Year() == getNow.Year() && getDatabaseTime.Month() == getNow.Month() && getDatabaseTime.Day() == getNow.Day(), si 194 | } 195 | 196 | // GetWagerUserStatus Get Status 197 | func GetWagerUserStatus(sdb *Scoredb, uid int64) (si WagerUserInputTable) { 198 | db := (*gorm.DB)(sdb) 199 | db.Debug().Model(&WagerUserInputTable{}).FirstOrCreate(&si, "uid = ? ", uid) 200 | return si 201 | } 202 | 203 | // UpdateWagerUserStatus update WagerUserStatus 204 | func UpdateWagerUserStatus(sdb *Scoredb, uid int64, time int64, coins int64) (err error) { 205 | db := (*gorm.DB)(sdb) 206 | si := WagerUserInputTable{ 207 | UID: uid, 208 | UserExistedStoppedTime: time, 209 | InputCountNumber: coins, 210 | } 211 | if err = db.Debug().Model(&WagerUserInputTable{}).First(&si, "uid = ? ", uid).Error; err != nil { 212 | // error handling... 213 | if gorm.IsRecordNotFoundError(err) { 214 | db.Debug().Model(&WagerUserInputTable{}).Create(&si) // newUser not user 215 | } 216 | } else { 217 | err = db.Debug().Model(&WagerUserInputTable{}).Where("uid = ? ", uid).Update( 218 | map[string]interface{}{ 219 | "uid": uid, 220 | "time": time, 221 | "coin": coins, 222 | }).Error 223 | } 224 | return 225 | } 226 | 227 | // InsertOrUpdateSignInCountByUID 插入或更新签到次数 228 | func InsertOrUpdateSignInCountByUID(sdb *Scoredb, uid int64, count int) (err error) { 229 | db := (*gorm.DB)(sdb) 230 | si := Signintable{ 231 | UID: uid, 232 | Count: count, 233 | } 234 | if err = db.Debug().Model(&Signintable{}).First(&si, "uid = ? ", uid).Error; err != nil { 235 | // error handling... 236 | if gorm.IsRecordNotFoundError(err) { 237 | db.Debug().Model(&Signintable{}).Create(&si) // newUser not user 238 | } 239 | } else { 240 | err = db.Debug().Model(&Signintable{}).Where("uid = ? ", uid).Update( 241 | map[string]interface{}{ 242 | "count": count, 243 | }).Error 244 | } 245 | return 246 | } 247 | 248 | func InsertUserCoins(sdb *Scoredb, uid int64, coins int) (err error) { // 修改金币数值 249 | db := (*gorm.DB)(sdb) 250 | si := Signintable{ 251 | UID: uid, 252 | Coins: coins, 253 | } 254 | if err = db.Debug().Model(&Signintable{}).First(&si, "uid = ? ", uid).Error; err != nil { 255 | // error handling... 256 | if gorm.IsRecordNotFoundError(err) { 257 | db.Debug().Model(&Signintable{}).Create(&si) // newUser not user 258 | } 259 | } else { 260 | err = db.Debug().Model(&Signintable{}).Where("uid = ? ", uid).Update( 261 | map[string]interface{}{ 262 | "coins": coins, 263 | }).Error 264 | } 265 | return 266 | } 267 | 268 | func UpdateUserSignInValue(sdb *Scoredb, uid int64) (err error) { 269 | db := (*gorm.DB)(sdb) 270 | si := Signintable{ 271 | UID: uid, 272 | SignUpdated: time.Now().Unix(), 273 | } 274 | if err = db.Debug().Model(&Signintable{}).First(&si, "uid = ? ", uid).Error; err != nil { 275 | // error handling... 276 | if gorm.IsRecordNotFoundError(err) { 277 | db.Debug().Model(&Signintable{}).Create(&si) // newUser not user 278 | } 279 | } else { 280 | err = db.Debug().Model(&Signintable{}).Where("uid = ? ", uid).Update( 281 | map[string]interface{}{ 282 | "sign": time.Now().Unix(), 283 | }).Error 284 | } 285 | return 286 | } 287 | 288 | func UpdateUserTime(sdb *Scoredb, counttime int, times string) (err error) { 289 | db := (*gorm.DB)(sdb) 290 | si := Globaltable{ 291 | Counttime: counttime, 292 | Times: times, 293 | } 294 | if err = db.Debug().Model(&Globaltable{}).First(&si, "times = ?", times).Error; err != nil { 295 | if gorm.IsRecordNotFoundError(err) { 296 | db.Debug().Model(&Globaltable{}).Create(&si) 297 | } 298 | } else { 299 | err = db.Debug().Model(&Globaltable{}).Where("times = ?", times).Update(map[string]interface{}{ 300 | "counttime": counttime, 301 | }).Error 302 | } 303 | return err 304 | } 305 | 306 | func WagerCoinsInsert(sdb *Scoredb, modifyCoins int, winner int, expected int) (err error) { 307 | db := (*gorm.DB)(sdb) 308 | si := WagerTable{Wagercount: modifyCoins, Winner: winner, Expected: expected} 309 | if err = db.Debug().Model(&WagerTable{}).First(&si, "winner = ? ", 0).Error; err != nil { 310 | if gorm.IsRecordNotFoundError(err) { 311 | db.Debug().Model(&WagerTable{}).Create(&si) 312 | } 313 | } else { 314 | err = db.Debug().Model(&WagerTable{}).Where("winner = ? ", 0).Update(map[string]interface{}{ 315 | "wagercount": modifyCoins, 316 | "expected": expected, 317 | "winner": winner, 318 | }).Error 319 | } 320 | return err 321 | } 322 | 323 | func CheckUserCoins(coins int) bool { // 参与一次60个柠檬片 324 | return coins-60 >= 0 325 | } 326 | 327 | func GetHourWord(t time.Time) (reply string) { 328 | switch { 329 | case 5 <= t.Hour() && t.Hour() < 12: 330 | reply = "今天又是新的一天呢ww" 331 | case 12 <= t.Hour() && t.Hour() < 14: 332 | reply = "吃饭了嘛w~如果没有快去吃饭哦w" 333 | case 14 <= t.Hour() && t.Hour() < 19: 334 | reply = "记得多去补点水呢~ww,辛苦了哦w" 335 | case 19 <= t.Hour() && t.Hour() < 24: 336 | reply = "晚上好吖w 今天过的开心嘛ww" 337 | case 0 <= t.Hour() && t.Hour() < 5: 338 | reply = "快去睡觉~已经很晚了w" 339 | default: 340 | } 341 | return 342 | } 343 | 344 | func GetLevel(count int) int { 345 | for k, v := range LevelArray { 346 | if count == v { 347 | return k 348 | } else if count < v { 349 | return k - 1 350 | } 351 | } 352 | return -1 353 | } 354 | -------------------------------------------------------------------------------- /utils/ctxext/speed.go: -------------------------------------------------------------------------------- 1 | // Package ctxext ctx扩展 2 | package ctxext 3 | 4 | import ( 5 | "time" 6 | "unsafe" 7 | 8 | rei "github.com/fumiama/ReiBot" 9 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 10 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 11 | ) 12 | 13 | // DefaultSingle 默认反并发处理 14 | // 15 | // 按 发送者 反并发 16 | // 并发时返回 "您有操作正在执行, 请稍后再试!" 17 | var DefaultSingle = rei.NewSingle( 18 | rei.WithKeyFn(func(ctx *rei.Ctx) int64 { 19 | switch msg := ctx.Value.(type) { 20 | case *tgba.Message: 21 | return msg.From.ID 22 | case *tgba.CallbackQuery: 23 | return msg.From.ID 24 | } 25 | return 0 26 | }), 27 | rei.WithPostFn[int64](func(ctx *rei.Ctx) { 28 | _, _ = ctx.SendPlainMessage(false, "您有操作正在执行, 请稍后再试!") 29 | }), 30 | ) 31 | 32 | // defaultLimiterManager 默认限速器管理 33 | // 34 | // 每 10s 5次触发 35 | var defaultLimiterManager = rate.NewManager[int64](time.Second*10, 5) 36 | 37 | type fakeLM struct { 38 | limiters unsafe.Pointer 39 | interval time.Duration 40 | burst int 41 | } 42 | 43 | // SetDefaultLimiterManagerParam 设置默认限速器参数 44 | // 45 | // 每 interval 时间 burst 次触发 46 | func SetDefaultLimiterManagerParam(interval time.Duration, burst int) { 47 | f := (*fakeLM)(unsafe.Pointer(defaultLimiterManager)) 48 | f.interval = interval 49 | f.burst = burst 50 | } 51 | 52 | // LimitByUser 默认限速器 每 10s 5次触发 53 | // 54 | // 按 发送者 限制 55 | func LimitByUser(ctx *rei.Ctx) *rate.Limiter { 56 | switch msg := ctx.Value.(type) { 57 | case *tgba.Message: 58 | return defaultLimiterManager.Load(msg.From.ID) 59 | case *tgba.CallbackQuery: 60 | return defaultLimiterManager.Load(msg.From.ID) 61 | } 62 | return defaultLimiterManager.Load(0) 63 | } 64 | 65 | // LimitByGroup 默认限速器 每 10s 5次触发 66 | // 67 | // 按群号限制 68 | func LimitByGroup(ctx *rei.Ctx) *rate.Limiter { 69 | switch msg := ctx.Value.(type) { 70 | case *tgba.Message: 71 | return defaultLimiterManager.Load(msg.Chat.ID) 72 | case *tgba.CallbackQuery: 73 | if msg.Message != nil { 74 | return defaultLimiterManager.Load(msg.Message.Chat.ID) 75 | } 76 | return defaultLimiterManager.Load(msg.From.ID) 77 | } 78 | return defaultLimiterManager.Load(0) 79 | } 80 | 81 | // LimiterManager 自定义限速器管理 82 | type LimiterManager struct { 83 | m *rate.LimiterManager[int64] 84 | } 85 | 86 | // NewLimiterManager 新限速器管理 87 | func NewLimiterManager(interval time.Duration, burst int) (m LimiterManager) { 88 | m.m = rate.NewManager[int64](interval, burst) 89 | return 90 | } 91 | 92 | // LimitByUser 自定义限速器 93 | // 94 | // 按 发送者 限制 95 | func (m LimiterManager) LimitByUser(ctx *rei.Ctx) *rate.Limiter { 96 | switch msg := ctx.Value.(type) { 97 | case *tgba.Message: 98 | return defaultLimiterManager.Load(msg.From.ID) 99 | case *tgba.CallbackQuery: 100 | return defaultLimiterManager.Load(msg.From.ID) 101 | } 102 | return defaultLimiterManager.Load(0) 103 | } 104 | 105 | // LimitByGroup 自定义限速器 106 | // 107 | // 按群号限制 108 | func (m LimiterManager) LimitByGroup(ctx *rei.Ctx) *rate.Limiter { 109 | switch msg := ctx.Value.(type) { 110 | case *tgba.Message: 111 | return defaultLimiterManager.Load(msg.Chat.ID) 112 | case *tgba.CallbackQuery: 113 | if msg.Message != nil { 114 | return defaultLimiterManager.Load(msg.Message.Chat.ID) 115 | } 116 | return defaultLimiterManager.Load(msg.From.ID) 117 | } 118 | return defaultLimiterManager.Load(0) 119 | } 120 | 121 | func MustMessageNotNil(ctx *rei.Ctx) bool { 122 | return ctx.Message != nil 123 | } 124 | -------------------------------------------------------------------------------- /utils/toolchain/toolchain.go: -------------------------------------------------------------------------------- 1 | // Package toolchain can make it more easily to code. 2 | package toolchain 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "hash/crc64" 8 | "image" 9 | "io" 10 | "math/rand" 11 | "net/http" 12 | "os" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "time" 17 | "unsafe" 18 | 19 | "github.com/FloatTech/floatbox/binary" 20 | "github.com/FloatTech/floatbox/file" 21 | "github.com/MoYoez/Lucy_reibot/utils/userlist" 22 | "github.com/MoYoez/Lucy_reibot/utils/userpackage" 23 | rei "github.com/fumiama/ReiBot" 24 | tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5" 25 | "github.com/wdvxdr1123/ZeroBot/extension/rate" 26 | ) 27 | 28 | var OnHoldSaver = rate.NewManager[int64](time.Hour*24, 1) // only update once. 29 | var OnGroupSaver = rate.NewManager[int64](time.Hour*24, 1) // only update once. 30 | 31 | // GetTargetAvatar GetUserTarget ID 32 | func GetTargetAvatar(ctx *rei.Ctx) image.Image { 33 | getUserName := ctx.Event.Value.(*tgba.Message).From.FirstName 34 | userID := ctx.Event.Value.(*tgba.Message).From.ID 35 | if getUserName == "Group" || getUserName == "Channel" { 36 | userID = ctx.Message.Chat.ID 37 | } 38 | getGroupChatConfig := tgba.ChatInfoConfig{ChatConfig: tgba.ChatConfig{ChatID: userID}} 39 | chatGroupInfo, err := ctx.Caller.GetChat(getGroupChatConfig) 40 | if err != nil { 41 | return nil 42 | } 43 | if chatGroupInfo.Photo == nil { 44 | return nil 45 | } 46 | chatPhoto := chatGroupInfo.Photo.SmallFileID 47 | avatar, err := ctx.Caller.GetFileDirectURL(chatPhoto) 48 | if err != nil { 49 | return nil 50 | } 51 | datas, err := http.Get(avatar) 52 | // avatar 53 | avatarByteUni, _, _ := image.Decode(datas.Body) 54 | return avatarByteUni 55 | } 56 | 57 | func GetReferTargetAvatar(ctx *rei.Ctx, uid int64) string { 58 | getGroupChatConfig := tgba.ChatInfoConfig{ChatConfig: tgba.ChatConfig{ChatID: uid}} 59 | chatGroupInfo, err := ctx.Caller.GetChat(getGroupChatConfig) 60 | if err != nil { 61 | return "" 62 | } 63 | if chatGroupInfo.Photo == nil { 64 | return "" 65 | } 66 | chatPhoto := chatGroupInfo.Photo.SmallFileID 67 | avatar, err := ctx.Caller.GetFileDirectURL(chatPhoto) 68 | return avatar 69 | } 70 | 71 | // GetChatUserInfoID GetID and UserName, support Channel | Userself and Annoy Group 72 | func GetChatUserInfoID(ctx *rei.Ctx) (id int64, name string) { 73 | getUserName := ctx.Event.Value.(*tgba.Message).From.FirstName 74 | switch { 75 | case getUserName == "Group" || getUserName == "Channel": 76 | getGroupChatConfig := tgba.ChatInfoConfig{ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}} 77 | chatGroupInfo, err := ctx.Caller.GetChat(getGroupChatConfig) 78 | if err != nil { 79 | return 80 | } 81 | return chatGroupInfo.ID, chatGroupInfo.Title 82 | default: 83 | return ctx.Event.Value.(*tgba.Message).From.ID, getUserName + " " + ctx.Event.Value.(*tgba.Message).From.LastName 84 | } 85 | 86 | } 87 | 88 | // GetThisGroupID Get Group ID 89 | func GetThisGroupID(ctx *rei.Ctx) (id int64) { 90 | if !ctx.Message.Chat.IsGroup() && !ctx.Message.Chat.IsSuperGroup() { 91 | return 0 92 | } 93 | return ctx.Message.Chat.ID 94 | } 95 | 96 | // GetNickNameFromUsername Use Sniper, not api. 97 | func GetNickNameFromUsername(username string) (name string) { 98 | // Method: https://github.com/XiaoMengXinX/Telegram_QuoteReply_Bot-Go/blob/master/api/bot.go 99 | if strings.HasPrefix(username, "@") { 100 | username = strings.Replace(username, "@", "", 1) 101 | } 102 | client := &http.Client{} 103 | req, _ := http.NewRequest("GET", fmt.Sprintf("https://t.me/%s", username), nil) 104 | resp, err := client.Do(req) 105 | if err != nil { 106 | return 107 | } 108 | defer resp.Body.Close() 109 | body, _ := io.ReadAll(resp.Body) 110 | reName := regexp.MustCompile(` 1 { 113 | name = match[1] 114 | } 115 | pageTitle := "" 116 | reTitle1 := regexp.MustCompile(``) 117 | reTitle2 := regexp.MustCompile(``) 118 | start := reTitle1.FindStringIndex(string(body)) 119 | end := reTitle2.FindStringIndex(string(body)) 120 | if start != nil && end != nil { 121 | pageTitle = string(body)[start[1]:end[0]] 122 | } 123 | 124 | if pageTitle == name { // 用户不存在 125 | name = "" 126 | } 127 | return 128 | } 129 | 130 | func GetNickNameFromUserid(userid int64) string { 131 | data := CoreFactory.GetUserSampleUserinfobyid(userid) 132 | if data == nil { 133 | return "" 134 | } 135 | return GetNickNameFromUsername(data.UserName) 136 | } 137 | 138 | // RandSenderPerDayN 每个用户每天随机数 139 | func RandSenderPerDayN(uid int64, n int) int { 140 | sum := crc64.New(crc64.MakeTable(crc64.ISO)) 141 | sum.Write(binary.StringToBytes(time.Now().Format("20060102"))) 142 | sum.Write((*[8]byte)(unsafe.Pointer(&uid))[:]) 143 | r := rand.New(rand.NewSource(int64(sum.Sum64()))) 144 | return r.Intn(n) 145 | } 146 | 147 | // SplitCommandTo Split Command and Adjust To. 148 | func SplitCommandTo(raw string, setCommandStopper int) (splitCommandLen int, splitInfo []string) { 149 | rawSplit := strings.SplitN(raw, " ", setCommandStopper) 150 | return len(rawSplit), rawSplit 151 | } 152 | 153 | // RequestImageTo Request Image and return PhotoSize To handle. 154 | func RequestImageTo(ctx *rei.Ctx, footpoint string) []tgba.PhotoSize { 155 | msg, ok := ctx.Value.(*tgba.Message) 156 | if ok && len(msg.Photo) > 0 { 157 | ctx.State["photos"] = msg.Photo 158 | return ctx.State["photos"].([]tgba.PhotoSize) 159 | } 160 | ctx.SendPlainMessage(true, footpoint) 161 | return nil 162 | 163 | } 164 | 165 | // FastSendRandMuiltText Send Muilt Text to help/ 166 | func FastSendRandMuiltText(ctx *rei.Ctx, raw ...string) error { 167 | _, err := ctx.SendPlainMessage(true, raw[rand.Intn(len(raw))]) 168 | return err 169 | } 170 | 171 | // FastSendRandMuiltPic Send Multi picture to help/ 172 | func FastSendRandMuiltPic(ctx *rei.Ctx, raw ...string) error { 173 | _, err := ctx.SendPhoto(tgba.FilePath(raw[rand.Intn(len(raw))]), true, "") 174 | return err 175 | } 176 | 177 | // StringInArray 检查列表是否有关键词 178 | func StringInArray(aim string, list []string) bool { 179 | for _, i := range list { 180 | if i == aim { 181 | return true 182 | } 183 | } 184 | return false 185 | } 186 | 187 | // StoreUserNickname store names in jsons 188 | func StoreUserNickname(userID string, nickname string) error { 189 | var userNicknameData map[string]interface{} 190 | filePath := file.BOTPATH + "/data/rbp/users.json" 191 | data, err := os.ReadFile(filePath) 192 | if err != nil { 193 | if os.IsNotExist(err) { 194 | _ = os.WriteFile(filePath, []byte("{}"), 0777) 195 | } else { 196 | return err 197 | } 198 | } 199 | _ = json.Unmarshal(data, &userNicknameData) 200 | userNicknameData[userID] = nickname // setdata. 201 | newData, err := json.Marshal(userNicknameData) 202 | if err != nil { 203 | return err 204 | } 205 | _ = os.WriteFile(filePath, newData, 0777) 206 | return nil 207 | } 208 | 209 | // LoadUserNickname Load UserNames, it will work on simai plugin 210 | func LoadUserNickname(userID string) string { 211 | var d map[string]string 212 | // read main files 213 | filePath := file.BOTPATH + "/data/rbp/users.json" 214 | data, err := os.ReadFile(filePath) 215 | if err != nil { 216 | return "你" 217 | } 218 | err = json.Unmarshal(data, &d) 219 | if err != nil { 220 | return "你" 221 | } 222 | result := d[userID] 223 | if result == "" { 224 | result = "你" 225 | } 226 | return result 227 | } 228 | 229 | func GetBotIsAdminInThisGroup(ctx *rei.Ctx) bool { 230 | getSelfMember, err := ctx.Caller.GetChatMember( 231 | tgba.GetChatMemberConfig{ 232 | ChatConfigWithUser: tgba.ChatConfigWithUser{ 233 | ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}, 234 | UserID: ctx.Caller.Self.ID, 235 | }, 236 | }, 237 | ) 238 | if err != nil { 239 | return false 240 | } 241 | return getSelfMember.IsCreator() || getSelfMember.IsAdministrator() 242 | } 243 | 244 | func GetTheTargetIsNormalUser(ctx *rei.Ctx) bool { 245 | // stop channel to take part in this. 246 | getUserChannelStatus := ctx.Event.Value.(*tgba.Message).From.FirstName 247 | if getUserChannelStatus == "Group" || getUserChannelStatus == "Channel" || ctx.Message.From.ID == 777000 { // unknownUser. 248 | return false 249 | } 250 | return true 251 | } 252 | 253 | func IsTargetSettedUserName(ctx *rei.Ctx) bool { 254 | return ctx.Message.From.UserName != "" 255 | } 256 | 257 | // FastSaveUserStatus I hope this will not ruin my machine. ( 258 | func FastSaveUserStatus(ctx *rei.Ctx) bool { 259 | // only save normal user 260 | 261 | if !OnHoldSaver.Load(ctx.Message.From.ID).Acquire() || !GetTheTargetIsNormalUser(ctx) || !IsTargetSettedUserName(ctx) { 262 | // save database onload time. 263 | return false 264 | } 265 | CoreFactory.StoreUserDataBase(ctx.Message.From.ID, ctx.Message.From.UserName) 266 | return true 267 | } 268 | 269 | func FastSaveUserGroupList(ctx *rei.Ctx) { 270 | if !OnGroupSaver.Load(ctx.Message.From.ID).Acquire() || !GetTheTargetIsNormalUser(ctx) || GetThisGroupID(ctx) == 0 { 271 | return 272 | } 273 | userlist.SaveUserOnList(strconv.FormatInt(ctx.Message.From.ID, 10), strconv.FormatInt(ctx.Message.Chat.ID, 10), ctx.Message.From.UserName) 274 | 275 | } 276 | 277 | // CheckIfthisUserInThisGroup Check the user if in this group. 278 | func CheckIfthisUserInThisGroup(userID int64, ctx *rei.Ctx) bool { 279 | group := GetThisGroupID(ctx) 280 | if group == 0 { 281 | // not a group. 282 | return false 283 | } 284 | getResult, err := ctx.Caller.GetChatMember( 285 | tgba.GetChatMemberConfig{ 286 | ChatConfigWithUser: tgba.ChatConfigWithUser{ 287 | ChatConfig: tgba.ChatConfig{ChatID: group}, 288 | UserID: userID, 289 | }, 290 | }, 291 | ) 292 | if err != nil { 293 | return false 294 | } 295 | if getResult.User != nil { 296 | return true 297 | } 298 | return false 299 | } 300 | 301 | // ListEntitiesMention List Entities and return a simple list with user. 302 | func ListEntitiesMention(ctx *rei.Ctx) []string { 303 | var tempList []string 304 | msg := ctx.Message.Text 305 | for _, entity := range ctx.Message.Entities { 306 | if entity.Type == "mention" { 307 | mentionText := msg[entity.Offset : entity.Offset+entity.Length] 308 | tempList = append(tempList, mentionText) 309 | } 310 | } 311 | return tempList 312 | } 313 | 314 | // GetUserIDFromUserName with @, only works when the data saved. 315 | func GetUserIDFromUserName(ctx *rei.Ctx, userName string) int64 { 316 | getUserData := CoreFactory.GetUserSampleUserinfo(strings.Replace(userName, "@", "", 1)) 317 | if getUserData.UserID == 0 { 318 | return 0 319 | } 320 | // check the user is in group? 321 | if !CheckIfthisUserInThisGroup(getUserData.UserID, ctx) { 322 | return 0 323 | } 324 | return getUserData.UserID 325 | } 326 | 327 | // ExtractNumbers Extract Numbers by using regexp. 328 | func ExtractNumbers(text string) int64 { 329 | re := regexp.MustCompile(`\d+`) 330 | numbers := re.FindAllString(text, 1) 331 | num, _ := strconv.ParseInt(numbers[0], 10, 64) 332 | return num 333 | } 334 | 335 | func GetUserNickNameByIDInGroup(ctx *rei.Ctx, id int64) string { 336 | if !CheckIfthisUserInThisGroup(id, ctx) { 337 | return "" 338 | } 339 | chatPrefer, err := ctx.Caller.GetChatMember(tgba.GetChatMemberConfig{ChatConfigWithUser: tgba.ChatConfigWithUser{ChatConfig: tgba.ChatConfig{ChatID: ctx.Message.Chat.ID}, UserID: id}}) 340 | if err != nil { 341 | panic(err) 342 | } 343 | return chatPrefer.User.FirstName + chatPrefer.User.LastName 344 | 345 | } 346 | -------------------------------------------------------------------------------- /utils/transform/main.go: -------------------------------------------------------------------------------- 1 | package transform 2 | 3 | // ReturnLucyMainDataIndex Lucy's main Path here. 4 | func ReturnLucyMainDataIndex(pluginName string) string { 5 | return "/root/Lucy_Project/main/Lucy_zerobot/data/" + pluginName + "/" 6 | } 7 | -------------------------------------------------------------------------------- /utils/userlist/main.go: -------------------------------------------------------------------------------- 1 | package userlist 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/FloatTech/floatbox/file" 8 | sql "github.com/FloatTech/sqlite" 9 | ) 10 | 11 | type UserList struct { 12 | UserID string `db:"userid"` 13 | UserName string `db:"username"` 14 | } 15 | 16 | var ( 17 | groupSaver = &sql.Sqlite{} 18 | groupLocker = sync.Mutex{} 19 | ) 20 | 21 | // handle when receiving user data, save it to database KV:{username : UserID} 22 | 23 | func init() { 24 | // when using Lucy's functions (need to sure the trigger id.) 25 | filePath := file.BOTPATH + "/data/rbp/group_lib.db" 26 | groupSaver.DBPath = filePath 27 | err := groupSaver.Open(time.Hour * 24) 28 | if err != nil { 29 | panic(err) 30 | } 31 | } 32 | 33 | // InitDataGroup Init A group if we cannot find it. 34 | func InitDataGroup(groupID string) error { 35 | return groupSaver.Create("group_"+groupID, &UserList{}) 36 | } 37 | 38 | func SaveUserOnList(userid string, groupID string, username string) { 39 | // check the table is existed? if not, create it. 40 | err := groupSaver.Insert("group_"+groupID, &UserList{UserID: userid, UserName: username}) 41 | if err != nil { 42 | InitDataGroup(groupID) 43 | } 44 | } 45 | 46 | func RemoveUserOnList(userid string, groupID string) { 47 | // check the table is existed? if not, create it. 48 | err := groupSaver.Del("group_"+groupID, "WHERE userid is "+userid) 49 | if err != nil { 50 | InitDataGroup(groupID) 51 | } 52 | } 53 | 54 | func PickUserOnGroup(gid string) string { 55 | var PickerAxe UserList 56 | err := groupSaver.Pick("group_"+gid, &PickerAxe) 57 | if err != nil { 58 | InitDataGroup(gid) 59 | } 60 | return PickerAxe.UserID 61 | } 62 | 63 | func GetThisGroupList(gid string) []string { 64 | getNum, _ := groupSaver.Count("group_" + gid) 65 | if getNum == 0 { 66 | return nil 67 | } 68 | var list []string 69 | var onTemploader UserList 70 | _ = groupSaver.FindFor("group_"+gid, &onTemploader, "WHERE id = 0", func() error { 71 | list = append(list, onTemploader.UserID) 72 | return nil 73 | }) 74 | return list 75 | } 76 | -------------------------------------------------------------------------------- /utils/userpackage/main.go: -------------------------------------------------------------------------------- 1 | // Package CoreFactory add simple event listener to database, record user id and some other info to judge user is correct. 2 | package CoreFactory 3 | 4 | import ( 5 | "strconv" 6 | "sync" 7 | "time" 8 | 9 | "github.com/FloatTech/floatbox/file" 10 | sql "github.com/FloatTech/sqlite" 11 | ) 12 | 13 | type Data struct { 14 | UserID int64 `db:"userid"` 15 | UserName string `db:"username"` // refer: username means @xx, can be direct to user by t.me/@username || channel / group user use save. 16 | } 17 | 18 | var ( 19 | coreSaver = &sql.Sqlite{} 20 | coreLocker = sync.Mutex{} 21 | ) 22 | 23 | // handle when receiving user data, save it to database KV:{username : UserID} 24 | 25 | func init() { 26 | // when using Lucy's functions (need to sure the trigger id.) 27 | filePath := file.BOTPATH + "/data/rbp/lib.db" 28 | coreSaver.DBPath = filePath 29 | err := coreSaver.Open(time.Hour * 24) 30 | if err != nil { 31 | return 32 | } 33 | _ = initDatabase() 34 | 35 | } 36 | 37 | // GetUserSampleUserinfo User Info. 38 | func GetUserSampleUserinfo(username string) *Data { 39 | coreLocker.Lock() 40 | defer coreLocker.Unlock() 41 | var ResultData Data 42 | coreSaver.Find("userinfo", &ResultData, "Where username is '"+username+"'") 43 | if &ResultData == nil { 44 | return nil 45 | } 46 | return &ResultData 47 | } 48 | 49 | // GetUserSampleUserinfobyid User Info. 50 | func GetUserSampleUserinfobyid(userid int64) *Data { 51 | coreLocker.Lock() 52 | defer coreLocker.Unlock() 53 | var ResultData Data 54 | coreSaver.Find("userinfo", &ResultData, "Where userid is "+strconv.FormatInt(userid, 10)) 55 | if &ResultData == nil { 56 | return nil 57 | } 58 | return &ResultData 59 | } 60 | 61 | func StoreUserDataBase(userid int64, userName string) error { 62 | coreLocker.Lock() 63 | defer coreLocker.Unlock() 64 | return coreSaver.Insert("userinfo", &Data{UserID: userid, UserName: userName}) 65 | } 66 | 67 | func initDatabase() error { 68 | coreLocker.Lock() 69 | defer coreLocker.Unlock() 70 | return coreSaver.Create("userinfo", &Data{}) 71 | } 72 | --------------------------------------------------------------------------------