├── .drone.yml ├── .github └── workflows │ └── goreleaser.yml ├── .gitignore ├── .goreleaser.yml ├── BanWord └── main.go ├── Bili ├── bili.go ├── live.go └── main.go ├── Config ├── config.go └── dbConfig.go ├── Core └── Core.go ├── Dockerfile ├── GroupManager ├── Chat │ ├── Local │ │ └── main.go │ ├── Moli │ │ └── main.go │ ├── XiaoI │ │ └── main.go │ ├── Zhai │ │ └── main.go │ ├── Zhai2 │ │ └── main.go │ └── main.go ├── QunInfo │ └── main.go ├── Web │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .postcssrc.js │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── babel.config.js │ ├── jsconfig.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── icons │ │ │ ├── favicon-128x128.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ └── favicon-96x96.png │ ├── quasar.conf.js │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── ava.png │ │ │ ├── bg.jpg │ │ │ ├── bg1.jpg │ │ │ ├── headbg.png │ │ │ └── quasar-logo-full.svg │ │ ├── boot │ │ │ ├── .gitkeep │ │ │ └── axios.js │ │ ├── components │ │ │ └── EssentialLink.vue │ │ ├── css │ │ │ ├── app.scss │ │ │ └── quasar.variables.scss │ │ ├── index.template.html │ │ ├── layouts │ │ │ └── MainLayout.vue │ │ ├── pages │ │ │ ├── Error.vue │ │ │ ├── Error404.vue │ │ │ ├── Index.vue │ │ │ └── login.vue │ │ ├── router │ │ │ ├── index.js │ │ │ └── routes.js │ │ └── store │ │ │ ├── index.js │ │ │ ├── module-example │ │ │ ├── actions.js │ │ │ ├── getters.js │ │ │ ├── index.js │ │ │ ├── mutations.js │ │ │ └── state.js │ │ │ └── store-flag.d.ts │ └── yarn.lock └── main.go ├── LICENSE ├── README.md ├── androidDns ├── dns.go └── dns_android.go ├── config.yaml.example ├── dictionary.txt ├── draw ├── ArialEnUnicodeBold.ttf ├── bg.png ├── draw.go └── techno-hideo-1.ttf ├── font.ttf ├── genAndYiqin └── main.go ├── githubManager ├── hookManager.go └── main.go ├── go.mod ├── kiss ├── main.go └── static │ ├── 1.png │ ├── 10.png │ ├── 11.png │ ├── 12.png │ ├── 13.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── logo.txt ├── main.go ├── pic ├── p1.png ├── p2.png ├── p3.png └── p4.png ├── setu ├── lolicon │ └── lolicon.go ├── main.go ├── pixiv │ ├── model.go │ └── pixiv.go └── setucore │ └── core.go ├── ssl └── main.go ├── taobao └── main.go ├── update.go ├── upx.sh ├── utils ├── cron.go ├── csrf.go ├── dns.go ├── ssl.go └── tools.go └── wordCloud └── main.go /.drone.yml: -------------------------------------------------------------------------------- 1 | # .drone.yml 2 | kind: pipeline 3 | name: default 4 | steps: 5 | - name: fetch 6 | image: docker:git 7 | commands: 8 | - git fetch --tags 9 | - name: Build Web 10 | image: node 11 | commands: 12 | - export NODE_OPTIONS=--openssl-legacy-provider 13 | - npm config set registry https://registry.npmjs.org 14 | - yarn config set registry https://registry.yarnpkg.com 15 | - npm install -g @quasar/cli 16 | - cd GroupManager/Web;yarn install;quasar build 17 | - name: test 18 | image: golang 19 | commands: 20 | - go mod tidy 21 | - go test -v ./... 22 | - name: release 23 | image: goreleaser/goreleaser 24 | environment: 25 | GITEA_TOKEN: 26 | from_secret: GITEA_TOKEN 27 | commands: 28 | - goreleaser release 29 | when: 30 | event: tag 31 | gitea_urls: 32 | api: https://git.mcenjoy.cn/api/v1/ 33 | download: https://git.mcenjoy.cn 34 | # set to true if you use a self-signed certificate 35 | skip_tls_verify: true -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'beta' 8 | tags: 9 | - 'v*' 10 | workflow_dispatch: 11 | inputs: 12 | version: 13 | description: 'Version (No "v")' 14 | required: true 15 | type: string 16 | pull_request: 17 | 18 | jobs: 19 | goreleaser: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - 23 | name: Checkout 24 | uses: actions/checkout@v2 25 | with: 26 | fetch-depth: 0 27 | - uses: actions/setup-node@v2 28 | with: 29 | node-version: '14' 30 | - 31 | name: Build Web 32 | run: | 33 | npm config set registry https://registry.npmjs.org 34 | npm install yarn -g 35 | yarn config set registry https://registry.yarnpkg.com 36 | npm install -g @quasar/cli 37 | chmod +x upx.sh;cd GroupManager/Web;yarn install;quasar build 38 | - 39 | name: Set up Go 40 | uses: actions/setup-go@v2 41 | with: 42 | go-version: 1.16 43 | - 44 | name: Tests 45 | run: | 46 | go mod tidy 47 | go test -v ./... 48 | - 49 | name: Build 50 | if: success() && startsWith(github.ref, 'refs/tags/') || ${{ inputs.version }} 51 | run: | 52 | GOOS=linux GOARCH=amd64 go build -o opqbot-manager-amd64 -ldflags="-s -w" . 53 | GOOS=linux GOARCH=arm go build -o opqbot-manager-arm -ldflags="-s -w" . 54 | GOOS=linux GOARCH=arm64 go build -o opqbot-manager-arm64 -ldflags="-s -w" . 55 | GOOS=linux GOARCH=386 go build -o opqbot-manager-386 -ldflags="-s -w" . 56 | ls 57 | - 58 | name: Set up QEMU 59 | if: success() && startsWith(github.ref, 'refs/tags/') || ${{ inputs.version }} 60 | uses: docker/setup-qemu-action@v1 61 | - 62 | name: Set up Docker Buildx 63 | if: success() && startsWith(github.ref, 'refs/tags/') || ${{ inputs.version }} 64 | uses: docker/setup-buildx-action@v1 65 | - name: Log into registry 66 | if: success() && startsWith(github.ref, 'refs/tags/') || ${{ inputs.version }} 67 | uses: docker/login-action@v1 68 | with: 69 | username: ${{ secrets.DOCKER_USERNAME }} 70 | password: ${{ secrets.DOCKER_HUB_TOKEN }} 71 | - 72 | name: Get Version 73 | id: get_version 74 | uses: battila7/get-version-action@v2.2.1 75 | - 76 | name: Build and push 77 | if: success() && startsWith(github.ref, 'refs/tags/') 78 | id: docker_build 79 | uses: docker/build-push-action@v2 80 | with: 81 | context: . 82 | push: true 83 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386 84 | tags: mcenjoy/opqbot-groupmanager:latest,${{ format('mcenjoy/opqbot-groupmanager:{0}', steps.get_version.outputs.version) }} 85 | - 86 | name: Build and push (manual) 87 | if: ${{ inputs.version }} 88 | id: docker_build_manual 89 | uses: docker/build-push-action@v2 90 | with: 91 | context: . 92 | push: true 93 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386 94 | tags: mcenjoy/opqbot-groupmanager:latest,${{ format('mcenjoy/opqbot-groupmanager:{0}', inputs.version) }} 95 | - 96 | name: Run GoReleaser 97 | uses: goreleaser/goreleaser-action@v2 98 | if: success() && startsWith(github.ref, 'refs/tags/') 99 | with: 100 | version: "v0.173.2" 101 | args: release --rm-dist --skip-validate 102 | env: 103 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /.*/ 3 | /.* 4 | /*.ttf 5 | config.yaml 6 | go.sum 7 | *.exe 8 | *.exe~ 9 | *.dllq 10 | *.so 11 | *.txt 12 | *.db 13 | *.dylib 14 | *.log 15 | _* 16 | dist/ 17 | OPQBot-QQGroupManager 18 | !.goreleaser.yml 19 | !logo.txt 20 | *_test.go 21 | chat.json 22 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goarm: 13 | - 6 14 | - 7 15 | goarch: 16 | - amd64 17 | - arm 18 | - arm64 19 | - 386 20 | goos: 21 | - linux 22 | - windows 23 | - darwin 24 | - android 25 | ignore: 26 | - goos: android 27 | goarch: arm 28 | - goos: darwin 29 | goarch: 386 30 | - goos: darwin 31 | goarch: arm 32 | - goos: windows 33 | goarch: arm 34 | - goos: windows 35 | goarch: arm64 36 | - goos: android 37 | goarch: 386 38 | archives: 39 | - 40 | name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 41 | format_overrides: 42 | - goos: windows 43 | format: zip 44 | replacements: 45 | darwin: Darwin 46 | linux: Linux 47 | windows: Windows 48 | 386: i386 49 | amd64: x86_64 50 | files: 51 | - config.yaml.example 52 | - LICENSE 53 | - README.md 54 | - font.ttf 55 | - dictionary.txt 56 | release: 57 | prerelease: auto 58 | checksum: 59 | name_template: 'checksums.txt' 60 | project_name: opqbot-manager 61 | changelog: 62 | sort: asc 63 | filters: 64 | exclude: 65 | - '^docs:' 66 | - '^test:' 67 | gitea_urls: 68 | api: https://git.mcenjoy.cn/api/v1/ 69 | download: https://git.mcenjoy.cn 70 | # set to true if you use a self-signed certificate 71 | skip_tls_verify: true 72 | -------------------------------------------------------------------------------- /BanWord/main.go: -------------------------------------------------------------------------------- 1 | package BanWord 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "github.com/mcoo/OPQBot" 7 | "log" 8 | "regexp" 9 | ) 10 | 11 | func Hook(b *Core.Bot) error { 12 | b.AddEvent(OPQBot.EventNameOnGroupMessage, func(botQQ int64, packet *OPQBot.GroupMsgPack) { 13 | if packet.FromUserID == botQQ { 14 | return 15 | } 16 | Config.Lock.RLock() 17 | var c Config.GroupConfig 18 | if v, ok := Config.CoreConfig.GroupConfig[packet.FromGroupID]; ok { 19 | c = v 20 | } else { 21 | c = Config.CoreConfig.DefaultGroupConfig 22 | } 23 | banQQ := Config.CoreConfig.BanQQ 24 | Config.Lock.RUnlock() 25 | 26 | for _, v := range banQQ { 27 | if packet.FromUserID == v { 28 | packet.Ban = true 29 | return 30 | } 31 | } 32 | 33 | if !c.Enable { 34 | return 35 | } 36 | if m, err := regexp.MatchString(c.ShutUpWord, packet.Content); err != nil { 37 | log.Println(err) 38 | return 39 | } else if m { 40 | err := packet.Bot.ReCallMsg(packet.FromGroupID, packet.MsgRandom, packet.MsgSeq) 41 | if err != nil { 42 | log.Println(err) 43 | } 44 | err = packet.Bot.SetForbidden(1, c.ShutUpTime, packet.FromGroupID, packet.FromUserID) 45 | if err != nil { 46 | log.Println(err) 47 | } 48 | packet.Bot.SendGroupTextMsg(packet.FromGroupID, OPQBot.MacroAt([]int64{packet.FromUserID})+"触发违禁词") 49 | packet.Ban = true 50 | return 51 | } 52 | }) 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /Bili/live.go: -------------------------------------------------------------------------------- 1 | package bili 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/gaoyanpao/biliLiveHelper" 7 | "sync" 8 | ) 9 | 10 | type LiveManager struct { 11 | biliClient map[int]*Client 12 | lock sync.RWMutex 13 | } 14 | 15 | type Client struct { 16 | C *biliLiveHelper.Client 17 | OnDanmu func(ctx *biliLiveHelper.Context) 18 | OnGift func(ctx *biliLiveHelper.Context) 19 | OnWelcome func(ctx *biliLiveHelper.Context) 20 | } 21 | 22 | func NewLiveManager() *LiveManager { 23 | return &LiveManager{biliClient: make(map[int]*Client), lock: sync.RWMutex{}} 24 | } 25 | 26 | func (l *LiveManager) AddClient(RoomId int) (c *Client, e error) { 27 | l.lock.Lock() 28 | defer l.lock.Unlock() 29 | client := biliLiveHelper.NewClient(RoomId) 30 | if client == nil { 31 | e = errors.New("Client Err ") 32 | return 33 | } 34 | client.RegHandleFunc(biliLiveHelper.CmdDanmuMsg, func(ctx *biliLiveHelper.Context) bool { 35 | if c.OnDanmu != nil { 36 | c.OnDanmu(ctx) 37 | } 38 | return false 39 | }) 40 | client.RegHandleFunc(biliLiveHelper.CmdSendGift, func(ctx *biliLiveHelper.Context) bool { 41 | if c.OnGift != nil { 42 | c.OnGift(ctx) 43 | } 44 | return false 45 | }) 46 | client.RegHandleFunc(biliLiveHelper.CmdWelcome, func(ctx *biliLiveHelper.Context) bool { 47 | if c.OnWelcome != nil { 48 | c.OnWelcome(ctx) 49 | } 50 | return false 51 | }) 52 | c = &Client{C: client} 53 | if _, ok := l.biliClient[RoomId]; ok { 54 | e = errors.New("已加入了Room") 55 | return 56 | } 57 | l.biliClient[RoomId] = c 58 | return 59 | } 60 | 61 | func (l *LiveManager) RemoveClient(RoomId int) error { 62 | l.lock.Lock() 63 | defer l.lock.Unlock() 64 | if v, ok := l.biliClient[RoomId]; ok { 65 | if v.C.IsConnected() { 66 | v.ExitRoom() 67 | } 68 | delete(l.biliClient, RoomId) 69 | return nil 70 | } else { 71 | return errors.New("Client不存在! ") 72 | } 73 | } 74 | 75 | func (c *Client) RegisterDanmuFunc(f func(ctx *biliLiveHelper.Context)) { 76 | c.OnDanmu = f 77 | } 78 | 79 | func (c *Client) RegisterGiftFunc(f func(ctx *biliLiveHelper.Context)) { 80 | c.OnGift = f 81 | } 82 | func (c *Client) RegisterWelcomeFunc(f func(ctx *biliLiveHelper.Context)) { 83 | c.OnWelcome = f 84 | } 85 | 86 | func (c *Client) GetRoomInfo() biliLiveHelper.SimpleRoomInfo { 87 | return c.C.SimpleRoomInfo 88 | } 89 | 90 | func (c *Client) ExitRoom() error { 91 | return c.C.Conn.Close() 92 | } 93 | 94 | // Start 阻塞 95 | func (c *Client) Start() error { 96 | 97 | return c.C.StartListen() 98 | } 99 | func GetLiveStatusString(status int) string { 100 | switch status { 101 | case 0: 102 | return "未开播" 103 | case 1: 104 | return "直播中" 105 | case 2: 106 | return "轮播中" 107 | default: 108 | return fmt.Sprintf("未知状态%d", status) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Bili/main.go: -------------------------------------------------------------------------------- 1 | package bili 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "encoding/base64" 7 | "fmt" 8 | "github.com/gaoyanpao/biliLiveHelper" 9 | "github.com/mcoo/OPQBot" 10 | "github.com/mcoo/requests" 11 | "github.com/sirupsen/logrus" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | type Module struct { 17 | } 18 | 19 | var log *logrus.Entry 20 | 21 | func (m *Module) ModuleInfo() Core.ModuleInfo { 22 | return Core.ModuleInfo{ 23 | Name: "Bili订阅姬", 24 | Author: "enjoy", 25 | Description: "订阅bilibili番剧和UP", 26 | Version: 0, 27 | } 28 | } 29 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 30 | log = l 31 | bi := NewManager() 32 | live := NewLiveManager() 33 | b.BotCronManager.AddJob(-1, "Bili", "*/5 * * * *", func() { 34 | update, fanju := bi.ScanUpdate() 35 | for _, v := range update { 36 | upName, gs, userId := bi.GetUpGroupsByMid(v.Mid) 37 | for _, g := range gs { 38 | if v1, ok := Config.CoreConfig.GroupConfig[g]; ok { 39 | if !v1.Bili { 40 | break 41 | } 42 | } 43 | res, _ := requests.Get(v.Pic) 44 | if userId == 0 { 45 | b.Send(OPQBot.SendMsgPack{ 46 | SendToType: OPQBot.SendToTypeGroup, 47 | ToUserUid: g, 48 | Content: OPQBot.SendTypePicMsgByBase64Content{ 49 | Content: fmt.Sprintf("不知道是谁订阅的UP主%s更新了\n%s\n%s", upName, v.Title, v.Description), 50 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 51 | Flash: false, 52 | }, 53 | }) 54 | } else { 55 | b.Send(OPQBot.SendMsgPack{ 56 | SendToType: OPQBot.SendToTypeGroup, 57 | ToUserUid: g, 58 | Content: OPQBot.SendTypePicMsgByBase64Content{ 59 | Content: OPQBot.MacroAt([]int64{userId}) + fmt.Sprintf("您订阅的UP主%s更新了\n%s\n%s", upName, v.Title, v.Description), 60 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 61 | Flash: false, 62 | }, 63 | }) 64 | } 65 | 66 | } 67 | } 68 | for _, v := range fanju { 69 | title, gs, userId := bi.GetFanjuGroupsByMid(v.Result.Media.MediaID) 70 | for _, g := range gs { 71 | if v1, ok := Config.CoreConfig.GroupConfig[g]; ok { 72 | if !v1.Bili { 73 | break 74 | } 75 | } 76 | res, _ := requests.Get(v.Result.Media.Cover) 77 | if userId == 0 { 78 | b.Send(OPQBot.SendMsgPack{ 79 | SendToType: OPQBot.SendToTypeGroup, 80 | ToUserUid: g, 81 | Content: OPQBot.SendTypePicMsgByBase64Content{ 82 | Content: fmt.Sprintf("不知道是谁订阅的番剧%s更新了\n%s", title, v.Result.Media.NewEp.IndexShow), 83 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 84 | Flash: false, 85 | }, 86 | }) 87 | } else { 88 | b.Send(OPQBot.SendMsgPack{ 89 | SendToType: OPQBot.SendToTypeGroup, 90 | ToUserUid: g, 91 | Content: OPQBot.SendTypePicMsgByBase64Content{ 92 | Content: OPQBot.MacroAt([]int64{userId}) + fmt.Sprintf("您订阅的番剧%s更新了\n%s", title, v.Result.Media.NewEp.IndexShow), 93 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 94 | Flash: false, 95 | }, 96 | }) 97 | } 98 | 99 | } 100 | } 101 | }) 102 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(botQQ int64, packet *OPQBot.GroupMsgPack) { 103 | if packet.FromUserID == botQQ { 104 | return 105 | } 106 | Config.Lock.RLock() 107 | var c Config.GroupConfig 108 | if v, ok := Config.CoreConfig.GroupConfig[packet.FromGroupID]; ok { 109 | c = v 110 | } else { 111 | c = Config.CoreConfig.DefaultGroupConfig 112 | } 113 | Config.Lock.RUnlock() 114 | if !c.Enable { 115 | return 116 | } 117 | cm := strings.Split(packet.Content, " ") 118 | s := b.Session.SessionStart(packet.FromUserID) 119 | if packet.Content == "退出订阅" { 120 | err := s.Delete("biliUps") 121 | if err != nil { 122 | log.Println(err) 123 | } 124 | b.SendGroupTextMsg(packet.FromGroupID, "已经退出订阅") 125 | return 126 | } 127 | if packet.Content == "本群番剧" { 128 | if !c.Bili { 129 | return 130 | } 131 | ups := "本群订阅番剧\n" 132 | 133 | if len(c.Fanjus) == 0 { 134 | b.SendGroupTextMsg(packet.FromGroupID, "本群没有订阅番剧") 135 | return 136 | } 137 | for mid, v1 := range c.Fanjus { 138 | ups += fmt.Sprintf("%d - %s-订阅用户为:%d \n", mid, v1.Title, v1.UserId) 139 | } 140 | b.SendGroupTextMsg(packet.FromGroupID, ups) 141 | } 142 | if packet.Content == "本群up" { 143 | if !c.Bili { 144 | return 145 | } 146 | ups := "本群订阅UPs\n" 147 | 148 | if len(c.BiliUps) == 0 { 149 | b.SendGroupTextMsg(packet.FromGroupID, "本群没有订阅UP主") 150 | return 151 | } 152 | for mid, v1 := range c.BiliUps { 153 | ups += fmt.Sprintf("%d - %s -订阅者:%d\n", mid, v1.Name, v1.UserId) 154 | } 155 | b.SendGroupTextMsg(packet.FromGroupID, ups) 156 | return 157 | } 158 | if v, err := s.Get("biliUps"); err == nil { 159 | id, err := strconv.Atoi(packet.Content) 160 | if err != nil { 161 | b.SendGroupTextMsg(packet.FromGroupID, "序号错误, 输入“退出订阅”退出") 162 | return 163 | } 164 | if v1, ok := v.(map[int]int64); ok { 165 | if v2, ok := v1[id]; ok { 166 | u, err := bi.SubscribeUpByMid(packet.FromGroupID, v2, packet.FromUserID) 167 | if err != nil { 168 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 169 | err = s.Delete("biliUps") 170 | if err != nil { 171 | log.Println(err) 172 | } 173 | return 174 | } 175 | r, _ := requests.Get(u.Data.Card.Face) 176 | b.SendGroupPicMsg(packet.FromGroupID, "成功订阅UP主"+u.Data.Card.Name, r.Content()) 177 | err = s.Delete("biliUps") 178 | if err != nil { 179 | log.Println(err) 180 | } 181 | return 182 | } else { 183 | b.SendGroupTextMsg(packet.FromGroupID, "序号不存在") 184 | return 185 | } 186 | 187 | } else { 188 | b.SendGroupTextMsg(packet.FromGroupID, "内部错误") 189 | err := s.Delete("biliUps") 190 | if err != nil { 191 | log.Println(err) 192 | } 193 | return 194 | } 195 | return 196 | } 197 | if len(cm) == 2 && cm[0] == "订阅up" { 198 | if !c.Bili { 199 | return 200 | } 201 | mid, err := strconv.ParseInt(cm[1], 10, 64) 202 | if err != nil { 203 | result, err := bi.SearchUp(cm[1]) 204 | //u, err := bi.SubscribeUpByKeyword(packet.FromGroupID, cm[1]) 205 | 206 | if err != nil { 207 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 208 | return 209 | } 210 | var ( 211 | resultStr []string 212 | r = map[int]int64{} 213 | ) 214 | i := 0 215 | for _, v := range result.Data.Result { 216 | if v.IsUpuser == 1 { 217 | resultStr = append(resultStr, fmt.Sprintf("[%d] %s(lv.%d) 粉丝数:%d", i+1, v.Uname, v.Level, v.Fans)) 218 | r[i+1] = v.Mid 219 | i++ 220 | if len(r) >= 6 { 221 | break 222 | } 223 | } 224 | } 225 | if len(r) == 0 { 226 | b.SendGroupTextMsg(packet.FromGroupID, "没有找到UP哟~") 227 | return 228 | } 229 | err = s.Set("biliUps", r) 230 | if err != nil { 231 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 232 | return 233 | } 234 | b.SendGroupTextMsg(packet.FromGroupID, fmt.Sprintf("====输入序号选择UP====\n%s", strings.Join(resultStr, "\n"))) 235 | return 236 | } 237 | u, err := bi.SubscribeUpByMid(packet.FromGroupID, mid, packet.FromUserID) 238 | if err != nil { 239 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 240 | return 241 | } 242 | r, _ := requests.Get(u.Data.Card.Face) 243 | b.SendGroupPicMsg(packet.FromGroupID, "成功订阅UP主"+u.Data.Card.Name, r.Content()) 244 | return 245 | } 246 | if len(cm) == 2 && cm[0] == "取消订阅up" { 247 | if !c.Bili { 248 | return 249 | } 250 | mid, err := strconv.ParseInt(cm[1], 10, 64) 251 | if err != nil { 252 | b.SendGroupTextMsg(packet.FromGroupID, "只能使用Mid取消订阅欧~") 253 | return 254 | } 255 | err = bi.UnSubscribeUp(packet.FromGroupID, mid) 256 | if err != nil { 257 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 258 | return 259 | } 260 | b.SendGroupTextMsg(packet.FromGroupID, "成功取消订阅UP主") 261 | return 262 | } 263 | if len(cm) == 2 && cm[0] == "订阅番剧" { 264 | if !c.Bili { 265 | return 266 | } 267 | mid, err := strconv.ParseInt(cm[1], 10, 64) 268 | if err != nil { 269 | u, err := bi.SubscribeFanjuByKeyword(packet.FromGroupID, cm[1], packet.FromUserID) 270 | if err != nil { 271 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 272 | return 273 | } 274 | r, _ := requests.Get(u.Result.Media.Cover) 275 | b.SendGroupPicMsg(packet.FromGroupID, "成功订阅番剧"+u.Result.Media.Title, r.Content()) 276 | return 277 | } 278 | u, err := bi.SubscribeFanjuByMid(packet.FromGroupID, mid, packet.FromUserID) 279 | if err != nil { 280 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 281 | return 282 | } 283 | r, _ := requests.Get(u.Result.Media.Cover) 284 | b.SendGroupPicMsg(packet.FromGroupID, "成功订阅番剧"+u.Result.Media.Title, r.Content()) 285 | return 286 | } 287 | if len(cm) == 2 && cm[0] == "取消订阅番剧" { 288 | if !c.Bili { 289 | return 290 | } 291 | mid, err := strconv.ParseInt(cm[1], 10, 64) 292 | if err != nil { 293 | b.SendGroupTextMsg(packet.FromGroupID, "只能使用Mid取消订阅欧~") 294 | return 295 | } 296 | err = bi.UnSubscribeFanju(packet.FromGroupID, mid) 297 | if err != nil { 298 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 299 | return 300 | } 301 | b.SendGroupTextMsg(packet.FromGroupID, "成功取消订阅番剧") 302 | return 303 | } 304 | if len(cm) == 2 && cm[0] == "stopBili" { 305 | s, err := strconv.Atoi(cm[1]) 306 | if err != nil { 307 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 308 | return 309 | } 310 | err = live.RemoveClient(s) 311 | if err != nil { 312 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 313 | return 314 | } 315 | b.SendGroupTextMsg(packet.FromGroupID, "已经断开连接了") 316 | return 317 | } 318 | if len(cm) == 2 && cm[0] == "biliLive" { 319 | if !Config.CoreConfig.BiliLive { 320 | b.SendGroupTextMsg(packet.FromGroupID, "该功能没有启动") 321 | return 322 | } 323 | s, err := strconv.Atoi(cm[1]) 324 | if err != nil { 325 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 326 | return 327 | } 328 | c, err := live.AddClient(s) 329 | if err != nil { 330 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 331 | return 332 | } 333 | c.OnGift = func(ctx *biliLiveHelper.Context) { 334 | data := ctx.Msg 335 | r, _ := requests.Get(data.Get("data").Get("face").MustString()) 336 | b.SendGroupPicMsg(packet.FromGroupID, fmt.Sprintf("%s%s%s", data.Get("data").Get("uname").MustString(), data.Get("data").Get("action").MustString(), data.Get("data").Get("giftName").MustString()), r.Content()) 337 | } 338 | go c.Start() 339 | info := c.GetRoomInfo() 340 | b.SendGroupTextMsg(packet.FromGroupID, fmt.Sprintf("房间: %s[%d]\n关注: %d\n人气: %d\n直播状态: %v", 341 | info.Title, 342 | info.RoomID, 343 | info.Attention, 344 | info.Online, 345 | GetLiveStatusString(info.LiveStatus))) 346 | return 347 | } 348 | }) 349 | if err != nil { 350 | return err 351 | } 352 | return nil 353 | } 354 | 355 | func init() { 356 | Core.RegisterModule(&Module{}) 357 | } 358 | -------------------------------------------------------------------------------- /Config/config.go: -------------------------------------------------------------------------------- 1 | package Config 2 | 3 | import ( 4 | "github.com/go-playground/webhooks/v6/github" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "sync" 9 | 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | type CoreConfigStruct struct { 14 | OPQWebConfig struct { 15 | CSRF string 16 | Host string 17 | Port int 18 | Username string 19 | Password string 20 | Enable bool 21 | } 22 | OPQBotConfig struct { 23 | Url string 24 | QQ int64 25 | } 26 | DBConfig struct { 27 | DBType string 28 | DBUserName string 29 | DBPassword string 30 | DBIP string 31 | DBPort string 32 | DBName string 33 | } 34 | SetuConfig struct { 35 | AutoGetPic bool 36 | PixivRefreshToken string 37 | PixivProxy string 38 | } 39 | SelectChatCore string 40 | ChatKey struct { 41 | XiaoI struct { 42 | Key string 43 | Secret string 44 | } 45 | Moli struct { 46 | Key string 47 | Secret string 48 | } 49 | } 50 | HtmlToImgUrl string 51 | DisableModule []string 52 | BanQQ []int64 53 | Debug bool 54 | BiliLive bool 55 | YiQing bool 56 | ReverseProxy string 57 | DefaultGroupConfig GroupConfig 58 | SuperAdminUin int64 59 | WhiteGroupList []int64 60 | BlackGroupList []int64 61 | GroupConfig map[int64]GroupConfig 62 | UserData map[int64]UserData 63 | GithubSub map[string]Repo 64 | LogLevel string 65 | } 66 | type Repo struct { 67 | Secret string 68 | WebHook *github.Webhook 69 | Groups []int64 70 | } 71 | type UserData struct { 72 | LastSignDay int 73 | LastZanDay int 74 | Count int 75 | SteamShare string 76 | } 77 | type Job struct { 78 | Cron string 79 | Type int 80 | Title string 81 | Content string 82 | } 83 | type GroupConfig struct { 84 | Enable bool 85 | EnableChat bool 86 | AdminUin int64 87 | Menu string 88 | MenuKeyWord string 89 | ShutUpWord string 90 | ShutUpTime int 91 | JoinVerifyTime int 92 | JoinAutoShutUpTime int 93 | Zan bool 94 | SignIn bool 95 | Bili bool 96 | BiliUps map[int64]Up 97 | Fanjus map[int64]Fanju 98 | Welcome string 99 | JoinVerifyType int 100 | Job map[string]Job 101 | } 102 | type Up struct { 103 | Name string 104 | Created int64 105 | UserId int64 106 | } 107 | type Fanju struct { 108 | Title string 109 | Id int64 110 | UserId int64 111 | } 112 | 113 | var ( 114 | CoreConfig = &CoreConfigStruct{} 115 | Lock = sync.RWMutex{} 116 | ) 117 | 118 | func Save() error { 119 | b, err := yaml.Marshal(CoreConfig) 120 | if err != nil { 121 | return err 122 | } 123 | err = ioutil.WriteFile("./config.yaml", b, 0777) 124 | if err != nil { 125 | return err 126 | } 127 | return nil 128 | } 129 | 130 | func init() { 131 | //watcher, err := fsnotify.NewWatcher() 132 | //if err != nil { 133 | // log.Fatal(err) 134 | //} 135 | 136 | if len(os.Args) == 2 && os.Args[1] == "first" { 137 | b, err := yaml.Marshal(CoreConfig) 138 | if err != nil { 139 | panic(err) 140 | } 141 | err = ioutil.WriteFile("./config.yaml.example", b, 0777) 142 | if err != nil { 143 | panic(err) 144 | } 145 | panic("已将默认配置文件写出") 146 | } 147 | b, err := ioutil.ReadFile("./config.yaml") 148 | if err != nil { 149 | log.Println("读取配置文件失败") 150 | panic(err) 151 | } 152 | err = yaml.Unmarshal(b, &CoreConfig) 153 | if err != nil { 154 | log.Println("读取配置文件失败") 155 | panic(err) 156 | } 157 | Save() 158 | //go func() { 159 | // for { 160 | // select { 161 | // case event, ok := <-watcher.Events: 162 | // if !ok { 163 | // return 164 | // } 165 | // if event.Op&fsnotify.Write == fsnotify.Write { 166 | // b, err := ioutil.ReadFile("./config.yaml") 167 | // if err != nil { 168 | // log.Println("读取配置文件失败") 169 | // break 170 | // } 171 | // Lock.Lock() 172 | // err = yaml.Unmarshal(b, &CoreConfig) 173 | // Lock.Unlock() 174 | // if err != nil { 175 | // log.Println("读取配置文件失败") 176 | // break 177 | // } 178 | // } 179 | // case err, ok := <-watcher.Errors: 180 | // if !ok { 181 | // return 182 | // } 183 | // log.Println("error:", err) 184 | // } 185 | // } 186 | //}() 187 | //err = watcher.Add("./config.yaml") 188 | //if err != nil { 189 | // log.Fatal(err) 190 | //} 191 | dbInit() 192 | } 193 | -------------------------------------------------------------------------------- /Config/dbConfig.go: -------------------------------------------------------------------------------- 1 | package Config 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "github.com/mcoo/sqlite" 8 | "gorm.io/driver/mysql" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | var ( 13 | DB *gorm.DB 14 | sqlDb *sql.DB 15 | //WithUps = SqlArg{ArgId: 1} 16 | //WithJobs = SqlArg{ArgId: 2} 17 | //WithFanjus = SqlArg{ArgId: 3} 18 | //WithGroups = SqlArg{ArgId: 4} 19 | ) 20 | 21 | func dbInit() { 22 | var err error 23 | conn := "" 24 | if CoreConfig.DBConfig.DBType == "mysql" { 25 | conn = fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", CoreConfig.DBConfig.DBUserName, CoreConfig.DBConfig.DBPassword, CoreConfig.DBConfig.DBIP, CoreConfig.DBConfig.DBPort, CoreConfig.DBConfig.DBName) 26 | DB, err = gorm.Open(mysql.Open(conn), &gorm.Config{}) 27 | } else if CoreConfig.DBConfig.DBType == "sqlite3" { 28 | conn = fmt.Sprintf("./%v.db", CoreConfig.DBConfig.DBName) 29 | DB, err = gorm.Open(sqlite.Open(conn), &gorm.Config{}) 30 | } else { 31 | panic(errors.New("not supported database adapter")) 32 | } 33 | if err != nil { 34 | panic(err) 35 | } 36 | sqlDb, err = DB.DB() 37 | if err == nil { 38 | sqlDb.SetMaxIdleConns(10) 39 | sqlDb.SetMaxOpenConns(100) 40 | } 41 | Lock.RLock() 42 | if CoreConfig.Debug { 43 | DB = DB.Debug() 44 | } 45 | Lock.RUnlock() 46 | //err = DB.AutoMigrate(&QQGroup{}, &SQLUp{}, &SQLFanju{},&SQLJob{}) 47 | //if err != nil { 48 | // log.Println(err) 49 | //} 50 | } 51 | 52 | //type QQGroup struct { 53 | // GroupID int64 `gorm:"primaryKey"` 54 | // Enable bool 55 | // AdminUin int64 56 | // Menu string 57 | // MenuKeyWord string 58 | // ShutUpWord string 59 | // ShutUpTime int 60 | // JoinVerifyTime int 61 | // JoinAutoShutUpTime int 62 | // Zan bool 63 | // SignIn bool 64 | // Bili bool 65 | // BiliUps []*SQLUp `gorm:"many2many:up_groups;foreignKey:GroupID"` 66 | // Fanjus []*SQLFanju `gorm:"many2many:fanju_groups;foreignKey:GroupID"` 67 | // Welcome string 68 | // JoinVerifyType int 69 | // Job []*SQLJob `gorm:"many2many:job_groups;foreignKey:GroupID"` 70 | //} 71 | //type SQLUp struct { 72 | // Name string 73 | // Created int64 74 | // UserId int64 `gorm:"primaryKey"` 75 | // Groups []*QQGroup `gorm:"many2many:up_groups;foreignKey:UserId"` 76 | //} 77 | //type SQLJob struct { 78 | // gorm.Model 79 | // Cron string 80 | // Type int 81 | // Title string 82 | // Content string 83 | // Groups []*QQGroup `gorm:"many2many:job_groups;"` 84 | //} 85 | //type SQLFanju struct { 86 | // Title string 87 | // Id int64 88 | // UserId int64 89 | // Groups []*QQGroup `gorm:"many2many:fanju_groups;foreignKey:Id"` 90 | //} 91 | //type SqlArg struct { 92 | // ArgId int 93 | //} 94 | //func SubUp(groupId,upId int64) { 95 | // 96 | //} 97 | //func DelUpSub(groupId int64,upId int64) error { 98 | // if upId == 0 || groupId == 0 { 99 | // return errors.New("参数非法") 100 | // } 101 | // c := DB 102 | // if CoreConfig.Debug { 103 | // c = c.Debug() 104 | // } 105 | // err := c.Model(&QQGroup{GroupID: groupId}).Association("BiliUps").Delete(SQLUp{UserId: upId}) 106 | // if err != nil { 107 | // return err 108 | // } 109 | // if DB.Model(&SQLUp{UserId: upId}).Association("Groups").Count() == 0 { 110 | // err = DB.Delete(&SQLUp{},upId).Error 111 | // } 112 | // return err 113 | //} 114 | //func GetGroupConfig(groupId int64,args ... SqlArg) (g QQGroup,e error) { 115 | // c := DB 116 | // if CoreConfig.Debug { 117 | // c = c.Debug() 118 | // } 119 | // for _,v := range args { 120 | // switch v.ArgId { 121 | // case 1: 122 | // c = c.Preload("BiliUps") 123 | // case 2: 124 | // c = c.Preload("Job") 125 | // case 3: 126 | // c = c.Preload("Fanjus") 127 | // } 128 | // } 129 | // e = c.Where("group_id = ?",groupId).First(&g).Error 130 | // return 131 | //} 132 | //func GetBiliUps(userId int64,args ... SqlArg) (g SQLUp,e error) { 133 | // c := DB 134 | // if CoreConfig.Debug { 135 | // c = c.Debug() 136 | // } 137 | // for _,v := range args { 138 | // switch v.ArgId { 139 | // case 4: 140 | // c = c.Preload("Groups") 141 | // } 142 | // } 143 | // e = c.Where("user_id = ?",userId).First(&g).Error 144 | // return 145 | //} 146 | -------------------------------------------------------------------------------- /Core/Core.go: -------------------------------------------------------------------------------- 1 | package Core 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/utils" 6 | "errors" 7 | "fmt" 8 | nested "github.com/antonfisher/nested-logrus-formatter" 9 | "github.com/mcoo/OPQBot" 10 | "github.com/sirupsen/logrus" 11 | "gorm.io/gorm" 12 | "runtime" 13 | ) 14 | 15 | var log = logrus.New() 16 | 17 | var Modules = make(map[string]Module) 18 | 19 | func init() { 20 | log.SetLevel(logrus.InfoLevel) 21 | log.SetFormatter(&nested.Formatter{ 22 | HideKeys: true, 23 | FieldsOrder: []string{"component", "category"}, 24 | TimestampFormat: "2006-01-02 15:04:05", 25 | }) 26 | } 27 | func GetLog() *logrus.Logger { 28 | return log 29 | } 30 | 31 | var hasLoad []string 32 | 33 | func hasLoaded(infoName string) bool { 34 | tmp := false 35 | for _, v1 := range hasLoad { 36 | if v1 == infoName { 37 | tmp = true 38 | break 39 | } 40 | } 41 | return tmp 42 | } 43 | func InitModule(b *Bot) { 44 | for _, v := range Modules { 45 | if err := startModule(b, v); err != nil { 46 | log.Error(err) 47 | } 48 | } 49 | } 50 | func startModule(b *Bot, module Module) error { 51 | info := module.ModuleInfo() 52 | if hasLoaded(info.Name) { 53 | return nil 54 | } 55 | l := log.WithField("Module", info.Name) 56 | for _, v2 := range info.RequireModule { 57 | if !hasLoaded(v2) { 58 | if v, ok := Modules[v2]; ok { 59 | err := startModule(b, v) 60 | if err != nil { 61 | return err 62 | } 63 | } else { 64 | return errors.New(fmt.Sprintf("缺少依赖%s 导入失败\n", v2)) 65 | } 66 | } 67 | } 68 | 69 | l.Infof("Author: %s - %s", info.Author, info.Description) 70 | l.Info("正在载入中") 71 | err := module.ModuleInit(b, l) 72 | if err != nil { 73 | l.Error("导入模块时出错!", err) 74 | } 75 | l.Infof("载入成功") 76 | hasLoad = append(hasLoad, info.Name) 77 | return nil 78 | } 79 | func RegisterModule(module Module) error { 80 | Config.Lock.RLock() 81 | disable := Config.CoreConfig.DisableModule 82 | Config.Lock.RUnlock() 83 | for _, v := range disable { 84 | if v == module.ModuleInfo().Name { 85 | log.Warn(module.ModuleInfo().Name + "模块已被禁止载入") 86 | return errors.New("模块已被禁止载入") 87 | } 88 | } 89 | if _, ok := Modules[module.ModuleInfo().Name]; ok { 90 | log.Error(module.ModuleInfo().Name + "模块名字已经被注册了") 91 | return errors.New(module.ModuleInfo().Name + "模块名字已经被注册了") 92 | } else { 93 | Modules[module.ModuleInfo().Name] = module 94 | } 95 | return nil 96 | } 97 | 98 | var lastTotalFreed uint64 99 | 100 | func (b *Bot) PrintMemStats() { 101 | var m runtime.MemStats 102 | runtime.ReadMemStats(&m) 103 | log.Printf("Alloc = %v TotalAlloc = %v Just Freed = %v Sys = %v NumGC = %v\n", 104 | m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC) 105 | lastTotalFreed = m.TotalAlloc - m.Alloc 106 | } 107 | 108 | // Bot 内置了"周期任务","数据库" 109 | type Bot struct { 110 | *OPQBot.BotManager 111 | BotCronManager utils.BotCron 112 | Modules map[string]*Module 113 | DB *gorm.DB 114 | } 115 | type ModuleInfo struct { 116 | Name string 117 | Author string 118 | Description string 119 | Version int 120 | RequireModule []string 121 | } 122 | type Module interface { 123 | ModuleInit(bot *Bot, log *logrus.Entry) error 124 | ModuleInfo() ModuleInfo 125 | } 126 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #FROM golang:alpine AS build 2 | #WORKDIR $GOPATH/src 3 | FROM alpine:latest AS build 4 | WORKDIR /apps 5 | ARG TARGETPLATFORM 6 | ARG BUILDPLATFORM 7 | COPY . . 8 | RUN ls -lh && echo $TARGETPLATFORM \ 9 | && [[ "$TARGETPLATFORM" == "linux/amd64" ]] \ 10 | && mv /apps/opqbot-manager-amd64 /apps/opqbot-manager || echo "not amd64" \ 11 | && [[ "$TARGETPLATFORM" == "linux/arm64" ]] \ 12 | && mv /apps/opqbot-manager-arm64 /apps/opqbot-manager || echo "not arm64" \ 13 | && [[ "$TARGETPLATFORM" == "linux/arm/v7" ]] \ 14 | && mv /apps/opqbot-manager-arm /apps/opqbot-manager || echo "not arm" \ 15 | && [[ "$TARGETPLATFORM" == "linux/386" ]] \ 16 | && mv /apps/opqbot-manager-386 /apps/opqbot-manager || echo "not 386" 17 | 18 | 19 | 20 | # if [[ "$TARGETPLATFORM" = "linux/amd64" ]]; \ 21 | # then \ 22 | # mv ./opqbot-manager-amd64 ./opqbot-manager; \ 23 | # fi \ 24 | # && if ["$TARGETPLATFORM" = "linux/arm64"]; \ 25 | # then \ 26 | # mv ./opqbot-manager-arm64 ./opqbot-manager; \ 27 | # fi \ 28 | # && if ["$TARGETPLATFORM" = "linux/arm/v7"]; \ 29 | # then \ 30 | # mv ./opqbot-manager-arm ./opqbot-manager; \ 31 | # fi \ 32 | # && if ["$TARGETPLATFORM" = "linux/386"]; \ 33 | # then \ 34 | # mv ./opqbot-manager-386 ./opqbot-manager; \ 35 | # fi 36 | 37 | RUN apk add upx \ 38 | && upx opqbot-manager \ 39 | || echo "UPX Install Failed!" 40 | # RUN go mod tidy\ 41 | # && go build -o opqbot-manager -ldflags="-s -w" . \ 42 | # && apk add upx \ 43 | # && upx opqbot-manager \ 44 | # || echo "UPX Install Failed!" 45 | 46 | FROM alpine:latest 47 | LABEL MAINTAINER enjoy 48 | ENV VERSION 1.0 49 | # create a new dir 50 | WORKDIR /apps 51 | COPY --from=build /apps/opqbot-manager /apps/opqbot-manager 52 | COPY config.yaml.example /apps/ 53 | COPY font.ttf /apps/ 54 | COPY dictionary.txt /apps/ 55 | # 设置时区 56 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone 57 | 58 | # 设置编码 59 | ENV LANG C.UTF-8 60 | 61 | EXPOSE 8888 62 | 63 | RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 64 | 65 | # 开RUN 66 | ENTRYPOINT ["/apps/opqbot-manager"] 67 | -------------------------------------------------------------------------------- /GroupManager/Chat/Local/main.go: -------------------------------------------------------------------------------- 1 | package Local 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/GroupManager/Chat" 6 | 7 | "math/rand" 8 | 9 | "github.com/sirupsen/logrus" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | var log *logrus.Entry 14 | 15 | type Core struct { 16 | } 17 | type LocalChat struct { 18 | gorm.Model 19 | GroupId int64 20 | Question string 21 | Answer string 22 | From int64 23 | } 24 | 25 | func (c *Core) Init(l *logrus.Entry) error { 26 | log = l 27 | err := Config.DB.AutoMigrate(&LocalChat{}) 28 | if err != nil { 29 | return err 30 | } 31 | return nil 32 | } 33 | 34 | func (c *Core) GetAnswer(question string, GroupId, userId int64) (string, []byte) { 35 | var answers []LocalChat 36 | if err := Config.DB.Where("question LIKE ?", question).Find(&answers).Error; err != nil { 37 | log.Error(err) 38 | return "", nil 39 | } 40 | if len(answers) == 0 { 41 | return "", nil 42 | } 43 | return answers[rand.Intn(len(answers))].Answer, nil 44 | 45 | } 46 | 47 | func (c *Core) AddAnswer(question, answer string, GroupId, userId int64) error { 48 | var tmp = LocalChat{ 49 | GroupId: GroupId, 50 | Question: question, 51 | Answer: answer, 52 | From: userId, 53 | } 54 | if err := Config.DB.Create(&tmp).Error; err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | 60 | func (c *Core) SetReplace(regexp string, target string) error { 61 | return nil 62 | } 63 | 64 | func init() { 65 | err := Chat.Register("local", &Core{}) 66 | if err != nil { 67 | panic(err) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /GroupManager/Chat/Moli/main.go: -------------------------------------------------------------------------------- 1 | package Moli 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/GroupManager/Chat" 6 | "errors" 7 | "github.com/mcoo/OPQBot" 8 | "github.com/mcoo/requests" 9 | "github.com/sirupsen/logrus" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | var log *logrus.Entry 15 | 16 | type Core struct { 17 | replace map[string]string 18 | key string 19 | secret string 20 | } 21 | 22 | func (c *Core) GetAnswer(question string, GroupId, userId int64) (string, []byte) { 23 | res, err := requests.Get("http://i.itpk.cn/api.php?question=" + OPQBot.DecodeFaceFromSentences(question, "%s") + "&limit=8&api_key=" + c.key + "&api_secret=" + c.secret) 24 | if err != nil { 25 | log.Error(err) 26 | return "", nil 27 | } 28 | if i, _ := regexp.MatchString(`http://`, res.Text()); i { 29 | return "", nil 30 | } 31 | return strings.ReplaceAll(res.Text(), "茉莉", "米娅"), nil 32 | } 33 | 34 | func (c *Core) AddAnswer(question, answer string, GroupId, userId int64) error { 35 | return nil 36 | } 37 | 38 | func (c *Core) SetReplace(regexp string, target string) error { 39 | return nil 40 | } 41 | 42 | func (c *Core) Init(l *logrus.Entry) error { 43 | log = l 44 | Config.Lock.RLock() 45 | c.key = Config.CoreConfig.ChatKey.Moli.Key 46 | c.secret = Config.CoreConfig.ChatKey.Moli.Secret 47 | Config.Lock.RUnlock() 48 | c.replace = map[string]string{"\\[cqname\\]": "\\[YOU\\]", "\\[name\\]": "米娅"} 49 | if c.key == "" || c.secret == "" { 50 | return errors.New("key和密匙没有填写") 51 | } 52 | return nil 53 | } 54 | func init() { 55 | err := Chat.Register("茉莉", &Core{}) 56 | if err != nil { 57 | panic(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /GroupManager/Chat/XiaoI/main.go: -------------------------------------------------------------------------------- 1 | package XiaoI 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/GroupManager/Chat" 6 | "OPQBot-QQGroupManager/utils" 7 | "crypto/sha1" 8 | "fmt" 9 | "github.com/sirupsen/logrus" 10 | "io" 11 | ) 12 | 13 | var log *logrus.Entry 14 | 15 | func Sha1(data string) string { 16 | t := sha1.New() 17 | _, err := io.WriteString(t, data) 18 | if err != nil { 19 | log.Error(err) 20 | return "" 21 | } 22 | return fmt.Sprintf("%X", t.Sum(nil)) 23 | } 24 | 25 | type Core struct { 26 | } 27 | 28 | func (c *Core) GetAnswer(question string, GroupId, userId int64) (string, []byte) { 29 | return "", nil 30 | } 31 | 32 | func (c *Core) AddAnswer(question, answer string, GroupId, userId int64) error { 33 | return nil 34 | } 35 | 36 | func (c *Core) SetReplace(regexp string, target string) error { 37 | return nil 38 | } 39 | 40 | func (c *Core) Init(l *logrus.Entry) error { 41 | log = l 42 | Config.Lock.RLock() 43 | key := Config.CoreConfig.ChatKey.XiaoI.Key 44 | secret := Config.CoreConfig.ChatKey.XiaoI.Secret 45 | Config.Lock.RUnlock() 46 | t1 := Sha1(key + ":xiaoi.com:" + secret) 47 | t2 := Sha1("POST:/ask.do") 48 | nonce := utils.RandomString(40) 49 | t3 := Sha1(t1 + ":" + nonce + ":" + t2) 50 | xAuth := fmt.Sprintf("app_key=\"%s\", nonce=\"%s\", signature=\"%s\"", key, nonce, t3) 51 | log.Info(xAuth) 52 | return nil 53 | } 54 | func init() { 55 | err := Chat.Register("XiaoI", &Core{}) 56 | if err != nil { 57 | panic(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /GroupManager/Chat/Zhai/main.go: -------------------------------------------------------------------------------- 1 | package Zhai 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/GroupManager/Chat" 5 | "math/rand" 6 | "strings" 7 | 8 | "github.com/mcoo/requests" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var log *logrus.Entry 13 | 14 | type Core struct { 15 | Data map[string][]string 16 | } 17 | 18 | func (c *Core) Init(l *logrus.Entry) error { 19 | log = l 20 | r, err := requests.Get("https://cdn.jsdelivr.net/gh/Kyomotoi/AnimeThesaurus@main/data.json") 21 | if err != nil { 22 | return err 23 | } 24 | err = r.Json(&c.Data) 25 | if err != nil { 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | func (c *Core) GetAnswer(question string, GroupId, userId int64) (string, []byte) { 32 | for k, v := range c.Data { 33 | if strings.Contains(question, k) { 34 | if len(v) == 0 { 35 | return "", nil 36 | } 37 | return v[rand.Intn(len(v))], nil 38 | } 39 | } 40 | return "", nil 41 | } 42 | 43 | func (c *Core) AddAnswer(question, answer string, GroupId, userId int64) error { 44 | return nil 45 | } 46 | 47 | func (c *Core) SetReplace(regexp string, target string) error { 48 | return nil 49 | } 50 | 51 | func init() { 52 | err := Chat.Register("二次元", &Core{}) 53 | if err != nil { 54 | panic(err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /GroupManager/Chat/Zhai2/main.go: -------------------------------------------------------------------------------- 1 | package Zhai2 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/GroupManager/Chat" 5 | "encoding/json" 6 | "io/ioutil" 7 | "math/rand" 8 | "strings" 9 | 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // ? 语料仓库未公开 14 | 15 | var log *logrus.Entry 16 | 17 | // https://raw.githubusercontent.com/opq-osc/awesome-2-methodology/main/resources/release/index.json 18 | type Data struct { 19 | OnTrigger struct { 20 | BotName struct { 21 | Response []struct { 22 | Value string `json:"value"` 23 | Image string `json:"image,omitempty"` 24 | } `json:"response"` 25 | } `json:"botName"` 26 | Keyword []struct { 27 | Trigger []string `json:"trigger"` 28 | Response []struct { 29 | Value string `json:"value,omitempty"` 30 | Image string `json:"image,omitempty"` 31 | } `json:"response"` 32 | } `json:"keyword"` 33 | Poke struct { 34 | Response []struct { 35 | Value string `json:"value"` 36 | Image string `json:"image"` 37 | } `json:"response"` 38 | } `json:"poke"` 39 | } `json:"onTrigger"` 40 | } 41 | 42 | var data Data 43 | 44 | type Core struct { 45 | } 46 | 47 | func (c *Core) Init(l *logrus.Entry) error { 48 | log = l 49 | tmp, err := ioutil.ReadFile("./chat.json") 50 | if err != nil { 51 | return err 52 | } 53 | err = json.Unmarshal(tmp, &data) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | func (c *Core) GetAnswer(question string, GroupId, userId int64) (string, []byte) { 60 | if question == "米娅" { 61 | a := data.OnTrigger.BotName.Response[rand.Intn(len(data.OnTrigger.BotName.Response))] 62 | var pic []byte 63 | if a.Image != "" { 64 | pic, _ = ioutil.ReadFile(a.Image) 65 | //pic = []byte(a.Image) 66 | } 67 | return strings.ReplaceAll(strings.ReplaceAll(a.Value, "{{bot.name}}", "米娅"), "{{user.nickname}}", "[YOU]"), pic 68 | } 69 | for _, v := range data.OnTrigger.Keyword { 70 | for _, v1 := range v.Trigger { 71 | if strings.Contains(question, strings.ReplaceAll(v1, "{{bot.name}}", "米娅")) { 72 | a := v.Response[rand.Intn(len(v.Response))] 73 | var pic []byte 74 | if a.Image != "" { 75 | pic, _ = ioutil.ReadFile(a.Image) 76 | //pic = []byte(a.Image) 77 | } 78 | return strings.ReplaceAll(strings.ReplaceAll(a.Value, "{{bot.name}}", "米娅"), "{{user.nickname}}", "[YOU]"), pic 79 | } 80 | } 81 | } 82 | return "", nil 83 | 84 | } 85 | 86 | func (c *Core) AddAnswer(question, answer string, GroupId, userId int64) error { 87 | return nil 88 | } 89 | 90 | func (c *Core) SetReplace(regexp string, target string) error { 91 | return nil 92 | } 93 | 94 | func init() { 95 | err := Chat.Register("二次元高浓度", &Core{}) 96 | if err != nil { 97 | panic(err) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /GroupManager/Chat/main.go: -------------------------------------------------------------------------------- 1 | package Chat 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "errors" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type Manager struct { 11 | SelectCore string 12 | } 13 | 14 | var ( 15 | log *logrus.Entry 16 | Providers map[string]ChatCore 17 | ) 18 | 19 | type ChatCore interface { 20 | Init(l *logrus.Entry) error 21 | GetAnswer(question string, GroupId, userId int64) (string, []byte) 22 | AddAnswer(question, answer string, GroupId, userId int64) error 23 | SetReplace(regexp string, target string) error 24 | } 25 | 26 | func StartChatCore(l *logrus.Entry) Manager { 27 | log = l 28 | Config.Lock.RLock() 29 | tmp := Config.CoreConfig.SelectChatCore 30 | Config.Lock.RUnlock() 31 | for k, v := range Providers { 32 | tmp := log.WithField("Provider", k) 33 | tmp.Info("载入中") 34 | err := v.Init(tmp) 35 | if err != nil { 36 | tmp.Error(err) 37 | delete(Providers, k) 38 | } 39 | tmp.Info("载入成功") 40 | } 41 | if _, ok := Providers[tmp]; ok { 42 | return Manager{SelectCore: tmp} 43 | } else { 44 | return Manager{SelectCore: ""} 45 | } 46 | } 47 | 48 | func init() { 49 | Providers = make(map[string]ChatCore) 50 | } 51 | func (m *Manager) Learn(Question, Answer string, GroupId, From int64) error { 52 | if v, ok := Providers["local"]; ok { 53 | return v.AddAnswer(Question, Answer, GroupId, From) 54 | } else { 55 | return errors.New("本地聊天系统出现故障") 56 | } 57 | } 58 | func (m *Manager) GetChatDB() string { 59 | return m.SelectCore 60 | } 61 | func (m *Manager) SetChatDB(db string) error { 62 | if _, ok := Providers[db]; ok { 63 | m.SelectCore = db 64 | Config.Lock.Lock() 65 | Config.CoreConfig.SelectChatCore = db 66 | Config.Save() 67 | Config.Lock.Unlock() 68 | return nil 69 | } 70 | return errors.New("数据库不存在") 71 | } 72 | func (m *Manager) GetAnswer(question string, groupId, userId int64) (string, []byte, error) { 73 | // 查找本地对话数据库 74 | if v, ok := Providers["local"]; ok { 75 | if answer, pic := v.GetAnswer(question, groupId, userId); answer != "" || pic != nil { 76 | return answer, pic, nil 77 | } 78 | } 79 | // 联网查询默认对话数据库 80 | if v, ok := Providers[m.SelectCore]; ok { 81 | if answer, pic := v.GetAnswer(question, groupId, userId); answer != "" || pic != nil { 82 | return answer, pic, nil 83 | } 84 | } 85 | // 遍历其他数据库 86 | for k, v := range Providers { 87 | if k == "local" || k == m.SelectCore { 88 | continue 89 | } 90 | if answer, pic := v.GetAnswer(question, groupId, userId); answer != "" { 91 | return answer, pic, nil 92 | } 93 | } 94 | return "", nil, errors.New("没有找到对话记录,无法回答") 95 | } 96 | func Register(name string, core ChatCore) error { 97 | if _, ok := Providers[name]; ok { 98 | return errors.New("Core已经注册了 ") 99 | } else { 100 | Providers[name] = core 101 | } 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /GroupManager/QunInfo/main.go: -------------------------------------------------------------------------------- 1 | package QunInfo 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Core" 5 | "errors" 6 | "github.com/mcoo/OPQBot/qzone" 7 | "github.com/mcoo/requests" 8 | "log" 9 | "net/http" 10 | "sort" 11 | "strconv" 12 | ) 13 | 14 | type Qun struct { 15 | b *Core.Bot 16 | RetryCount int 17 | QQ string 18 | Gtk string 19 | Gtk2 string 20 | PSkey string 21 | Skey string 22 | Uin string 23 | } 24 | 25 | func NewQun(b *Core.Bot) Qun { 26 | q := Qun{b: b, RetryCount: 5} 27 | q.GetCookie() 28 | return q 29 | } 30 | func (q *Qun) GetBkn() string { 31 | return qzone.GenderGTK(q.Skey) 32 | } 33 | func (q *Qun) GetReq() *requests.Request { 34 | r := requests.Requests() 35 | c := &http.Cookie{ 36 | Name: "pt2gguin", 37 | Value: q.Uin, 38 | } 39 | r.SetCookie(c) 40 | c = &http.Cookie{ 41 | Name: "uin", 42 | Value: q.Uin, 43 | } 44 | r.SetCookie(c) 45 | c = &http.Cookie{ 46 | Name: "skey", 47 | Value: q.Skey, 48 | } 49 | r.SetCookie(c) 50 | c = &http.Cookie{ 51 | Name: "p_skey", 52 | Value: q.PSkey, 53 | } 54 | r.SetCookie(c) 55 | c = &http.Cookie{ 56 | Name: "p_uin", 57 | Value: q.Uin, 58 | } 59 | r.SetCookie(c) 60 | log.Println(r.Cookies) 61 | return r 62 | } 63 | func (q *Qun) GetCookie() { 64 | cookie, _ := q.b.GetUserCookie() 65 | q.Skey = cookie.Skey 66 | q.PSkey = cookie.PSkey.Qun 67 | q.Gtk = qzone.GenderGTK(cookie.Skey) 68 | q.Gtk2 = qzone.GenderGTK(cookie.PSkey.Qun) 69 | q.QQ = strconv.FormatInt(q.b.QQ, 10) 70 | q.Uin = "o" + q.QQ 71 | } 72 | func (q *Qun) GetInfo() { 73 | req := q.GetReq() 74 | res, _ := req.Get("https://qun.qq.com/cgi-bin/qunwelcome/myinfo?callback=?&bkn=" + q.GetBkn()) 75 | log.Println(res.Text()) 76 | } 77 | 78 | type GroupInfoResult struct { 79 | Retcode int `json:"retcode"` 80 | Msg string `json:"msg"` 81 | Data struct { 82 | GroupInfo struct { 83 | GroupCode string `json:"groupCode"` 84 | GroupName string `json:"groupName"` 85 | GroupMember int `json:"groupMember"` 86 | CreateDate string `json:"createDate"` 87 | } `json:"groupInfo"` 88 | ActiveData struct { 89 | ActiveData int `json:"activeData"` 90 | GroupMember int `json:"groupMember"` 91 | Ratio int `json:"ratio"` 92 | DataList []struct { 93 | Date int `json:"date"` 94 | Number int `json:"number"` 95 | } `json:"dataList"` 96 | } `json:"activeData"` 97 | MsgInfo struct { 98 | Total int `json:"total"` 99 | DataList []struct { 100 | Date int `json:"date"` 101 | Number int `json:"number"` 102 | } `json:"dataList"` 103 | } `json:"msgInfo"` 104 | JoinData struct { 105 | Total int `json:"total"` 106 | DataList []struct { 107 | Date int `json:"date"` 108 | Number int `json:"number"` 109 | } `json:"dataList"` 110 | } `json:"joinData"` 111 | ExitData struct { 112 | Total int `json:"total"` 113 | DataList []struct { 114 | Date int `json:"date"` 115 | Number int `json:"number"` 116 | } `json:"dataList"` 117 | } `json:"exitData"` 118 | ApplyData struct { 119 | Total int `json:"total"` 120 | DataList []struct { 121 | Date int `json:"date"` 122 | Number int `json:"number"` 123 | } `json:"dataList"` 124 | } `json:"applyData"` 125 | MemberData struct { 126 | Total int `json:"total"` 127 | DataList []struct { 128 | Date int `json:"date"` 129 | Number int `json:"number"` 130 | } `json:"dataList"` 131 | } `json:"memberData"` 132 | LastDataTime int `json:"lastDataTime"` 133 | } `json:"data"` 134 | } 135 | type GroupMembersResult struct { 136 | Retcode int `json:"retcode"` 137 | Msg string `json:"msg"` 138 | Data struct { 139 | ListNext int `json:"listNext"` 140 | SpeakRank []struct { 141 | Uin string `json:"uin"` 142 | Avatar string `json:"avatar"` 143 | Nickname string `json:"nickname"` 144 | Active int `json:"active"` 145 | MsgCount int `json:"msgCount"` 146 | } `json:"speakRank"` 147 | } `json:"data"` 148 | } 149 | 150 | func (q *Qun) GetGroupInfo(groupId int64, time int) (result GroupInfoResult, e error) { 151 | if q.RetryCount <= 0 { 152 | return result, errors.New("超过重试次数") 153 | } 154 | req := q.GetReq() 155 | strGroupId := strconv.FormatInt(groupId, 10) 156 | req.Header.Set("qname-service", "976321:131072") 157 | req.Header.Set("qname-space", "Production") 158 | req.Header.Set("referer", "https://qun.qq.com/m/qun/activedata/active.html?_wv=3&_wwv=128&gc="+strGroupId+"&src=2") 159 | 160 | res, e := req.Get("https://qun.qq.com/m/qun/activedata/proxy/domain/qun.qq.com/cgi-bin/manager/report/index?gc=" + strGroupId + "&time=" + strconv.Itoa(time) + "&bkn=" + q.GetBkn()) 161 | if e != nil { 162 | return result, e 163 | } 164 | e = res.Json(&result) 165 | if e != nil { 166 | 167 | log.Println(res.R.Request.RequestURI, res.Text()) 168 | return result, e 169 | } 170 | if result.Retcode == 100000 { 171 | q.RetryCount -= 1 172 | return q.GetGroupInfo(groupId, time) 173 | } 174 | q.RetryCount = 5 175 | return result, nil 176 | } 177 | func (q *Qun) GetGroupMembersInfo(groupId int64, time int) (result GroupMembersResult, e error) { 178 | if q.RetryCount <= 0 { 179 | return result, errors.New("超过重试次数") 180 | } 181 | req := q.GetReq() 182 | strGroupId := strconv.FormatInt(groupId, 10) 183 | req.Header.Set("qname-service", "976321:131072") 184 | req.Header.Set("qname-space", "Production") 185 | req.Header.Set("referer", "https://qun.qq.com/m/qun/activedata/active.html?_wv=3&_wwv=128&gc="+strGroupId+"&src=2") 186 | res, e := req.Get("https://qun.qq.com/m/qun/activedata/proxy/domain/qun.qq.com/cgi-bin/manager/report/list?gc=" + strGroupId + "&time=" + strconv.Itoa(time) + "&bkn=" + q.GetBkn() + "&type=0&start=0") 187 | if e != nil { 188 | return result, e 189 | } 190 | e = res.Json(&result) 191 | if e != nil { 192 | 193 | log.Println(res.R.Request.RequestURI, res.Text()) 194 | return result, e 195 | } 196 | if result.Retcode == 100000 { 197 | q.RetryCount -= 1 198 | return q.GetGroupMembersInfo(groupId, time) 199 | } 200 | sort.Slice(result.Data.SpeakRank, func(i, j int) bool { 201 | return result.Data.SpeakRank[i].MsgCount > result.Data.SpeakRank[j].MsgCount 202 | }) 203 | q.RetryCount = 5 204 | return result, nil 205 | } 206 | -------------------------------------------------------------------------------- /GroupManager/Web/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /GroupManager/Web/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | 8 | .eslintrc.js -------------------------------------------------------------------------------- /GroupManager/Web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | parserOptions: { 8 | parser: 'babel-eslint', 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module' // Allows for the use of imports 11 | }, 12 | 13 | env: { 14 | browser: true 15 | }, 16 | 17 | // Rules order is important, please avoid shuffling them 18 | extends: [ 19 | // Base ESLint recommended rules 20 | // 'eslint:recommended', 21 | 22 | 23 | // Uncomment any of the lines below to choose desired strictness, 24 | // but leave only one uncommented! 25 | // See https://eslint.vuejs.org/rules/#available-rules 26 | 'plugin:vue/essential', // Priority A: Essential (Error Prevention) 27 | // 'plugin:vue/strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 28 | // 'plugin:vue/recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 29 | 30 | 'standard' 31 | 32 | ], 33 | 34 | plugins: [ 35 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file 36 | // required to lint *.vue files 37 | 'vue', 38 | 39 | ], 40 | 41 | globals: { 42 | ga: 'readonly', // Google Analytics 43 | cordova: 'readonly', 44 | __statics: 'readonly', 45 | process: 'readonly', 46 | Capacitor: 'readonly', 47 | chrome: 'readonly' 48 | }, 49 | 50 | // add your custom rules here 51 | rules: { 52 | // allow async-await 53 | 'generator-star-spacing': 'off', 54 | // allow paren-less arrow functions 55 | 'arrow-parens': 'off', 56 | 'one-var': 'off', 57 | 58 | 'import/first': 'off', 59 | 'import/named': 'error', 60 | 'import/namespace': 'error', 61 | 'import/default': 'error', 62 | 'import/export': 'error', 63 | 'import/extensions': 'off', 64 | 'import/no-unresolved': 'off', 65 | 'import/no-extraneous-dependencies': 'off', 66 | 'prefer-promise-reject-errors': 'off', 67 | 68 | 69 | // allow debugger during development only 70 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /GroupManager/Web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | 9 | # Cordova related directories and files 10 | /src-cordova/node_modules 11 | /src-cordova/platforms 12 | /src-cordova/plugins 13 | /src-cordova/www 14 | 15 | # Capacitor related directories and files 16 | /src-capacitor/www 17 | /src-capacitor/node_modules 18 | 19 | # BEX related directories and files 20 | /src-bex/www 21 | /src-bex/js/core 22 | 23 | # Log files 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Editor directories and files 29 | .idea 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | -------------------------------------------------------------------------------- /GroupManager/Web/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /GroupManager/Web/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | 5 | "octref.vetur" 6 | ], 7 | "unwantedRecommendations": [ 8 | "hookyqr.beautify", 9 | "dbaeumer.jshint", 10 | "ms-vscode.vscode-typescript-tslint-plugin" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /GroupManager/Web/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vetur.validation.template": false, 3 | "vetur.format.enable": false, 4 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"], 5 | 6 | "vetur.experimental.templateInterpolationService": true 7 | } 8 | -------------------------------------------------------------------------------- /GroupManager/Web/README.md: -------------------------------------------------------------------------------- 1 | # opq (opq) 2 | 3 | opq-web 4 | 5 | ## Install the dependencies 6 | ```bash 7 | yarn 8 | ``` 9 | 10 | ### Start the app in development mode (hot-code reloading, error reporting, etc.) 11 | ```bash 12 | quasar dev 13 | ``` 14 | 15 | ### Lint the files 16 | ```bash 17 | yarn run lint 18 | ``` 19 | 20 | ### Build the app for production 21 | ```bash 22 | quasar build 23 | ``` 24 | 25 | ### Customize the configuration 26 | See [Configuring quasar.conf.js](https://v1.quasar.dev/quasar-cli/quasar-conf-js). 27 | -------------------------------------------------------------------------------- /GroupManager/Web/babel.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | presets: [ 4 | '@quasar/babel-preset-app' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /GroupManager/Web/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": [ 6 | "src/*" 7 | ], 8 | "app/*": [ 9 | "*" 10 | ], 11 | "components/*": [ 12 | "src/components/*" 13 | ], 14 | "layouts/*": [ 15 | "src/layouts/*" 16 | ], 17 | "pages/*": [ 18 | "src/pages/*" 19 | ], 20 | "assets/*": [ 21 | "src/assets/*" 22 | ], 23 | "boot/*": [ 24 | "src/boot/*" 25 | ], 26 | "vue$": [ 27 | "node_modules/vue/dist/vue.esm.js" 28 | ] 29 | } 30 | }, 31 | "exclude": [ 32 | "dist", 33 | ".quasar", 34 | "node_modules" 35 | ] 36 | } -------------------------------------------------------------------------------- /GroupManager/Web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "opq", 3 | "version": "0.0.1", 4 | "description": "opq-web", 5 | "productName": "opq", 6 | "author": "mcoo <2435932516@qq.com>", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --ext .js,.vue ./", 10 | "test": "echo \"No test specified\" && exit 0" 11 | }, 12 | "dependencies": { 13 | "@quasar/extras": "^1.0.0", 14 | "axios": "^0.21.1", 15 | "core-js": "^3.6.5", 16 | "js-md5": "^0.7.3", 17 | "quasar": "^1.0.0" 18 | }, 19 | "devDependencies": { 20 | "@quasar/app": "^2.0.0", 21 | "babel-eslint": "^10.0.1", 22 | "eslint": "^7.21.0", 23 | "eslint-config-standard": "^16.0.2", 24 | "eslint-plugin-import": "^2.14.0", 25 | "eslint-plugin-node": "^11.0.0", 26 | "eslint-plugin-promise": "^4.0.1", 27 | "eslint-plugin-vue": "^7.7.0", 28 | "eslint-webpack-plugin": "^2.4.0" 29 | }, 30 | "browserslist": [ 31 | "last 10 Chrome versions", 32 | "last 10 Firefox versions", 33 | "last 4 Edge versions", 34 | "last 7 Safari versions", 35 | "last 8 Android versions", 36 | "last 8 ChromeAndroid versions", 37 | "last 8 FirefoxAndroid versions", 38 | "last 10 iOS versions", 39 | "last 5 Opera versions" 40 | ], 41 | "engines": { 42 | "node": ">= 10.18.1", 43 | "npm": ">= 6.13.4", 44 | "yarn": ">= 1.21.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /GroupManager/Web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/public/favicon.ico -------------------------------------------------------------------------------- /GroupManager/Web/public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /GroupManager/Web/public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /GroupManager/Web/public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /GroupManager/Web/public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /GroupManager/Web/quasar.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 3 | * the ES6 features that are supported by your Node version. https://node.green/ 4 | */ 5 | 6 | // Configuration for your app 7 | // https://v1.quasar.dev/quasar-cli/quasar-conf-js 8 | /* eslint-env node */ 9 | const ESLintPlugin = require('eslint-webpack-plugin') 10 | 11 | module.exports = function (/* ctx */) { 12 | return { 13 | // https://v1.quasar.dev/quasar-cli/supporting-ts 14 | supportTS: false, 15 | 16 | // https://v1.quasar.dev/quasar-cli/prefetch-feature 17 | // preFetch: true, 18 | 19 | // app boot file (/src/boot) 20 | // --> boot files are part of "main.js" 21 | // https://v1.quasar.dev/quasar-cli/boot-files 22 | boot: [ 23 | 24 | 'axios' 25 | ], 26 | 27 | // https://v1.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css 28 | css: [ 29 | 'app.scss' 30 | ], 31 | 32 | // https://github.com/quasarframework/quasar/tree/dev/extras 33 | extras: [ 34 | // 'ionicons-v4', 35 | // 'mdi-v5', 36 | // 'fontawesome-v5', 37 | // 'eva-icons', 38 | // 'themify', 39 | // 'line-awesome', 40 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 41 | 'fontawesome-v5', 42 | 'roboto-font', // optional, you are not bound to it 43 | 'material-icons' // optional, you are not bound to it 44 | ], 45 | 46 | // Full list of options: https://v1.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build 47 | build: { 48 | vueRouterMode: 'history', // available values: 'hash', 'history' 49 | 50 | // transpile: false, 51 | 52 | // Add dependencies for transpiling with Babel (Array of string/regex) 53 | // (from node_modules, which are by default not transpiled). 54 | // Applies only if "transpile" is set to true. 55 | // transpileDependencies: [], 56 | 57 | // rtl: false, // https://v1.quasar.dev/options/rtl-support 58 | // preloadChunks: true, 59 | // showProgress: false, 60 | // gzip: true, 61 | // analyze: true, 62 | 63 | // Options below are automatically set depending on the env, set them if you want to override 64 | // extractCSS: false, 65 | 66 | // https://v1.quasar.dev/quasar-cli/handling-webpack 67 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 68 | chainWebpack (chain) { 69 | chain.plugin('eslint-webpack-plugin') 70 | .use(ESLintPlugin, [{ extensions: ['js', 'vue'] }]) 71 | } 72 | }, 73 | 74 | // Full list of options: https://v1.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer 75 | devServer: { 76 | https: false, 77 | port: 8080, 78 | open: true // opens browser window automatically 79 | }, 80 | 81 | // https://v1.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework 82 | framework: { 83 | iconSet: 'material-icons', // Quasar icon set 84 | lang: 'en-us', // Quasar language pack 85 | config: {}, 86 | 87 | // Possible values for "importStrategy": 88 | // * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives 89 | // * 'all' - Manually specify what to import 90 | importStrategy: 'auto', 91 | 92 | // For special cases outside of where "auto" importStrategy can have an impact 93 | // (like functional components as one of the examples), 94 | // you can manually specify Quasar components/directives to be available everywhere: 95 | // 96 | // components: [], 97 | // directives: [], 98 | 99 | // Quasar plugins 100 | plugins: [ 101 | 'Notify', 102 | 'Dialog' 103 | ] 104 | }, 105 | 106 | // animations: 'all', // --- includes all animations 107 | // https://v1.quasar.dev/options/animations 108 | animations: [], 109 | 110 | // https://v1.quasar.dev/quasar-cli/developing-ssr/configuring-ssr 111 | ssr: { 112 | pwa: false 113 | }, 114 | 115 | // https://v1.quasar.dev/quasar-cli/developing-pwa/configuring-pwa 116 | pwa: { 117 | workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' 118 | workboxOptions: {}, // only for GenerateSW 119 | manifest: { 120 | name: 'opq', 121 | short_name: 'opq', 122 | description: 'opq-web', 123 | display: 'standalone', 124 | orientation: 'portrait', 125 | background_color: '#ffffff', 126 | theme_color: '#027be3', 127 | icons: [ 128 | { 129 | src: 'icons/icon-128x128.png', 130 | sizes: '128x128', 131 | type: 'image/png' 132 | }, 133 | { 134 | src: 'icons/icon-192x192.png', 135 | sizes: '192x192', 136 | type: 'image/png' 137 | }, 138 | { 139 | src: 'icons/icon-256x256.png', 140 | sizes: '256x256', 141 | type: 'image/png' 142 | }, 143 | { 144 | src: 'icons/icon-384x384.png', 145 | sizes: '384x384', 146 | type: 'image/png' 147 | }, 148 | { 149 | src: 'icons/icon-512x512.png', 150 | sizes: '512x512', 151 | type: 'image/png' 152 | } 153 | ] 154 | } 155 | }, 156 | 157 | // Full list of options: https://v1.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova 158 | cordova: { 159 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 160 | }, 161 | 162 | // Full list of options: https://v1.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor 163 | capacitor: { 164 | hideSplashscreen: true 165 | }, 166 | 167 | // Full list of options: https://v1.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron 168 | electron: { 169 | bundler: 'packager', // 'packager' or 'builder' 170 | 171 | packager: { 172 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 173 | 174 | // OS X / Mac App Store 175 | // appBundleId: '', 176 | // appCategoryType: '', 177 | // osxSign: '', 178 | // protocol: 'myapp://path', 179 | 180 | // Windows only 181 | // win32metadata: { ... } 182 | }, 183 | 184 | builder: { 185 | // https://www.electron.build/configuration/configuration 186 | 187 | appId: 'opq' 188 | }, 189 | 190 | // More info: https://v1.quasar.dev/quasar-cli/developing-electron-apps/node-integration 191 | nodeIntegration: true, 192 | 193 | extendWebpack (/* cfg */) { 194 | // do something with Electron main process Webpack cfg 195 | // chainWebpack also available besides this extendWebpack 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /GroupManager/Web/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /GroupManager/Web/src/assets/ava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/src/assets/ava.png -------------------------------------------------------------------------------- /GroupManager/Web/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/src/assets/bg.jpg -------------------------------------------------------------------------------- /GroupManager/Web/src/assets/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/src/assets/bg1.jpg -------------------------------------------------------------------------------- /GroupManager/Web/src/assets/headbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/src/assets/headbg.png -------------------------------------------------------------------------------- /GroupManager/Web/src/assets/quasar-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 66 | 69 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 104 | 105 | 106 | 107 | 113 | 118 | 126 | 133 | 142 | 151 | 160 | 169 | 178 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /GroupManager/Web/src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/GroupManager/Web/src/boot/.gitkeep -------------------------------------------------------------------------------- /GroupManager/Web/src/boot/axios.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import { stringify } from 'qs' 4 | axios.defaults.withCredentials = true 5 | // axios.defaults.baseURL = window.location.protocol + '//' + window.location.hostname + ':8888/' 6 | 7 | axios.interceptors.request.use( 8 | (config) => { 9 | // 兼容 post 跨域问题 10 | if (config.method === 'post') { 11 | // 修改 Content-Type 12 | config.headers['Content-Type'] = 13 | 'application/x-www-form-urlencoded' 14 | 15 | // 将对象参数转换为序列化的 URL 形式(key=val&key=val) 16 | config.data = stringify(config.data) 17 | } 18 | return config 19 | }, 20 | (error) => { 21 | console.log(error) 22 | return Promise.reject(error) 23 | } 24 | ) 25 | axios.interceptors.response.use(res => { 26 | if (res.data.code === 10010 || res.data.code === 10011) { 27 | this.$router.replace('/login') 28 | } 29 | return res 30 | }) 31 | 32 | function getCookie (cname) { 33 | const name = cname + '=' 34 | const ca = document.cookie.split(';') 35 | for (let i = 0; i < ca.length; i++) { 36 | const c = ca[i].trim() 37 | if (c.indexOf(name) === 0) return c.substring(name.length, c.length) 38 | } 39 | return '' 40 | } 41 | function formatDate (value) { 42 | const date = new Date(value) 43 | const y = date.getFullYear() 44 | let MM = date.getMonth() + 1 45 | MM = MM < 10 ? ('0' + MM) : MM 46 | let d = date.getDate() 47 | d = d < 10 ? ('0' + d) : d 48 | let h = date.getHours() 49 | h = h < 10 ? ('0' + h) : h 50 | let m = date.getMinutes() 51 | m = m < 10 ? ('0' + m) : m 52 | let s = date.getSeconds() 53 | s = s < 10 ? ('0' + s) : s 54 | return y + '-' + MM + '-' + d + ' ' + h + ':' + m + ':' + s 55 | } 56 | Vue.prototype.$cookie = getCookie 57 | Vue.prototype.$axios = axios 58 | Vue.prototype.$timeformat = formatDate 59 | import md5 from 'js-md5' 60 | Vue.prototype.$md5 = md5 61 | -------------------------------------------------------------------------------- /GroupManager/Web/src/components/EssentialLink.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 48 | -------------------------------------------------------------------------------- /GroupManager/Web/src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | -------------------------------------------------------------------------------- /GroupManager/Web/src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary : #1976D2; 16 | $secondary : #26A69A; 17 | $accent : #9C27B0; 18 | 19 | $dark : #1D1D1D; 20 | 21 | $positive : #21BA45; 22 | $negative : #C10015; 23 | $info : #31CCEC; 24 | $warning : #F2C037; 25 | -------------------------------------------------------------------------------- /GroupManager/Web/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /GroupManager/Web/src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 76 | -------------------------------------------------------------------------------- /GroupManager/Web/src/pages/Error.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 53 | -------------------------------------------------------------------------------- /GroupManager/Web/src/pages/Error404.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /GroupManager/Web/src/pages/login.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 186 | 187 | 202 | -------------------------------------------------------------------------------- /GroupManager/Web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import routes from './routes' 5 | 6 | Vue.use(VueRouter) 7 | 8 | /* 9 | * If not building with SSR mode, you can 10 | * directly export the Router instantiation; 11 | * 12 | * The function below can be async too; either use 13 | * async/await or return a Promise which resolves 14 | * with the Router instance. 15 | */ 16 | 17 | export default function (/* { store, ssrContext } */) { 18 | const Router = new VueRouter({ 19 | scrollBehavior: () => ({ x: 0, y: 0 }), 20 | routes, 21 | 22 | // Leave these as they are and change in quasar.conf.js instead! 23 | // quasar.conf.js -> build -> vueRouterMode 24 | // quasar.conf.js -> build -> publicPath 25 | mode: process.env.VUE_ROUTER_MODE, 26 | base: process.env.VUE_ROUTER_BASE 27 | }) 28 | Router.beforeEach((to, from, next) => { 29 | // console.log(to) 30 | if (to.path === '/error') { 31 | next() 32 | } 33 | if (to.meta.requireAuth) { 34 | if (Router.app.$store.state.User.auth) { 35 | next() 36 | } else { 37 | Router.app.$axios.get('api/status').then(function (response) { 38 | if (response.data.code === 1) { 39 | Router.app.$store.commit('User/pushFunc', { auth: response.data.code === 1, username: response.data.data }) 40 | next() 41 | } else { 42 | next({ 43 | path: '/login', 44 | query: { redirect: to.path, info: '未登录或登录时间过长令牌失效了,请重新登录!' } 45 | }) 46 | } 47 | }).catch(() => { 48 | next({ 49 | path: '/login', 50 | query: { redirect: to.path, info: '后端出现错误,请尝试重新登录' } 51 | }) 52 | }) 53 | } 54 | } else { 55 | next() 56 | } 57 | 58 | // Router.app.$axios.get('api/status').then(function (response) { 59 | // Router.app.$store.commit('User/pushFunc', { auth: response.data.code === 1, username: response.data.data }) 60 | // if (to.meta.requireAuth) { 61 | // if (response.data.code === 1) { 62 | // next() 63 | // } else { 64 | // next({ 65 | // path: '/login', 66 | // query: { redirect: to.fullPath, info: '未登录或登录时间过长令牌失效了,请重新登录!' } 67 | // }) 68 | // } 69 | // } else { 70 | // next() 71 | // } 72 | // }).catch(function (error) { 73 | // console.log(error.message) 74 | // let title = '错误' 75 | // if (error.message === 'Network Error') { 76 | // error.message = '与后端连接出现问题,网站暂时无法使用!' 77 | // title = '网络错误' 78 | // } 79 | // next({ 80 | // path: '/error', 81 | // query: { info: error.message, title: title, back: to.fullPath } 82 | // }) 83 | // }) 84 | }) 85 | return Router 86 | } 87 | -------------------------------------------------------------------------------- /GroupManager/Web/src/router/routes.js: -------------------------------------------------------------------------------- 1 | 2 | const routes = [ 3 | { 4 | path: '/error', 5 | component: () => import('pages/Error.vue') 6 | }, 7 | { 8 | path: '/login', 9 | component: () => import('pages/login.vue') 10 | }, 11 | { 12 | path: '/', 13 | component: () => import('layouts/MainLayout.vue'), 14 | children: [ 15 | { path: '', component: () => import('pages/Index.vue'), meta: { requireAuth: true } } 16 | ] 17 | }, 18 | 19 | // Always leave this as last one, 20 | // but you can also remove it 21 | { 22 | path: '*', 23 | component: () => import('pages/Error404.vue') 24 | } 25 | ] 26 | 27 | export default routes 28 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import User from './module-example' 4 | // import example from './module-example' 5 | 6 | Vue.use(Vuex) 7 | 8 | /* 9 | * If not building with SSR mode, you can 10 | * directly export the Store instantiation; 11 | * 12 | * The function below can be async too; either use 13 | * async/await or return a Promise which resolves 14 | * with the Store instance. 15 | */ 16 | 17 | export default function (/* { ssrContext } */) { 18 | const Store = new Vuex.Store({ 19 | modules: { 20 | User 21 | // example 22 | }, 23 | 24 | // enable strict mode (adds overhead!) 25 | // for dev mode only 26 | strict: process.env.DEBUGGING 27 | }) 28 | 29 | return Store 30 | } 31 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/module-example/actions.js: -------------------------------------------------------------------------------- 1 | export function someAction (/* context */) { 2 | } 3 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/module-example/getters.js: -------------------------------------------------------------------------------- 1 | export function someGetter (/* state */) { 2 | } 3 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/module-example/index.js: -------------------------------------------------------------------------------- 1 | import state from './state' 2 | import * as getters from './getters' 3 | import * as mutations from './mutations' 4 | import * as actions from './actions' 5 | 6 | export default { 7 | namespaced: true, 8 | getters, 9 | mutations, 10 | actions, 11 | state 12 | } 13 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/module-example/mutations.js: -------------------------------------------------------------------------------- 1 | export const pushFunc = (state, data) => { 2 | state.auth = data.auth 3 | state.username = data.username 4 | } 5 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/module-example/state.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return { 3 | auth: false, 4 | role: 0, 5 | username: '' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /GroupManager/Web/src/store/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OPQ Open Source Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OPQBot-GroupManager 2 | OPQBot 群管理机器人 3 | 目前处于测试状态,软件不一定稳定 4 | # 支持的功能 5 | ## Bilibili订阅 6 | ### 命令 7 | - 订阅up {up的mid/up名称} 8 | - 取消订阅up {up的Mid} 9 | - 取消订阅番剧 {番剧Mid} 10 | - 订阅番剧 {番剧的名称/mid} 11 | - 本群up 12 | - 本群番剧 13 | ## Github订阅 14 | ### 说明 15 | 需要在GitHub仓库中设置好 webhook的地址 例如 `http://*****/github/webhook/opq-osc/OPQBot-GroupManager` 16 | 后面`opq-osc/OPQBot-GroupManager`是仓库地址,而secret是需要私聊QQ机器人输入的东西 17 | ### 命令 18 | - 本群Github 19 | - 取消订阅Github {仓库名称} 20 | - 订阅Github {仓库名称} 21 | 22 | ## 入群验证码/入群自动禁言 23 | ## 关键词禁言 24 | ## 群成员管理 25 | ## Web面板 26 | # 使用教程 27 | 1. 下载编译好的二进制文件 28 | 2. 将`config.yaml.example`内需要更改的配置项填写好并重命名为`config.yaml`, 启动程序即可😊 29 | ![](https://github.com/opq-osc/OPQBot-GroupManager/blob/master/pic/p1.png) 30 | ![](https://github.com/opq-osc/OPQBot-GroupManager/blob/master/pic/p2.png) 31 | ![](https://github.com/opq-osc/OPQBot-GroupManager/blob/master/pic/p3.png) 32 | ![](https://github.com/opq-osc/OPQBot-GroupManager/blob/master/pic/p4.png) 33 | -------------------------------------------------------------------------------- /androidDns/dns.go: -------------------------------------------------------------------------------- 1 | // +build !android 2 | 3 | package androidDns 4 | 5 | func SetDns() { 6 | } 7 | -------------------------------------------------------------------------------- /androidDns/dns_android.go: -------------------------------------------------------------------------------- 1 | // +build android 2 | 3 | package androidDns 4 | 5 | import ( 6 | "context" 7 | "log" 8 | "net" 9 | ) 10 | 11 | const bootstrapDNS = "8.8.8.8:53" 12 | 13 | func SetDns() { 14 | log.Println("安卓设置DNS") 15 | var dialer net.Dialer 16 | net.DefaultResolver = &net.Resolver{ 17 | PreferGo: false, 18 | Dial: func(context context.Context, _, _ string) (net.Conn, error) { 19 | conn, err := dialer.DialContext(context, "udp", bootstrapDNS) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return conn, nil 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config.yaml.example: -------------------------------------------------------------------------------- 1 | opqwebconfig: 2 | host: 0.0.0.0 3 | port: 8888 4 | username: enjoy 5 | password: "1234567" 6 | enable: true 7 | opqbotconfig: 8 | url: "http://127.0.0.1:8888" 9 | qq: 0 10 | dbconfig: 11 | dbtype: "sqlite3" 12 | dbusername: "" 13 | dbpassword: "" 14 | dbip: "" 15 | dbport: "" 16 | dbname: "data" 17 | reverseproxy: "" 18 | defaultgroupconfig: 19 | enable: true 20 | adminuin: 0 21 | menu: |- 22 | 菜单 23 | 1. 签到 24 | 2. 积分 25 | 3. 赞我 26 | menukeyword: '#菜单|#功能' 27 | shutupword: SSR|SS 28 | shutuptime: 100 29 | joinverifytime: 0 30 | joinautoshutuptime: 0 31 | zan: false 32 | bili: true 33 | signin: true 34 | welcome: "" 35 | joinverifytype: 0 36 | job: {} 37 | superadminuin: 2435932516 38 | whitegrouplist: [] 39 | blackgrouplist: [] 40 | groupconfig: 41 | 863872040: 42 | enable: true 43 | adminuin: 0 44 | menu: |- 45 | 菜单 46 | 1. 签到 47 | 2. 积分 48 | 3. 赞我 49 | menukeyword: '#菜单|#功能' 50 | shutupword: SSR|SS 51 | shutuptime: 100 52 | joinverifytime: 240 53 | joinautoshutuptime: 0 54 | zan: true 55 | signin: true 56 | welcome: "" 57 | joinverifytype: 1 58 | job: {} 59 | userdata: 60 | 0: 61 | lastsignday: 0 62 | lastzanday: 24 63 | count: 0 64 | -------------------------------------------------------------------------------- /draw/ArialEnUnicodeBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/draw/ArialEnUnicodeBold.ttf -------------------------------------------------------------------------------- /draw/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/draw/bg.png -------------------------------------------------------------------------------- /draw/draw.go: -------------------------------------------------------------------------------- 1 | package draw 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Core" 5 | "OPQBot-QQGroupManager/GroupManager/QunInfo" 6 | "bytes" 7 | "crypto/rand" 8 | _ "embed" 9 | "fmt" 10 | "github.com/disintegration/imaging" 11 | "github.com/mcoo/requests" 12 | "github.com/sirupsen/logrus" 13 | "image" 14 | _ "image/jpeg" 15 | "image/png" 16 | "math/big" 17 | 18 | "github.com/mcoo/gg" 19 | ) 20 | 21 | //go:embed techno-hideo-1.ttf 22 | var techno []byte 23 | 24 | //go:embed bg.png 25 | var bg []byte 26 | 27 | //go:embed ArialEnUnicodeBold.ttf 28 | var ArialEnUnicodeBold []byte 29 | 30 | var log *logrus.Logger 31 | 32 | func init() { 33 | log = Core.GetLog() 34 | } 35 | func GetAvatar(avatar string) (image.Image, error) { 36 | res, err := requests.Get(avatar) 37 | if err != nil { 38 | return nil, err 39 | } 40 | img, _, err := image.Decode(bytes.NewBuffer(res.Content())) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return img, nil 45 | } 46 | func DrawCircle(img image.Image, r int) image.Image { 47 | ava := imaging.Resize(img, r, r, imaging.Lanczos) 48 | c := gg.NewContext(r, r) 49 | // 画圆形 50 | c.DrawCircle(float64(r/2), float64(r/2), float64(r/2)) 51 | // 对画布进行裁剪 52 | c.Clip() 53 | c.DrawImage(ava, 0, 0) 54 | return c.Image() 55 | } 56 | func DrawGroupInfo(GroupInfo QunInfo.GroupInfoResult, GroupMemberInfo QunInfo.GroupMembersResult) ([]byte, error) { 57 | bgimg, err := png.Decode(bytes.NewReader(bg)) 58 | if err != nil { 59 | return nil, err 60 | } 61 | dc := gg.NewContextForImage(bgimg) 62 | err = dc.LoadFontFaceFromBytes(ArialEnUnicodeBold, 10) 63 | if err != nil { 64 | return nil, err 65 | } 66 | dc.SetRGB(200, 200, 200) 67 | a := 0 68 | for _, i := range GroupMemberInfo.Data.SpeakRank { 69 | if a >= 4 { 70 | break 71 | } 72 | ava, err := GetAvatar(i.Avatar) 73 | if err != nil { 74 | log.Error() 75 | continue 76 | } 77 | 78 | dc.DrawImage(DrawCircle(ava, 64), 16, 136+101*a) 79 | 80 | text := TruncateText(dc, fmt.Sprintf("%s (%s)", i.Nickname, i.Uin), 252) 81 | dc.DrawString(text, 96, float64(158+101*a)) 82 | text = TruncateText(dc, fmt.Sprintf("活跃度 %d 发言条数 %d", i.Active, i.MsgCount), 252) 83 | dc.DrawString(text, 96, float64(196+101*a)) 84 | a += 1 85 | } 86 | dc.LoadFontFaceFromBytes(ArialEnUnicodeBold, 6) 87 | dc.SetRGB(150, 150, 150) 88 | log.Println(GroupInfo.Data.ActiveData) 89 | text := "" 90 | if len(GroupInfo.Data.ActiveData.DataList) == 0 { 91 | text = TruncateText(dc, fmt.Sprintf("昨日活跃人数null 消息条数null 加群null人 退群null人 申请入群null人"), 390) 92 | } else { 93 | text = TruncateText(dc, fmt.Sprintf("昨日活跃人数%d 消息条数%d 加群%d人 退群%d人 申请入群%d人", 94 | GroupInfo.Data.ActiveData.DataList[len(GroupInfo.Data.ActiveData.DataList)-1].Number, 95 | GroupInfo.Data.MsgInfo.DataList[len(GroupInfo.Data.MsgInfo.DataList)-1].Number, 96 | GroupInfo.Data.JoinData.DataList[len(GroupInfo.Data.JoinData.DataList)-1].Number, 97 | GroupInfo.Data.ExitData.DataList[len(GroupInfo.Data.ExitData.DataList)-1].Number, 98 | GroupInfo.Data.ApplyData.DataList[len(GroupInfo.Data.ApplyData.DataList)-1].Number, 99 | ), 390) 100 | } 101 | 102 | dc.DrawString(text, 4, 526) 103 | // 226- 141 + 141 -126 104 | buf := new(bytes.Buffer) 105 | err = png.Encode(buf, dc.Image()) 106 | if err != nil { 107 | return nil, err 108 | } 109 | return buf.Bytes(), nil 110 | 111 | } 112 | func TruncateText(dc *gg.Context, originalText string, maxTextWidth float64) string { 113 | tmpStr := "" 114 | result := make([]rune, 0) 115 | for _, r := range originalText { 116 | tmpStr = tmpStr + string(r) 117 | w, _ := dc.MeasureString(tmpStr) 118 | if w > maxTextWidth { 119 | if len(tmpStr) <= 1 { 120 | return "" 121 | } else { 122 | break 123 | } 124 | } else { 125 | result = append(result, r) 126 | } 127 | } 128 | return string(result) 129 | } 130 | func Draw6Number() ([]byte, string, error) { 131 | num := "" 132 | for i := 0; i < 6; i++ { 133 | n, _ := rand.Int(rand.Reader, big.NewInt(10)) 134 | num += n.String() 135 | } 136 | 137 | c := gg.NewContext(300, 120) 138 | if err := c.LoadFontFaceFromBytes(techno, 60); err != nil { 139 | panic(err) 140 | } 141 | c.MeasureString(num) 142 | c.SetHexColor("#FFFFFF") 143 | c.Clear() 144 | 145 | c.SetRGB(0, 0, 0) 146 | c.Fill() 147 | 148 | c.DrawStringWrapped(num, 20, 30, 0, 0, 300, 1.5, gg.AlignLeft) 149 | //c.SetRGB(0, 0, 0) 150 | //c.Fill() 151 | buf := new(bytes.Buffer) 152 | err := png.Encode(buf, c.Image()) 153 | if err != nil { 154 | return nil, num, err 155 | } 156 | return buf.Bytes(), num, nil 157 | } 158 | -------------------------------------------------------------------------------- /draw/techno-hideo-1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/draw/techno-hideo-1.ttf -------------------------------------------------------------------------------- /font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/font.ttf -------------------------------------------------------------------------------- /genAndYiqin/main.go: -------------------------------------------------------------------------------- 1 | package genAndYiqin 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "github.com/sirupsen/logrus" 10 | 11 | "github.com/mcoo/OPQBot" 12 | 13 | "io/ioutil" 14 | 15 | "net/http" 16 | 17 | "strings" 18 | ) 19 | 20 | type GenChaxunRes struct { 21 | Data []DataRes `json:"data"` 22 | } 23 | 24 | type DataRes struct { 25 | Definitions []Items `json:"definitions"` 26 | Tags []TageItem `json:"tags"` 27 | } 28 | 29 | type Items struct { 30 | Content string `json:"content"` 31 | Plaintext string `json:"plaintext"` 32 | Images []ImageItem `json:"images"` 33 | } 34 | 35 | type ImageItem struct { 36 | Full FullItem `json:"full"` 37 | } 38 | 39 | type FullItem struct { 40 | Path string `json:"path"` 41 | } 42 | type TageItem struct { 43 | Name string `json:"name"` 44 | } 45 | type Module struct { 46 | } 47 | type YiqingRes struct { 48 | Title string `json:"title"` 49 | Time string `json:"time"` 50 | IncrTime string `json:"incrTime"` 51 | logcation Logcation `json:"logcation"` 52 | Colums []Colums `json:"colums"` 53 | MainReport struct { 54 | Id int `json:"id"` 55 | Area string `json:"area"` 56 | Report string `json:"report"` 57 | Dateline string `json:"dateline"` 58 | Date int64 `json:"date"` 59 | } `json:"mainReport"` 60 | ContryData struct { 61 | SureCnt string `json:"sure_cnt"` 62 | SureNewCnt string `json:"sure_new_cnt"` 63 | RestSureCnt string `json:"rest_sure_cnt"` 64 | RestSureCntIncr string `json:"rest_sure_cnt_incr"` 65 | InputCnt string `json:"input_cnt"` 66 | HiddenCnt string `json:"hidden_cnt"` 67 | HiddenCntIncr string `json:"hidden_cnt_incr"` 68 | CureCnt string `json:"cure_cnt"` 69 | YstCureCnt string `json:"yst_cure_cnt"` 70 | YstDieCnt string `json:"yst_die_cnt"` 71 | YstLikeCnt string `json:"yst_like_cnt"` 72 | YstSureCnt string `json:"yst_sure_cnt"` 73 | YstSureHid string `json:"yst_sure_hid"` 74 | } 75 | } 76 | 77 | type Colums struct { 78 | Title string `json:"title"` 79 | List []List `json:"list"` 80 | } 81 | 82 | type List struct { 83 | Current int64 `json:"current"` 84 | Incr string `json:"incr"` 85 | } 86 | 87 | type Logcation struct { 88 | Province string `json:"province"` 89 | City string `json:"city"` 90 | } 91 | 92 | var log *logrus.Entry 93 | 94 | func (m *Module) ModuleInfo() Core.ModuleInfo { 95 | return Core.ModuleInfo{ 96 | Name: "梗查询和疫情订阅", 97 | Author: "bypanghu", 98 | Description: "", 99 | Version: 0, 100 | } 101 | } 102 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 103 | log = l 104 | b.BotCronManager.AddJob(-1, "Yiqing", "0 8,18 * * *", func() { 105 | client := &http.Client{} 106 | baseUrl := "https://m.sm.cn/api/rest?method=Huoshenshan.local" 107 | req, err := http.NewRequest("GET", baseUrl, nil) 108 | req.Header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1") 109 | req.Header.Add("referer", "https://broccoli.uc.cn/") 110 | if err != nil { 111 | panic(err) 112 | } 113 | response, _ := client.Do(req) 114 | defer response.Body.Close() 115 | s, err := ioutil.ReadAll(response.Body) 116 | var res YiqingRes 117 | json.Unmarshal(s, &res) 118 | ups := fmt.Sprintf("疫情报告") 119 | ups += fmt.Sprintf("%s-%s\n全国单日报告%s\n", res.Title, res.Time, res.MainReport.Report) 120 | ups += fmt.Sprintf("[表情190][表情190][表情190]信息总览[表情190][表情190][表情190]\n") 121 | ups += fmt.Sprintf("[表情145]全国累计确诊%s个昨日新增%s个\n", res.ContryData.SureCnt, res.ContryData.YstCureCnt) 122 | ups += fmt.Sprintf("[表情145]全国现存确诊%s个昨日新增%s个\n", res.ContryData.RestSureCnt, res.ContryData.RestSureCntIncr) 123 | ups += fmt.Sprintf("[表情145]累计输入确诊%s个\n", res.ContryData.InputCnt) 124 | ups += fmt.Sprintf("[表情145]全国累计治愈%s个昨日新增%s个\n", res.ContryData.CureCnt, res.ContryData.YstCureCnt) 125 | ups += fmt.Sprintf("[表情66][表情66][表情66]疫情当下,请注意保护安全") 126 | b.SendGroupTextMsg(-1, fmt.Sprintf(ups)) 127 | fmt.Println(ups) 128 | }) 129 | 130 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(botQQ int64, packet *OPQBot.GroupMsgPack) { 131 | if packet.FromUserID == botQQ { 132 | return 133 | } 134 | Config.Lock.RLock() 135 | var c Config.GroupConfig 136 | if v, ok := Config.CoreConfig.GroupConfig[packet.FromGroupID]; ok { 137 | c = v 138 | } else { 139 | c = Config.CoreConfig.DefaultGroupConfig 140 | } 141 | Config.Lock.RUnlock() 142 | if !c.Enable { 143 | return 144 | } 145 | cm := strings.Split(packet.Content, " ") 146 | if len(cm) == 2 && cm[0] == "梗查询" { 147 | b.SendGroupTextMsg(packet.FromGroupID, fmt.Sprintf("正在查询梗%s", cm[1])) 148 | client := &http.Client{} 149 | baseUrl := "https://api.jikipedia.com/go/search_entities" 150 | postData := make(map[string]interface{}) 151 | postData["phrase"] = cm[1] 152 | postData["page"] = 1 153 | bytesData, err := json.Marshal(postData) 154 | if err != nil { 155 | fmt.Println(err.Error()) 156 | return 157 | } 158 | reader := bytes.NewReader(bytesData) 159 | req, err := http.NewRequest("POST", baseUrl, reader) 160 | req.Header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1") 161 | req.Header.Add("referer", "https://broccoli.uc.cn/") 162 | req.Header.Set("Content-Type", "application/json;charset=UTF-8") 163 | if err != nil { 164 | panic(err) 165 | } 166 | response, _ := client.Do(req) 167 | defer response.Body.Close() 168 | s, err := ioutil.ReadAll(response.Body) 169 | var res GenChaxunRes 170 | json.Unmarshal(s, &res) 171 | var content string 172 | for i, a := range res.Data { 173 | if i == 1 { 174 | for j, b := range a.Definitions { 175 | if j == 0 { 176 | content = b.Plaintext 177 | } 178 | } 179 | } 180 | } 181 | if content == "" { 182 | b.SendGroupTextMsg(packet.FromGroupID, "没有查询到该梗") 183 | } else { 184 | b.SendGroupTextMsg(packet.FromGroupID, fmt.Sprintf("%s", content)) 185 | } 186 | return 187 | } 188 | if packet.Content == "疫情信息" { 189 | b.SendGroupTextMsg(packet.FromGroupID, "正在查找信息") 190 | client := &http.Client{} 191 | baseUrl := "https://m.sm.cn/api/rest?method=Huoshenshan.local" 192 | req, err := http.NewRequest("GET", baseUrl, nil) 193 | req.Header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1") 194 | req.Header.Add("referer", "https://broccoli.uc.cn/") 195 | if err != nil { 196 | panic(err) 197 | } 198 | response, _ := client.Do(req) 199 | defer response.Body.Close() 200 | s, err := ioutil.ReadAll(response.Body) 201 | var res YiqingRes 202 | json.Unmarshal(s, &res) 203 | ups := fmt.Sprintf("%s-%s\n全国单日报告%s\n", res.Title, res.Time, res.MainReport.Report) 204 | ups += fmt.Sprintf("[表情190][表情190][表情190]信息总览[表情190][表情190][表情190]\n") 205 | ups += fmt.Sprintf("[表情145]全国累计确诊%s个昨日新增%s个\n", res.ContryData.SureCnt, res.ContryData.YstCureCnt) 206 | ups += fmt.Sprintf("[表情145]全国现存确诊%s个昨日新增%s个\n", res.ContryData.RestSureCnt, res.ContryData.RestSureCntIncr) 207 | ups += fmt.Sprintf("[表情145]累计输入确诊%s个\n", res.ContryData.InputCnt) 208 | ups += fmt.Sprintf("[表情145]全国累计治愈%s个昨日新增%s个\n", res.ContryData.CureCnt, res.ContryData.YstCureCnt) 209 | ups += fmt.Sprintf("[表情66][表情66][表情66]疫情当下,请注意保护安全") 210 | b.SendGroupTextMsg(packet.FromGroupID, fmt.Sprintf(ups)) 211 | log.Println(ups) 212 | } 213 | }) 214 | if err != nil { 215 | return err 216 | } 217 | return nil 218 | } 219 | 220 | func init() { 221 | Core.RegisterModule(&Module{}) 222 | } 223 | -------------------------------------------------------------------------------- /githubManager/hookManager.go: -------------------------------------------------------------------------------- 1 | package githubManager 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "errors" 7 | "fmt" 8 | "github.com/go-playground/webhooks/v6/github" 9 | "github.com/kataras/iris/v12" 10 | "github.com/mcoo/requests" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | type Manager struct { 17 | github map[string]Config.Repo 18 | githubLock sync.RWMutex 19 | b *Core.Bot 20 | } 21 | 22 | func (m *Manager) DelRepo(repo string, groupId int64) error { 23 | m.githubLock.Lock() 24 | defer m.githubLock.Unlock() 25 | if v, ok := m.github[repo]; ok { 26 | for i, v1 := range v.Groups { 27 | if v1 == groupId { 28 | v.Groups = append(v.Groups[:i], v.Groups[i+1:]...) 29 | m.github[repo] = v 30 | if len(v.Groups) == 0 { 31 | delete(m.github, repo) 32 | } 33 | break 34 | } 35 | } 36 | m.Save() 37 | return nil 38 | 39 | } else { 40 | return errors.New("订阅不存在!无法删除 ") 41 | } 42 | } 43 | func (m *Manager) AddRepo(repo string, Secret string, groupId int64) error { 44 | m.githubLock.Lock() 45 | defer m.githubLock.Unlock() 46 | 47 | if v, ok := m.github[repo]; ok { 48 | if v.Secret != Secret { 49 | v.Secret = Secret 50 | hook, _ := github.New(github.Options.Secret(Secret)) 51 | v.WebHook = hook 52 | } 53 | for _, v1 := range v.Groups { 54 | if v1 == groupId { 55 | m.Save() 56 | return errors.New("已经订阅过了") 57 | } 58 | } 59 | v.Groups = append(v.Groups, groupId) 60 | m.github[repo] = v 61 | } else { 62 | if Secret == "" { 63 | return errors.New("需要Secret, 请直接私聊我发送Secret") 64 | } 65 | hook, _ := github.New(github.Options.Secret(Secret)) 66 | m.github[repo] = Config.Repo{ 67 | WebHook: hook, 68 | Groups: []int64{groupId}, 69 | Secret: Secret, 70 | } 71 | } 72 | err := m.Save() 73 | if err != nil { 74 | return err 75 | } 76 | return nil 77 | } 78 | 79 | func NewManager(app *iris.Application, bot *Core.Bot) Manager { 80 | Config.Lock.RLock() 81 | defer Config.Lock.RUnlock() 82 | g := map[string]Config.Repo{} 83 | for k, v := range Config.CoreConfig.GithubSub { 84 | hook, _ := github.New(github.Options.Secret(v.Secret)) 85 | v.WebHook = hook 86 | g[k] = v 87 | } 88 | m := Manager{github: g, githubLock: sync.RWMutex{}, b: bot} 89 | app.Any("github/webhook/{root:path}", func(ctx iris.Context) { 90 | h, err := m.GetRepo(ctx.Params().GetString("root")) 91 | if err != nil { 92 | ctx.StatusCode(404) 93 | return 94 | } 95 | payload, err := h.WebHook.Parse((*ctx).Request(), github.IssuesEvent, github.RepositoryEvent, github.PushEvent, github.PingEvent, github.ReleaseEvent, github.PullRequestEvent) 96 | if err != nil { 97 | log.Println(err) 98 | if err == github.ErrEventNotFound { 99 | ctx.StatusCode(404) 100 | return 101 | } 102 | if err == github.ErrHMACVerificationFailed { 103 | ctx.StatusCode(502) 104 | return 105 | } 106 | } 107 | switch v := payload.(type) { 108 | case github.PingPayload: 109 | log.Println(v) 110 | case github.IssuesPayload: 111 | switch v.Action { 112 | case "open": 113 | r, _ := requests.Get(v.Sender.AvatarURL) 114 | for _, v1 := range h.Groups { 115 | m.b.SendGroupPicMsg(v1, fmt.Sprintf("%s在%s发布了新的Issues: %s\n欢迎各位大佬前往解答!", v.Sender.Login, v.Repository.FullName, v.Issue.Title), r.Content()) 116 | } 117 | } 118 | 119 | case github.RepositoryPayload: 120 | switch v.Action { 121 | case "created": 122 | r, _ := requests.Get(v.Sender.AvatarURL) 123 | for _, v1 := range h.Groups { 124 | m.b.SendGroupPicMsg(v1, fmt.Sprintf("%s在%s发布了新的仓库: %s\n欢迎Star哟", v.Sender.Login, v.Organization.Login, v.Repository.FullName), r.Content()) 125 | } 126 | case "transferred": 127 | r, _ := requests.Get(v.Sender.AvatarURL) 128 | for _, v1 := range h.Groups { 129 | m.b.SendGroupPicMsg(v1, fmt.Sprintf("%s在%s发布了新的仓库: %s\n欢迎Star哟", v.Sender.Login, v.Organization.Login, v.Repository.FullName), r.Content()) 130 | } 131 | } 132 | 133 | case github.PushPayload: 134 | var commitString []string 135 | for _, v1 := range v.Commits { 136 | t, err := time.Parse("2006-01-02T15:04:05-07:00", v1.Timestamp) 137 | if err != nil { 138 | log.Println(err) 139 | commitString = append(commitString, fmt.Sprintf("[%s] %s", v1.Timestamp, v1.Message)) 140 | } else { 141 | commitString = append(commitString, fmt.Sprintf("[%s] %s", t.Format("2006-01-02 15:04:05"), v1.Message)) 142 | } 143 | } 144 | if len(commitString) == 0 { 145 | return 146 | } 147 | r, _ := requests.Get(v.Sender.AvatarURL) 148 | for _, v1 := range h.Groups { 149 | m.b.SendGroupPicMsg(v1, fmt.Sprintf("%s\n%s发起了Push\nCommit:\n%s", v.Repository.FullName, v.Pusher.Name, strings.Join(commitString, "\n")), r.Content()) 150 | } 151 | case github.ReleasePayload: 152 | r, _ := requests.Get(v.Sender.AvatarURL) 153 | switch v.Action { 154 | case "published": 155 | for _, v1 := range h.Groups { 156 | m.b.SendGroupPicMsg(v1, fmt.Sprintf("%s\n%s发布了新版本:\n%s", v.Repository.FullName, v.Sender.Login, v.Release.TagName), r.Content()) 157 | } 158 | default: 159 | 160 | } 161 | 162 | case github.PullRequestPayload: 163 | r, _ := requests.Get(v.PullRequest.User.AvatarURL) 164 | msg := "" 165 | switch v.Action { 166 | case "closed": 167 | msg = fmt.Sprintf("%s\n%s关闭了PR:%s to %s", v.Repository.FullName, v.PullRequest.User.Login, v.PullRequest.Head.Label, v.PullRequest.Base.Label) 168 | case "opened": 169 | msg = fmt.Sprintf("%s\n%s打开了PR:%s to %s", v.Repository.FullName, v.PullRequest.User.Login, v.PullRequest.Head.Label, v.PullRequest.Base.Label) 170 | default: 171 | ctx.StatusCode(503) 172 | return 173 | } 174 | for _, v1 := range h.Groups { 175 | m.b.SendGroupPicMsg(v1, msg, r.Content()) 176 | } 177 | 178 | } 179 | }) 180 | return m 181 | } 182 | func (m *Manager) GetGroupSubList(groupId int64) (r map[string]Config.Repo) { 183 | m.githubLock.RLock() 184 | defer m.githubLock.RUnlock() 185 | r = map[string]Config.Repo{} 186 | for k, v := range m.github { 187 | for _, v1 := range v.Groups { 188 | if v1 == groupId { 189 | r[k] = v 190 | } 191 | } 192 | } 193 | return 194 | } 195 | 196 | func (m *Manager) GetRepo(repo string) (r Config.Repo, err error) { 197 | m.githubLock.RLock() 198 | defer m.githubLock.RUnlock() 199 | var ok bool 200 | if r, ok = m.github[repo]; ok { 201 | return 202 | } else { 203 | err = errors.New("没有订阅该Repo") 204 | return 205 | } 206 | 207 | } 208 | 209 | func (m *Manager) Save() error { 210 | Config.Lock.Lock() 211 | defer Config.Lock.Unlock() 212 | Config.CoreConfig.GithubSub = m.github 213 | return Config.Save() 214 | } 215 | -------------------------------------------------------------------------------- /githubManager/main.go: -------------------------------------------------------------------------------- 1 | package githubManager 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "OPQBot-QQGroupManager/GroupManager" 7 | "fmt" 8 | "github.com/sirupsen/logrus" 9 | 10 | "github.com/mcoo/OPQBot" 11 | "strings" 12 | ) 13 | 14 | type Module struct { 15 | } 16 | 17 | var log *logrus.Entry 18 | 19 | func (m *Module) ModuleInfo() Core.ModuleInfo { 20 | return Core.ModuleInfo{ 21 | Name: "Github订阅姬", 22 | Author: "enjoy", 23 | Description: "", 24 | Version: 0, 25 | RequireModule: []string{"群管理插件"}, 26 | } 27 | } 28 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 29 | log = l 30 | g := NewManager(GroupManager.App, b) 31 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(botQQ int64, packet *OPQBot.GroupMsgPack) { 32 | if packet.FromUserID == botQQ { 33 | return 34 | } 35 | Config.Lock.RLock() 36 | var c Config.GroupConfig 37 | if v, ok := Config.CoreConfig.GroupConfig[packet.FromGroupID]; ok { 38 | c = v 39 | } else { 40 | c = Config.CoreConfig.DefaultGroupConfig 41 | } 42 | Config.Lock.RUnlock() 43 | if !c.Enable { 44 | return 45 | } 46 | cm := strings.Split(packet.Content, " ") 47 | s := b.Session.SessionStart(packet.FromUserID) 48 | if packet.Content == "本群Github" { 49 | githubs := "本群订阅Github仓库\n" 50 | list := g.GetGroupSubList(packet.FromGroupID) 51 | if len(list) == 0 { 52 | b.SendGroupTextMsg(packet.FromGroupID, "本群没有订阅Github仓库") 53 | return 54 | } 55 | for k, _ := range list { 56 | githubs += fmt.Sprintf("%s \n", k) 57 | } 58 | b.SendGroupTextMsg(packet.FromGroupID, githubs) 59 | return 60 | } 61 | if len(cm) == 2 && cm[0] == "取消订阅Github" { 62 | err := g.DelRepo(cm[1], packet.FromGroupID) 63 | if err != nil { 64 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 65 | return 66 | } 67 | b.SendGroupTextMsg(packet.FromGroupID, "取消订阅成功!") 68 | return 69 | } 70 | if len(cm) == 2 && cm[0] == "订阅Github" { 71 | b.SendGroupTextMsg(packet.FromGroupID, "请私聊我发送该仓库的Webhook Secret!") 72 | err := s.Set("github", cm[1]) 73 | if err != nil { 74 | log.Println(err) 75 | return 76 | } 77 | err = s.Set("github_groupId", packet.FromGroupID) 78 | if err != nil { 79 | log.Println(err) 80 | return 81 | } 82 | return 83 | } 84 | }) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | _, err = b.AddEvent(OPQBot.EventNameOnFriendMessage, func(qq int64, packet *OPQBot.FriendMsgPack) { 90 | s := b.Session.SessionStart(packet.FromUin) 91 | if v, err := s.GetString("github"); err == nil { 92 | groupidI, err := s.Get("github_groupId") 93 | if err != nil { 94 | b.SendFriendTextMsg(packet.FromUin, err.Error()) 95 | return 96 | } 97 | groupId, ok := groupidI.(int64) 98 | if !ok { 99 | b.SendFriendTextMsg(packet.FromUin, "内部错误") 100 | return 101 | } 102 | err = g.AddRepo(v, packet.Content, groupId) 103 | if err != nil { 104 | b.SendFriendTextMsg(packet.FromUin, err.Error()) 105 | s.Delete("github") 106 | s.Delete("github_groupId") 107 | return 108 | } 109 | b.SendFriendTextMsg(packet.FromUin, "成功!") 110 | s.Delete("github") 111 | s.Delete("github_groupId") 112 | } 113 | }) 114 | if err != nil { 115 | log.Println(err) 116 | } 117 | return nil 118 | } 119 | 120 | func init() { 121 | Core.RegisterModule(&Module{}) 122 | } 123 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module OPQBot-QQGroupManager 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Joker/hpp v1.0.0 // indirect 7 | github.com/antonfisher/nested-logrus-formatter v1.3.1 8 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 9 | github.com/dimiro1/banner v1.1.0 10 | github.com/disintegration/imaging v1.6.2 11 | github.com/gaoyanpao/biliLiveHelper v0.0.0-20210205164551-dca3842ab39b 12 | github.com/go-ego/gse v0.67.0 13 | github.com/go-playground/webhooks/v6 v6.0.0-beta.3 14 | github.com/google/uuid v1.2.0 15 | github.com/gorilla/websocket v1.5.0 // indirect 16 | github.com/kataras/iris/v12 v12.2.0-alpha2.0.20210427211137-fa175eb84754 17 | github.com/mattn/go-colorable v0.1.12 18 | github.com/mcoo/OPQBot v0.2.1 19 | github.com/mcoo/gg v1.3.0-edit 20 | github.com/mcoo/requests v0.0.2 21 | github.com/mcoo/sqlite v1.1.4-fix-1 22 | github.com/mcoo/wordclouds v0.0.3 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.1 // indirect 25 | github.com/robfig/cron/v3 v3.0.1 26 | github.com/sirupsen/logrus v1.8.1 27 | github.com/smartystreets/goconvey v1.6.4 // indirect 28 | github.com/stretchr/testify v1.7.0 // indirect 29 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect 30 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect 31 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect 32 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 33 | gorm.io/driver/mysql v1.1.1 34 | gorm.io/gorm v1.21.11 35 | ) 36 | -------------------------------------------------------------------------------- /kiss/main.go: -------------------------------------------------------------------------------- 1 | package kiss 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Core" 5 | mydraw "OPQBot-QQGroupManager/draw" 6 | "bytes" 7 | "embed" 8 | "encoding/json" 9 | "github.com/mcoo/OPQBot" 10 | "github.com/mcoo/gg" 11 | "github.com/sirupsen/logrus" 12 | "image" 13 | "image/color" 14 | "image/color/palette" 15 | "image/draw" 16 | "image/gif" 17 | "image/png" 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | type Module struct { 23 | } 24 | 25 | var ( 26 | log *logrus.Entry 27 | ) 28 | 29 | //go:embed static 30 | var static embed.FS 31 | var ( 32 | OPERATOR_X = []int{92, 135, 84, 80, 155, 60, 50, 98, 35, 38, 70, 84, 75} 33 | OPERATOR_Y = []int{64, 40, 105, 110, 82, 96, 80, 55, 65, 100, 80, 65, 65} 34 | TARGET_X = []int{58, 62, 42, 50, 56, 18, 28, 54, 46, 60, 35, 20, 40} 35 | TARGET_Y = []int{90, 95, 100, 100, 100, 120, 110, 100, 100, 100, 115, 120, 96} 36 | ) 37 | 38 | func (m *Module) ModuleInfo() Core.ModuleInfo { 39 | return Core.ModuleInfo{ 40 | Name: "Kiss", 41 | Author: "enjoy", 42 | Description: "嘿嘿嘿", 43 | Version: 0, 44 | RequireModule: []string{"群管理插件"}, 45 | } 46 | } 47 | 48 | type AtMsg struct { 49 | Content string `json:"Content"` 50 | UserExt []struct { 51 | QQNick string `json:"QQNick"` 52 | QQUID int64 `json:"QQUid"` 53 | } `json:"UserExt"` 54 | UserID []int64 `json:"UserID"` 55 | } 56 | 57 | var pics []image.Image 58 | 59 | func isInPalette(p color.Palette, c color.Color) int { 60 | ret := -1 61 | for i, v := range p { 62 | if v == c { 63 | return i 64 | } 65 | } 66 | return ret 67 | } 68 | func getPalette(m image.Image) color.Palette { 69 | p := color.Palette{color.RGBA{}} 70 | p9 := color.Palette(palette.WebSafe) 71 | b := m.Bounds() 72 | black := false 73 | for y := b.Min.Y; y < b.Max.Y; y++ { 74 | for x := b.Min.X; x < b.Max.X; x++ { 75 | c := m.At(x, y) 76 | cc := p9.Convert(c) 77 | if cc == p9[0] { 78 | black = true 79 | } 80 | if isInPalette(p, cc) == -1 { 81 | p = append(p, cc) 82 | } 83 | } 84 | } 85 | if len(p) < 256 && black == true { 86 | p[0] = color.RGBA{} // transparent 87 | p = append(p, p9[0]) 88 | } 89 | return p 90 | } 91 | func DrawKiss(ava1, ava2 image.Image) []byte { 92 | var disposals []byte 93 | var images []*image.Paletted 94 | var delays []int 95 | 96 | for i, v := range pics { 97 | bg := gg.NewContextForImage(v) 98 | bg.DrawImage(ava2, TARGET_X[i], TARGET_Y[i]) 99 | bg.DrawImage(ava1, OPERATOR_X[i], OPERATOR_Y[i]) 100 | img := bg.Image() 101 | cp := getPalette(img) 102 | disposals = append(disposals, gif.DisposalNone) //透明图片需要设置 103 | p := image.NewPaletted(image.Rect(0, 0, bg.Height(), bg.Width()), cp) 104 | draw.Draw(p, p.Bounds(), img, image.Point{}, draw.Src) 105 | images = append(images, p) 106 | delays = append(delays, 0) 107 | 108 | } 109 | g := &gif.GIF{ 110 | Image: images, 111 | Delay: delays, 112 | LoopCount: 0, 113 | Disposal: disposals, 114 | } 115 | buf := new(bytes.Buffer) 116 | err := gif.EncodeAll(buf, g) 117 | if err != nil { 118 | log.Error(err) 119 | } 120 | return buf.Bytes() 121 | } 122 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 123 | log = l 124 | for i := 1; i <= 13; i++ { 125 | picBytes, err := static.ReadFile("static/" + strconv.Itoa(i) + ".png") 126 | if err != nil { 127 | continue 128 | } 129 | pic, err := png.Decode(bytes.NewBuffer(picBytes)) 130 | if err != nil { 131 | continue 132 | } 133 | pics = append(pics, pic) 134 | } 135 | //GroupManager.App.Post("/api/kiss", func(ctx iris.Context) { 136 | // srcPic := ctx.FormValueDefault("src","") 137 | // targetPic := ctx.FormValueDefault("target","") 138 | // if srcPic == "" || targetPic == "" { 139 | // ctx.JSON(GroupManager.WebResult{ 140 | // Code: 1, 141 | // Info: "error", 142 | // Data: "字段不匹配", 143 | // }) 144 | // return 145 | // } 146 | // avaTmp1, err := mydraw.GetAvatar(srcPic) 147 | // if err != nil { 148 | // ctx.JSON(GroupManager.WebResult{ 149 | // Code: 1, 150 | // Info: "error", 151 | // Data: err.Error(), 152 | // }) 153 | // return 154 | // } 155 | // avaTmp2, err := mydraw.GetAvatar(targetPic) 156 | // if err != nil { 157 | // ctx.JSON(GroupManager.WebResult{ 158 | // Code: 1, 159 | // Info: "error", 160 | // Data: err.Error(), 161 | // }) 162 | // return 163 | // } 164 | // gifPic := DrawKiss(mydraw.DrawCircle(avaTmp1, 40), mydraw.DrawCircle(avaTmp2, 50)) 165 | // jsonPic,err := ctx.URLParamBool("json") 166 | // if err != nil { 167 | // jsonPic = true 168 | // } 169 | // if jsonPic { 170 | // ctx.JSON(GroupManager.WebResult{ 171 | // Code: 0, 172 | // Info: "success", 173 | // Data: base64.StdEncoding.EncodeToString(gifPic), 174 | // }) 175 | // } else { 176 | // ctx.StatusCode(200) 177 | // ctx.Header("content-type", "image/gif") 178 | // ctx.Write(gifPic) 179 | // } 180 | // 181 | //}) 182 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(qq int64, packet *OPQBot.GroupMsgPack) { 183 | if packet.FromUserID != b.QQ { 184 | if packet.MsgType == "AtMsg" && strings.Contains(packet.Content, "kiss") { 185 | var atInfo AtMsg 186 | if json.Unmarshal([]byte(packet.Content), &atInfo) == nil { 187 | if len(atInfo.UserID) == 1 { 188 | avaTmp1, err := mydraw.GetAvatar("http://q1.qlogo.cn/g?b=qq&s=640&nk=" + strconv.FormatInt(packet.FromUserID, 10)) 189 | if err != nil { 190 | log.Error(err) 191 | return 192 | } 193 | avaTmp2, err := mydraw.GetAvatar("http://q1.qlogo.cn/g?b=qq&s=640&nk=" + strconv.FormatInt(atInfo.UserID[0], 10)) 194 | if err != nil { 195 | log.Error(err) 196 | return 197 | } 198 | gifPic := DrawKiss(mydraw.DrawCircle(avaTmp1, 40), mydraw.DrawCircle(avaTmp2, 50)) 199 | b.SendGroupPicMsg(packet.FromGroupID, "", gifPic) 200 | } else if len(atInfo.UserID) >= 2 { 201 | avaTmp1, err := mydraw.GetAvatar("http://q1.qlogo.cn/g?b=qq&s=640&nk=" + strconv.FormatInt(atInfo.UserID[0], 10)) 202 | if err != nil { 203 | log.Error(err) 204 | return 205 | } 206 | avaTmp2, err := mydraw.GetAvatar("http://q1.qlogo.cn/g?b=qq&s=640&nk=" + strconv.FormatInt(atInfo.UserID[1], 10)) 207 | if err != nil { 208 | log.Error(err) 209 | return 210 | } 211 | gifPic := DrawKiss(mydraw.DrawCircle(avaTmp1, 40), mydraw.DrawCircle(avaTmp2, 50)) 212 | b.SendGroupPicMsg(packet.FromGroupID, "", gifPic) 213 | } 214 | } 215 | } 216 | } 217 | }) 218 | if err != nil { 219 | return err 220 | } 221 | return nil 222 | } 223 | func init() { 224 | Core.RegisterModule(&Module{}) 225 | } 226 | -------------------------------------------------------------------------------- /kiss/static/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/1.png -------------------------------------------------------------------------------- /kiss/static/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/10.png -------------------------------------------------------------------------------- /kiss/static/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/11.png -------------------------------------------------------------------------------- /kiss/static/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/12.png -------------------------------------------------------------------------------- /kiss/static/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/13.png -------------------------------------------------------------------------------- /kiss/static/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/2.png -------------------------------------------------------------------------------- /kiss/static/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/3.png -------------------------------------------------------------------------------- /kiss/static/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/4.png -------------------------------------------------------------------------------- /kiss/static/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/5.png -------------------------------------------------------------------------------- /kiss/static/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/6.png -------------------------------------------------------------------------------- /kiss/static/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/7.png -------------------------------------------------------------------------------- /kiss/static/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/8.png -------------------------------------------------------------------------------- /kiss/static/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/kiss/static/9.png -------------------------------------------------------------------------------- /logo.txt: -------------------------------------------------------------------------------- 1 | 2 | ____ _____ ____ ____ _ _____ __ __ 3 | / __ \| __ \ / __ \| _ \ | | / ____| | \/ | 4 | | | | | |__) | | | | |_) | ___ | |_ _ | | __ _ __ ___ _ _ _ __ | \ / | __ _ _ __ __ _ __ _ ___ _ __ 5 | | | | | ___/| | | | _ < / _ \| __| (_) | | |_ | '__/ _ \| | | | '_ \| |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__| 6 | | |__| | | | |__| | |_) | (_) | |_ | |__| | | | (_) | |_| | |_) | | | | (_| | | | | (_| | (_| | __/ | 7 | \____/|_| \___\_\____/ \___/ \__| \_____|_| \___/ \__,_| .__/|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| 8 | | | __/ | 9 | |_| |___/ 10 | Version: {{ .version }} 11 | Commit: 12 | GoVersion: {{ .GoVersion }} 13 | GOOS: {{ .GOOS }} 14 | GOARCH: {{ .GOARCH }} 15 | NumCPU: {{ .NumCPU }} 16 | Now: {{ .Now "2006-01-02 15:04:05" }} -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/BanWord" 5 | _ "OPQBot-QQGroupManager/Bili" 6 | "OPQBot-QQGroupManager/Config" 7 | "OPQBot-QQGroupManager/Core" 8 | "OPQBot-QQGroupManager/GroupManager" 9 | "OPQBot-QQGroupManager/androidDns" 10 | _ "OPQBot-QQGroupManager/genAndYiqin" 11 | _ "OPQBot-QQGroupManager/githubManager" 12 | _ "OPQBot-QQGroupManager/kiss" 13 | _ "OPQBot-QQGroupManager/setu" 14 | _ "OPQBot-QQGroupManager/taobao" 15 | _ "OPQBot-QQGroupManager/wordCloud" 16 | "bytes" 17 | _ "embed" 18 | "fmt" 19 | "math/rand" 20 | "net/http" 21 | _ "net/http/pprof" 22 | "strings" 23 | "time" 24 | 25 | "github.com/dimiro1/banner" 26 | "github.com/mattn/go-colorable" 27 | "github.com/sirupsen/logrus" 28 | 29 | //_ "OPQBot-QQGroupManager/steam" 30 | "OPQBot-QQGroupManager/utils" 31 | 32 | _ "github.com/go-playground/webhooks/v6/github" 33 | "github.com/mcoo/OPQBot" 34 | ) 35 | 36 | //go:embed logo.txt 37 | var logo string 38 | 39 | var ( 40 | version = "unknown" 41 | date = "none" 42 | log *logrus.Logger 43 | ) 44 | 45 | func init() { 46 | rand.Seed(time.Now().Unix()) 47 | } 48 | func main() { 49 | isEnabled := true 50 | isColorEnabled := true 51 | banner.Init(colorable.NewColorableStdout(), isEnabled, isColorEnabled, bytes.NewBufferString(strings.ReplaceAll(logo, "{{ .version }}", version))) 52 | log = Core.GetLog() 53 | if Config.CoreConfig.LogLevel != "" { 54 | switch Config.CoreConfig.LogLevel { 55 | case "info": 56 | log.SetLevel(logrus.InfoLevel) 57 | case "debug": 58 | log.SetLevel(logrus.DebugLevel) 59 | log.SetReportCaller(true) 60 | case "warn": 61 | log.SetLevel(logrus.WarnLevel) 62 | case "error": 63 | log.SetLevel(logrus.ErrorLevel) 64 | } 65 | 66 | } 67 | if Config.CoreConfig.Debug { 68 | log.Warn("注意当前处于DEBUG模式,会开放25569端口,如果你不清楚请关闭DEBUG,因为这样可能泄漏你的信息!😥") 69 | go func() { 70 | ip := ":25569" 71 | if err := http.ListenAndServe(ip, nil); err != nil { 72 | fmt.Printf("start pprof failed on %s\n", ip) 73 | } 74 | }() 75 | } 76 | log.Println("QQ Group Manager -️" + version + " 编译时间 " + date) 77 | androidDns.SetDns() 78 | go CheckUpdate() 79 | b := Core.Bot{Modules: map[string]*Core.Module{}, BotManager: OPQBot.NewBotManager(Config.CoreConfig.OPQBotConfig.QQ, Config.CoreConfig.OPQBotConfig.Url)} 80 | _, err := b.AddEvent(OPQBot.EventNameOnDisconnected, func() { 81 | log.Println("断开服务器") 82 | }) 83 | if err != nil { 84 | log.Println(err) 85 | } 86 | b.BotCronManager = utils.NewBotCronManager() 87 | b.BotCronManager.Start() 88 | b.DB = Config.DB 89 | _, err = b.AddEvent(OPQBot.EventNameOnConnected, func() { 90 | log.Println("连接服务器成功") 91 | }) 92 | if err != nil { 93 | log.Println(err) 94 | } 95 | _ = BanWord.Hook(&b) 96 | Core.InitModule(&b) 97 | err = b.Start() 98 | if err != nil { 99 | log.Error(err) 100 | } 101 | GroupManager.Start <- struct{}{} 102 | b.Wait() 103 | } 104 | -------------------------------------------------------------------------------- /pic/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/pic/p1.png -------------------------------------------------------------------------------- /pic/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/pic/p2.png -------------------------------------------------------------------------------- /pic/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/pic/p3.png -------------------------------------------------------------------------------- /pic/p4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opq-osc/OPQBot-GroupManager/d5a880e3c4247795a85bedce6b405b4d5a03d0f9/pic/p4.png -------------------------------------------------------------------------------- /setu/lolicon/lolicon.go: -------------------------------------------------------------------------------- 1 | package lolicon 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Core" 5 | "OPQBot-QQGroupManager/setu/setucore" 6 | "github.com/mcoo/requests" 7 | "github.com/sirupsen/logrus" 8 | "strings" 9 | ) 10 | 11 | var log *logrus.Entry 12 | 13 | type Provider struct { 14 | } 15 | 16 | func (p *Provider) InitProvider(l *logrus.Entry, bot *Core.Bot) { 17 | log = l 18 | log.Info("启动成功") 19 | } 20 | func (p *Provider) SearchPic(word string, r18 bool, num int) ([]setucore.Pic, error) { 21 | return nil, nil 22 | } 23 | func (p *Provider) SearchPicFromUser(word string, userId string, r18 bool, num int) ([]setucore.Pic, error) { 24 | return nil, nil 25 | } 26 | func (p *Provider) SearchPicToDB() (num int, e error) { 27 | res, err := requests.Get("https://api.lolicon.app/setu/v2?num=100&proxy=false&r18=2") 28 | if err != nil { 29 | return 0, err 30 | } 31 | var setus SetuRes 32 | if err = res.Json(&setus); err != nil { 33 | return 0, err 34 | } 35 | for _, v := range setus.Data { 36 | pic := setucore.Pic{ 37 | Id: v.Pid, 38 | Page: v.P, 39 | Title: v.Title, 40 | Author: v.Author, 41 | AuthorID: v.Uid, 42 | OriginalPicUrl: v.Urls.Original, 43 | Tag: strings.Join(v.Tags, ","), 44 | R18: v.R18, 45 | LastSendTime: 0, 46 | } 47 | err = setucore.AddPicToDB(pic) 48 | if err == nil { 49 | num += 1 50 | } 51 | } 52 | return 53 | } 54 | 55 | type SetuRes struct { 56 | Error string `json:"error"` 57 | Data []struct { 58 | Pid int `json:"pid"` 59 | P int `json:"p"` 60 | Uid int `json:"uid"` 61 | Title string `json:"title"` 62 | Author string `json:"author"` 63 | R18 bool `json:"r18"` 64 | Width int `json:"width"` 65 | Height int `json:"height"` 66 | Tags []string `json:"tags"` 67 | Ext string `json:"ext"` 68 | UploadDate int64 `json:"uploadDate"` 69 | Urls struct { 70 | Original string `json:"original"` 71 | } `json:"urls"` 72 | } `json:"data"` 73 | } 74 | -------------------------------------------------------------------------------- /setu/main.go: -------------------------------------------------------------------------------- 1 | package setu 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "OPQBot-QQGroupManager/GroupManager" 7 | "OPQBot-QQGroupManager/setu/lolicon" 8 | "OPQBot-QQGroupManager/setu/pixiv" 9 | "OPQBot-QQGroupManager/setu/setucore" 10 | "encoding/base64" 11 | "fmt" 12 | "github.com/kataras/iris/v12" 13 | "github.com/mcoo/OPQBot" 14 | "github.com/mcoo/requests" 15 | "github.com/sirupsen/logrus" 16 | "regexp" 17 | "strconv" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | type Module struct { 23 | } 24 | 25 | var ( 26 | log *logrus.Entry 27 | ) 28 | 29 | func (m *Module) ModuleInfo() Core.ModuleInfo { 30 | return Core.ModuleInfo{ 31 | Name: "Setu姬", 32 | Author: "enjoy", 33 | Description: "思路来源于https://github.com/opq-osc/OPQ-SetuBot 天乐giegie的setu机器人", 34 | Version: 0, 35 | RequireModule: []string{"群管理插件"}, 36 | } 37 | } 38 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 39 | log = l 40 | setucore.StartSetuCore(b.DB, l) 41 | px := &pixiv.Provider{} 42 | loli := &lolicon.Provider{} 43 | setucore.RegisterProvider(px, log.WithField("SetuProvider", "Pixiv Core"), b) 44 | setucore.RegisterProvider(loli, log.WithField("SetuProvider", "Lolicon Core"), b) 45 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(qq int64, packet *OPQBot.GroupMsgPack) { 46 | if packet.FromUserID != b.QQ { 47 | //cm := strings.Split(packet.Content, " ") 48 | cm := strings.SplitN(packet.Content, " ", 2) 49 | if len(cm) >= 2 && cm[0] == "爬取" && packet.FromUserID == Config.CoreConfig.SuperAdminUin { 50 | df := 100 51 | if count, err := strconv.Atoi(cm[1]); err == nil && count > 1 { 52 | df = count 53 | if count > 5000 { 54 | df = 5000 55 | } 56 | } 57 | b.SendGroupTextMsg(packet.FromGroupID, "开始爬取") 58 | getNum := 0 59 | maxtryCont := 70 60 | for i := 0; getNum < df; i++ { 61 | tmp, err := loli.SearchPicToDB() 62 | if err != nil { 63 | break 64 | } 65 | if tmp == 0 { 66 | break 67 | } 68 | if i > maxtryCont { 69 | break 70 | } 71 | getNum += tmp 72 | } 73 | b.SendGroupTextMsg(packet.FromGroupID, "爬取了"+strconv.Itoa(getNum)+"张") 74 | } 75 | 76 | command, _ := regexp.Compile("搜([0-9]+)张图") 77 | if len(cm) >= 2 && command.MatchString(cm[0]) { 78 | getNum := 1 79 | tmp := command.FindStringSubmatch(cm[0]) 80 | if len(tmp) == 2 { 81 | if count, err := strconv.Atoi(tmp[1]); err == nil && count > 1 { 82 | getNum = count 83 | if count > 10 { 84 | getNum = 10 85 | } 86 | } 87 | } 88 | 89 | pics, err := px.SearchPic(cm[1], false, getNum) 90 | if err != nil { 91 | log.Error(err) 92 | return 93 | } 94 | if len(pics) == 0 { 95 | b.SendGroupTextMsg(packet.FromGroupID, "没有找到有关"+cm[1]+"的图片") 96 | return 97 | } 98 | 99 | for _, v := range pics { 100 | res, err := requests.Get(strings.ReplaceAll(v.OriginalPicUrl, "i.pximg.net", "i-cf.pximg.net"), requests.Header{"referer": "https://www.pixiv.net/"}) 101 | if err != nil { 102 | log.Error(err) 103 | return 104 | } 105 | b.Send(OPQBot.SendMsgPack{ 106 | SendToType: OPQBot.SendToTypeGroup, 107 | ToUserUid: packet.FromGroupID, 108 | Content: OPQBot.SendTypePicMsgByBase64Content{ 109 | Content: fmt.Sprintf("标题:%s\n作者:%s\n作品链接:%s\n原图链接:%s\n30s自动撤回\n%s", v.Title, v.Author, "www.pixiv.net/artworks/"+strconv.Itoa(v.Id), v.OriginalPicUrl, OPQBot.MacroId()), 110 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 111 | Flash: false, 112 | }, 113 | CallbackFunc: func(Code int, Info string, record OPQBot.MyRecord) { 114 | time.Sleep(30 * time.Second) 115 | err := b.ReCallMsg(packet.FromGroupID, record.MsgRandom, record.MsgSeq) 116 | if err != nil { 117 | log.Warn(err) 118 | } 119 | }, 120 | }) 121 | if err != nil { 122 | log.Error(err) 123 | } 124 | } 125 | 126 | } 127 | command2, _ := regexp.Compile("搜id为(.+)的([0-9]+)张图") 128 | if command2.MatchString(cm[0]) { 129 | getNum := 1 130 | tmp := command2.FindStringSubmatch(cm[0]) 131 | if len(tmp) == 3 { 132 | if _, err := strconv.Atoi(tmp[1]); err == nil { 133 | 134 | if count, err := strconv.Atoi(tmp[2]); err == nil && count > 1 { 135 | getNum = count 136 | if count > 10 { 137 | getNum = 10 138 | } 139 | } 140 | pics, err := px.SearchPicFromUser("", tmp[1], false, getNum) 141 | if err != nil { 142 | log.Error(err) 143 | return 144 | } 145 | if len(pics) == 0 { 146 | b.SendGroupTextMsg(packet.FromGroupID, "没有找到有关"+cm[1]+"的图片") 147 | return 148 | } 149 | 150 | for _, v := range pics { 151 | res, err := requests.Get(strings.ReplaceAll(v.OriginalPicUrl, "i.pximg.net", "i-cf.pximg.net"), requests.Header{"referer": "https://www.pixiv.net/"}) 152 | if err != nil { 153 | log.Error(err) 154 | return 155 | } 156 | b.Send(OPQBot.SendMsgPack{ 157 | SendToType: OPQBot.SendToTypeGroup, 158 | ToUserUid: packet.FromGroupID, 159 | Content: OPQBot.SendTypePicMsgByBase64Content{ 160 | Content: fmt.Sprintf("标题:%s\n作者:%s\n作品链接:%s\n原图链接:%s\n30s自动撤回\n%s", v.Title, v.Author, "www.pixiv.net/artworks/"+strconv.Itoa(v.Id), v.OriginalPicUrl, OPQBot.MacroId()), 161 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 162 | Flash: false, 163 | }, 164 | CallbackFunc: func(Code int, Info string, record OPQBot.MyRecord) { 165 | time.Sleep(30 * time.Second) 166 | err := b.ReCallMsg(packet.FromGroupID, record.MsgRandom, record.MsgSeq) 167 | if err != nil { 168 | log.Warn(err) 169 | } 170 | }, 171 | }) 172 | if err != nil { 173 | log.Error(err) 174 | } 175 | } 176 | } 177 | } 178 | } 179 | command1, _ := regexp.Compile("搜(.+)的([0-9]+)张图") 180 | if command1.MatchString(cm[0]) { 181 | getNum := 1 182 | tmp := command1.FindStringSubmatch(cm[0]) 183 | if len(tmp) == 3 { 184 | if count, err := strconv.Atoi(tmp[2]); err == nil && count > 1 { 185 | getNum = count 186 | if count > 10 { 187 | getNum = 10 188 | } 189 | } 190 | pics, err := px.SearchPicFromUser(tmp[1], "", false, getNum) 191 | if err != nil { 192 | log.Error(err) 193 | return 194 | } 195 | if len(pics) == 0 { 196 | b.SendGroupTextMsg(packet.FromGroupID, "没有找到有关的图片") 197 | return 198 | } 199 | 200 | for _, v := range pics { 201 | res, err := requests.Get(strings.ReplaceAll(v.OriginalPicUrl, "i.pximg.net", "i-cf.pximg.net"), requests.Header{"referer": "https://www.pixiv.net/"}) 202 | if err != nil { 203 | log.Error(err) 204 | return 205 | } 206 | b.Send(OPQBot.SendMsgPack{ 207 | SendToType: OPQBot.SendToTypeGroup, 208 | ToUserUid: packet.FromGroupID, 209 | Content: OPQBot.SendTypePicMsgByBase64Content{ 210 | Content: fmt.Sprintf("标题:%s\n作者:%s\n作品链接:%s\n原图链接:%s\n30s自动撤回\n%s", v.Title, v.Author, "www.pixiv.net/artworks/"+strconv.Itoa(v.Id), v.OriginalPicUrl, OPQBot.MacroId()), 211 | Base64: base64.StdEncoding.EncodeToString(res.Content()), 212 | Flash: false, 213 | }, 214 | CallbackFunc: func(Code int, Info string, record OPQBot.MyRecord) { 215 | time.Sleep(30 * time.Second) 216 | err := b.ReCallMsg(packet.FromGroupID, record.MsgRandom, record.MsgSeq) 217 | if err != nil { 218 | log.Warn(err) 219 | } 220 | }, 221 | }) 222 | if err != nil { 223 | log.Error(err) 224 | } 225 | } 226 | } 227 | } 228 | 229 | } 230 | }) 231 | if err != nil { 232 | log.Error(err) 233 | } 234 | GroupManager.App.Get("/api/pic", func(ctx iris.Context) { 235 | word := ctx.URLParamDefault("word", "") 236 | author := ctx.URLParamDefault("author", "") 237 | r18, err := ctx.URLParamBool("r18") 238 | if err != nil { 239 | r18 = false 240 | } 241 | jump, err := ctx.URLParamBool("jump") 242 | if err != nil { 243 | jump = false 244 | } 245 | cat, err := ctx.URLParamBool("cat") 246 | if err != nil { 247 | cat = false 248 | } 249 | 250 | if author != "" { 251 | if _, err := strconv.Atoi(author); err == nil { 252 | pics, err := px.SearchPicFromUser("", author, r18, 1) 253 | if err != nil { 254 | log.Error(err) 255 | return 256 | } 257 | if len(pics) == 0 { 258 | ctx.JSON(GroupManager.WebResult{ 259 | Code: 1, 260 | Message: "无法取出图片!", 261 | Data: nil, 262 | }) 263 | return 264 | } 265 | if cat { 266 | pics[0].OriginalPicUrl = strings.ReplaceAll(pics[0].OriginalPicUrl, "pximg.net", "pixiv.cat") 267 | } 268 | if jump { 269 | ctx.StatusCode(302) 270 | ctx.Header("Location", pics[0].OriginalPicUrl) 271 | return 272 | } 273 | ctx.JSON(GroupManager.WebResult{ 274 | Code: 0, 275 | Message: "success", 276 | Data: pics, 277 | }) 278 | } else { 279 | pics, err := px.SearchPicFromUser(author, "", r18, 1) 280 | if err != nil { 281 | log.Error(err) 282 | return 283 | } 284 | if len(pics) == 0 { 285 | ctx.JSON(GroupManager.WebResult{ 286 | Code: 1, 287 | Message: "无法取出图片!", 288 | Data: nil, 289 | }) 290 | return 291 | } 292 | if cat { 293 | pics[0].OriginalPicUrl = strings.ReplaceAll(pics[0].OriginalPicUrl, "pximg.net", "pixiv.cat") 294 | } 295 | if jump { 296 | ctx.StatusCode(302) 297 | ctx.Header("Location", pics[0].OriginalPicUrl) 298 | return 299 | } 300 | ctx.JSON(GroupManager.WebResult{ 301 | Code: 0, 302 | Message: "success", 303 | Data: pics, 304 | }) 305 | } 306 | 307 | return 308 | } 309 | //ctx.JSON(GroupManager.WebResult{ 310 | // Code: 1, 311 | // Message: "参数错误", 312 | // Data: nil, 313 | //}) 314 | pics, err := px.SearchPic(word, r18, 1) 315 | if err != nil { 316 | log.Error(err) 317 | return 318 | } 319 | if len(pics) == 0 { 320 | ctx.JSON(GroupManager.WebResult{ 321 | Code: 1, 322 | Message: "无法取出图片!", 323 | Data: nil, 324 | }) 325 | return 326 | } 327 | if cat { 328 | pics[0].OriginalPicUrl = strings.ReplaceAll(pics[0].OriginalPicUrl, "pximg.net", "pixiv.cat") 329 | } 330 | if jump { 331 | ctx.StatusCode(302) 332 | ctx.Header("Location", pics[0].OriginalPicUrl) 333 | return 334 | } 335 | ctx.JSON(GroupManager.WebResult{ 336 | Code: 0, 337 | Message: "success", 338 | Data: pics, 339 | }) 340 | 341 | }) 342 | return nil 343 | } 344 | func init() { 345 | Core.RegisterModule(&Module{}) 346 | } 347 | -------------------------------------------------------------------------------- /setu/pixiv/model.go: -------------------------------------------------------------------------------- 1 | package pixiv 2 | 3 | import "time" 4 | 5 | type ErrResult struct { 6 | HasError bool `json:"has_error"` 7 | Errors struct { 8 | System struct { 9 | Message string `json:"message"` 10 | Code int `json:"code"` 11 | } `json:"system"` 12 | } `json:"errors"` 13 | Error string `json:"error"` 14 | } 15 | 16 | type LoginSuccessResult struct { 17 | AccessToken string `json:"access_token"` 18 | ExpiresIn int `json:"expires_in"` 19 | TokenType string `json:"token_type"` 20 | Scope string `json:"scope"` 21 | RefreshToken string `json:"refresh_token"` 22 | User struct { 23 | ProfileImageUrls struct { 24 | Px16X16 string `json:"px_16x16"` 25 | Px50X50 string `json:"px_50x50"` 26 | Px170X170 string `json:"px_170x170"` 27 | } `json:"profile_image_urls"` 28 | ID string `json:"id"` 29 | Name string `json:"name"` 30 | Account string `json:"account"` 31 | MailAddress string `json:"mail_address"` 32 | IsPremium bool `json:"is_premium"` 33 | XRestrict int `json:"x_restrict"` 34 | IsMailAuthorized bool `json:"is_mail_authorized"` 35 | RequirePolicyAgreement bool `json:"require_policy_agreement"` 36 | } `json:"user"` 37 | Response struct { 38 | AccessToken string `json:"access_token"` 39 | ExpiresIn int `json:"expires_in"` 40 | TokenType string `json:"token_type"` 41 | Scope string `json:"scope"` 42 | RefreshToken string `json:"refresh_token"` 43 | User struct { 44 | ProfileImageUrls struct { 45 | Px16X16 string `json:"px_16x16"` 46 | Px50X50 string `json:"px_50x50"` 47 | Px170X170 string `json:"px_170x170"` 48 | } `json:"profile_image_urls"` 49 | ID string `json:"id"` 50 | Name string `json:"name"` 51 | Account string `json:"account"` 52 | MailAddress string `json:"mail_address"` 53 | IsPremium bool `json:"is_premium"` 54 | XRestrict int `json:"x_restrict"` 55 | IsMailAuthorized bool `json:"is_mail_authorized"` 56 | RequirePolicyAgreement bool `json:"require_policy_agreement"` 57 | } `json:"user"` 58 | } `json:"response"` 59 | } 60 | type UserResult struct { 61 | UserPreviews []struct { 62 | User struct { 63 | Id int `json:"id"` 64 | Name string `json:"name"` 65 | Account string `json:"account"` 66 | ProfileImageUrls struct { 67 | Medium string `json:"medium"` 68 | } `json:"profile_image_urls"` 69 | IsFollowed bool `json:"is_followed"` 70 | } `json:"user"` 71 | Illusts []struct { 72 | Id int `json:"id"` 73 | Title string `json:"title"` 74 | Type string `json:"type"` 75 | ImageUrls struct { 76 | SquareMedium string `json:"square_medium"` 77 | Medium string `json:"medium"` 78 | Large string `json:"large"` 79 | } `json:"image_urls"` 80 | Caption string `json:"caption"` 81 | Restrict int `json:"restrict"` 82 | User struct { 83 | Id int `json:"id"` 84 | Name string `json:"name"` 85 | Account string `json:"account"` 86 | ProfileImageUrls struct { 87 | Medium string `json:"medium"` 88 | } `json:"profile_image_urls"` 89 | IsFollowed bool `json:"is_followed"` 90 | } `json:"user"` 91 | Tags []struct { 92 | Name string `json:"name"` 93 | TranslatedName *string `json:"translated_name"` 94 | } `json:"tags"` 95 | Tools []interface{} `json:"tools"` 96 | CreateDate time.Time `json:"create_date"` 97 | PageCount int `json:"page_count"` 98 | Width int `json:"width"` 99 | Height int `json:"height"` 100 | SanityLevel int `json:"sanity_level"` 101 | XRestrict int `json:"x_restrict"` 102 | Series interface{} `json:"series"` 103 | MetaSinglePage struct { 104 | OriginalImageUrl string `json:"original_image_url"` 105 | } `json:"meta_single_page"` 106 | MetaPages []interface{} `json:"meta_pages"` 107 | TotalView int `json:"total_view"` 108 | TotalBookmarks int `json:"total_bookmarks"` 109 | IsBookmarked bool `json:"is_bookmarked"` 110 | Visible bool `json:"visible"` 111 | IsMuted bool `json:"is_muted"` 112 | } `json:"illusts"` 113 | Novels []interface{} `json:"novels"` 114 | IsMuted bool `json:"is_muted"` 115 | } `json:"user_previews"` 116 | NextUrl interface{} `json:"next_url"` 117 | } 118 | type IllustResult struct { 119 | Illusts []struct { 120 | ID int `json:"id"` 121 | Title string `json:"title"` 122 | Type string `json:"type"` 123 | ImageUrls struct { 124 | SquareMedium string `json:"square_medium"` 125 | Medium string `json:"medium"` 126 | Large string `json:"large"` 127 | } `json:"image_urls"` 128 | Caption string `json:"caption"` 129 | Restrict int `json:"restrict"` 130 | User struct { 131 | ID int `json:"id"` 132 | Name string `json:"name"` 133 | Account string `json:"account"` 134 | ProfileImageUrls struct { 135 | Medium string `json:"medium"` 136 | } `json:"profile_image_urls"` 137 | IsFollowed bool `json:"is_followed"` 138 | } `json:"user"` 139 | Tags []struct { 140 | Name string `json:"name"` 141 | TranslatedName string `json:"translated_name"` 142 | } `json:"tags"` 143 | Tools []string `json:"tools"` 144 | CreateDate time.Time `json:"create_date"` 145 | PageCount int `json:"page_count"` 146 | Width int `json:"width"` 147 | Height int `json:"height"` 148 | SanityLevel int `json:"sanity_level"` 149 | XRestrict int `json:"x_restrict"` 150 | Series interface{} `json:"series"` 151 | MetaSinglePage struct { 152 | OriginalImageURL string `json:"original_image_url"` 153 | } `json:"meta_single_page,omitempty"` 154 | MetaPages []struct { 155 | ImageUrls struct { 156 | Original string `json:"original"` 157 | } `json:"image_urls"` 158 | } `json:"meta_pages"` 159 | TotalView int `json:"total_view"` 160 | TotalBookmarks int `json:"total_bookmarks"` 161 | IsBookmarked bool `json:"is_bookmarked"` 162 | Visible bool `json:"visible"` 163 | IsMuted bool `json:"is_muted"` 164 | } `json:"illusts"` 165 | SearchSpanLimit int `json:"search_span_limit"` 166 | } 167 | -------------------------------------------------------------------------------- /setu/pixiv/pixiv.go: -------------------------------------------------------------------------------- 1 | package pixiv 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "OPQBot-QQGroupManager/setu/setucore" 7 | "OPQBot-QQGroupManager/utils" 8 | "crypto/sha256" 9 | "encoding/base64" 10 | "encoding/csv" 11 | "errors" 12 | "fmt" 13 | "github.com/mcoo/OPQBot" 14 | "github.com/mcoo/requests" 15 | "github.com/sirupsen/logrus" 16 | "io" 17 | "os" 18 | "strconv" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | var ( 24 | log *logrus.Entry 25 | codeVerifier string 26 | CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT" 27 | CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj" 28 | USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)" 29 | REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback" 30 | ) 31 | 32 | type Provider struct { 33 | c Client 34 | } 35 | 36 | func (p *Provider) ImportPic() { 37 | fs, _ := os.Open("./setuDoing.json") 38 | r := csv.NewReader(fs) 39 | for { 40 | row, err := r.Read() 41 | if err != nil && err != io.EOF { 42 | log.Fatalf("can not read, err is %+v", err) 43 | } 44 | if err == io.EOF { 45 | break 46 | } 47 | id, err := strconv.Atoi(row[0]) 48 | if err != nil { 49 | continue 50 | } 51 | aid, err := strconv.Atoi(row[3]) 52 | if err != nil { 53 | continue 54 | } 55 | var i = 0 56 | if setucore.PicInDB(row[4]) { 57 | log.Warn("图片已存在!") 58 | continue 59 | } 60 | tmp := setucore.Pic{ 61 | Id: id, 62 | Page: i, 63 | Title: row[1], 64 | Author: row[2], 65 | AuthorID: aid, 66 | OriginalPicUrl: row[4], 67 | Tag: row[5], 68 | R18: row[6] == "True", 69 | } 70 | for { 71 | if err1 := setucore.AddPicToDB(tmp); err1 == nil || err1.Error() != "该图片在数据库中已存在!" { 72 | break 73 | } 74 | i += 1 75 | tmp = setucore.Pic{ 76 | Id: id, 77 | Page: i, 78 | Title: row[1], 79 | Author: row[2], 80 | AuthorID: aid, 81 | OriginalPicUrl: row[4], 82 | Tag: row[5], 83 | R18: row[6] == "True", 84 | } 85 | } 86 | } 87 | } 88 | func (p *Provider) autoGetPic() { 89 | log.Info("自动获取Pixiv图片排行榜") 90 | result, err := p.c.GetDailyIllust() 91 | if err != nil { 92 | log.Warn(err) 93 | } 94 | addPicNum := 0 95 | for _, v := range result.Illusts { 96 | originPicUrl := "" 97 | if v.PageCount == 1 { 98 | originPicUrl = v.MetaSinglePage.OriginalImageURL 99 | } else { 100 | originPicUrl = v.MetaPages[0].ImageUrls.Original 101 | } 102 | var tag []string 103 | tagHasR18 := false 104 | for _, v1 := range v.Tags { 105 | tmp := "" 106 | if v1.TranslatedName != "" { 107 | tmp = v1.TranslatedName 108 | } else { 109 | tmp = v1.Name 110 | } 111 | if tmp == "R18" || tmp == "R-18" { 112 | tagHasR18 = true 113 | } 114 | tag = append(tag, tmp) 115 | } 116 | tmp := setucore.Pic{ 117 | Id: v.ID, 118 | Title: v.Title, 119 | Author: v.User.Name, 120 | AuthorID: v.User.ID, 121 | OriginalPicUrl: originPicUrl, 122 | Tag: strings.Join(tag, ","), 123 | R18: v.XRestrict >= 1 || tagHasR18, 124 | } 125 | err := setucore.AddPicToDB(tmp) 126 | if err != nil { 127 | //log.Warn(err) 128 | continue 129 | } 130 | addPicNum += 1 131 | } 132 | log.Info("联网添加到本地数据库数据图片数量为:", addPicNum) 133 | } 134 | func (p *Provider) InitProvider(l *logrus.Entry, b *Core.Bot) { 135 | log = l 136 | Config.Lock.RLock() 137 | p.c.Proxy = Config.CoreConfig.SetuConfig.PixivProxy 138 | p.c.refreshToken = Config.CoreConfig.SetuConfig.PixivRefreshToken 139 | autoGetPic := Config.CoreConfig.SetuConfig.AutoGetPic 140 | Config.Lock.RUnlock() 141 | //p.ImportPic() 142 | if p.c.refreshToken == "" { 143 | p.c.GenerateLoginUrl() 144 | } else { 145 | err := p.c.RefreshToken() 146 | if err != nil { 147 | log.Error(err) 148 | } 149 | } 150 | if autoGetPic { 151 | err := b.BotCronManager.AddJob(-1, "setuAuto", "0 8 * * *", func() { 152 | p.autoGetPic() 153 | }) 154 | if err != nil { 155 | log.Error(err) 156 | } 157 | } 158 | _, err := b.AddEvent(OPQBot.EventNameOnFriendMessage, func(qq int64, packet *OPQBot.FriendMsgPack) { 159 | if packet.FromUin != b.QQ { 160 | if strings.HasPrefix(packet.Content, "code=") && !p.c.Login { 161 | code := strings.TrimPrefix(packet.Content, "code=") 162 | err := p.c.LoginPixiv(code) 163 | if err != nil { 164 | log.Error(err) 165 | } 166 | } 167 | if p.c.Login && packet.Content == "px用户信息" { 168 | r, _ := requests.Get(p.c.loginInfo.Response.User.ProfileImageUrls.Px50X50) 169 | b.SendFriendPicMsg(packet.FromUin, fmt.Sprintf("用户名: %s (%s)", p.c.loginInfo.User.Name, p.c.loginInfo.User.ID), r.Content()) 170 | } 171 | } 172 | }) 173 | if err != nil { 174 | log.Error(err) 175 | } 176 | } 177 | func (p *Provider) SearchPicFromUser(word, userId string, r18 bool, num int) ([]setucore.Pic, error) { 178 | dbPic, err := setucore.SearchUserPicFromDB(word, userId, r18, num) 179 | if err != nil { 180 | log.Warn(err) 181 | return nil, err 182 | } 183 | if len(dbPic) < num { 184 | log.Info("本地数据库数据量不够,联网获取中...") 185 | if userId != "" { 186 | result, err := p.c.GetUserPics(userId) 187 | if err != nil { 188 | log.Warn(err) 189 | } 190 | addPicNum := 0 191 | for _, v := range result.Illusts { 192 | originPicUrl := "" 193 | var tag []string 194 | tagHasR18 := false 195 | for _, v1 := range v.Tags { 196 | tmp := "" 197 | if v1.TranslatedName != "" { 198 | tmp = v1.TranslatedName 199 | } else { 200 | tmp = v1.Name 201 | } 202 | if tmp == "R18" || tmp == "R-18" { 203 | tagHasR18 = true 204 | } 205 | tag = append(tag, tmp) 206 | } 207 | if v.PageCount == 1 { 208 | originPicUrl = v.MetaSinglePage.OriginalImageURL 209 | tmp := setucore.Pic{ 210 | Id: v.ID, 211 | Page: 0, 212 | Title: v.Title, 213 | Author: v.User.Name, 214 | AuthorID: v.User.ID, 215 | OriginalPicUrl: originPicUrl, 216 | Tag: strings.Join(tag, ","), 217 | R18: v.XRestrict >= 1 || tagHasR18, 218 | } 219 | err := setucore.AddPicToDB(tmp) 220 | if err != nil { 221 | //log.Warn(err) 222 | continue 223 | } 224 | addPicNum += 1 225 | } else { 226 | for i, v1 := range v.MetaPages { 227 | originPicUrl = v1.ImageUrls.Original 228 | tmp := setucore.Pic{ 229 | Id: v.ID, 230 | Page: i, 231 | Title: v.Title, 232 | Author: v.User.Name, 233 | AuthorID: v.User.ID, 234 | OriginalPicUrl: originPicUrl, 235 | Tag: strings.Join(tag, ","), 236 | R18: v.XRestrict >= 1 || tagHasR18, 237 | } 238 | err := setucore.AddPicToDB(tmp) 239 | if err != nil { 240 | //log.Warn(err) 241 | continue 242 | } 243 | addPicNum += 1 244 | } 245 | } 246 | } 247 | log.Info("联网添加到本地数据库数据关于作者", word, "的记录数量为:", addPicNum) 248 | dbPic, err = setucore.SearchUserPicFromDB(word, userId, r18, num) 249 | if err != nil { 250 | log.Warn(err) 251 | return nil, err 252 | } 253 | } 254 | if word != "" { 255 | users, err := p.c.SearchUser(word) 256 | if err != nil { 257 | log.Warn(err) 258 | return nil, err 259 | } 260 | if len(users.UserPreviews) == 0 { 261 | return nil, errors.New("没有找到该用户") 262 | } 263 | userId = strconv.Itoa(users.UserPreviews[0].User.Id) 264 | result, err := p.c.GetUserPics(userId) 265 | if err != nil { 266 | log.Warn(err) 267 | } 268 | addPicNum := 0 269 | for _, v := range result.Illusts { 270 | originPicUrl := "" 271 | var tag []string 272 | tagHasR18 := false 273 | for _, v1 := range v.Tags { 274 | tmp := "" 275 | if v1.TranslatedName != "" { 276 | tmp = v1.TranslatedName 277 | } else { 278 | tmp = v1.Name 279 | } 280 | if tmp == "R18" || tmp == "R-18" { 281 | tagHasR18 = true 282 | } 283 | tag = append(tag, tmp) 284 | } 285 | if v.PageCount == 1 { 286 | originPicUrl = v.MetaSinglePage.OriginalImageURL 287 | tmp := setucore.Pic{ 288 | Id: v.ID, 289 | Page: 0, 290 | Title: v.Title, 291 | Author: v.User.Name, 292 | AuthorID: v.User.ID, 293 | OriginalPicUrl: originPicUrl, 294 | Tag: strings.Join(tag, ","), 295 | R18: v.XRestrict >= 1 || tagHasR18, 296 | } 297 | err := setucore.AddPicToDB(tmp) 298 | if err != nil { 299 | //log.Warn(err) 300 | continue 301 | } 302 | addPicNum += 1 303 | } else { 304 | for i, v1 := range v.MetaPages { 305 | originPicUrl = v1.ImageUrls.Original 306 | tmp := setucore.Pic{ 307 | Id: v.ID, 308 | Page: i, 309 | Title: v.Title, 310 | Author: v.User.Name, 311 | AuthorID: v.User.ID, 312 | OriginalPicUrl: originPicUrl, 313 | Tag: strings.Join(tag, ","), 314 | R18: v.XRestrict >= 1 || tagHasR18, 315 | } 316 | err := setucore.AddPicToDB(tmp) 317 | if err != nil { 318 | //log.Warn(err) 319 | continue 320 | } 321 | addPicNum += 1 322 | } 323 | } 324 | } 325 | log.Info("联网添加到本地数据库数据关于作者", word, "的记录数量为:", addPicNum) 326 | dbPic, err = setucore.SearchUserPicFromDB(word, userId, r18, num) 327 | if err != nil { 328 | log.Warn(err) 329 | return nil, err 330 | } 331 | } 332 | 333 | } 334 | if len(dbPic) > 0 { 335 | setucore.SetPicSendTime(dbPic) 336 | } 337 | return dbPic, nil 338 | } 339 | func (p *Provider) SearchPic(word string, r18 bool, num int) ([]setucore.Pic, error) { 340 | dbPic, err := setucore.SearchPicFromDB(word, r18, num) 341 | if err != nil { 342 | log.Warn(err) 343 | return nil, err 344 | } 345 | if len(dbPic) < num { 346 | log.Info("本地数据库数据量不够,联网获取中...") 347 | result, err := p.c.SearchIllust(word) 348 | if err != nil { 349 | log.Warn(err) 350 | } 351 | addPicNum := 0 352 | for _, v := range result.Illusts { 353 | originPicUrl := "" 354 | var tag []string 355 | tagHasR18 := false 356 | for _, v1 := range v.Tags { 357 | tmp := "" 358 | if v1.TranslatedName != "" { 359 | tmp = v1.TranslatedName 360 | } else { 361 | tmp = v1.Name 362 | } 363 | if tmp == "R18" || tmp == "R-18" { 364 | tagHasR18 = true 365 | } 366 | tag = append(tag, tmp) 367 | } 368 | if v.PageCount == 1 { 369 | originPicUrl = v.MetaSinglePage.OriginalImageURL 370 | tmp := setucore.Pic{ 371 | Id: v.ID, 372 | Page: 0, 373 | Title: v.Title, 374 | Author: v.User.Name, 375 | AuthorID: v.User.ID, 376 | OriginalPicUrl: originPicUrl, 377 | Tag: strings.Join(tag, ","), 378 | R18: v.XRestrict >= 1 || tagHasR18, 379 | } 380 | err := setucore.AddPicToDB(tmp) 381 | if err != nil { 382 | //log.Warn(err) 383 | continue 384 | } 385 | addPicNum += 1 386 | } else { 387 | for i, v1 := range v.MetaPages { 388 | originPicUrl = v1.ImageUrls.Original 389 | tmp := setucore.Pic{ 390 | Id: v.ID, 391 | Page: i, 392 | Title: v.Title, 393 | Author: v.User.Name, 394 | AuthorID: v.User.ID, 395 | OriginalPicUrl: originPicUrl, 396 | Tag: strings.Join(tag, ","), 397 | R18: v.XRestrict >= 1 || tagHasR18, 398 | } 399 | err := setucore.AddPicToDB(tmp) 400 | if err != nil { 401 | //log.Warn(err) 402 | continue 403 | } 404 | addPicNum += 1 405 | } 406 | } 407 | } 408 | log.Info("联网添加到本地数据库数据关于", word, "的记录数量为:", addPicNum) 409 | dbPic, err = setucore.SearchPicFromDB(word, r18, num) 410 | if err != nil { 411 | log.Warn(err) 412 | return nil, err 413 | } 414 | } 415 | if len(dbPic) > 0 { 416 | setucore.SetPicSendTime(dbPic) 417 | } 418 | return dbPic, nil 419 | } 420 | 421 | type Client struct { 422 | Proxy string 423 | Login bool 424 | tokenExpiresTime time.Time 425 | refreshToken string 426 | Token string 427 | loginInfo LoginSuccessResult 428 | } 429 | 430 | func (c *Client) RefreshToken() error { 431 | // 尝试通过 refreshToken 获取Token 432 | if time.Now().After(c.tokenExpiresTime) { 433 | 434 | req := requests.Requests() 435 | if c.Proxy != "" { 436 | req.Proxy(c.Proxy) 437 | } 438 | r, err := req.Post("https://oauth.secure.pixiv.net/auth/token", 439 | requests.Datas{ 440 | "client_id": CLIENT_ID, 441 | "client_secret": CLIENT_SECRET, 442 | "grant_type": "refresh_token", 443 | "refresh_token": c.refreshToken, 444 | "get_secure_url": "1", 445 | }, 446 | requests.Header{ 447 | "User-Agent": USER_AGENT, 448 | "host": "oauth.secure.pixiv.net", 449 | }) 450 | if err != nil { 451 | return err 452 | } 453 | tmp := map[string]interface{}{} 454 | err = r.Json(&tmp) 455 | if err != nil { 456 | return err 457 | } 458 | if _, ok := tmp["has_error"]; ok { 459 | result := ErrResult{} 460 | err = r.Json(&result) 461 | if err != nil { 462 | return err 463 | } 464 | return errors.New(fmt.Sprintf("[%d]%s", result.Errors.System.Code, result.Errors.System.Message)) 465 | } 466 | result := LoginSuccessResult{} 467 | err = r.Json(&result) 468 | if err != nil { 469 | return err 470 | } 471 | c.loginInfo = result 472 | c.Login = true 473 | c.Token = result.AccessToken 474 | c.refreshToken = result.RefreshToken 475 | Config.Lock.Lock() 476 | Config.CoreConfig.SetuConfig.PixivRefreshToken = result.RefreshToken 477 | Config.Save() 478 | Config.Lock.Unlock() 479 | 480 | c.tokenExpiresTime = time.Now().Add(time.Second * time.Duration(result.ExpiresIn)) 481 | log.Println("登录成功") 482 | } 483 | return nil 484 | } 485 | func (c *Client) GetUserInfo() LoginSuccessResult { 486 | return c.loginInfo 487 | } 488 | func (c *Client) LoginPixiv(code string) error { 489 | // 尝试通过 Code 获取 Refresh Token 490 | if c.refreshToken != "" { 491 | return c.RefreshToken() 492 | } 493 | req := requests.Requests() 494 | if c.Proxy != "" { 495 | req.Proxy(c.Proxy) 496 | } 497 | r, err := req.Post("https://oauth.secure.pixiv.net/auth/token", 498 | requests.Datas{ 499 | "client_id": CLIENT_ID, 500 | "client_secret": CLIENT_SECRET, 501 | "code": code, 502 | "code_verifier": codeVerifier, 503 | "grant_type": "authorization_code", 504 | "include_policy": "true", 505 | "redirect_uri": REDIRECT_URI, 506 | }, 507 | requests.Header{ 508 | "User-Agent": USER_AGENT, 509 | "host": "oauth.secure.pixiv.net", 510 | }) 511 | if err != nil { 512 | return err 513 | } 514 | tmp := map[string]interface{}{} 515 | err = r.Json(&tmp) 516 | if err != nil { 517 | return err 518 | } 519 | if _, ok := tmp["has_error"]; ok { 520 | result := ErrResult{} 521 | err = r.Json(&result) 522 | if err != nil { 523 | return err 524 | } 525 | return errors.New(fmt.Sprintf("[%d]%s", result.Errors.System.Code, result.Errors.System.Message)) 526 | } 527 | result := LoginSuccessResult{} 528 | err = r.Json(&result) 529 | if err != nil { 530 | return err 531 | } 532 | c.loginInfo = result 533 | c.Login = true 534 | c.Token = result.AccessToken 535 | c.refreshToken = result.RefreshToken 536 | Config.Lock.Lock() 537 | Config.CoreConfig.SetuConfig.PixivRefreshToken = result.RefreshToken 538 | Config.Save() 539 | Config.Lock.Unlock() 540 | c.tokenExpiresTime = time.Now().Add(time.Second * time.Duration(result.ExpiresIn)) 541 | log.Println("登录成功") 542 | return nil 543 | } 544 | func (c *Client) GenerateLoginUrl() { 545 | codeVerifier = Base64UrlSafeEncode([]byte(utils.RandomString(32))) 546 | h := sha256.New() 547 | h.Write([]byte(codeVerifier)) 548 | sum := h.Sum(nil) 549 | codeChallenge := Base64UrlSafeEncode(sum) 550 | log.Println("未登录! 请登录以下网址,然后将链接中的code参数私信发送给机器人完成登录。请发送\"code=xxxxxxx\"") 551 | log.Println("https://app-api.pixiv.net/web/v1/login?code_challenge=" + codeChallenge + "&code_challenge_method=S256&client=pixiv-android") 552 | } 553 | func (c *Client) GetHeader() requests.Header { 554 | err := c.RefreshToken() 555 | if err != nil { 556 | log.Warn(err) 557 | } 558 | if c.Token != "" { 559 | return requests.Header{ 560 | "host": "app-api.pixiv.net", 561 | "app-os": "ios", 562 | "Accept-Language": "zh-cn", 563 | "User-Agent": "PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)", 564 | "Authorization": fmt.Sprintf("Bearer %s", c.Token), 565 | } 566 | } else { 567 | return requests.Header{ 568 | "Accept-Language": "zh-cn", 569 | "User-Agent": USER_AGENT, 570 | } 571 | } 572 | 573 | } 574 | func (c *Client) SearchIllust(word string) (result IllustResult, err error) { 575 | var res *requests.Response 576 | req := requests.Requests() 577 | if c.Proxy != "" { 578 | req.Proxy(c.Proxy) 579 | } 580 | res, err = req.Get(fmt.Sprintf("https://app-api.pixiv.net/v1/search/popular-preview/illust?word=%s&search_target=partial_match_for_tags&sort=date_desc&filter=for_ios", word), c.GetHeader()) 581 | if err != nil { 582 | return 583 | } 584 | err = res.Json(&result) 585 | return 586 | } 587 | func (c *Client) SearchUser(word string) (result UserResult, err error) { 588 | var res *requests.Response 589 | req := requests.Requests() 590 | if c.Proxy != "" { 591 | req.Proxy(c.Proxy) 592 | } 593 | res, err = req.Get(fmt.Sprintf("https://app-api.pixiv.net/v1/search/user?filter=for_android&word=%s", word), c.GetHeader()) 594 | if err != nil { 595 | return 596 | } 597 | err = res.Json(&result) 598 | return 599 | } 600 | func (c *Client) GetUserPics(userId string) (result IllustResult, err error) { 601 | log.Info(userId) 602 | var res *requests.Response 603 | req := requests.Requests() 604 | if c.Proxy != "" { 605 | req.Proxy(c.Proxy) 606 | } 607 | res, err = req.Get(fmt.Sprintf("https://app-api.pixiv.net/v1/user/illusts?user_id=%s&filter=for_ios", userId), c.GetHeader()) 608 | if err != nil { 609 | return 610 | } 611 | err = res.Json(&result) 612 | return 613 | } 614 | func (c *Client) GetDailyIllust() (result IllustResult, err error) { 615 | var res *requests.Response 616 | req := requests.Requests() 617 | if c.Proxy != "" { 618 | req.Proxy(c.Proxy) 619 | } 620 | res, err = req.Get("https://app-api.pixiv.net/v1/illust/ranking?mode=day&filter=for_ios", c.GetHeader()) 621 | if err != nil { 622 | return 623 | } 624 | err = res.Json(&result) 625 | return 626 | } 627 | func Base64URLDecode(data string) ([]byte, error) { 628 | var missing = (4 - len(data)%4) % 4 629 | data += strings.Repeat("=", missing) 630 | res, err := base64.URLEncoding.DecodeString(data) 631 | fmt.Println(" decodebase64urlsafe is :", string(res), err) 632 | return base64.URLEncoding.DecodeString(data) 633 | } 634 | func Base64UrlSafeEncode(source []byte) string { 635 | // Base64 Url Safe is the same as Base64 but does not contain '/' and '+' (replaced by '_' and '-') and trailing '=' are removed. 636 | bytearr := base64.StdEncoding.EncodeToString(source) 637 | safeurl := strings.Replace(string(bytearr), "/", "_", -1) 638 | safeurl = strings.Replace(safeurl, "+", "-", -1) 639 | safeurl = strings.Replace(safeurl, "=", "", -1) 640 | return safeurl 641 | } 642 | -------------------------------------------------------------------------------- /setu/setucore/core.go: -------------------------------------------------------------------------------- 1 | package setucore 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Core" 5 | "errors" 6 | "github.com/sirupsen/logrus" 7 | "gorm.io/gorm" 8 | "time" 9 | ) 10 | 11 | type Provider interface { 12 | InitProvider(l *logrus.Entry, bot *Core.Bot) 13 | SearchPic(word string, r18 bool, num int) ([]Pic, error) 14 | SearchPicFromUser(word string, userId string, r18 bool, num int) ([]Pic, error) 15 | } 16 | type Pic struct { 17 | Id int `gorm:"primaryKey"` 18 | Page int `gorm:"primaryKey;default:0"` 19 | Title string 20 | Author string 21 | AuthorID int 22 | OriginalPicUrl string 23 | Tag string `gorm:"index;size:255"` 24 | R18 bool 25 | LastSendTime int64 `gorm:"not null"` 26 | } 27 | 28 | var ( 29 | log *logrus.Entry 30 | db *gorm.DB 31 | ) 32 | 33 | func StartSetuCore(_db *gorm.DB, _log *logrus.Entry) { 34 | db = _db 35 | log = _log 36 | db.AutoMigrate(&Pic{}) 37 | } 38 | func RegisterProvider(p Provider, log *logrus.Entry, bot *Core.Bot) { 39 | p.InitProvider(log, bot) 40 | } 41 | func SearchPicFromDB(word string, r18 bool, num int) (pics []Pic, e error) { 42 | if word == "" { 43 | e = db.Where("r18 = ? AND last_send_time < ?", r18, time.Now().Unix()-1800).Limit(num).Order("last_send_time asc").Find(&pics).Error 44 | return 45 | } 46 | e = db.Where("tag LIKE ? AND r18 = ? AND last_send_time < ?", "%"+word+"%", r18, time.Now().Unix()-1800).Limit(num).Order("last_send_time asc").Find(&pics).Error 47 | return 48 | } 49 | func SearchUserPicFromDB(word, userId string, r18 bool, num int) (pics []Pic, e error) { 50 | if userId != "" { 51 | e = db.Where("r18 = ? AND last_send_time < ? AND author_id = ?", r18, time.Now().Unix()-1800, userId).Limit(num).Order("last_send_time asc").Find(&pics).Error 52 | return 53 | } 54 | e = db.Where("author LIKE ? AND r18 = ? AND last_send_time < ?", "%"+word+"%", r18, time.Now().Unix()-1800).Limit(num).Order("last_send_time asc").Find(&pics).Error 55 | return 56 | } 57 | func AddPicToDB(pic Pic) error { 58 | var num int64 59 | db.Model(&pic).Where("id = ? AND page = ?", pic.Id, pic.Page).Count(&num) 60 | if num > 0 { 61 | return errors.New("该图片在数据库中已存在!") 62 | } 63 | return db.Create(&pic).Error 64 | } 65 | func PicInDB(picUrl string) bool { 66 | var num int64 67 | db.Model(&Pic{}).Where("original_pic_url = ?", picUrl).Count(&num) 68 | if num > 0 { 69 | return true 70 | } 71 | return false 72 | } 73 | func SetPicSendTime(pics []Pic) { 74 | for _, v := range pics { 75 | db.Model(&Pic{}).Where("id = ? AND page = ?", v.Id, v.Page).Updates(&Pic{LastSendTime: time.Now().Unix()}) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /ssl/main.go: -------------------------------------------------------------------------------- 1 | package ssl 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "OPQBot-QQGroupManager/utils" 7 | "fmt" 8 | "github.com/mcoo/OPQBot" 9 | "github.com/sirupsen/logrus" 10 | "strings" 11 | ) 12 | 13 | type Module struct { 14 | } 15 | 16 | var log *logrus.Entry 17 | 18 | func (m *Module) ModuleInfo() Core.ModuleInfo { 19 | return Core.ModuleInfo{ 20 | Name: "SSL扫描插件", 21 | Author: "enjoy", 22 | Description: "", 23 | Version: 0, 24 | } 25 | } 26 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 27 | log = l 28 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(botQQ int64, packet *OPQBot.GroupMsgPack) { 29 | if packet.FromUserID == botQQ { 30 | return 31 | } 32 | Config.Lock.RLock() 33 | var c Config.GroupConfig 34 | if v, ok := Config.CoreConfig.GroupConfig[packet.FromGroupID]; ok { 35 | c = v 36 | } else { 37 | c = Config.CoreConfig.DefaultGroupConfig 38 | } 39 | Config.Lock.RUnlock() 40 | if !c.Enable { 41 | return 42 | } 43 | cm := strings.Split(packet.Content, " ") 44 | if len(cm) == 2 && cm[0] == "SSL" { 45 | ssl, err := utils.SSLStatus("github.com") 46 | if err != nil { 47 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 48 | return 49 | } 50 | content := "SSL证书建议:" 51 | for _, v := range ssl.Data.Status.Suggests { 52 | content += fmt.Sprintf("\n- %s", v.Tip) 53 | } 54 | content += "\n具体信息:" 55 | for _, v := range ssl.Data.Status.Protocols { 56 | content += fmt.Sprintf("\n- %s %t", v.Name, v.Support) 57 | } 58 | for _, v := range ssl.Data.Status.ProtocolDetail { 59 | content += fmt.Sprintf("\n- %s %t", v.Name, v.Support) 60 | } 61 | b.SendGroupTextMsg(packet.FromGroupID, OPQBot.MacroAt([]int64{packet.FromUserID})+content) 62 | return 63 | } 64 | if len(cm) == 2 && cm[0] == "DNS" { 65 | dns, err := utils.DnsQuery(cm[1]) 66 | if err != nil { 67 | b.SendGroupTextMsg(packet.FromGroupID, err.Error()) 68 | return 69 | } 70 | content := fmt.Sprintf("[%s]\n", cm[1]) 71 | if dns.Data.Num86[0].Answer.Error == "" { 72 | content += fmt.Sprintf("中国: %s [%s] %ss\n", dns.Data.Num86[0].Answer.Records[0].Value, dns.Data.Num86[0].Answer.Records[0].IPLocation, dns.Data.Num86[0].Answer.TimeConsume) 73 | } else { 74 | content += "中国: " + dns.Data.Num86[0].Answer.Error + "\n" 75 | } 76 | if dns.Data.Num01[0].Answer.Error == "" { 77 | content += fmt.Sprintf("美国: %s [%s] %ss\n", dns.Data.Num01[0].Answer.Records[0].Value, dns.Data.Num01[0].Answer.Records[0].IPLocation, dns.Data.Num01[0].Answer.TimeConsume) 78 | } else { 79 | content += "美国: " + dns.Data.Num01[0].Answer.Error + "\n" 80 | } 81 | if dns.Data.Num852[0].Answer.Error == "" { 82 | content += fmt.Sprintf("香港: %s [%s] %ss", dns.Data.Num852[0].Answer.Records[0].Value, dns.Data.Num852[0].Answer.Records[0].IPLocation, dns.Data.Num852[0].Answer.TimeConsume) 83 | } else { 84 | content += "香港: " + dns.Data.Num852[0].Answer.Error 85 | } 86 | b.SendGroupTextMsg(packet.FromGroupID, OPQBot.MacroAt([]int64{packet.FromUserID})+content) 87 | return 88 | } 89 | }) 90 | if err != nil { 91 | return err 92 | } 93 | return nil 94 | } 95 | 96 | func init() { 97 | Core.RegisterModule(&Module{}) 98 | } 99 | -------------------------------------------------------------------------------- /taobao/main.go: -------------------------------------------------------------------------------- 1 | package taobao 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Core" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/mcoo/OPQBot" 8 | "github.com/sirupsen/logrus" 9 | "io/ioutil" 10 | "net/http" 11 | "time" 12 | ) 13 | 14 | type Module struct { 15 | MsgChannel chan OPQBot.GroupMsgPack 16 | ImgServer string 17 | } 18 | 19 | var ( 20 | log *logrus.Entry 21 | ) 22 | 23 | type setu struct { 24 | Title string `json:"title"` 25 | Pic string `json:"pic"` 26 | } 27 | 28 | func (m *Module) ModuleInfo() Core.ModuleInfo { 29 | return Core.ModuleInfo{ 30 | Name: "淘宝买家秀", 31 | Author: "bypanghu", 32 | Description: "生成淘宝买家秀", 33 | Version: 0, 34 | } 35 | } 36 | 37 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 38 | log = l 39 | 40 | _, err := b.AddEvent(OPQBot.EventNameOnGroupMessage, func(botQQ int64, packet *OPQBot.GroupMsgPack) { 41 | if packet.FromUserID == botQQ { 42 | return 43 | } 44 | if packet.Content == "买家秀" { 45 | client := &http.Client{} 46 | baseUrl := "https://api.vvhan.com/api/tao?type=json" 47 | req, err := http.NewRequest("GET", baseUrl, nil) 48 | req.Header.Add("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1") 49 | if err != nil { 50 | panic(err) 51 | } 52 | response, _ := client.Do(req) 53 | defer response.Body.Close() 54 | s, err := ioutil.ReadAll(response.Body) 55 | var res setu 56 | json.Unmarshal(s, &res) 57 | b.Send(OPQBot.SendMsgPack{ 58 | SendToType: OPQBot.SendToTypeGroup, 59 | ToUserUid: packet.FromGroupID, 60 | Content: OPQBot.SendTypePicMsgByUrlContent{ 61 | Content: fmt.Sprintf("%s\n图片地址为:%s\n30s自动撤回\n%s", res.Title, res.Pic, OPQBot.MacroId()), 62 | PicUrl: res.Pic, 63 | Flash: false, 64 | }, 65 | CallbackFunc: func(Code int, Info string, record OPQBot.MyRecord) { 66 | time.Sleep(30 * time.Second) 67 | err := b.ReCallMsg(packet.FromGroupID, record.MsgRandom, record.MsgSeq) 68 | if err != nil { 69 | log.Warn(err) 70 | } 71 | }, 72 | }) 73 | } 74 | 75 | }) 76 | return err 77 | } 78 | 79 | func init() { 80 | Core.RegisterModule(&Module{}) 81 | } 82 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mcoo/requests" 5 | "runtime" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type GithubRelease struct { 11 | URL string `json:"url"` 12 | AssetsURL string `json:"assets_url"` 13 | UploadURL string `json:"upload_url"` 14 | HTMLURL string `json:"html_url"` 15 | ID int `json:"id"` 16 | Author struct { 17 | Login string `json:"login"` 18 | ID int `json:"id"` 19 | NodeID string `json:"node_id"` 20 | AvatarURL string `json:"avatar_url"` 21 | GravatarID string `json:"gravatar_id"` 22 | URL string `json:"url"` 23 | HTMLURL string `json:"html_url"` 24 | FollowersURL string `json:"followers_url"` 25 | FollowingURL string `json:"following_url"` 26 | GistsURL string `json:"gists_url"` 27 | StarredURL string `json:"starred_url"` 28 | SubscriptionsURL string `json:"subscriptions_url"` 29 | OrganizationsURL string `json:"organizations_url"` 30 | ReposURL string `json:"repos_url"` 31 | EventsURL string `json:"events_url"` 32 | ReceivedEventsURL string `json:"received_events_url"` 33 | Type string `json:"type"` 34 | SiteAdmin bool `json:"site_admin"` 35 | } `json:"author"` 36 | NodeID string `json:"node_id"` 37 | TagName string `json:"tag_name"` 38 | TargetCommitish string `json:"target_commitish"` 39 | Name string `json:"name"` 40 | Draft bool `json:"draft"` 41 | Prerelease bool `json:"prerelease"` 42 | CreatedAt time.Time `json:"created_at"` 43 | PublishedAt time.Time `json:"published_at"` 44 | Assets []struct { 45 | URL string `json:"url"` 46 | ID int `json:"id"` 47 | NodeID string `json:"node_id"` 48 | Name string `json:"name"` 49 | Label string `json:"label"` 50 | Uploader struct { 51 | Login string `json:"login"` 52 | ID int `json:"id"` 53 | NodeID string `json:"node_id"` 54 | AvatarURL string `json:"avatar_url"` 55 | GravatarID string `json:"gravatar_id"` 56 | URL string `json:"url"` 57 | HTMLURL string `json:"html_url"` 58 | FollowersURL string `json:"followers_url"` 59 | FollowingURL string `json:"following_url"` 60 | GistsURL string `json:"gists_url"` 61 | StarredURL string `json:"starred_url"` 62 | SubscriptionsURL string `json:"subscriptions_url"` 63 | OrganizationsURL string `json:"organizations_url"` 64 | ReposURL string `json:"repos_url"` 65 | EventsURL string `json:"events_url"` 66 | ReceivedEventsURL string `json:"received_events_url"` 67 | Type string `json:"type"` 68 | SiteAdmin bool `json:"site_admin"` 69 | } `json:"uploader"` 70 | ContentType string `json:"content_type"` 71 | State string `json:"state"` 72 | Size int `json:"size"` 73 | DownloadCount int `json:"download_count"` 74 | CreatedAt time.Time `json:"created_at"` 75 | UpdatedAt time.Time `json:"updated_at"` 76 | BrowserDownloadURL string `json:"browser_download_url"` 77 | } `json:"assets"` 78 | TarballURL string `json:"tarball_url"` 79 | ZipballURL string `json:"zipball_url"` 80 | Body string `json:"body"` 81 | } 82 | 83 | func CheckUpdate() { 84 | res, err := requests.Get("https://api.github.com/repos/opq-osc/OPQBot-GroupManager/releases/latest") 85 | if err != nil { 86 | log.Println(err) 87 | return 88 | } 89 | var result GithubRelease 90 | err = res.Json(&result) 91 | if err != nil { 92 | log.Println(err) 93 | return 94 | } 95 | if !strings.HasSuffix(result.TagName, version) { 96 | log.Println("检测到更新欧~ " + result.TagName) 97 | if result.Prerelease { 98 | log.Println("注意最新版本为预发行版本") 99 | } 100 | downloadName := "opqbot-manager_" + runtime.GOOS + "_" + strings.ReplaceAll(strings.ReplaceAll(runtime.GOARCH, "386", "i386"), "amd64", "x86_64") 101 | for _, v := range result.Assets { 102 | if strings.HasPrefix(strings.ToLower(v.Name), downloadName) { 103 | log.Println("下载链接", v.BrowserDownloadURL) 104 | } 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /upx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $1 $2 4 | if [ "$1" != "android" ]; then 5 | upx $2; 6 | fi 7 | exit 0; -------------------------------------------------------------------------------- /utils/cron.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "sync" 7 | 8 | "github.com/robfig/cron/v3" 9 | ) 10 | 11 | type BotCron struct { 12 | c *cron.Cron 13 | jobs map[string]cron.EntryID 14 | locker sync.Mutex 15 | } 16 | 17 | func NewBotCronManager() BotCron { 18 | return BotCron{c: cron.New(), jobs: map[string]cron.EntryID{}, locker: sync.Mutex{}} 19 | } 20 | 21 | func (m *BotCron) AddJob(qqGroup int64, jobName string, spec string, cmd func()) error { 22 | Name := strconv.FormatInt(qqGroup, 10) + "-" + jobName 23 | m.locker.Lock() 24 | defer m.locker.Unlock() 25 | if _, ok := m.jobs[Name]; ok { 26 | return errors.New("Job已经存在了,不能重复添加!") 27 | } else { 28 | if id, err := m.c.AddFunc(spec, cmd); err != nil { 29 | return err 30 | } else { 31 | m.jobs[Name] = id 32 | return nil 33 | } 34 | } 35 | } 36 | 37 | func (m *BotCron) Remove(qqGroup int64, jobName string) error { 38 | Name := strconv.FormatInt(qqGroup, 10) + "-" + jobName 39 | m.locker.Lock() 40 | defer m.locker.Unlock() 41 | if v, ok := m.jobs[Name]; ok { 42 | m.c.Remove(v) 43 | delete(m.jobs, Name) 44 | return nil 45 | } else { 46 | return errors.New("Job不存在!") 47 | } 48 | } 49 | 50 | func (m *BotCron) List() map[string]cron.EntryID { 51 | m.locker.Lock() 52 | defer m.locker.Unlock() 53 | return m.jobs 54 | } 55 | 56 | func (m *BotCron) Start() { 57 | m.c.Start() 58 | } 59 | -------------------------------------------------------------------------------- /utils/csrf.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | ) 7 | 8 | func Md5V(str string) string { 9 | h := md5.New() 10 | h.Write([]byte(str)) 11 | return hex.EncodeToString(h.Sum(nil)) 12 | } 13 | -------------------------------------------------------------------------------- /utils/dns.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/mcoo/requests" 4 | 5 | type DNSResult struct { 6 | Code int `json:"code"` 7 | Data struct { 8 | Num86 []struct { 9 | Answer struct { 10 | TimeConsume string `json:"time_consume"` 11 | Records []struct { 12 | TTL int `json:"ttl"` 13 | Value string `json:"value"` 14 | IPLocation string `json:"ip_location"` 15 | } `json:"records"` 16 | Error string `json:"error"` 17 | } `json:"answer"` 18 | } `json:"86"` 19 | Num852 []struct { 20 | Answer struct { 21 | TimeConsume string `json:"time_consume"` 22 | Records []struct { 23 | TTL int `json:"ttl"` 24 | Value string `json:"value"` 25 | IPLocation string `json:"ip_location"` 26 | } `json:"records"` 27 | Error string `json:"error"` 28 | } `json:"answer"` 29 | } `json:"852"` 30 | Num01 []struct { 31 | Answer struct { 32 | TimeConsume string `json:"time_consume"` 33 | Records []struct { 34 | TTL int `json:"ttl"` 35 | Value string `json:"value"` 36 | IPLocation string `json:"ip_location"` 37 | } `json:"records"` 38 | Error string `json:"error"` 39 | } `json:"answer"` 40 | } `json:"01"` 41 | } `json:"data"` 42 | } 43 | 44 | func DnsQuery(host string) (r DNSResult, e error) { 45 | res, e := requests.Get("https://myssl.com/api/v1/tools/dns_query?qtype=1&qmode=-1&host=" + host) 46 | if e != nil { 47 | return 48 | } 49 | e = res.Json(&r) 50 | if e != nil { 51 | return 52 | } 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /utils/ssl.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/mcoo/requests" 5 | "time" 6 | ) 7 | 8 | type SSLResult struct { 9 | Code int `json:"code"` 10 | Data struct { 11 | Version string `json:"version"` 12 | Hosts []string `json:"hosts"` 13 | CheckHost string `json:"check_host"` 14 | Status struct { 15 | Suggests []struct { 16 | Code int `json:"code"` 17 | Tip string `json:"tip"` 18 | Link string `json:"link"` 19 | Level int `json:"level"` 20 | } `json:"suggests"` 21 | ProtocolDetail []struct { 22 | Name string `json:"name"` 23 | Support bool `json:"support"` 24 | } `json:"protocol_detail"` 25 | Protocols []struct { 26 | Name string `json:"name"` 27 | Support bool `json:"support"` 28 | } `json:"protocols"` 29 | Certs struct { 30 | Rsas []struct { 31 | LeafCertInfo struct { 32 | Hash string `json:"hash"` 33 | CertStatus int `json:"cert_status"` 34 | CertStatusText string `json:"cert_status_text"` 35 | CommonName string `json:"common_name"` 36 | PublickeyAlgorithm string `json:"publickey_algorithm"` 37 | Issuer string `json:"issuer"` 38 | SignatureAlgorithm string `json:"signature_algorithm"` 39 | Organization string `json:"organization"` 40 | OrganizationUnit string `json:"organization_unit"` 41 | Sans []string `json:"sans"` 42 | Transparency string `json:"transparency"` 43 | IsCtQualified int `json:"is_ct_qualified"` 44 | CertType string `json:"cert_type"` 45 | CertDomainType int `json:"cert_domain_type"` 46 | BrandName string `json:"brand_name"` 47 | ValidFrom time.Time `json:"valid_from"` 48 | ValidTo time.Time `json:"valid_to"` 49 | IsSni bool `json:"is_sni"` 50 | OcspMustStaple bool `json:"ocsp_must_staple"` 51 | OcspURL []string `json:"ocsp_url"` 52 | OcspStatus int `json:"ocsp_status"` 53 | Country string `json:"country"` 54 | Locality string `json:"locality"` 55 | StreetAddress string `json:"street_address"` 56 | PostalCode string `json:"postal_code"` 57 | } `json:"leaf_cert_info"` 58 | CertsFormServer struct { 59 | ProvidedNumber int `json:"provided_number"` 60 | Certs []struct { 61 | Order int `json:"order"` 62 | CommonName string `json:"common_name"` 63 | Hash string `json:"hash"` 64 | Pin string `json:"pin"` 65 | ValidTo time.Time `json:"valid_to"` 66 | IsExpired bool `json:"is_expired"` 67 | KeyAlgo string `json:"key_algo"` 68 | SignAlgo string `json:"sign_algo"` 69 | IssuerCommonName string `json:"issuer_common_name"` 70 | } `json:"certs"` 71 | } `json:"certs_form_server"` 72 | Chain struct { 73 | Certs []struct { 74 | Type string `json:"type"` 75 | Missing bool `json:"missing"` 76 | FromServer bool `json:"from_server"` 77 | CommonName string `json:"common_name"` 78 | PublickeyAlgorithm string `json:"publickey_algorithm"` 79 | SignatureAlgorithm string `json:"signature_algorithm"` 80 | Sha1 string `json:"sha1"` 81 | Pin string `json:"pin"` 82 | ExpiresIn int `json:"expires_in"` 83 | Issuer string `json:"issuer"` 84 | BeginTime time.Time `json:"begin_time"` 85 | EndTime time.Time `json:"end_time"` 86 | Order int `json:"order,omitempty"` 87 | IsCa bool `json:"is_ca"` 88 | } `json:"certs"` 89 | HasRoot bool `json:"has_root"` 90 | HasOtherCa bool `json:"has_other_ca"` 91 | MissCa bool `json:"miss_ca"` 92 | ID string `json:"id"` 93 | } `json:"chain"` 94 | } `json:"rsas"` 95 | Eccs []interface{} `json:"eccs"` 96 | Sigs []interface{} `json:"sigs"` 97 | Encs []interface{} `json:"encs"` 98 | } `json:"certs"` 99 | Ciphers interface{} `json:"ciphers"` 100 | } `json:"status"` 101 | } `json:"data"` 102 | } 103 | 104 | func SSLStatus(host string) (r SSLResult, e error) { 105 | res, e := requests.Get("https://myssl.com/api/v1/ssl_status?port=443&c=0&domain=" + host) 106 | if e != nil { 107 | return 108 | } 109 | e = res.Json(&r) 110 | if e != nil { 111 | return 112 | } 113 | return 114 | } 115 | -------------------------------------------------------------------------------- /utils/tools.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/axgle/mahonia" 5 | "math/rand" 6 | ) 7 | 8 | var defaultLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 9 | 10 | func ConvertToByte(src string, srcCode string, targetCode string) []byte { 11 | srcCoder := mahonia.NewDecoder(srcCode) 12 | srcResult := srcCoder.ConvertString(src) 13 | tagCoder := mahonia.NewDecoder(targetCode) 14 | _, cdata, _ := tagCoder.Translate([]byte(srcResult), true) 15 | return cdata 16 | } 17 | 18 | // RandomString returns a random string with a fixed length 19 | func RandomString(n int, allowedChars ...[]rune) string { 20 | var letters []rune 21 | 22 | if len(allowedChars) == 0 { 23 | letters = defaultLetters 24 | } else { 25 | letters = allowedChars[0] 26 | } 27 | 28 | b := make([]rune, n) 29 | for i := range b { 30 | b[i] = letters[rand.Intn(len(letters))] 31 | } 32 | 33 | return string(b) 34 | } 35 | -------------------------------------------------------------------------------- /wordCloud/main.go: -------------------------------------------------------------------------------- 1 | package wordCloud 2 | 3 | import ( 4 | "OPQBot-QQGroupManager/Config" 5 | "OPQBot-QQGroupManager/Core" 6 | "bytes" 7 | "errors" 8 | "fmt" 9 | "github.com/go-ego/gse" 10 | "github.com/mcoo/OPQBot" 11 | "github.com/mcoo/requests" 12 | "github.com/mcoo/wordclouds" 13 | "github.com/sirupsen/logrus" 14 | "gorm.io/gorm" 15 | "image/color" 16 | "image/png" 17 | "time" 18 | ) 19 | 20 | var DefaultColors = []color.RGBA{ 21 | {0x1b, 0x1b, 0x1b, 0xff}, 22 | {0x48, 0x48, 0x4B, 0xff}, 23 | {0x59, 0x3a, 0xee, 0xff}, 24 | {0x65, 0xCD, 0xFA, 0xff}, 25 | {0x70, 0xD6, 0xBF, 0xff}, 26 | {153, 50, 204, 255}, 27 | {100, 149, 237, 255}, 28 | {0, 255, 127, 255}, 29 | {255, 0, 0, 255}, 30 | } 31 | 32 | type Module struct { 33 | db *gorm.DB 34 | MsgChannel chan OPQBot.GroupMsgPack 35 | ImgServer string 36 | } 37 | 38 | var ( 39 | log *logrus.Entry 40 | ) 41 | 42 | func (m *Module) ModuleInfo() Core.ModuleInfo { 43 | return Core.ModuleInfo{ 44 | Name: "词云生成", 45 | Author: "enjoy", 46 | Description: "给群生成聊天词云 还可以查询奥运信息呢!", 47 | Version: 0, 48 | } 49 | } 50 | 51 | type HotWord struct { 52 | gorm.Model 53 | GroupId int64 `gorm:"index"` 54 | Word string `gorm:"index"` 55 | Count int `gorm:"not null"` 56 | HotTime int64 `gorm:"index;not null"` 57 | } 58 | 59 | func (m *Module) DoHotWord() { 60 | var segmenter gse.Segmenter 61 | err := segmenter.LoadDict("./dictionary.txt") 62 | if err != nil { 63 | log.Error(err) 64 | } 65 | //var segmented sego.Segmenter 66 | //segmented.LoadDictionary("./dictionary.txt") 67 | for { 68 | msg := <-m.MsgChannel 69 | tmp := segmenter.Segment([]byte(OPQBot.DecodeFaceFromSentences(msg.Content, "%s"))) 70 | for _, v := range gse.ToSlice(tmp, false) { 71 | if len([]rune(v)) > 1 { 72 | err := m.AddHotWord(v, msg.FromGroupID) 73 | if err != nil { 74 | log.Error(err) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | type Result struct { 82 | Code int `json:"code"` 83 | Message string `json:"message"` 84 | Result struct { 85 | Data string `json:"data"` 86 | } `json:"result"` 87 | } 88 | type ReqStruct struct { 89 | To string `json:"to"` 90 | Fetcher struct { 91 | Name string `json:"name"` 92 | Params struct { 93 | Data string `json:"data"` 94 | } `json:"params"` 95 | } `json:"fetcher"` 96 | Converter struct { 97 | Extend struct { 98 | JavascriptDelay string `json:"javascript-delay"` 99 | } `json:"extend"` 100 | } `json:"converter"` 101 | Template string `json:"template"` 102 | } 103 | 104 | func (m *Module) GetUrlPic(url string, width, height, javascriptDelay int) (string, error) { 105 | r, err := requests.PostJson(m.ImgServer, fmt.Sprintf(`{ 106 | "to": "image", 107 | "converter": { 108 | "uri": "%s", 109 | "width": %d, 110 | "height": %d, 111 | "extend": { 112 | "javascript-delay": "%d" 113 | } 114 | }, 115 | "template": "" 116 | }`, url, width, height, javascriptDelay)) 117 | if err != nil { 118 | return "", err 119 | } 120 | var result Result 121 | err = r.Json(&result) 122 | if err != nil { 123 | return "", err 124 | } 125 | if result.Code != 0 { 126 | log.Error() 127 | return "", errors.New(fmt.Sprintf("[%d] %s", result.Code, result.Message)) 128 | } 129 | return result.Result.Data, nil 130 | } 131 | func (m *Module) ModuleInit(b *Core.Bot, l *logrus.Entry) error { 132 | log = l 133 | m.db = b.DB 134 | b.BotCronManager.AddJob(-1, "Yiqing", "0 8,18 * * *", func() { 135 | m.DelOldWord() 136 | }) 137 | m.MsgChannel = make(chan OPQBot.GroupMsgPack, 30) 138 | go m.DoHotWord() 139 | err := b.DB.AutoMigrate(&HotWord{}) 140 | if err != nil { 141 | return err 142 | } 143 | Config.Lock.RLock() 144 | m.ImgServer = Config.CoreConfig.HtmlToImgUrl 145 | Config.Lock.RUnlock() 146 | 147 | _, err = b.AddEvent(OPQBot.EventNameOnGroupMessage, func(qq int64, packet *OPQBot.GroupMsgPack) { 148 | if packet.FromUserID != b.QQ { 149 | if packet.MsgType == "TextMsg" { 150 | m.MsgChannel <- *packet 151 | } 152 | if packet.Content == "今日词云" { 153 | hotWords, err := m.GetTodayWord(packet.FromGroupID) 154 | if err != nil { 155 | log.Error(err) 156 | return 157 | } 158 | sendMsg := "今日本群词云" 159 | hotMap := map[string]int{} 160 | for i := 0; i < len(hotWords); i++ { 161 | if len([]rune(hotWords[i].Word)) <= 1 { 162 | continue 163 | } 164 | hotMap[hotWords[i].Word] = hotWords[i].Count 165 | } 166 | log.Info(hotMap) 167 | colors := make([]color.Color, 0) 168 | for _, c := range DefaultColors { 169 | colors = append(colors, c) 170 | } 171 | b.PrintMemStats() 172 | img := wordclouds.NewWordcloud(hotMap, wordclouds.FontMaxSize(200), wordclouds.FontMinSize(40), wordclouds.FontFile("./font.ttf"), 173 | wordclouds.Height(1324), 174 | wordclouds.Width(1324), wordclouds.Colors(colors)).Draw() 175 | b.PrintMemStats() 176 | buf := new(bytes.Buffer) 177 | err = png.Encode(buf, img) 178 | if err != nil { 179 | log.Error(err) 180 | return 181 | } 182 | b.PrintMemStats() 183 | b.SendGroupPicMsg(packet.FromGroupID, sendMsg, buf.Bytes()) 184 | } 185 | if packet.Content == "奥运赛程" { 186 | pic, err := m.GetUrlPic("https://tiyu.baidu.com/tokyoly/delegation/8567/tab/%E8%B5%9B%E7%A8%8B/type/all", 360, 640, 0) 187 | if err != nil { 188 | log.Error(err) 189 | return 190 | } 191 | b.Send(OPQBot.SendMsgPack{ 192 | SendToType: OPQBot.SendToTypeGroup, 193 | ToUserUid: packet.FromGroupID, 194 | Content: OPQBot.SendTypePicMsgByBase64Content{ 195 | Content: "", 196 | Base64: pic, 197 | Flash: false, 198 | }, 199 | CallbackFunc: nil, 200 | }) 201 | return 202 | } 203 | if packet.Content == "中国奥运" { 204 | pic, err := m.GetUrlPic("https://tiyu.baidu.com/tokyoly/delegation/8567/tab/%E5%A5%96%E7%89%8C%E6%98%8E%E7%BB%86", 360, 640, 0) 205 | if err != nil { 206 | log.Error(err) 207 | return 208 | } 209 | b.Send(OPQBot.SendMsgPack{ 210 | SendToType: OPQBot.SendToTypeGroup, 211 | ToUserUid: packet.FromGroupID, 212 | Content: OPQBot.SendTypePicMsgByBase64Content{ 213 | Content: "", 214 | Base64: pic, 215 | Flash: false, 216 | }, 217 | CallbackFunc: nil, 218 | }) 219 | return 220 | } 221 | if packet.Content == "奥运" { 222 | pic, err := m.GetUrlPic("https://tiyu.baidu.com/tokyoly/home/tab/%E5%A5%96%E7%89%8C%E6%A6%9C", 360, 640, 0) 223 | if err != nil { 224 | log.Error(err) 225 | return 226 | } 227 | b.Send(OPQBot.SendMsgPack{ 228 | SendToType: OPQBot.SendToTypeGroup, 229 | ToUserUid: packet.FromGroupID, 230 | Content: OPQBot.SendTypePicMsgByBase64Content{ 231 | Content: "", 232 | Base64: pic, 233 | Flash: false, 234 | }, 235 | CallbackFunc: nil, 236 | }) 237 | return 238 | } 239 | if packet.Content == "本周词云" { 240 | hotWords, err := m.GetWeeklyWord(packet.FromGroupID) 241 | if err != nil { 242 | log.Error(err) 243 | return 244 | } 245 | sendMsg := "本周词云" 246 | hotMap := map[string]int{} 247 | for i := 0; i < len(hotWords); i++ { 248 | if len([]rune(hotWords[i].Word)) <= 1 { 249 | continue 250 | } 251 | hotMap[hotWords[i].Word] = hotWords[i].Count 252 | } 253 | log.Info(hotMap) 254 | colors := make([]color.Color, 0) 255 | for _, c := range DefaultColors { 256 | colors = append(colors, c) 257 | } 258 | 259 | //b.PrintMemStats() 260 | img := wordclouds.NewWordcloud(hotMap, wordclouds.FontMaxSize(200), wordclouds.FontMinSize(40), wordclouds.FontFile("./font.ttf"), 261 | wordclouds.Height(1324), 262 | wordclouds.Width(1324), wordclouds.Colors(colors)).Draw() 263 | //b.PrintMemStats() 264 | buf := new(bytes.Buffer) 265 | err = png.Encode(buf, img) 266 | if err != nil { 267 | log.Error(err) 268 | return 269 | } 270 | //b.PrintMemStats() 271 | b.SendGroupPicMsg(packet.FromGroupID, sendMsg, buf.Bytes()) 272 | } 273 | } 274 | }) 275 | if err != nil { 276 | return err 277 | } 278 | return nil 279 | } 280 | func (m *Module) DelOldWord() { 281 | t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Add(24*time.Hour).Format("2006-01-02")+" 00:00:00", time.Local) 282 | _ = m.db.Where(" (? - hot_time) >= 691200", t.Unix()).Delete(&HotWord{}).Error 283 | } 284 | func (m *Module) AddHotWord(word string, groupId int64) error { 285 | var hotWord []HotWord 286 | 287 | t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Add(24*time.Hour).Format("2006-01-02")+" 00:00:00", time.Local) 288 | err := m.db.Where(" (? - hot_time) <= 86400 AND group_id = ? AND word = ?", t.Unix(), groupId, word).Find(&hotWord).Error 289 | if err != nil { 290 | return err 291 | } 292 | 293 | if len(hotWord) > 0 { 294 | err := m.db.Model(&hotWord[0]).Update("count", hotWord[0].Count+1).Error 295 | if err != nil { 296 | return err 297 | } 298 | } else { 299 | tmp := HotWord{ 300 | GroupId: groupId, 301 | Word: word, 302 | Count: 1, 303 | HotTime: time.Now().Unix(), 304 | } 305 | err := m.db.Create(&tmp).Error 306 | if err != nil { 307 | return err 308 | } 309 | } 310 | return nil 311 | } 312 | func (m *Module) GetTodayWord(groupId int64) ([]HotWord, error) { 313 | var hotWord []HotWord 314 | t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Add(24*time.Hour).Format("2006-01-02")+" 00:00:00", time.Local) 315 | err := m.db.Where("(? - hot_time) <= 86400 AND group_id = ?", t.Unix(), groupId).Limit(100).Find(&hotWord).Error 316 | if err != nil { 317 | return nil, err 318 | } 319 | 320 | return hotWord, nil 321 | } 322 | func (m *Module) GetWeeklyWord(groupId int64) ([]HotWord, error) { 323 | var hotWord []HotWord 324 | t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Add(24*time.Hour).Format("2006-01-02")+" 00:00:00", time.Local) 325 | 326 | err := m.db.Where("(? - hot_time) <= 604800 AND group_id = ?", t.Unix(), groupId).Limit(100).Find(&hotWord).Error 327 | if err != nil { 328 | return nil, err 329 | } 330 | 331 | return hotWord, nil 332 | } 333 | func init() { 334 | Core.RegisterModule(&Module{}) 335 | } 336 | --------------------------------------------------------------------------------