1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-06-30 03:43:25 +00:00

Compare commits

...

97 Commits

Author SHA1 Message Date
9c1390c75c feat: support sign server 2023-06-27 18:11:06 +08:00
b958046a27 Merge branch 'master' into dev 2023-06-27 17:16:14 +08:00
19906eba36 修复群匿名消息事件中的重复的sub_type #2216 (#2219) 2023-06-20 21:54:07 +08:00
8e6e79f734 bootstrap改进 (#2192)
* 保证云函数能直接调用启动脚本

* 添加末尾空格

以免某些shell无法正确执行。

* 还原 .gitignore

还原因为粗心提交上去的本地文件忽略

---------

Signed-off-by: BuildTools <x123456789fy@outlook.com>
2023-06-08 15:17:12 +08:00
9b9ecd6a41 chore(docker): adjust dockerfile and entrypoint (#2194)
* chore(docker): adjust dockerfile and entrypoint
2023-06-04 10:23:45 +08:00
c8e480d12f Update default_config.yml (#2151) 2023-06-04 09:59:46 +08:00
5bf64ee743 Fix #2119 (#2186)
* fix: 尝试修复 #2070 (#2071)

* Close file after uploading it to private chat

fix #2119
上传文件到私聊后,释放文件。
2023-06-04 09:56:40 +08:00
bad3c86912 修改网易云音乐url格式 (#2146)
* 修改网易云音乐url格式
2023-05-30 17:26:22 +08:00
2af55d6a67 Merge branch 'dev' 2023-04-13 00:19:43 +08:00
42606a825d internal/download: disable http when not visiting go-cqhttp.org 2023-04-09 17:51:32 +08:00
1ed675d5bf internal/t544: add //go:noescape 2023-04-09 17:37:12 +08:00
91b4394d9b optimize(t544): drop unsafe (#2076)
Updates #2075 #2072 #2051
2023-04-09 17:25:57 +08:00
0b90074a48 feat: http timeout setting 2023-04-08 17:08:53 +08:00
55cb80dccc onebot: pick Attr, Value from log/slog
[wip]
2023-04-08 12:11:20 +08:00
54995fc101 fix: 尝试修复 #2070 (#2071) 2023-04-08 12:11:20 +08:00
8acc9f39c2 fix: 尝试修复 #2070 (#2071) 2023-04-08 12:05:45 +08:00
13325634c0 make lint happy 2023-04-08 11:34:19 +08:00
7b2d1fd573 rf: remove sse2 check 2023-04-05 01:35:05 +08:00
637d46f282 rf: remove useless code 2023-04-03 20:28:25 +08:00
1e42b2c450 feat: login error message 2023-04-03 20:26:43 +08:00
749cde2a6d fix #1782 2023-04-02 18:32:49 +08:00
0f0e711111 fix comment 2023-04-02 18:09:37 +08:00
233e276d6a feat: t544 support 8.9.38.10545 2023-04-02 18:06:25 +08:00
1ab1cba84c rf: change protocol auto-update to manual update 2023-04-02 18:04:13 +08:00
268ac07271 ci: make lint happy 2023-04-01 22:31:57 +08:00
c486c254d8 rf: move gocq/encryption -> internal/encryption 2023-04-01 22:29:20 +08:00
6ad62a2642 impl: t544 sign algorithm 2023-04-01 22:02:21 +08:00
9762a66ba2 Merge branch 'dev' of github.com:/Mrs4s/go-cqhttp into dev 2023-04-01 21:51:05 +08:00
43c6e3dcf5 fix https://github.com/Mrs4s/go-cqhttp/issues/2036 (#2040)
https://pkg.go.dev/os/exec#hdr-Executables_in_the_current_directory
2023-04-01 21:46:04 +08:00
6a17c70689 update dep 2023-04-01 16:41:16 +08:00
d70d66d6d7 fix #2010 2023-03-27 09:51:47 +08:00
43ff36e3e8 update dep. fix #2017 2023-03-27 09:40:02 +08:00
008d546f1a Update cqcode.go (#2001)
fix #1998
2023-03-20 10:34:05 +08:00
82ecf19480 fix #1989 2023-03-18 14:04:13 +08:00
588728aa62 log: print code 235 reason 2023-03-18 13:47:39 +08:00
ddfe24f6db Merge pull request #1991 from fumiama/dev
fix(goreleaser): git rev-list --count master
2023-03-18 13:42:35 +08:00
5d492c7b38 Merge branch 'master' into dev 2023-03-18 11:55:33 +08:00
d77dc9ef64 fix(goreleaser): checkout 2023-03-18 11:54:29 +08:00
98c2a2218a fix(goreleaser): checkout 2023-03-18 11:51:24 +08:00
3ccc2c6087 fix(goreleaser): checkout 2023-03-18 11:50:40 +08:00
e6e30c0a10 fix(goreleaser): git rev-list --count master 2023-03-18 11:39:05 +08:00
1815ed769d chore(deps): bump golang.org/x/image from 0.3.0 to 0.5.0 (#1913)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.3.0 to 0.5.0.
- [Release notes](https://github.com/golang/image/releases)
- [Commits](https://github.com/golang/image/compare/v0.3.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-18 10:41:27 +08:00
cc4a981c90 Merge branch 'dev' of github.com:/Mrs4s/go-cqhttp into dev 2023-03-18 10:35:35 +08:00
174d99f94b fix: add timeout setting for default http client 2023-03-18 10:35:19 +08:00
dd33cd9598 ci(chore): Fix stylings 2023-03-18 02:28:25 +00:00
4ad7da7a9a feat: add sign-server flag 2023-03-18 10:27:48 +08:00
73bd3c92f3 update dep & add limitation for qrcode login 2023-03-18 10:22:40 +08:00
3a60e081f2 feat: protocol updater supports fallback to ghproxy 2023-03-17 22:30:13 +08:00
1d0b513b96 ci(chore): Fix stylings 2023-03-14 18:35:19 +00:00
0312f05f6e feat: basic protocol auto-updater 2023-03-15 02:33:04 +08:00
b85c2ecb07 Merge pull request #1973 from yanyongyu/patch-1
CI: add build cache and image for branch `dev`
2023-03-14 22:48:22 +08:00
0b106d8ef5 db/sqlite: use ParseDuration 2023-03-14 20:56:11 +08:00
40e4f40525 db/sqlite: change ttl to millisecond 2023-03-14 20:53:17 +08:00
8124879c77 update docker action 2023-03-13 14:08:55 +08:00
3f4630b6d1 ref: improve protocol display name 2023-03-13 01:01:43 +08:00
6a291840d7 update dep 2023-03-13 00:44:37 +08:00
a0e3291725 feat: display login error code 2023-03-11 01:30:45 +08:00
069f9d1335 fix: block login process when account has been banned 2023-03-11 01:29:33 +08:00
91facb54ce update dep 2023-03-11 01:12:57 +08:00
414f067431 Update GitHub Workflows actions version (#1958)
* Update CI workflow actions version

* Update lint workflow actions version

* Update release workflow actions version

* Update Docker build workflow actions version

* disable lint cache to prevent bugs

* Update golinter action to latest

* Update Go to 1.20 in CI workflow
2023-03-07 23:53:12 +08:00
a704009484 fix: login message error 2023-03-05 19:49:43 +08:00
278d6260c8 feat: implement t544 energy 2023-03-05 19:36:50 +08:00
485d5c0df9 dep: update MiraiGo 2023-03-05 18:24:37 +08:00
e3fd0771ae dep: update MiraiGo 2023-03-05 18:04:06 +08:00
8cb8428785 Merge pull request #1944 from fumiama/winres
feat(windows): add icon and metadata
2023-03-05 18:01:51 +08:00
95adb403e9 Merge pull request #1943 from fumiama/title
feat: 在启动时设置标题为 `go-cqhttp 版本 版权`
2023-03-05 18:01:00 +08:00
84dcf46ae2 Merge pull request #1940 from fumiama/quickedit
fix: 在登录时不禁用快速编辑
2023-03-02 21:38:18 +08:00
bef2ba6f08 feat(windows): add icon and metadata 2023-03-02 21:27:40 +08:00
04c4446496 fix: linkname 2023-03-02 15:08:50 +08:00
c3840a5988 feat: 在启动时设置标题为 go-cqhttp 版本 版权 2023-03-02 13:25:21 +08:00
291942357b fix: 在登录时不禁用快速编辑 2023-03-01 20:56:28 +08:00
c24aa8d8a0 Merge pull request #1938 from fumiama/quickedit
feat: 禁用快速编辑&优化启动流程
2023-03-01 19:45:29 +08:00
07b1e6b72e fix RestoreInputMode 2023-02-28 22:50:23 +08:00
377d7af2c1 add RestoreInputMode 2023-02-28 22:47:23 +08:00
d867451ef6 fix input 2023-02-28 22:40:52 +08:00
c4d703dc86 fix mouse scroll 2023-02-28 22:11:05 +08:00
dbddd18e3a 优化注释 2023-02-28 21:14:35 +08:00
1b8ebf55a5 make lint happy 2023-02-28 20:49:54 +08:00
63d9ffa90b make lint happy 2023-02-28 20:47:40 +08:00
2830676e3b make lint happy 2023-02-28 20:44:46 +08:00
ddd52ca933 feat: 禁用快速编辑&优化启动流程 2023-02-28 20:39:25 +08:00
72173337ae api-gen: clean up 2023-02-27 16:00:28 +08:00
9b0fae6346 api-gen: fix import path 2023-02-27 15:22:19 +08:00
4ceacc38d5 onebot: move to pkg/onebot 2023-02-27 15:17:27 +08:00
1dba273b61 remove replace in go.mod 2023-02-20 15:13:15 +08:00
cb1604a098 update MiraiGo 2023-02-20 15:08:53 +08:00
0c9f7a1f8f fix device 2023-02-20 14:06:08 +08:00
edfcd41ed6 dep: update MiraiGo 2023-02-19 20:10:46 +08:00
811cfdca98 cmd/gocq: support select ticket input method 2023-02-19 15:00:19 +08:00
0e5f3ed555 server: support ob12 http long polling 2023-02-19 14:13:18 +08:00
90fa530a02 ci: make revive happy 2023-02-16 23:31:04 +08:00
c80adf5795 coolq: handle v11/v12 specific message
(1) V11: at V12: mention/mention_all
(2) V11: record V12: voice
2023-02-16 23:28:31 +08:00
debc1ed1ae coolq: unified string/array message conversion
change to 2 step:
(1): parse to []internal/msg.Element, use msg.ParseObject/msg.ParseString
(2): transform to []IMessageElement, can share functions
2023-02-16 14:51:23 +08:00
43dd9aa76d rf: move coolq/cqcode to internal/msg 2023-02-15 22:29:42 +08:00
9c0525b3d4 all: use *onebot.Spec 2023-02-15 21:49:05 +08:00
cf717ad762 internal/onebot: new package 2023-02-15 14:24:57 +08:00
6b3aabd9af update MiraiGo 2023-02-14 23:27:15 +08:00
65 changed files with 2181 additions and 1084 deletions

View File

@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- 'master' - 'master'
- 'dev'
# Sequence of patterns matched against refs/tags # Sequence of patterns matched against refs/tags
tags: tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
@ -20,10 +21,10 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set time zone - name: Set time zone
uses: szenius/set-timezone@v1.0 uses: szenius/set-timezone@v1.1
with: with:
timezoneLinux: "Asia/Shanghai" timezoneLinux: "Asia/Shanghai"
timezoneMacos: "Asia/Shanghai" timezoneMacos: "Asia/Shanghai"
@ -37,7 +38,7 @@ jobs:
# password: ${{ secrets.DOCKERHUB_TOKEN }} # password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -45,7 +46,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v4
with: with:
images: | images: |
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
@ -61,16 +62,18 @@ jobs:
type=semver,pattern={{major}} type=semver,pattern={{major}}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v2
- name: Build and push - name: Build and push
id: docker_build id: docker_build
uses: docker/build-push-action@v2 uses: docker/build-push-action@v4
with: with:
context: . context: .
push: true push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -24,18 +24,12 @@ jobs:
goarch: "386" goarch: "386"
fail-fast: true fail-fast: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Go environment - name: Setup Go environment
uses: actions/setup-go@v2.1.3 uses: actions/setup-go@v3
with: with:
cache: true
go-version: '1.20' go-version: '1.20'
- name: Cache downloaded module
uses: actions/cache@v2
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.goos }}-${{ matrix.goarch }}-${{ hashFiles('**/go.sum') }}
- name: Build binary file - name: Build binary file
env: env:
GOOS: ${{ matrix.goos }} GOOS: ${{ matrix.goos }}
@ -49,7 +43,7 @@ jobs:
export LD_FLAGS="-w -s -X github.com/Mrs4s/go-cqhttp/internal/base.Version=${COMMIT_ID::7}" export LD_FLAGS="-w -s -X github.com/Mrs4s/go-cqhttp/internal/base.Version=${COMMIT_ID::7}"
go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" . go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" .
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
if: ${{ !github.head_ref }} if: ${{ !github.head_ref }}
with: with:
name: ${{ matrix.goos }}_${{ matrix.goarch }} name: ${{ matrix.goos }}_${{ matrix.goarch }}

View File

@ -7,15 +7,15 @@ jobs:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Go environment - name: Setup Go environment
uses: actions/setup-go@v2.1.3 uses: actions/setup-go@v3
with: with:
go-version: '1.20' go-version: '1.20'
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v2 uses: golangci/golangci-lint-action@v3
with: with:
version: latest version: latest

View File

@ -10,20 +10,20 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2.3.4 run: |
with: git version
fetch-depth: 0 git clone https://github.com/Mrs4s/go-cqhttp.git /home/runner/work/go-cqhttp/go-cqhttp
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v3
with: with:
go-version: '1.20' go-version: '1.20'
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v4
with: with:
version: latest version: latest
args: release --rm-dist args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored
View File

@ -12,6 +12,10 @@ internal/btree/*.db
# binary builds # binary builds
go-cqhttp go-cqhttp
*.exe
# macos # macos
.DS_Store .DS_Store
# windwos rc
*.syso

View File

@ -56,6 +56,7 @@ run:
skip-dirs: skip-dirs:
- db - db
- cmd/api-generator - cmd/api-generator
- internal/encryption
tests: true tests: true
# output configuration options # output configuration options

View File

@ -3,6 +3,9 @@ env:
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
- go install github.com/tc-hib/go-winres@latest
- go generate winres/init.go
- go-winres make
release: release:
draft: true draft: true
discussion_category_name: General discussion_category_name: General

View File

@ -42,3 +42,4 @@ WORKDIR /data
VOLUME [ "/data" ] VOLUME [ "/data" ]
ENTRYPOINT [ "/docker-entrypoint.sh" ] ENTRYPOINT [ "/docker-entrypoint.sh" ]
CMD [ "/app/cqhttp" ]

View File

@ -1,6 +1,6 @@
<p align="center"> <p align="center">
<a href="https://ishkong.github.io/go-cqhttp-docs/"> <a href="https://ishkong.github.io/go-cqhttp-docs/">
<img src="https://user-images.githubusercontent.com/25968335/120111974-8abef880-c139-11eb-99cd-fa928348b198.png" width="200" height="200" alt="go-cqhttp"> <img src="winres/icon.png" width="200" height="200" alt="go-cqhttp">
</a> </a>
</p> </p>

View File

@ -10,11 +10,17 @@ import (
"go/token" "go/token"
"io" "io"
"os" "os"
"reflect"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
) )
var supported = flag.Bool("supported", false, "genRouter supported.go")
var output = flag.String("o", "", "output file")
var pkg = flag.String("pkg", "", "package name")
var src = flag.String("path", "", "source file")
type Param struct { type Param struct {
Name string Name string
Type string Type string
@ -23,7 +29,6 @@ type Param struct {
type Router struct { type Router struct {
Func string Func string
Version []uint16
Path []string Path []string
PathV11 []string // v11 only PathV11 []string // v11 only
PathV12 []string // v12 only PathV12 []string // v12 only
@ -44,70 +49,43 @@ func (g *generator) WriteString(s string) {
io.WriteString(g.out, s) io.WriteString(g.out, s)
} }
func (g *generator) generate(routers []Router) { func (g *generator) writef(format string, a ...any) {
var actions []string // for onebot v12 get_supported_actions fmt.Fprintf(g.out, format, a...)
for _, router := range routers { }
if len(router.PathV12) > 0 {
actions = append(actions, router.PathV12...)
}
if len(router.Path) > 0 {
actions = append(actions, router.Path...)
}
}
for i := range actions {
actions[i] = `"` + actions[i] + `"`
}
// TODO: v12 和 all 的 switch-case 由常量改为数组寻址, 以利用 get_supported_actions func (g *generator) header() {
g.WriteString("// Code generated by cmd/api-generator. DO NOT EDIT.\n\n") g.WriteString("// Code generated by cmd/api-generator. DO NOT EDIT.\n\n")
g.WriteString("package api\n\nimport (\n\n") g.writef("package %s\n\n", *pkg)
}
func (g *generator) genRouter(routers []Router) {
g.WriteString("import (\n\n")
g.WriteString("\"github.com/Mrs4s/go-cqhttp/coolq\"\n") g.WriteString("\"github.com/Mrs4s/go-cqhttp/coolq\"\n")
g.WriteString("\"github.com/Mrs4s/go-cqhttp/global\"\n") g.WriteString("\"github.com/Mrs4s/go-cqhttp/global\"\n")
g.WriteString("\"github.com/Mrs4s/go-cqhttp/pkg/onebot\"\n")
g.WriteString(")\n\n") g.WriteString(")\n\n")
g.WriteString(fmt.Sprintf(`func (c *Caller) call(action string, version uint16, p Getter) global.MSG { g.WriteString(`func (c *Caller) call(action string, spec *onebot.Spec, p Getter) global.MSG {`)
var converter coolq.IDConverter = func(id any) any { genVer := func(path int) {
return coolq.ConvertIDWithVersion(id,version) g.writef(`if spec.Version == %d {
}
if version == 12 {
if action == "get_supported_actions" {
return coolq.OK([]string{%v})
}
switch action { switch action {
`, strings.Join(actions, ","))) `, path)
for _, router := range routers { for _, router := range routers {
g.router(router, PathV12) g.router(router, path)
}
g.WriteString("}}\n")
} }
io.WriteString(g.out, `}}`) genVer(PathV11)
io.WriteString(g.out, "\n") genVer(PathV12)
g.WriteString(`if version == 11 { // generic path
switch action { g.WriteString("switch action {\n")
`)
for _, router := range routers {
g.router(router, PathV11)
}
io.WriteString(g.out, `}}`)
io.WriteString(g.out, "\n")
io.WriteString(g.out, "switch action {\n")
for _, router := range routers { for _, router := range routers {
g.router(router, PathAll) g.router(router, PathAll)
} }
io.WriteString(g.out, `}`) g.WriteString("}\n")
io.WriteString(g.out, "\n") g.WriteString("return coolq.Failed(404, \"API_NOT_FOUND\", \"API不存在\")}")
io.WriteString(g.out, "return coolq.Failed(404, \"API_NOT_FOUND\", \"API不存在\")}")
} }
func (g *generator) router(router Router, pathVersion int) { func (g *generator) router(router Router, pathVersion int) {
/*
checkVersion := func(v uint16) bool {
for _, ver := range router.Version {
if ver == v {
return true
}
}
return false
}
*/
path := router.Path path := router.Path
if pathVersion == PathV11 { if pathVersion == PathV11 {
path = router.PathV11 path = router.PathV11
@ -128,26 +106,17 @@ func (g *generator) router(router Router, pathVersion int) {
} }
g.WriteString(":\n") g.WriteString(":\n")
if len(router.Version) == 1 { // 目前来说只需要判断一个版本的情况
check := make([]string, 0, len(router.Version))
for _, ver := range router.Version {
check = append(check, fmt.Sprintf("version != %v", ver))
}
fmt.Fprintf(g.out, "if %v {\n", strings.Join(check, " && "))
fmt.Fprintf(g.out, "return coolq.Failed(405, \"VERSION_ERROR\", \"API版本不匹配\")}\n")
}
for i, p := range router.Params { for i, p := range router.Params {
if p.Name == "version" || p.Name == "converter" { if p.Type == "*onebot.Spec" {
continue continue
} }
if p.Default == "" { if p.Default == "" {
v := "p.Get(" + strconv.Quote(p.Name) + ")" v := "p.Get(" + strconv.Quote(p.Name) + ")"
fmt.Fprintf(g.out, "p%d := %s\n", i, conv(v, p.Type)) g.writef("p%d := %s\n", i, conv(v, p.Type))
} else { } else {
fmt.Fprintf(g.out, "p%d := %s\n", i, p.Default) g.writef("p%d := %s\n", i, p.Default)
fmt.Fprintf(g.out, "if pt := p.Get(%s); pt.Exists() {\n", strconv.Quote(p.Name)) g.writef("if pt := p.Get(%s); pt.Exists() {\n", strconv.Quote(p.Name))
fmt.Fprintf(g.out, "p%d = %s\n}\n", i, conv("pt", p.Type)) g.writef("p%d = %s\n}\n", i, conv("pt", p.Type))
} }
} }
@ -156,15 +125,11 @@ func (g *generator) router(router Router, pathVersion int) {
if i != 0 { if i != 0 {
g.WriteString(", ") g.WriteString(", ")
} }
if p.Name == "version" { if p.Type == "*onebot.Spec" {
fmt.Fprintf(g.out, "version") g.WriteString("spec")
continue continue
} }
if p.Name == "converter" { g.writef("p%d", i)
fmt.Fprintf(g.out, "converter")
continue
}
fmt.Fprintf(g.out, "p%d", i)
} }
g.WriteString(")\n") g.WriteString(")\n")
} }
@ -172,8 +137,8 @@ func (g *generator) router(router Router, pathVersion int) {
func conv(v, t string) string { func conv(v, t string) string {
switch t { switch t {
default: default:
panic("unknown type: " + t) panic("unsupported type: " + t)
case "gjson.Result", "IDConverter": case "gjson.Result", "*onebot.Spec":
return v return v
case "int64": case "int64":
return v + ".Int()" return v + ".Int()"
@ -194,7 +159,6 @@ func conv(v, t string) string {
func main() { func main() {
var routers []Router var routers []Router
src := flag.String("path", "", "source file")
flag.Parse() flag.Parse()
fset := token.NewFileSet() fset := token.NewFileSet()
for _, s := range strings.Split(*src, ",") { for _, s := range strings.Split(*src, ",") {
@ -206,23 +170,15 @@ func main() {
for _, decl := range file.Decls { for _, decl := range file.Decls {
switch decl := decl.(type) { switch decl := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if !decl.Name.IsExported() || decl.Recv == nil { if !decl.Name.IsExported() || decl.Recv == nil ||
continue typeName(decl.Recv.List[0].Type) != "*CQBot" {
}
if st, ok := decl.Recv.List[0].Type.(*ast.StarExpr); !ok || st.X.(*ast.Ident).Name != "CQBot" {
continue continue
} }
router := Router{Func: decl.Name.Name} router := Router{Func: decl.Name.Name}
// compute params // compute params
for _, p := range decl.Type.Params.List { for _, p := range decl.Type.Params.List {
var typ string typ := typeName(p.Type)
switch t := p.Type.(type) {
case *ast.Ident:
typ = t.Name
case *ast.SelectorExpr:
typ = t.X.(*ast.Ident).Name + "." + t.Sel.Name
}
for _, name := range p.Names { for _, name := range p.Names {
router.Params = append(router.Params, Param{Name: snakecase(name.Name), Type: typ}) router.Params = append(router.Params, Param{Name: snakecase(name.Name), Type: typ})
} }
@ -259,13 +215,6 @@ func main() {
} }
} }
} }
case "version":
version := strings.Split(args, ",")
for _, v := range version {
if i, err := strconv.ParseUint(v, 10, 16); err == nil {
router.Version = append(router.Version, uint16(i))
}
}
} }
sort.Slice(router.Path, func(i, j int) bool { sort.Slice(router.Path, func(i, j int) bool {
return router.Path[i] < router.Path[j] return router.Path[i] < router.Path[j]
@ -304,12 +253,17 @@ func main() {
out := new(bytes.Buffer) out := new(bytes.Buffer)
g := &generator{out: out} g := &generator{out: out}
g.generate(routers) g.header()
if *supported {
g.genSupported(routers)
} else {
g.genRouter(routers)
}
source, err := format.Source(out.Bytes()) source, err := format.Source(out.Bytes())
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = os.WriteFile("api.go", source, 0o644) err = os.WriteFile(*output, source, 0o644)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -328,7 +282,7 @@ func unquote(s string) string {
func parseMap(input string, sep string) map[string]string { func parseMap(input string, sep string) map[string]string {
out := make(map[string]string) out := make(map[string]string)
for _, arg := range strings.Split(input, ",") { for _, arg := range strings.Split(input, ",") {
k, v, ok := cut(arg, sep) k, v, ok := strings.Cut(arg, sep)
if !ok { if !ok {
out[k] = "true" out[k] = "true"
} }
@ -346,20 +300,13 @@ func match(text string) (string, string) {
return "", "" return "", ""
} }
text = strings.Trim(text, "@)") text = strings.Trim(text, "@)")
cmd, args, ok := cut(text, "(") cmd, args, ok := strings.Cut(text, "(")
if !ok { if !ok {
return "", "" return "", ""
} }
return cmd, unquote(args) return cmd, unquote(args)
} }
func cut(s, sep string) (before, after string, found bool) {
if i := strings.Index(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, "", false
}
// some abbreviations need translation before transforming ro snake case // some abbreviations need translation before transforming ro snake case
var replacer = strings.NewReplacer("ID", "Id") var replacer = strings.NewReplacer("ID", "Id")
@ -393,3 +340,16 @@ func convDefault(s string, t string) string {
} }
return "" return ""
} }
func typeName(x ast.Node) string {
switch x := x.(type) {
case *ast.Ident:
return x.Name
case *ast.SelectorExpr:
return typeName(x.X) + "." + x.Sel.Name
case *ast.StarExpr:
return "*" + typeName(x.X)
default:
panic("unhandled type: " + reflect.TypeOf(x).String())
}
}

View File

@ -0,0 +1,44 @@
package main
import "html/template"
func (g *generator) genSupported(routers []Router) {
var v11, v12 []string // for onebot v12 get_supported_actions
for _, router := range routers {
if len(router.PathV11) > 0 {
v11 = append(v11, router.PathV11...)
}
if len(router.PathV11) > 0 {
v12 = append(v12, router.PathV12...)
}
if len(router.Path) > 0 {
v11 = append(v11, router.Path...)
v12 = append(v12, router.Path...)
}
}
type S struct {
V11 []string
V12 []string
}
tmpl, err := template.New("").Parse(supportedTemplete)
if err != nil {
panic(err)
}
err = tmpl.Execute(g.out, &S{V11: v11, V12: v12})
if err != nil {
panic(err)
}
}
const supportedTemplete = `
var supportedV11 = []string{
{{range .V11}} "{{.}}",
{{end}}
}
var supportedV12 = []string{
{{range .V12}} "{{.}}",
{{end}}
}`

View File

@ -3,18 +3,22 @@ package gocq
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/hex"
"fmt" "fmt"
"image" "image"
"image/png" "image/png"
"net/http"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/utils" "github.com/Mrs4s/MiraiGo/utils"
"github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"gopkg.ilharper.com/x/isatty" "gopkg.ilharper.com/x/isatty"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
@ -52,6 +56,7 @@ func readIfTTY(de string) (str string) {
} }
var cli *client.QQClient var cli *client.QQClient
var device *client.DeviceInfo
// ErrSMSRequestError SMS请求出错 // ErrSMSRequestError SMS请求出错
var ErrSMSRequestError = errors.New("sms request error") var ErrSMSRequestError = errors.New("sms request error")
@ -152,23 +157,15 @@ func loginResponseProcessor(res *client.LoginResponse) error {
var text string var text string
switch res.Error { switch res.Error {
case client.SliderNeededError: case client.SliderNeededError:
log.Warnf("登录需要滑条验证码, 请选择验证方式: ") log.Warnf("登录需要滑条验证码, 请验证后重试.")
log.Warnf("1. 使用浏览器抓取滑条并登录") ticket := getTicket(res.VerifyUrl)
log.Warnf("2. 使用手机QQ扫码验证 (需要手Q和gocq在同一网络下).") if ticket == "" {
log.Warn("请输入(1 - 2)") log.Infof("按 Enter 继续....")
text = readIfTTY("1") readLine()
if strings.Contains(text, "1") { os.Exit(0)
ticket := getTicket(res.VerifyUrl)
if ticket == "" {
os.Exit(0)
}
res, err = cli.SubmitTicket(ticket)
continue
} }
cli.Disconnect() res, err = cli.SubmitTicket(ticket)
cli.Release() continue
cli = client.NewClientEmpty()
return qrcodeLogin()
case client.NeedCaptcha: case client.NeedCaptcha:
log.Warnf("登录需要验证码.") log.Warnf("登录需要验证码.")
_ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644) _ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
@ -212,38 +209,45 @@ func loginResponseProcessor(res *client.LoginResponse) error {
os.Exit(0) os.Exit(0)
case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError: case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError:
msg := res.ErrorMessage msg := res.ErrorMessage
if strings.Contains(msg, "版本") { log.Warnf("登录失败: %v Code: %v", msg, res.Code)
msg = "密码错误或账号被冻结" switch res.Code {
} else if strings.Contains(msg, "冻结") { case 235:
log.Fatalf("账号被冻结") log.Warnf("设备信息被封禁, 请删除 device.json 后重试.")
case 237:
log.Warnf("登录过于频繁, 请在手机QQ登录并根据提示完成认证后等一段时间重试")
case 45:
log.Warnf("你的账号被限制登录, 请配置 SignServer 后重试")
} }
log.Warnf("登录失败: %v", msg) log.Infof("按 Enter 继续....")
log.Infof("按 Enter 或等待 5s 后继续....") readLine()
readLineTimeout(time.Second * 5)
os.Exit(0) os.Exit(0)
} }
} }
} }
func getTicket(u string) (str string) { func getTicket(u string) string {
log.Warnf("请选择提交滑块ticket方式:")
log.Warnf("1. 自动提交")
log.Warnf("2. 手动抓取提交")
log.Warn("请输入(1 - 2)")
text := readLine()
id := utils.RandomString(8) id := utils.RandomString(8)
log.Warnf("请前往该地址验证 -> %v <- 或输入手动抓取的 ticketEnter 提交)", strings.ReplaceAll(u, "https://ssl.captcha.qq.com/template/wireless_mqq_captcha.html?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id))) auto := !strings.Contains(text, "2")
manual := make(chan string, 1) if auto {
go func() { u = strings.ReplaceAll(u, "https://ssl.captcha.qq.com/template/wireless_mqq_captcha.html?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id))
manual <- readLine() }
}() log.Warnf("请前往该地址验证 -> %v ", u)
ticker := time.NewTicker(time.Second) if !auto {
defer ticker.Stop() log.Warn("请输入ticket (Enter 提交)")
return readLine()
}
for count := 120; count > 0; count-- { for count := 120; count > 0; count-- {
select { str := fetchCaptcha(id)
case <-ticker.C: if str != "" {
str = fetchCaptcha(id) return str
if str != "" {
return
}
case str = <-manual:
return
} }
time.Sleep(time.Second)
} }
log.Warnf("验证超时") log.Warnf("验证超时")
return "" return ""
@ -260,3 +264,49 @@ func fetchCaptcha(id string) string {
} }
return "" return ""
} }
func energy(uin uint64, id string, appVersion string, salt []byte) ([]byte, error) {
signServer := base.SignServer
if !strings.HasSuffix(signServer, "/") {
signServer += "/"
}
response, err := download.Request{
Method: http.MethodGet,
URL: signServer + "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt)),
}.Bytes()
if err != nil {
log.Warnf("获取T544 sign时出现错误: %v server: %v", err, signServer)
return nil, err
}
data, err := hex.DecodeString(gjson.GetBytes(response, "data").String())
if err != nil {
log.Warnf("获取T544 sign时出现错误: %v", err)
return nil, err
}
if len(data) == 0 {
log.Warnf("获取T544 sign时出现错误: %v", "data is empty")
return nil, errors.New("data is empty")
}
return data, nil
}
func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) {
signServer := base.SignServer
if !strings.HasSuffix(signServer, "/") {
signServer += "/"
}
response, err := download.Request{
Method: http.MethodPost,
URL: signServer + "sign",
Header: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
Body: bytes.NewReader([]byte(fmt.Sprintf("uin=%v&qua=%s&cmd=%s&seq=%v&buffer=%v", uin, qua, cmd, seq, hex.EncodeToString(buff)))),
}.Bytes()
if err != nil {
log.Warnf("获取sso sign时出现错误: %v server: %v", err, signServer)
return nil, nil, nil, err
}
sign, _ = hex.DecodeString(gjson.GetBytes(response, "data.sign").String())
extra, _ = hex.DecodeString(gjson.GetBytes(response, "data.extra").String())
token, _ = hex.DecodeString(gjson.GetBytes(response, "data.token").String())
return sign, extra, token, nil
}

View File

@ -14,9 +14,12 @@ import (
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/wrapper"
para "github.com/fumiama/go-hide-param" para "github.com/fumiama/go-hide-param"
rotatelogs "github.com/lestrrat-go/file-rotatelogs" rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
"golang.org/x/term" "golang.org/x/term"
@ -26,6 +29,7 @@ import (
"github.com/Mrs4s/go-cqhttp/global/terminal" "github.com/Mrs4s/go-cqhttp/global/terminal"
"github.com/Mrs4s/go-cqhttp/internal/base" "github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/Mrs4s/go-cqhttp/internal/cache" "github.com/Mrs4s/go-cqhttp/internal/cache"
"github.com/Mrs4s/go-cqhttp/internal/download"
"github.com/Mrs4s/go-cqhttp/internal/selfdiagnosis" "github.com/Mrs4s/go-cqhttp/internal/selfdiagnosis"
"github.com/Mrs4s/go-cqhttp/internal/selfupdate" "github.com/Mrs4s/go-cqhttp/internal/selfupdate"
"github.com/Mrs4s/go-cqhttp/modules/servers" "github.com/Mrs4s/go-cqhttp/modules/servers"
@ -41,8 +45,12 @@ var allowStatus = [...]client.UserOnlineStatus{
client.StatusGaming, client.StatusVacationing, client.StatusWatchingTV, client.StatusFitness, client.StatusGaming, client.StatusVacationing, client.StatusWatchingTV, client.StatusFitness,
} }
// Main 启动主程序 // InitBase 解析参数并检测
func Main() { //
// 如果在 windows 下双击打开了程序,程序将在此函数释出脚本后终止;
// 如果传入 -h 参数,程序将打印帮助后终止;
// 如果传入 -d 参数,程序将在启动 daemon 后终止。
func InitBase() {
base.Parse() base.Parse()
if !base.FastStart && terminal.RunningByDoubleClick() { if !base.FastStart && terminal.RunningByDoubleClick() {
err := terminal.NoMoreDoubleClick() err := terminal.NoMoreDoubleClick()
@ -50,7 +58,7 @@ func Main() {
log.Errorf("遇到错误: %v", err) log.Errorf("遇到错误: %v", err)
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
} }
return os.Exit(0)
} }
switch { switch {
case base.LittleH: case base.LittleH:
@ -65,7 +73,10 @@ func Main() {
} }
} }
base.Init() base.Init()
}
// PrepareData 准备 log, 缓存, 数据库, 必须在 InitBase 之后执行
func PrepareData() {
rotateOptions := []rotatelogs.Option{ rotateOptions := []rotatelogs.Option{
rotatelogs.WithRotationTime(time.Hour * 24), rotatelogs.WithRotationTime(time.Hour * 24),
} }
@ -95,13 +106,17 @@ func Main() {
mkCacheDir(global.VideoPath, "视频") mkCacheDir(global.VideoPath, "视频")
mkCacheDir(global.CachePath, "发送图片") mkCacheDir(global.CachePath, "发送图片")
mkCacheDir(path.Join(global.ImagePath, "guild-images"), "频道图片缓存") mkCacheDir(path.Join(global.ImagePath, "guild-images"), "频道图片缓存")
mkCacheDir(global.VersionsPath, "版本缓存")
cache.Init() cache.Init()
db.Init() db.Init()
if err := db.Open(); err != nil { if err := db.Open(); err != nil {
log.Fatalf("打开数据库失败: %v", err) log.Fatalf("打开数据库失败: %v", err)
} }
}
// LoginInteract 登录交互, 可能需要键盘输入, 必须在 InitBase, PrepareData 之后执行
func LoginInteract() {
var byteKey []byte var byteKey []byte
arg := os.Args arg := os.Args
if len(arg) > 1 { if len(arg) > 1 {
@ -138,65 +153,71 @@ func Main() {
} }
if !global.PathExists("device.json") { if !global.PathExists("device.json") {
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.") log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
client.GenRandomDevice() device = client.GenRandomDevice()
_ = os.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0o644) _ = os.WriteFile("device.json", device.ToJson(), 0o644)
log.Info("已生成设备信息并保存到 device.json 文件.") log.Info("已生成设备信息并保存到 device.json 文件.")
} else { } else {
log.Info("将使用 device.json 内的设备信息运行Bot.") log.Info("将使用 device.json 内的设备信息运行Bot.")
if err := client.SystemDeviceInfo.ReadJson([]byte(global.ReadAllText("device.json"))); err != nil { device = new(client.DeviceInfo)
if err := device.ReadJson([]byte(global.ReadAllText("device.json"))); err != nil {
log.Fatalf("加载设备信息失败: %v", err) log.Fatalf("加载设备信息失败: %v", err)
} }
} }
if base.SignServer != "-" && base.SignServer != "" {
log.Infof("使用服务器 %s 进行数据包签名", base.SignServer)
wrapper.DandelionEnergy = energy
wrapper.FekitGetSign = sign
} else {
log.Warnf("警告: 未配置签名服务器, 这可能会导致登录 45 错误码或发送消息被风控")
}
if base.Account.Encrypt { if base.Account.Encrypt {
if !global.PathExists("password.encrypt") { if !global.PathExists("password.encrypt") {
if base.Account.Password == "" { if base.Account.Password == "" {
log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.") log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.")
readLine() } else {
os.Exit(0) log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
} }
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
readLine() readLine()
os.Exit(0) os.Exit(0)
} else {
if base.Account.Password != "" {
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
readLine()
os.Exit(0)
}
if len(byteKey) == 0 {
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
cancel := make(chan struct{}, 1)
state, _ := term.GetState(int(os.Stdin.Fd()))
go func() {
select {
case <-cancel:
return
case <-time.After(time.Second * 45):
log.Infof("解密key输入超时")
time.Sleep(3 * time.Second)
_ = term.Restore(int(os.Stdin.Fd()), state)
os.Exit(0)
}
}()
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
cancel <- struct{}{}
} else {
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
}
encrypt, _ := os.ReadFile("password.encrypt")
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
if err != nil {
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
}
copy(base.PasswordHash[:], ph)
} }
if base.Account.Password != "" {
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
readLine()
os.Exit(0)
}
if len(byteKey) == 0 {
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
cancel := make(chan struct{}, 1)
state, _ := term.GetState(int(os.Stdin.Fd()))
go func() {
select {
case <-cancel:
return
case <-time.After(time.Second * 45):
log.Infof("解密key输入超时")
time.Sleep(3 * time.Second)
_ = term.Restore(int(os.Stdin.Fd()), state)
os.Exit(0)
}
}()
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
cancel <- struct{}{}
} else {
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
}
encrypt, _ := os.ReadFile("password.encrypt")
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
if err != nil {
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
}
copy(base.PasswordHash[:], ph)
} else if len(base.Account.Password) > 0 { } else if len(base.Account.Password) > 0 {
base.PasswordHash = md5.Sum([]byte(base.Account.Password)) base.PasswordHash = md5.Sum([]byte(base.Account.Password))
} }
@ -205,10 +226,27 @@ func Main() {
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
} }
log.Info("开始尝试登录并同步消息...") log.Info("开始尝试登录并同步消息...")
log.Infof("使用协议: %s", client.SystemDeviceInfo.Protocol) log.Infof("使用协议: %s", device.Protocol.Version())
cli = newClient() cli = newClient()
cli.UseDevice(device)
isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt
isTokenLogin := false isTokenLogin := false
if isQRCodeLogin && cli.Device().Protocol != 2 {
log.Warn("当前协议不支持二维码登录, 请配置账号密码登录.")
os.Exit(0)
}
// 加载本地版本信息, 一般是在上次登录时保存的
versionFile := path.Join(global.VersionsPath, fmt.Sprint(int(cli.Device().Protocol))+".json")
if global.PathExists(versionFile) {
b, err := os.ReadFile(versionFile)
if err == nil {
_ = cli.Device().Protocol.Version().UpdateFromJson(b)
}
log.Infof("从文件 %s 读取协议版本 %v.", versionFile, cli.Device().Protocol.Version())
}
saveToken := func() { saveToken := func() {
base.AccountToken = cli.GenToken() base.AccountToken = cli.GenToken()
_ = os.WriteFile("session.token", base.AccountToken, 0o644) _ = os.WriteFile("session.token", base.AccountToken, 0o644)
@ -239,6 +277,7 @@ func Main() {
cli.Disconnect() cli.Disconnect()
cli.Release() cli.Release()
cli = newClient() cli = newClient()
cli.UseDevice(device)
} else { } else {
isTokenLogin = true isTokenLogin = true
} }
@ -248,6 +287,29 @@ func Main() {
cli.Uin = base.Account.Uin cli.Uin = base.Account.Uin
cli.PasswordMd5 = base.PasswordHash cli.PasswordMd5 = base.PasswordHash
} }
if !base.FastStart {
log.Infof("正在检查协议更新...")
currentVersionName := device.Protocol.Version().SortVersionName
remoteVersion, err := getRemoteLatestProtocolVersion(int(device.Protocol.Version().Protocol))
if err == nil {
remoteVersionName := gjson.GetBytes(remoteVersion, "sort_version_name").String()
if remoteVersionName != currentVersionName {
switch {
case !base.UpdateProtocol:
log.Infof("检测到协议更新: %s -> %s", currentVersionName, remoteVersionName)
log.Infof("如果登录时出现版本过低错误, 可尝试使用 -update-protocol 参数启动")
case !isTokenLogin:
_ = device.Protocol.Version().UpdateFromJson(remoteVersion)
log.Infof("协议版本已更新: %s -> %s", currentVersionName, remoteVersionName)
default:
log.Infof("检测到协议更新: %s -> %s", currentVersionName, remoteVersionName)
log.Infof("由于使用了会话缓存, 无法自动更新协议, 请删除缓存后重试")
}
}
} else if err.Error() != "remote version unavailable" {
log.Warnf("检查协议更新失败: %v", err)
}
}
if !isTokenLogin { if !isTokenLogin {
if !isQRCodeLogin { if !isQRCodeLogin {
if err := commonLogin(); err != nil { if err := commonLogin(); err != nil {
@ -311,6 +373,7 @@ func Main() {
}) })
saveToken() saveToken()
cli.AllowSlider = true cli.AllowSlider = true
download.SetTimeout(time.Duration(base.HTTPTimeout) * time.Second) // 在登录完成后设置, 防止在堵塞协议更新
log.Infof("登录成功 欢迎使用: %v", cli.Nickname) log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
log.Info("开始加载好友列表...") log.Info("开始加载好友列表...")
global.Check(cli.ReloadFriendList(), true) global.Check(cli.ReloadFriendList(), true)
@ -322,11 +385,16 @@ func Main() {
base.Account.Status = 0 base.Account.Status = 0
} }
cli.SetOnlineStatus(allowStatus[base.Account.Status]) cli.SetOnlineStatus(allowStatus[base.Account.Status])
servers.Run(coolq.NewQQBot(cli)) servers.Run(coolq.NewQQBot(cli))
log.Info("资源初始化完成, 开始处理信息.") log.Info("资源初始化完成, 开始处理信息.")
log.Info("アトリは、高性能ですから!") log.Info("アトリは、高性能ですから!")
}
// WaitSignal 在新线程检查更新和网络并等待信号, 必须在 InitBase, PrepareData, LoginInteract 之后执行
//
// - 直接返回: os.Interrupt, syscall.SIGTERM
// - dump stack: syscall.SIGQUIT, syscall.SIGUSR1
func WaitSignal() {
go func() { go func() {
selfupdate.CheckUpdate() selfupdate.CheckUpdate()
selfdiagnosis.NetworkDiagnosis(cli) selfdiagnosis.NetworkDiagnosis(cli)
@ -389,6 +457,23 @@ func newClient() *client.QQClient {
return c return c
} }
var remoteVersions = map[int]string{
1: "https://raw.githubusercontent.com/RomiChan/protocol-versions/master/android_phone.json",
6: "https://raw.githubusercontent.com/RomiChan/protocol-versions/master/android_pad.json",
}
func getRemoteLatestProtocolVersion(protocolType int) ([]byte, error) {
url, ok := remoteVersions[protocolType]
if !ok {
return nil, errors.New("remote version unavailable")
}
response, err := download.Request{URL: url}.Bytes()
if err != nil {
return download.Request{URL: "https://ghproxy.com/" + url}.Bytes()
}
return response, nil
}
type protocolLogger struct{} type protocolLogger struct{}
const fromProtocol = "Protocol -> " const fromProtocol = "Protocol -> "

View File

@ -27,8 +27,10 @@ import (
"github.com/Mrs4s/go-cqhttp/internal/base" "github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/Mrs4s/go-cqhttp/internal/cache" "github.com/Mrs4s/go-cqhttp/internal/cache"
"github.com/Mrs4s/go-cqhttp/internal/download" "github.com/Mrs4s/go-cqhttp/internal/download"
"github.com/Mrs4s/go-cqhttp/internal/msg"
"github.com/Mrs4s/go-cqhttp/internal/param" "github.com/Mrs4s/go-cqhttp/internal/param"
"github.com/Mrs4s/go-cqhttp/modules/filter" "github.com/Mrs4s/go-cqhttp/modules/filter"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
) )
type guildMemberPageToken struct { type guildMemberPageToken struct {
@ -327,13 +329,13 @@ func (bot *CQBot) CQGetTopicChannelFeeds(guildID, channelID uint64) global.MSG {
// //
// https://git.io/Jtz1L // https://git.io/Jtz1L
// @route(get_friend_list) // @route(get_friend_list)
func (bot *CQBot) CQGetFriendList(version uint16) global.MSG { func (bot *CQBot) CQGetFriendList(spec *onebot.Spec) global.MSG {
fs := make([]global.MSG, 0, len(bot.Client.FriendList)) fs := make([]global.MSG, 0, len(bot.Client.FriendList))
for _, f := range bot.Client.FriendList { for _, f := range bot.Client.FriendList {
fs = append(fs, global.MSG{ fs = append(fs, global.MSG{
"nickname": f.Nickname, "nickname": f.Nickname,
"remark": f.Remark, "remark": f.Remark,
"user_id": ConvertIDWithVersion(f.Uin, version), "user_id": spec.ConvertID(f.Uin),
}) })
} }
return OK(fs) return OK(fs)
@ -399,14 +401,14 @@ func (bot *CQBot) CQDeleteFriend(uin int64) global.MSG {
// //
// https://git.io/Jtz1t // https://git.io/Jtz1t
// @route(get_group_list) // @route(get_group_list)
func (bot *CQBot) CQGetGroupList(noCache bool, converter IDConverter) global.MSG { func (bot *CQBot) CQGetGroupList(noCache bool, spec *onebot.Spec) global.MSG {
gs := make([]global.MSG, 0, len(bot.Client.GroupList)) gs := make([]global.MSG, 0, len(bot.Client.GroupList))
if noCache { if noCache {
_ = bot.Client.ReloadGroupList() _ = bot.Client.ReloadGroupList()
} }
for _, g := range bot.Client.GroupList { for _, g := range bot.Client.GroupList {
gs = append(gs, global.MSG{ gs = append(gs, global.MSG{
"group_id": converter(g.Code), "group_id": spec.ConvertID(g.Code),
"group_name": g.Name, "group_name": g.Name,
"group_create_time": g.GroupCreateTime, "group_create_time": g.GroupCreateTime,
"group_level": g.GroupLevel, "group_level": g.GroupLevel,
@ -421,7 +423,7 @@ func (bot *CQBot) CQGetGroupList(noCache bool, converter IDConverter) global.MSG
// //
// https://git.io/Jtz1O // https://git.io/Jtz1O
// @route(get_group_info) // @route(get_group_info)
func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, converter IDConverter) global.MSG { func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, spec *onebot.Spec) global.MSG {
group := bot.Client.FindGroup(groupID) group := bot.Client.FindGroup(groupID)
if group == nil || noCache { if group == nil || noCache {
group, _ = bot.Client.GetGroupInfo(groupID) group, _ = bot.Client.GetGroupInfo(groupID)
@ -435,7 +437,7 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, converter IDConver
for _, g := range info { for _, g := range info {
if g.Code == groupID { if g.Code == groupID {
return OK(global.MSG{ return OK(global.MSG{
"group_id": converter(g.Code), "group_id": spec.ConvertID(g.Code),
"group_name": g.Name, "group_name": g.Name,
"group_memo": g.Memo, "group_memo": g.Memo,
"group_create_time": 0, "group_create_time": 0,
@ -447,7 +449,7 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, converter IDConver
} }
} else { } else {
return OK(global.MSG{ return OK(global.MSG{
"group_id": converter(group.Code), "group_id": spec.ConvertID(group.Code),
"group_name": group.Name, "group_name": group.Name,
"group_create_time": group.GroupCreateTime, "group_create_time": group.GroupCreateTime,
"group_level": group.GroupLevel, "group_level": group.GroupLevel,
@ -615,6 +617,7 @@ func (bot *CQBot) CQUploadPrivateFile(userID int64, file, name string) global.MS
log.Warnf("上传私聊文件 %v 失败: %+v", file, err) log.Warnf("上传私聊文件 %v 失败: %+v", file, err)
return Failed(100, "OPEN_FILE_ERROR", "打开文件失败") return Failed(100, "OPEN_FILE_ERROR", "打开文件失败")
} }
defer func() { _ = fileBody.Close() }()
localFile := &client.LocalFile{ localFile := &client.LocalFile{
FileName: name, FileName: name,
Body: fileBody, Body: fileBody,
@ -752,7 +755,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b
var elem []message.IMessageElement var elem []message.IMessageElement
if m.Type == gjson.JSON { if m.Type == gjson.JSON {
elem = bot.ConvertObjectMessage(m, message.SourceGroup) elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGroup)
} else { } else {
str := m.String() str := m.String()
if str == "" { if str == "" {
@ -762,7 +765,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b
if autoEscape { if autoEscape {
elem = []message.IMessageElement{message.NewText(str)} elem = []message.IMessageElement{message.NewText(str)}
} else { } else {
elem = bot.ConvertStringMessage(str, message.SourceGroup) elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGroup)
} }
} }
fixAt(elem) fixAt(elem)
@ -806,7 +809,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
var elem []message.IMessageElement var elem []message.IMessageElement
if m.Type == gjson.JSON { if m.Type == gjson.JSON {
elem = bot.ConvertObjectMessage(m, message.SourceGuildChannel) elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGuildChannel)
} else { } else {
str := m.String() str := m.String()
if str == "" { if str == "" {
@ -816,7 +819,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
if autoEscape { if autoEscape {
elem = []message.IMessageElement{message.NewText(str)} elem = []message.IMessageElement{message.NewText(str)}
} else { } else {
elem = bot.ConvertStringMessage(str, message.SourceGuildChannel) elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGuildChannel)
} }
} }
fixAt(elem) fixAt(elem)
@ -850,7 +853,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
for i, elem := range elems { for i, elem := range elems {
p := &elems[i] p := &elems[i]
switch o := elem.(type) { switch o := elem.(type) {
case *LocalVideoElement: case *msg.LocalVideo:
w.do(func() { w.do(func() {
gm, err := bot.uploadLocalVideo(source, o) gm, err := bot.uploadLocalVideo(source, o)
if err != nil { if err != nil {
@ -859,7 +862,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
*p = gm *p = gm
} }
}) })
case *LocalImageElement: case *msg.LocalImage:
w.do(func() { w.do(func() {
gm, err := bot.uploadLocalImage(source, o) gm, err := bot.uploadLocalImage(source, o)
if err != nil { if err != nil {
@ -926,7 +929,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
} }
} }
} }
content := bot.ConvertObjectMessage(c, sourceType) content := bot.ConvertObjectMessage(onebot.V11, c, sourceType)
if uin != 0 && name != "" && len(content) > 0 { if uin != 0 && name != "" && len(content) > 0 {
return &message.ForwardNode{ return &message.ForwardNode{
SenderId: uin, SenderId: uin,
@ -1019,7 +1022,7 @@ func (bot *CQBot) CQSendPrivateForwardMessage(userID int64, m gjson.Result) glob
func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Result, autoEscape bool) global.MSG { func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Result, autoEscape bool) global.MSG {
var elem []message.IMessageElement var elem []message.IMessageElement
if m.Type == gjson.JSON { if m.Type == gjson.JSON {
elem = bot.ConvertObjectMessage(m, message.SourcePrivate) elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourcePrivate)
} else { } else {
str := m.String() str := m.String()
if str == "" { if str == "" {
@ -1028,7 +1031,7 @@ func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Resu
if autoEscape { if autoEscape {
elem = []message.IMessageElement{message.NewText(str)} elem = []message.IMessageElement{message.NewText(str)}
} else { } else {
elem = bot.ConvertStringMessage(str, message.SourcePrivate) elem = bot.ConvertStringMessage(onebot.V11, str, message.SourcePrivate)
} }
} }
mid := bot.SendPrivateMessage(userID, groupID, &message.SendingMessage{Elements: elem}) mid := bot.SendPrivateMessage(userID, groupID, &message.SendingMessage{Elements: elem})
@ -1444,7 +1447,7 @@ func (bot *CQBot) CQGetStrangerInfo(userID int64) global.MSG {
// CQHandleQuickOperation 隐藏API-对事件执行快速操作 // CQHandleQuickOperation 隐藏API-对事件执行快速操作
// //
// https://git.io/Jtz15 // https://git.io/Jtz15
// @route(".handle_quick_operation") // @route11(".handle_quick_operation")
func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global.MSG { func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global.MSG {
postType := context.Get("post_type").Str postType := context.Get("post_type").Str
@ -1845,7 +1848,11 @@ func (bot *CQBot) CQCanSendRecord() global.MSG {
// @route(ocr_image,".ocr_image") // @route(ocr_image,".ocr_image")
// @rename(image_id->image) // @rename(image_id->image)
func (bot *CQBot) CQOcrImage(imageID string) global.MSG { func (bot *CQBot) CQOcrImage(imageID string) global.MSG {
img, err := bot.makeImageOrVideoElem(map[string]string{"file": imageID}, false, message.SourceGroup) // TODO: fix this
var elem msg.Element
elem.Type = "image"
elem.Data = []msg.Pair{{K: "file", V: imageID}}
img, err := bot.makeImageOrVideoElem(elem, false, message.SourceGroup)
if err != nil { if err != nil {
log.Warnf("load image error: %v", err) log.Warnf("load image error: %v", err)
return Failed(100, "LOAD_FILE_ERROR", err.Error()) return Failed(100, "LOAD_FILE_ERROR", err.Error())
@ -1902,8 +1909,8 @@ func (bot *CQBot) CQSetGroupAnonymousBan(groupID int64, flag string, duration in
// //
// https://git.io/JtzMe // https://git.io/JtzMe
// @route(get_status) // @route(get_status)
func (bot *CQBot) CQGetStatus(version uint16) global.MSG { func (bot *CQBot) CQGetStatus(spec *onebot.Spec) global.MSG {
if version == 11 { if spec.Version == 11 {
return OK(global.MSG{ return OK(global.MSG{
"app_initialized": true, "app_initialized": true,
"app_enabled": true, "app_enabled": true,
@ -2012,7 +2019,7 @@ func (bot *CQBot) CQGetVersionInfo() global.MSG {
"runtime_version": runtime.Version(), "runtime_version": runtime.Version(),
"runtime_os": runtime.GOOS, "runtime_os": runtime.GOOS,
"version": base.Version, "version": base.Version,
"protocol_name": client.SystemDeviceInfo.Protocol, "protocol_name": bot.Client.Device().Protocol,
}) })
} }
@ -2105,6 +2112,13 @@ func (bot *CQBot) CQReloadEventFilter(file string) global.MSG {
return OK(nil) return OK(nil)
} }
// CQGetSupportedActions 获取支持的动作列表
//
// @route(get_supported_actions)
func (bot *CQBot) CQGetSupportedActions(spec *onebot.Spec) global.MSG {
return OK(spec.SupportedActions)
}
// OK 生成成功返回值 // OK 生成成功返回值
func OK(data any) global.MSG { func OK(data any) global.MSG {
return global.MSG{"data": data, "retcode": 0, "status": "ok", "message": ""} return global.MSG{"data": data, "retcode": 0, "status": "ok", "message": ""}

View File

@ -3,9 +3,10 @@ package coolq
import ( import (
"runtime" "runtime"
"github.com/tidwall/gjson"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/base" "github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/tidwall/gjson"
) )
// CQGetVersion 获取版本信息 OneBotV12 // CQGetVersion 获取版本信息 OneBotV12
@ -27,6 +28,7 @@ func (bot *CQBot) CQGetVersion() global.MSG {
// //
// @route12(send_message) // @route12(send_message)
// @rename(m->message) // @rename(m->message)
func (bot *CQBot) CQSendMessageV12(groupID, userID, detailType string, m gjson.Result) global.MSG { func (bot *CQBot) CQSendMessageV12(groupID, userID, detailType string, m gjson.Result) global.MSG { // nolint
// TODO: implement
return OK(nil) return OK(nil)
} }

View File

@ -26,6 +26,8 @@ import (
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/base" "github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/Mrs4s/go-cqhttp/internal/mime" "github.com/Mrs4s/go-cqhttp/internal/mime"
"github.com/Mrs4s/go-cqhttp/internal/msg"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
) )
// CQBot CQBot结构体,存储Bot实例相关配置 // CQBot CQBot结构体,存储Bot实例相关配置
@ -114,7 +116,7 @@ func NewQQBot(cli *client.QQClient) *CQBot {
for { for {
<-t.C <-t.C
bot.dispatchEvent("meta_event/heartbeat", global.MSG{ bot.dispatchEvent("meta_event/heartbeat", global.MSG{
"status": bot.CQGetStatus(11)["data"], "status": bot.CQGetStatus(onebot.V11)["data"],
"interval": base.HeartbeatInterval.Milliseconds(), "interval": base.HeartbeatInterval.Milliseconds(),
}) })
} }
@ -146,7 +148,7 @@ func (w *worker) wait() {
} }
// uploadLocalImage 上传本地图片 // uploadLocalImage 上传本地图片
func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement) (message.IMessageElement, error) { func (bot *CQBot) uploadLocalImage(target message.Source, img *msg.LocalImage) (message.IMessageElement, error) {
if img.File != "" { if img.File != "" {
f, err := os.Open(img.File) f, err := os.Open(img.File)
if err != nil { if err != nil {
@ -171,7 +173,7 @@ func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement
} }
img.Stream = bytes.NewReader(stream.Bytes()) img.Stream = bytes.NewReader(stream.Bytes())
} }
i, err := bot.Client.UploadImage(target, img.Stream, 4) i, err := bot.Client.UploadImage(target, img.Stream)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -186,20 +188,20 @@ func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement
} }
// uploadLocalVideo 上传本地短视频至群聊 // uploadLocalVideo 上传本地短视频至群聊
func (bot *CQBot) uploadLocalVideo(target message.Source, v *LocalVideoElement) (*message.ShortVideoElement, error) { func (bot *CQBot) uploadLocalVideo(target message.Source, v *msg.LocalVideo) (*message.ShortVideoElement, error) {
video, err := os.Open(v.File) video, err := os.Open(v.File)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = video.Close() }() defer func() { _ = video.Close() }()
return bot.Client.UploadShortVideo(target, video, v.thumb, 4) return bot.Client.UploadShortVideo(target, video, v.Thumb)
} }
func removeLocalElement(elements []message.IMessageElement) []message.IMessageElement { func removeLocalElement(elements []message.IMessageElement) []message.IMessageElement {
var j int var j int
for i, e := range elements { for i, e := range elements {
switch e.(type) { switch e.(type) {
case *LocalImageElement, *LocalVideoElement: case *msg.LocalImage, *msg.LocalVideo:
case *message.VoiceElement: // 未上传的语音消息, 也删除 case *message.VoiceElement: // 未上传的语音消息, 也删除
case nil: case nil:
default: default:
@ -229,7 +231,7 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
for i, m := range elements { for i, m := range elements {
p := &elements[i] p := &elements[i]
switch e := m.(type) { switch e := m.(type) {
case *LocalImageElement: case *msg.LocalImage:
w.do(func() { w.do(func() {
m, err := bot.uploadLocalImage(target, e) m, err := bot.uploadLocalImage(target, e)
if err != nil { if err != nil {
@ -247,7 +249,7 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
*p = m *p = m
} }
}) })
case *LocalVideoElement: case *msg.LocalVideo:
w.do(func() { w.do(func() {
m, err := bot.uploadLocalVideo(target, e) m, err := bot.uploadLocalVideo(target, e)
if err != nil { if err != nil {
@ -273,7 +275,7 @@ func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) (in
m.Elements = bot.uploadMedia(source, m.Elements) m.Elements = bot.uploadMedia(source, m.Elements)
for _, e := range m.Elements { for _, e := range m.Elements {
switch i := e.(type) { switch i := e.(type) {
case *PokeElement: case *msg.Poke:
if group != nil { if group != nil {
if mem := group.FindMember(i.Target); mem != nil { if mem := group.FindMember(i.Target); mem != nil {
mem.Poke() mem.Poke()
@ -318,7 +320,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
m.Elements = bot.uploadMedia(source, m.Elements) m.Elements = bot.uploadMedia(source, m.Elements)
for _, e := range m.Elements { for _, e := range m.Elements {
switch i := e.(type) { switch i := e.(type) {
case *PokeElement: case *msg.Poke:
bot.Client.SendFriendPoke(i.Target) bot.Client.SendFriendPoke(i.Target)
return 0 return 0
case *message.MusicShareElement: case *message.MusicShareElement:
@ -420,7 +422,7 @@ func (bot *CQBot) SendGuildChannelMessage(guildID, channelID uint64, m *message.
bot.Client.SendGuildMusicShare(guildID, channelID, i) bot.Client.SendGuildMusicShare(guildID, channelID, i)
return "-1" // todo: fix this return "-1" // todo: fix this
case *message.VoiceElement, *PokeElement: case *message.VoiceElement, *msg.Poke:
log.Warnf("警告: 频道暂不支持发送 %v 消息", i.Type().String()) log.Warnf("警告: 频道暂不支持发送 %v 消息", i.Type().String())
continue continue
} }

View File

@ -1,7 +1,6 @@
package coolq package coolq
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
@ -13,8 +12,6 @@ import (
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
) )
type IDConverter func(id any) any
func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG { func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG {
sex := "unknown" sex := "unknown"
if m.Gender == 1 { // unknown = 0xff if m.Gender == 1 { // unknown = 0xff
@ -95,7 +92,7 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
"name": m.Sender.AnonymousInfo.AnonymousNick, "name": m.Sender.AnonymousInfo.AnonymousNick,
} }
gm["sender"].(global.MSG)["nickname"] = "匿名消息" gm["sender"].(global.MSG)["nickname"] = "匿名消息"
gm["sub_type"] = "anonymous" typ = "message/group/anonymous"
} else { } else {
group := bot.Client.FindGroup(m.GroupCode) group := bot.Client.FindGroup(m.GroupCode)
mem := group.FindMember(m.Sender.Uin) mem := group.FindMember(m.Sender.Uin)
@ -223,10 +220,3 @@ func toStringMessage(m []message.IMessageElement, source message.Source) string
func fU64(v uint64) string { func fU64(v uint64) string {
return strconv.FormatUint(v, 10) return strconv.FormatUint(v, 10)
} }
func ConvertIDWithVersion(v any, version uint16) any {
if version == 12 {
return fmt.Sprint(v)
}
return v
}

File diff suppressed because it is too large Load Diff

View File

@ -1,79 +0,0 @@
// Package cqcode provides CQCode util functions.
package cqcode
import "strings"
// EscapeText 将字符串raw中部分字符转义
//
// - & -> &amp;
// - [ -> &#91;
// - ] -> &#93;
func EscapeText(s string) string {
count := strings.Count(s, "&")
count += strings.Count(s, "[")
count += strings.Count(s, "]")
if count == 0 {
return s
}
// Apply replacements to buffer.
var b strings.Builder
b.Grow(len(s) + count*4)
start := 0
for i := 0; i < count; i++ {
j := start
for index, r := range s[start:] {
if r == '&' || r == '[' || r == ']' {
j += index
break
}
}
b.WriteString(s[start:j])
switch s[j] {
case '&':
b.WriteString("&amp;")
case '[':
b.WriteString("&#91;")
case ']':
b.WriteString("&#93;")
}
start = j + 1
}
b.WriteString(s[start:])
return b.String()
}
// EscapeValue 将字符串value中部分字符转义
//
// - , -> &#44;
// - & -> &amp;
// - [ -> &#91;
// - ] -> &#93;
func EscapeValue(value string) string {
ret := EscapeText(value)
return strings.ReplaceAll(ret, ",", "&#44;")
}
// UnescapeText 将字符串content中部分字符反转义
//
// - &amp; -> &
// - &#91; -> [
// - &#93; -> ]
func UnescapeText(content string) string {
ret := content
ret = strings.ReplaceAll(ret, "&#91;", "[")
ret = strings.ReplaceAll(ret, "&#93;", "]")
ret = strings.ReplaceAll(ret, "&amp;", "&")
return ret
}
// UnescapeValue 将字符串content中部分字符反转义
//
// - &#44; -> ,
// - &amp; -> &
// - &#91; -> [
// - &#93; -> ]
func UnescapeValue(content string) string {
ret := strings.ReplaceAll(content, "&#44;", ",")
return UnescapeText(ret)
}

View File

@ -136,7 +136,7 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
bot.dispatch(gm) bot.dispatch(gm)
} }
func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEvent) { func (bot *CQBot) tempMessageEvent(_ *client.QQClient, e *client.TempMessageEvent) {
m := e.Message m := e.Message
bot.checkMedia(m.Elements, m.Sender.Uin) bot.checkMedia(m.Elements, m.Sender.Uin)
source := message.Source{ source := message.Source{
@ -475,6 +475,9 @@ func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEven
} }
func (bot *CQBot) joinGroupEvent(c *client.QQClient, group *client.GroupInfo) { func (bot *CQBot) joinGroupEvent(c *client.QQClient, group *client.GroupInfo) {
if group == nil {
return
}
log.Infof("Bot进入了群 %v.", formatGroupName(group)) log.Infof("Bot进入了群 %v.", formatGroupName(group))
bot.dispatch(bot.groupIncrease(group.Code, 0, c.Uin)) bot.dispatch(bot.groupIncrease(group.Code, 0, c.Uin))
} }
@ -488,7 +491,7 @@ func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *client.GroupLeaveEvent)
bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator)) bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
} }
func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.MemberPermissionChangedEvent) { func (bot *CQBot) memberPermissionChangedEvent(_ *client.QQClient, e *client.MemberPermissionChangedEvent) {
st := "unset" st := "unset"
if e.NewPermission == client.Administrator { if e.NewPermission == client.Administrator {
st = "set" st = "set"
@ -499,7 +502,7 @@ func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.Mem
}) })
} }
func (bot *CQBot) memberCardUpdatedEvent(c *client.QQClient, e *client.MemberCardUpdatedEvent) { func (bot *CQBot) memberCardUpdatedEvent(_ *client.QQClient, e *client.MemberCardUpdatedEvent) {
log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName) log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
bot.dispatchEvent("notice/group_card", global.MSG{ bot.dispatchEvent("notice/group_card", global.MSG{
"group_id": e.Group.Code, "group_id": e.Group.Code,
@ -523,7 +526,7 @@ func (bot *CQBot) memberLeaveEvent(_ *client.QQClient, e *client.MemberLeaveGrou
bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator)) bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
} }
func (bot *CQBot) friendRequestEvent(c *client.QQClient, e *client.NewFriendRequest) { func (bot *CQBot) friendRequestEvent(_ *client.QQClient, e *client.NewFriendRequest) {
log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message) log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message)
flag := strconv.FormatInt(e.RequestId, 10) flag := strconv.FormatInt(e.RequestId, 10)
bot.friendReqCache.Store(flag, e) bot.friendReqCache.Store(flag, e)
@ -534,7 +537,7 @@ func (bot *CQBot) friendRequestEvent(c *client.QQClient, e *client.NewFriendRequ
}) })
} }
func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent) { func (bot *CQBot) friendAddedEvent(_ *client.QQClient, e *client.NewFriendEvent) {
log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin) log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin)
bot.tempSessionCache.Delete(e.Friend.Uin) bot.tempSessionCache.Delete(e.Friend.Uin)
bot.dispatchEvent("notice/friend_add", global.MSG{ bot.dispatchEvent("notice/friend_add", global.MSG{
@ -542,7 +545,7 @@ func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent)
}) })
} }
func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRequest) { func (bot *CQBot) groupInvitedEvent(_ *client.QQClient, e *client.GroupInvitedRequest) {
log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin) log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin)
flag := strconv.FormatInt(e.RequestId, 10) flag := strconv.FormatInt(e.RequestId, 10)
bot.dispatchEvent("request/group/invite", global.MSG{ bot.dispatchEvent("request/group/invite", global.MSG{
@ -554,7 +557,7 @@ func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRe
}) })
} }
func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupRequest) { func (bot *CQBot) groupJoinReqEvent(_ *client.QQClient, e *client.UserJoinGroupRequest) {
log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin) log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin)
flag := strconv.FormatInt(e.RequestId, 10) flag := strconv.FormatInt(e.RequestId, 10)
bot.dispatchEvent("request/group/add", global.MSG{ bot.dispatchEvent("request/group/add", global.MSG{
@ -566,7 +569,7 @@ func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupR
}) })
} }
func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.OtherClientStatusChangedEvent) { func (bot *CQBot) otherClientStatusChangedEvent(_ *client.QQClient, e *client.OtherClientStatusChangedEvent) {
if e.Online { if e.Online {
log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind) log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
} else { } else {

View File

@ -11,10 +11,12 @@ import (
sql "github.com/FloatTech/sqlite" sql "github.com/FloatTech/sqlite"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/utils" "github.com/Mrs4s/MiraiGo/utils"
"github.com/Mrs4s/go-cqhttp/db" "github.com/Mrs4s/go-cqhttp/db"
) )
@ -26,8 +28,8 @@ type database struct {
// config mongodb 相关配置 // config mongodb 相关配置
type config struct { type config struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
CacheTTL time.Duration `yaml:"cachettl"` CacheTTL string `yaml:"cachettl"`
} }
func init() { func init() {
@ -38,7 +40,11 @@ func init() {
if !conf.Enable { if !conf.Enable {
return nil return nil
} }
return &database{db: new(sql.Sqlite), ttl: conf.CacheTTL} duration, err := time.ParseDuration(conf.CacheTTL)
if err != nil {
log.Fatalf("illegal ttl config: %v", err)
}
return &database{db: new(sql.Sqlite), ttl: duration}
}) })
} }

View File

@ -17,4 +17,4 @@ chown -R ${UID}:${GID} /app /data
chmod +x /app/cqhttp chmod +x /app/cqhttp
echo "Starting..." echo "Starting..."
su-exec ${USER} /app/cqhttp su-exec ${USER} "$@"

View File

@ -33,9 +33,15 @@ func EncoderSilk(data []byte) ([]byte, error) {
// EncodeMP4 将给定视频文件编码为MP4 // EncodeMP4 将给定视频文件编码为MP4
func EncodeMP4(src string, dst string) error { // -y 覆盖文件 func EncodeMP4(src string, dst string) error { // -y 覆盖文件
cmd1 := exec.Command("ffmpeg", "-i", src, "-y", "-c", "copy", "-map", "0", dst) cmd1 := exec.Command("ffmpeg", "-i", src, "-y", "-c", "copy", "-map", "0", dst)
if errors.Is(cmd1.Err, exec.ErrDot) {
cmd1.Err = nil
}
err := cmd1.Run() err := cmd1.Run()
if err != nil { if err != nil {
cmd2 := exec.Command("ffmpeg", "-i", src, "-y", "-c:v", "h264", "-c:a", "mp3", dst) cmd2 := exec.Command("ffmpeg", "-i", src, "-y", "-c:v", "h264", "-c:a", "mp3", dst)
if errors.Is(cmd2.Err, exec.ErrDot) {
cmd2.Err = nil
}
return errors.Wrap(cmd2.Run(), "convert mp4 failed") return errors.Wrap(cmd2.Run(), "convert mp4 failed")
} }
return err return err
@ -44,5 +50,8 @@ func EncodeMP4(src string, dst string) error { // -y 覆盖文件
// ExtractCover 获取给定视频文件的Cover // ExtractCover 获取给定视频文件的Cover
func ExtractCover(src string, target string) error { func ExtractCover(src string, target string) error {
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-ss", "0", "-frames:v", "1", target) cmd := exec.Command("ffmpeg", "-i", src, "-y", "-ss", "0", "-frames:v", "1", target)
if errors.Is(cmd.Err, exec.ErrDot) {
cmd.Err = nil
}
return errors.Wrap(cmd.Run(), "extract video cover failed") return errors.Wrap(cmd.Run(), "extract video cover failed")
} }

View File

@ -27,6 +27,8 @@ const (
VoicePath = "data/voices" VoicePath = "data/voices"
// VideoPath go-cqhttp使用的视频缓存目录 // VideoPath go-cqhttp使用的视频缓存目录
VideoPath = "data/videos" VideoPath = "data/videos"
// VersionsPath go-cqhttp使用的版本信息目录
VersionsPath = "data/versions"
// CachePath go-cqhttp使用的缓存目录 // CachePath go-cqhttp使用的缓存目录
CachePath = "data/cache" CachePath = "data/cache"
// DumpsPath go-cqhttp使用错误转储目录 // DumpsPath go-cqhttp使用错误转储目录

View File

@ -14,9 +14,9 @@ import (
func SetupMainSignalHandler() <-chan struct{} { func SetupMainSignalHandler() <-chan struct{} {
mainOnce.Do(func() { mainOnce.Do(func() {
mainStopCh = make(chan struct{}) mainStopCh = make(chan struct{})
mc := make(chan os.Signal, 3) mc := make(chan os.Signal, 4)
closeOnce := sync.Once{} closeOnce := sync.Once{}
signal.Notify(mc, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1) signal.Notify(mc, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGUSR1)
go func() { go func() {
for { for {
switch <-mc { switch <-mc {
@ -24,7 +24,7 @@ func SetupMainSignalHandler() <-chan struct{} {
closeOnce.Do(func() { closeOnce.Do(func() {
close(mainStopCh) close(mainStopCh)
}) })
case syscall.SIGUSR1: case syscall.SIGQUIT, syscall.SIGUSR1:
dumpStack() dumpStack()
} }
} }

View File

@ -1,2 +1,2 @@
// Package terminal 包含用于检测在windows下是否通过双击运行go-cqhttp的函数 // Package terminal 包含用于检测在windows下是否通过双击运行go-cqhttp, 禁用快速编辑, 启用VT100的函数
package terminal package terminal

View File

@ -1,9 +1,8 @@
//go:build !windows //go:build !windows
// +build !windows
package terminal package terminal
// RunningByDoubleClick 检查是否通过双击直接运行,非Windows系统永远返回false // RunningByDoubleClick 检查是否通过双击直接运行非Windows系统永远返回false
func RunningByDoubleClick() bool { func RunningByDoubleClick() bool {
return false return false
} }

View File

@ -1,6 +1,3 @@
//go:build windows
// +build windows
package terminal package terminal
import ( import (

View File

@ -0,0 +1,13 @@
//go:build !windows
package terminal
// RestoreInputMode 还原输入模式非Windows系统永远返回nil
func RestoreInputMode() error {
return nil
}
// DisableQuickEdit 禁用快速编辑非Windows系统永远返回nil
func DisableQuickEdit() error {
return nil
}

View File

@ -0,0 +1,44 @@
package terminal
import (
"os"
"golang.org/x/sys/windows"
)
var inputmode uint32
// RestoreInputMode 还原输入模式
func RestoreInputMode() error {
if inputmode == 0 {
return nil
}
stdin := windows.Handle(os.Stdin.Fd())
return windows.SetConsoleMode(stdin, inputmode)
}
// DisableQuickEdit 禁用快速编辑
func DisableQuickEdit() error {
stdin := windows.Handle(os.Stdin.Fd())
var mode uint32
err := windows.GetConsoleMode(stdin, &mode)
if err != nil {
return err
}
inputmode = mode
mode &^= windows.ENABLE_QUICK_EDIT_MODE // 禁用快速编辑模式
mode |= windows.ENABLE_EXTENDED_FLAGS // 启用扩展标志
mode &^= windows.ENABLE_MOUSE_INPUT // 禁用鼠标输入
mode |= windows.ENABLE_PROCESSED_INPUT // 启用控制输入
mode &^= windows.ENABLE_INSERT_MODE // 禁用插入模式
mode |= windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT // 启用输入回显&逐行输入
mode &^= windows.ENABLE_WINDOW_INPUT // 禁用窗口输入
mode &^= windows.ENABLE_VIRTUAL_TERMINAL_INPUT // 禁用虚拟终端输入
return windows.SetConsoleMode(stdin, mode)
}

15
global/terminal/title.go Normal file
View File

@ -0,0 +1,15 @@
//go:build !windows
package terminal
import (
"fmt"
"time"
"github.com/Mrs4s/go-cqhttp/internal/base"
)
// SetTitle 设置标题为 go-cqhttp `版本` `版权`
func SetTitle() {
fmt.Printf("\033]0;go-cqhttp "+base.Version+" © 2020 - %d Mrs4s"+"\007", time.Now().Year())
}

View File

@ -0,0 +1,29 @@
package terminal
import (
"fmt"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
"github.com/Mrs4s/go-cqhttp/internal/base"
)
func setConsoleTitle(title string) error {
p0, err := syscall.UTF16PtrFromString(title)
if err != nil {
return err
}
r1, _, err := windows.NewLazySystemDLL("kernel32.dll").NewProc("SetConsoleTitleW").Call(uintptr(unsafe.Pointer(p0)))
if r1 == 0 {
return err
}
return nil
}
// SetTitle 设置标题为 go-cqhttp `版本` `版权`
func SetTitle() {
_ = setConsoleTitle(fmt.Sprintf("go-cqhttp "+base.Version+" © 2020 - %d Mrs4s", time.Now().Year()))
}

8
global/terminal/vt100.go Normal file
View File

@ -0,0 +1,8 @@
//go:build !windows
package terminal
// EnableVT100 启用颜色、控制字符非Windows系统永远返回nil
func EnableVT100() error {
return nil
}

View File

@ -0,0 +1,23 @@
package terminal
import (
"os"
"golang.org/x/sys/windows"
)
// EnableVT100 启用颜色、控制字符
func EnableVT100() error {
stdout := windows.Handle(os.Stdout.Fd())
var mode uint32
err := windows.GetConsoleMode(stdout, &mode)
if err != nil {
return err
}
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING // 启用虚拟终端处理
mode |= windows.ENABLE_PROCESSED_OUTPUT // 启用处理后的输出
return windows.SetConsoleMode(stdout, mode)
}

10
go.mod
View File

@ -5,7 +5,7 @@ go 1.20
require ( require (
github.com/FloatTech/sqlite v1.5.7 github.com/FloatTech/sqlite v1.5.7
github.com/Microsoft/go-winio v0.6.0 github.com/Microsoft/go-winio v0.6.0
github.com/Mrs4s/MiraiGo v0.0.0-20230213132655-3ff1fee1b645 github.com/Mrs4s/MiraiGo v0.0.0-20230627090859-19e3d172596e
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc
github.com/fumiama/go-base16384 v1.6.1 github.com/fumiama/go-base16384 v1.6.1
@ -21,7 +21,7 @@ require (
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60 github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60
go.mongodb.org/mongo-driver v1.11.0 go.mongodb.org/mongo-driver v1.11.0
golang.org/x/crypto v0.3.0 golang.org/x/crypto v0.3.0
golang.org/x/image v0.3.0 golang.org/x/image v0.5.0
golang.org/x/sys v0.2.0 golang.org/x/sys v0.2.0
golang.org/x/term v0.2.0 golang.org/x/term v0.2.0
golang.org/x/time v0.2.0 golang.org/x/time v0.2.0
@ -44,7 +44,7 @@ require (
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect
@ -53,7 +53,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
golang.org/x/text v0.6.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect
@ -66,5 +66,3 @@ require (
modernc.org/strutil v1.1.3 // indirect modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect modernc.org/token v1.0.1 // indirect
) )
replace github.com/remyoudompheng/bigfft => github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b

17
go.sum
View File

@ -4,8 +4,8 @@ github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJG
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Mrs4s/MiraiGo v0.0.0-20230213132655-3ff1fee1b645 h1:KHWuWmhF2nacb2mKqA3OJorerCEo9n6BNizMuBACa38= github.com/Mrs4s/MiraiGo v0.0.0-20230627090859-19e3d172596e h1:99itMjI//+KaFF0+0QCBg/uHhGMJ99jG2lP6z/UnOsU=
github.com/Mrs4s/MiraiGo v0.0.0-20230213132655-3ff1fee1b645/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0= github.com/Mrs4s/MiraiGo v0.0.0-20230627090859-19e3d172596e/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d h1:/Xuj3fIiMY2ls1TwvPKmaqQrtJsPY+c9s+0lOScVHd8= github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d h1:/Xuj3fIiMY2ls1TwvPKmaqQrtJsPY+c9s+0lOScVHd8=
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA= github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA= github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
@ -19,8 +19,6 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b h1:Zt3pFQditAdWTHCOVkiloc9ZauBoWrb37guFV4iIRvE=
github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/fumiama/go-base16384 v1.6.1 h1:4yb4JgmBJDnQtq3XGXXdLrVwEnRpjhMUt4eAcsNeA30= github.com/fumiama/go-base16384 v1.6.1 h1:4yb4JgmBJDnQtq3XGXXdLrVwEnRpjhMUt4eAcsNeA30=
github.com/fumiama/go-base16384 v1.6.1/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= github.com/fumiama/go-base16384 v1.6.1/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-hide-param v0.1.4 h1:y7TRTzZMdCH9GOXnIzU3B+1BSkcmvejVGmGsz4t0DGU= github.com/fumiama/go-hide-param v0.1.4 h1:y7TRTzZMdCH9GOXnIzU3B+1BSkcmvejVGmGsz4t0DGU=
@ -86,6 +84,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
@ -128,8 +129,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -177,8 +178,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -23,20 +23,23 @@ var (
// config file flags // config file flags
var ( var (
Debug bool // 是否开启 debug 模式 Debug bool // 是否开启 debug 模式
RemoveReplyAt bool // 是否删除reply后的at RemoveReplyAt bool // 是否删除reply后的at
ExtraReplyData bool // 是否上报额外reply信息 ExtraReplyData bool // 是否上报额外reply信息
IgnoreInvalidCQCode bool // 是否忽略无效CQ码 IgnoreInvalidCQCode bool // 是否忽略无效CQ码
SplitURL bool // 是否分割URL SplitURL bool // 是否分割URL
ForceFragmented bool // 是否启用强制分片 ForceFragmented bool // 是否启用强制分片
SkipMimeScan bool // 是否跳过Mime扫描 SkipMimeScan bool // 是否跳过Mime扫描
ConvertWebpImage bool // 是否转换Webp图片 ConvertWebpImage bool // 是否转换Webp图片
ReportSelfMessage bool // 是否上报自身消息 ReportSelfMessage bool // 是否上报自身消息
UseSSOAddress bool // 是否使用服务器下发的新地址进行重连 UseSSOAddress bool // 是否使用服务器下发的新地址进行重连
LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志 LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志
LogColorful bool // 是否启用日志颜色 LogColorful bool // 是否启用日志颜色
FastStart bool // 是否为快速启动 FastStart bool // 是否为快速启动
AllowTempSession bool // 是否允许发送临时会话信息 AllowTempSession bool // 是否允许发送临时会话信息
UpdateProtocol bool // 是否更新协议
SignServer string // 使用特定的服务器进行签名
HTTPTimeout int
PostFormat string // 上报格式 string or array PostFormat string // 上报格式 string or array
Proxy string // 存储 proxy_rewrite,用于设置代理 Proxy string // 存储 proxy_rewrite,用于设置代理
@ -60,6 +63,7 @@ func Parse() {
flag.StringVar(&LittleWD, "w", "", "cover the working directory") flag.StringVar(&LittleWD, "w", "", "cover the working directory")
d := flag.Bool("D", false, "debug mode") d := flag.Bool("D", false, "debug mode")
flag.BoolVar(&FastStart, "faststart", false, "skip waiting 5 seconds") flag.BoolVar(&FastStart, "faststart", false, "skip waiting 5 seconds")
flag.BoolVar(&UpdateProtocol, "update-protocol", false, "update protocol")
flag.Parse() flag.Parse()
if *d { if *d {
@ -84,6 +88,8 @@ func Init() {
ReportSelfMessage = conf.Message.ReportSelfMessage ReportSelfMessage = conf.Message.ReportSelfMessage
UseSSOAddress = conf.Account.UseSSOAddress UseSSOAddress = conf.Account.UseSSOAddress
AllowTempSession = conf.Account.AllowTempSession AllowTempSession = conf.Account.AllowTempSession
SignServer = conf.Account.SignServer
HTTPTimeout = conf.Message.HTTPTimeout
} }
{ // others { // others
Proxy = conf.Message.ProxyRewrite Proxy = conf.Message.ProxyRewrite

View File

@ -4,6 +4,7 @@ package download
import ( import (
"bufio" "bufio"
"compress/gzip" "compress/gzip"
"crypto/tls"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -12,6 +13,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@ -21,17 +23,31 @@ import (
var client = &http.Client{ var client = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: func(request *http.Request) (u *url.URL, e error) { Proxy: func(request *http.Request) (*url.URL, error) {
if base.Proxy == "" { if base.Proxy == "" {
return http.ProxyFromEnvironment(request) return http.ProxyFromEnvironment(request)
} }
return url.Parse(base.Proxy) return url.Parse(base.Proxy)
}, },
ForceAttemptHTTP2: false, // Disable http2
MaxConnsPerHost: 0, TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
MaxIdleConns: 0,
MaxIdleConnsPerHost: 999, MaxIdleConnsPerHost: 999,
}, },
Timeout: time.Second * 5,
}
var clienth2 = &http.Client{
Transport: &http.Transport{
Proxy: func(request *http.Request) (*url.URL, error) {
if base.Proxy == "" {
return http.ProxyFromEnvironment(request)
}
return url.Parse(base.Proxy)
},
ForceAttemptHTTP2: true,
MaxIdleConnsPerHost: 999,
},
Timeout: time.Second * 5,
} }
// ErrOverSize 响应主体过大时返回此错误 // ErrOverSize 响应主体过大时返回此错误
@ -40,15 +56,36 @@ var ErrOverSize = errors.New("oversize")
// UserAgent HTTP请求时使用的UA // UserAgent HTTP请求时使用的UA
const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66" const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
// SetTimeout set internal/download client timeout
func SetTimeout(t time.Duration) {
if t == 0 {
t = time.Second * 10
}
client.Timeout = t
clienth2.Timeout = t
}
// Request is a file download request // Request is a file download request
type Request struct { type Request struct {
Method string
URL string URL string
Header map[string]string Header map[string]string
Limit int64 Limit int64
Body io.Reader
}
func (r Request) client() *http.Client {
if strings.Contains(r.URL, "go-cqhttp.org") {
return clienth2
}
return client
} }
func (r Request) do() (*http.Response, error) { func (r Request) do() (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, r.URL, nil) if r.Method == "" {
r.Method = http.MethodGet
}
req, err := http.NewRequest(r.Method, r.URL, r.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -58,7 +95,7 @@ func (r Request) do() (*http.Response, error) {
req.Header.Set(k, v) req.Header.Set(k, v)
} }
return client.Do(req) return r.client().Do(req)
} }
func (r Request) body() (io.ReadCloser, error) { func (r Request) body() (io.ReadCloser, error) {
@ -79,7 +116,7 @@ func (r Request) body() (io.ReadCloser, error) {
return resp.Body, err return resp.Body, err
} }
// Bytes 对给定URL发送Get请求,返回响应主体 // Bytes 对给定URL发送请求返回响应主体
func (r Request) Bytes() ([]byte, error) { func (r Request) Bytes() ([]byte, error) {
rd, err := r.body() rd, err := r.body()
if err != nil { if err != nil {
@ -89,7 +126,7 @@ func (r Request) Bytes() ([]byte, error) {
return io.ReadAll(rd) return io.ReadAll(rd)
} }
// JSON 发送GET请求, 并转换响应为JSON // JSON 发送请求, 并转换响应为JSON
func (r Request) JSON() (gjson.Result, error) { func (r Request) JSON() (gjson.Result, error) {
rd, err := r.body() rd, err := r.body()
if err != nil { if err != nil {

View File

@ -1,4 +1,5 @@
package cqcode // Package msg 提供了go-cqhttp消息中间表示CQ码处理等等
package msg
import ( import (
"bytes" "bytes"
@ -8,16 +9,105 @@ import (
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
) )
// @@@ CQ码转义处理 @@@
// EscapeText 将字符串raw中部分字符转义
//
// - & -> &amp;
// - [ -> &#91;
// - ] -> &#93;
func EscapeText(s string) string {
count := strings.Count(s, "&")
count += strings.Count(s, "[")
count += strings.Count(s, "]")
if count == 0 {
return s
}
// Apply replacements to buffer.
var b strings.Builder
b.Grow(len(s) + count*4)
start := 0
for i := 0; i < count; i++ {
j := start
for index, r := range s[start:] {
if r == '&' || r == '[' || r == ']' {
j += index
break
}
}
b.WriteString(s[start:j])
switch s[j] {
case '&':
b.WriteString("&amp;")
case '[':
b.WriteString("&#91;")
case ']':
b.WriteString("&#93;")
}
start = j + 1
}
b.WriteString(s[start:])
return b.String()
}
// EscapeValue 将字符串value中部分字符转义
//
// - , -> &#44;
// - & -> &amp;
// - [ -> &#91;
// - ] -> &#93;
func EscapeValue(value string) string {
ret := EscapeText(value)
return strings.ReplaceAll(ret, ",", "&#44;")
}
// UnescapeText 将字符串content中部分字符反转义
//
// - &amp; -> &
// - &#91; -> [
// - &#93; -> ]
func UnescapeText(content string) string {
ret := content
ret = strings.ReplaceAll(ret, "&#91;", "[")
ret = strings.ReplaceAll(ret, "&#93;", "]")
ret = strings.ReplaceAll(ret, "&amp;", "&")
return ret
}
// UnescapeValue 将字符串content中部分字符反转义
//
// - &#44; -> ,
// - &amp; -> &
// - &#91; -> [
// - &#93; -> ]
func UnescapeValue(content string) string {
ret := strings.ReplaceAll(content, "&#44;", ",")
return UnescapeText(ret)
}
// @@@ 消息中间表示 @@@
// Pair key value pair
type Pair struct {
K string
V string
}
// Element single message // Element single message
type Element struct { type Element struct {
Type string Type string
Data []Pair Data []Pair
} }
// Pair key value pair // Get 获取指定值
type Pair struct { func (e *Element) Get(k string) string {
K string for _, datum := range e.Data {
V string if datum.K == k {
return datum.V
}
}
return ""
} }
// CQCode convert element to cqcode // CQCode convert element to cqcode
@ -60,7 +150,7 @@ func (e *Element) MarshalJSON() ([]byte, error) {
buf.WriteByte('"') buf.WriteByte('"')
buf.WriteString(data.K) buf.WriteString(data.K)
buf.WriteString(`":`) buf.WriteString(`":`)
writeQuote(buf, data.V) buf.WriteString(QuoteJSON(data.V))
} }
buf.WriteString(`}}`) buf.WriteString(`}}`)
}), nil }), nil
@ -68,9 +158,10 @@ func (e *Element) MarshalJSON() ([]byte, error) {
const hex = "0123456789abcdef" const hex = "0123456789abcdef"
func writeQuote(b *bytes.Buffer, s string) { // QuoteJSON 按JSON转义为字符加上双引号
func QuoteJSON(s string) string {
i, j := 0, 0 i, j := 0, 0
var b strings.Builder
b.WriteByte('"') b.WriteByte('"')
for j < len(s) { for j < len(s) {
c := s[j] c := s[j]
@ -151,4 +242,5 @@ func writeQuote(b *bytes.Buffer, s string) {
b.WriteString(s[i:]) b.WriteString(s[i:])
b.WriteByte('"') b.WriteByte('"')
return b.String()
} }

View File

@ -1,7 +1,6 @@
package cqcode package msg
import ( import (
"bytes"
"encoding/json" "encoding/json"
"testing" "testing"
) )
@ -14,16 +13,14 @@ func jsonMarshal(s string) string {
return string(b) return string(b)
} }
func Test_quote(t *testing.T) { func TestQuoteJSON(t *testing.T) {
testcase := []string{ testcase := []string{
"\u0005", // issue 1773 "\u0005", // issue 1773
"\v", "\v",
} }
for _, input := range testcase { for _, input := range testcase {
var b bytes.Buffer got := QuoteJSON(input)
writeQuote(&b, input)
got := b.String()
expected := jsonMarshal(input) expected := jsonMarshal(input)
if got != expected { if got != expected {
t.Errorf("want %v but got %v", expected, got) t.Errorf("want %v but got %v", expected, got)

44
internal/msg/local.go Normal file
View File

@ -0,0 +1,44 @@
package msg
import (
"io"
"github.com/Mrs4s/MiraiGo/message"
)
// Poke 拍一拍
type Poke struct {
Target int64
}
// Type 获取元素类型ID
func (e *Poke) Type() message.ElementType {
// Make message.IMessageElement Happy
return message.At
}
// LocalImage 本地图片
type LocalImage struct {
Stream io.ReadSeeker
File string
URL string
Flash bool
EffectID int32
}
// Type implements the message.IMessageElement.
func (e *LocalImage) Type() message.ElementType {
return message.Image
}
// LocalVideo 本地视频
type LocalVideo struct {
File string
Thumb io.ReadSeeker
}
// Type impl message.IMessageElement
func (e *LocalVideo) Type() message.ElementType {
return message.Video
}

104
internal/msg/parse.go Normal file
View File

@ -0,0 +1,104 @@
package msg
import (
"github.com/tidwall/gjson"
)
// ParseObject 将消息JSON对象转为消息元素数组
func ParseObject(m gjson.Result) (r []Element) {
convert := func(e gjson.Result) {
var elem Element
elem.Type = e.Get("type").Str
e.Get("data").ForEach(func(key, value gjson.Result) bool {
pair := Pair{K: key.Str, V: value.String()}
elem.Data = append(elem.Data, pair)
return true
})
r = append(r, elem)
}
if m.IsArray() {
m.ForEach(func(_, e gjson.Result) bool {
convert(e)
return true
})
}
if m.IsObject() {
convert(m)
}
return
}
func text(txt string) Element {
return Element{
Type: "text",
Data: []Pair{
{
K: "text",
V: txt,
},
},
}
}
// ParseString 将字符串(CQ码)转为消息元素数组
func ParseString(raw string) (r []Element) {
var elem Element
for raw != "" {
i := 0
for i < len(raw) && !(raw[i] == '[' && i+4 < len(raw) && raw[i:i+4] == "[CQ:") {
i++
}
if i > 0 {
r = append(r, text(UnescapeText(raw[:i])))
}
if i+4 > len(raw) {
return
}
raw = raw[i+4:] // skip "[CQ:"
i = 0
for i < len(raw) && raw[i] != ',' && raw[i] != ']' {
i++
}
if i+1 > len(raw) {
return
}
elem.Type = raw[:i]
elem.Data = nil // reset data
raw = raw[i:]
i = 0
for {
if raw[0] == ']' {
r = append(r, elem)
raw = raw[1:]
break
}
raw = raw[1:]
for i < len(raw) && raw[i] != '=' {
i++
}
if i+1 > len(raw) {
return
}
key := raw[:i]
raw = raw[i+1:] // skip "="
i = 0
for i < len(raw) && raw[i] != ',' && raw[i] != ']' {
i++
}
if i+1 > len(raw) {
return
}
elem.Data = append(elem.Data, Pair{
K: key,
V: UnescapeValue(raw[:i]),
})
raw = raw[i:]
i = 0
}
}
return
}

View File

@ -1,22 +1,18 @@
package coolq package msg
import ( import (
"fmt" "fmt"
"strings" "strings"
"testing" "testing"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils" "github.com/Mrs4s/MiraiGo/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/Mrs4s/go-cqhttp/coolq/cqcode"
) )
var bot = CQBot{} func TestParseString(_ *testing.T) {
// TODO: add more text
func TestCQBot_ConvertStringMessage(t *testing.T) { for _, v := range ParseString(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`) {
for _, v := range bot.ConvertStringMessage(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`, message.SourcePrivate) {
fmt.Println(v) fmt.Println(v)
} }
} }
@ -26,17 +22,18 @@ var (
benchArray = gjson.Parse(`[{"type":"text","data":{"text":"asdfqwerqwerqwer"}},{"type":"face","data":{"id":"115","text":"111"}},{"type":"text","data":{"text":"asdfasdfasdfasdfasdfasdfasd"}},{"type":"face","data":{"id":"217"}},{"type":"text","data":{"text":"] "}},{"type":"text","data":{"text":"123"}},{"type":"text","data":{"text":" ["}}]`) benchArray = gjson.Parse(`[{"type":"text","data":{"text":"asdfqwerqwerqwer"}},{"type":"face","data":{"id":"115","text":"111"}},{"type":"text","data":{"text":"asdfasdfasdfasdfasdfasdfasd"}},{"type":"face","data":{"id":"217"}},{"type":"text","data":{"text":"] "}},{"type":"text","data":{"text":"123"}},{"type":"text","data":{"text":" ["}}]`)
) )
func BenchmarkCQBot_ConvertStringMessage(b *testing.B) { func BenchmarkParseString(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
bot.ConvertStringMessage(bench, message.SourcePrivate) ParseString(bench)
} }
b.SetBytes(int64(len(bench))) b.SetBytes(int64(len(bench)))
} }
func BenchmarkCQBot_ConvertObjectMessage(b *testing.B) { func BenchmarkParseObject(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
bot.ConvertObjectMessage(benchArray, message.SourcePrivate) ParseObject(benchArray)
} }
b.SetBytes(int64(len(benchArray.Raw)))
} }
const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&987654321[]&` const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&987654321[]&`
@ -44,16 +41,7 @@ const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&98765
func BenchmarkCQCodeEscapeText(b *testing.B) { func BenchmarkCQCodeEscapeText(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
ret := bText ret := bText
cqcode.EscapeText(ret) EscapeText(ret)
}
}
func BenchmarkCQCodeEscapeTextBefore(b *testing.B) {
for i := 0; i < b.N; i++ {
ret := bText
ret = strings.ReplaceAll(ret, "&", "&amp;")
ret = strings.ReplaceAll(ret, "[", "&#91;")
strings.ReplaceAll(ret, "]", "&#93;")
} }
} }
@ -64,6 +52,6 @@ func TestCQCodeEscapeText(t *testing.T) {
ret = strings.ReplaceAll(ret, "&", "&amp;") ret = strings.ReplaceAll(ret, "&", "&amp;")
ret = strings.ReplaceAll(ret, "[", "&#91;") ret = strings.ReplaceAll(ret, "[", "&#91;")
ret = strings.ReplaceAll(ret, "]", "&#93;") ret = strings.ReplaceAll(ret, "]", "&#93;")
assert.Equal(t, ret, cqcode.EscapeText(rs)) assert.Equal(t, ret, EscapeText(rs))
} }
} }

View File

@ -1,5 +1,4 @@
//go:build !windows //go:build !windows
// +build !windows
package selfupdate package selfupdate

10
main.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
"github.com/Mrs4s/go-cqhttp/cmd/gocq" "github.com/Mrs4s/go-cqhttp/cmd/gocq"
"github.com/Mrs4s/go-cqhttp/global/terminal"
_ "github.com/Mrs4s/go-cqhttp/db/leveldb" // leveldb 数据库支持 _ "github.com/Mrs4s/go-cqhttp/db/leveldb" // leveldb 数据库支持
_ "github.com/Mrs4s/go-cqhttp/modules/silk" // silk编码模块 _ "github.com/Mrs4s/go-cqhttp/modules/silk" // silk编码模块
@ -13,5 +14,12 @@ import (
) )
func main() { func main() {
gocq.Main() terminal.SetTitle()
gocq.InitBase()
gocq.PrepareData()
gocq.LoginInteract()
_ = terminal.DisableQuickEdit()
_ = terminal.EnableVT100()
gocq.WaitSignal()
_ = terminal.RestoreInputMode()
} }

View File

@ -5,34 +5,16 @@ package api
import ( import (
"github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
) )
func (c *Caller) call(action string, version uint16, p Getter) global.MSG { func (c *Caller) call(action string, spec *onebot.Spec, p Getter) global.MSG {
var converter coolq.IDConverter = func(id any) any { if spec.Version == 11 {
return coolq.ConvertIDWithVersion(id, version)
}
if version == 12 {
if action == "get_supported_actions" {
return coolq.OK([]string{".get_word_slices", ".handle_quick_operation", ".ocr_image", "ocr_image", "_get_group_notice", "_get_model_show", "_send_group_notice", "_set_model_show", "check_url_safely", "create_group_file_folder", "create_guild_role", "delete_essence_msg", "delete_friend", "delete_group_file", "delete_group_folder", "delete_guild_role", "delete_msg", "delete_unidirectional_friend", "download_file", "get_essence_msg_list", "get_forward_msg", "get_friend_list", "get_group_at_all_remain", "get_group_file_system_info", "get_group_file_url", "get_group_files_by_folder", "get_group_honor_info", "get_group_info", "get_group_list", "get_group_member_info", "get_group_member_list", "get_group_msg_history", "get_group_root_files", "get_group_system_msg", "get_guild_channel_list", "get_guild_list", "get_guild_member_list", "get_guild_member_profile", "get_guild_meta_by_guest", "get_guild_msg", "get_guild_roles", "get_guild_service_profile", "get_image", "get_self_info", "get_msg", "get_online_clients", "get_status", "get_user_info", "get_topic_channel_feeds", "get_unidirectional_friend_list", "get_version", "mark_msg_as_read", "qidian_get_account_info", "reload_event_filter", "send_group_sign", "send_guild_channel_msg", "send_message", "set_essence_msg", "set_friend_add_request", "set_group_add_request", "set_group_admin", "set_group_anonymous_ban", "set_group_ban", "set_group_card", "set_group_kick", "set_group_leave", "set_group_name", "set_group_portrait", "set_group_special_title", "set_group_whole_ban", "set_guild_member_role", "set_qq_profile", "update_guild_role", "upload_group_file"})
}
switch action {
case "get_self_info":
return c.bot.CQGetLoginInfo()
case "get_user_info":
p0 := p.Get("user_id").Int()
return c.bot.CQGetStrangerInfo(p0)
case "get_version":
return c.bot.CQGetVersion()
case "send_message":
p0 := p.Get("group_id").String()
p1 := p.Get("user_id").String()
p2 := p.Get("detail_type").String()
p3 := p.Get("message")
return c.bot.CQSendMessageV12(p0, p1, p2, p3)
}
}
if version == 11 {
switch action { switch action {
case ".handle_quick_operation":
p0 := p.Get("context")
p1 := p.Get("operation")
return c.bot.CQHandleQuickOperation(p0, p1)
case "can_send_image": case "can_send_image":
return c.bot.CQCanSendImage() return c.bot.CQCanSendImage()
case "can_send_record": case "can_send_record":
@ -78,14 +60,27 @@ func (c *Caller) call(action string, version uint16, p Getter) global.MSG {
return c.bot.CQSendPrivateMessage(p0, p1, p2, p3) return c.bot.CQSendPrivateMessage(p0, p1, p2, p3)
} }
} }
if spec.Version == 12 {
switch action {
case "get_self_info":
return c.bot.CQGetLoginInfo()
case "get_user_info":
p0 := p.Get("user_id").Int()
return c.bot.CQGetStrangerInfo(p0)
case "get_version":
return c.bot.CQGetVersion()
case "send_message":
p0 := p.Get("group_id").String()
p1 := p.Get("user_id").String()
p2 := p.Get("detail_type").String()
p3 := p.Get("message")
return c.bot.CQSendMessageV12(p0, p1, p2, p3)
}
}
switch action { switch action {
case ".get_word_slices": case ".get_word_slices":
p0 := p.Get("content").String() p0 := p.Get("content").String()
return c.bot.CQGetWordSlices(p0) return c.bot.CQGetWordSlices(p0)
case ".handle_quick_operation":
p0 := p.Get("context")
p1 := p.Get("operation")
return c.bot.CQHandleQuickOperation(p0, p1)
case ".ocr_image", "ocr_image": case ".ocr_image", "ocr_image":
p0 := p.Get("image").String() p0 := p.Get("image").String()
return c.bot.CQOcrImage(p0) return c.bot.CQOcrImage(p0)
@ -160,7 +155,7 @@ func (c *Caller) call(action string, version uint16, p Getter) global.MSG {
p0 := p.Get("[message_id,id].0").String() p0 := p.Get("[message_id,id].0").String()
return c.bot.CQGetForwardMessage(p0) return c.bot.CQGetForwardMessage(p0)
case "get_friend_list": case "get_friend_list":
return c.bot.CQGetFriendList(version) return c.bot.CQGetFriendList(spec)
case "get_group_at_all_remain": case "get_group_at_all_remain":
p0 := p.Get("group_id").Int() p0 := p.Get("group_id").Int()
return c.bot.CQGetAtAllRemain(p0) return c.bot.CQGetAtAllRemain(p0)
@ -183,10 +178,10 @@ func (c *Caller) call(action string, version uint16, p Getter) global.MSG {
case "get_group_info": case "get_group_info":
p0 := p.Get("group_id").Int() p0 := p.Get("group_id").Int()
p1 := p.Get("no_cache").Bool() p1 := p.Get("no_cache").Bool()
return c.bot.CQGetGroupInfo(p0, p1, converter) return c.bot.CQGetGroupInfo(p0, p1, spec)
case "get_group_list": case "get_group_list":
p0 := p.Get("no_cache").Bool() p0 := p.Get("no_cache").Bool()
return c.bot.CQGetGroupList(p0, converter) return c.bot.CQGetGroupList(p0, spec)
case "get_group_member_info": case "get_group_member_info":
p0 := p.Get("group_id").Int() p0 := p.Get("group_id").Int()
p1 := p.Get("user_id").Int() p1 := p.Get("user_id").Int()
@ -241,7 +236,9 @@ func (c *Caller) call(action string, version uint16, p Getter) global.MSG {
p0 := p.Get("no_cache").Bool() p0 := p.Get("no_cache").Bool()
return c.bot.CQGetOnlineClients(p0) return c.bot.CQGetOnlineClients(p0)
case "get_status": case "get_status":
return c.bot.CQGetStatus(version) return c.bot.CQGetStatus(spec)
case "get_supported_actions":
return c.bot.CQGetSupportedActions(spec)
case "get_topic_channel_feeds": case "get_topic_channel_feeds":
p0 := p.Get("guild_id").Uint() p0 := p.Get("guild_id").Uint()
p1 := p.Get("channel_id").Uint() p1 := p.Get("channel_id").Uint()

View File

@ -6,9 +6,10 @@ import (
"github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
) )
//go:generate go run github.com/Mrs4s/go-cqhttp/cmd/api-generator -path=./../../coolq/api.go,./../../coolq/api_v12.go //go:generate go run ./../../cmd/api-generator -pkg api -path=./../../coolq/api.go,./../../coolq/api_v12.go -o api.go
// Getter 参数获取 // Getter 参数获取
type Getter interface { type Getter interface {
@ -16,7 +17,7 @@ type Getter interface {
} }
// Handler 中间件 // Handler 中间件
type Handler func(action string, p Getter) global.MSG type Handler func(action string, spe *onebot.Spec, p Getter) global.MSG
// Caller api route caller // Caller api route caller
type Caller struct { type Caller struct {
@ -25,13 +26,13 @@ type Caller struct {
} }
// Call specific API // Call specific API
func (c *Caller) Call(action string, version uint16, p Getter) global.MSG { func (c *Caller) Call(action string, spec *onebot.Spec, p Getter) global.MSG {
for _, fn := range c.handlers { for _, fn := range c.handlers {
if ret := fn(action, p); ret != nil { if ret := fn(action, spec, p); ret != nil {
return ret return ret
} }
} }
return c.call(action, version, p) return c.call(action, spec, p)
} }
// Use add handlers to the API caller // Use add handlers to the API caller

View File

@ -35,6 +35,7 @@ type Account struct {
ReLogin *Reconnect `yaml:"relogin"` ReLogin *Reconnect `yaml:"relogin"`
UseSSOAddress bool `yaml:"use-sso-address"` UseSSOAddress bool `yaml:"use-sso-address"`
AllowTempSession bool `yaml:"allow-temp-session"` AllowTempSession bool `yaml:"allow-temp-session"`
SignServer string `yaml:"sign-server"`
} }
// Config 总配置文件 // Config 总配置文件
@ -56,6 +57,7 @@ type Config struct {
ExtraReplyData bool `yaml:"extra-reply-data"` ExtraReplyData bool `yaml:"extra-reply-data"`
SkipMimeScan bool `yaml:"skip-mime-scan"` SkipMimeScan bool `yaml:"skip-mime-scan"`
ConvertWebpImage bool `yaml:"convert-webp-image"` ConvertWebpImage bool `yaml:"convert-webp-image"`
HTTPTimeout int `yaml:"http-timeout"`
} `yaml:"message"` } `yaml:"message"`
Output struct { Output struct {

View File

@ -16,6 +16,15 @@ account: # 账号相关
# 是否允许发送临时会话消息 # 是否允许发送临时会话消息
allow-temp-session: false allow-temp-session: false
# 数据包的签名服务器
# 兼容 https://github.com/fuqiuluo/unidbg-fetch-qsign
# 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个服务器
# 示例:
# sign-server: 'http://127.0.0.1:8080' # 本地签名服务器
# sign-server: 'https://signserver.example.com' # 线上签名服务器
# 服务器可使用docker在本地搭建或者使用他人开放的服务
sign-server: '-'
heartbeat: heartbeat:
# 心跳频率, 单位秒 # 心跳频率, 单位秒
# -1 为关闭心跳 # -1 为关闭心跳
@ -45,6 +54,8 @@ message:
skip-mime-scan: false skip-mime-scan: false
# 是否自动转换 WebP 图片 # 是否自动转换 WebP 图片
convert-webp-image: false convert-webp-image: false
# http超时时间
http-timeout: 0
output: output:
# 日志等级 trace,debug,info,warn,error # 日志等级 trace,debug,info,warn,error

View File

@ -32,6 +32,9 @@ func encode(record []byte, tempName string) (silkWav []byte, err error) {
// 2.转换pcm // 2.转换pcm
pcmPath := path.Join(silkCachePath, tempName+".pcm") pcmPath := path.Join(silkCachePath, tempName+".pcm")
cmd := exec.Command("ffmpeg", "-i", rawPath, "-f", "s16le", "-ar", "24000", "-ac", "1", pcmPath) cmd := exec.Command("ffmpeg", "-i", rawPath, "-f", "s16le", "-ar", "24000", "-ac", "1", pcmPath)
if errors.Is(cmd.Err, exec.ErrDot) {
cmd.Err = nil
}
if base.Debug { if base.Debug {
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr

78
pkg/onebot/attr.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package onebot
import (
"time"
)
// An Attr is a key-value pair.
type Attr struct {
Key string
Value Value
}
// String returns an Attr for a string value.
func String(key, value string) Attr {
return Attr{key, StringValue(value)}
}
// Int64 returns an Attr for an int64.
func Int64(key string, value int64) Attr {
return Attr{key, Int64Value(value)}
}
// Int converts an int to an int64 and returns
// an Attr with that value.
func Int(key string, value int) Attr {
return Int64(key, int64(value))
}
// Uint64 returns an Attr for a uint64.
func Uint64(key string, v uint64) Attr {
return Attr{key, Uint64Value(v)}
}
// Float64 returns an Attr for a floating-point number.
func Float64(key string, v float64) Attr {
return Attr{key, Float64Value(v)}
}
// Bool returns an Attr for a bool.
func Bool(key string, v bool) Attr {
return Attr{key, BoolValue(v)}
}
// Time returns an Attr for a time.Time.
// It discards the monotonic portion.
func Time(key string, v time.Time) Attr {
return Attr{key, TimeValue(v)}
}
// Duration returns an Attr for a time.Duration.
func Duration(key string, v time.Duration) Attr {
return Attr{key, DurationValue(v)}
}
// Group returns an Attr for a Group Value.
// The caller must not subsequently mutate the
// argument slice.
//
// Use Group to collect several Attrs under a single
// key on a log line, or as the result of LogValue
// in order to log a single value as multiple Attrs.
func Group(key string, as ...Attr) Attr {
return Attr{key, GroupValue(as...)}
}
// Any returns an Attr for the supplied value.
// See [Value.AnyValue] for how values are treated.
func Any(key string, value any) Attr {
return Attr{key, AnyValue(value)}
}
func (a Attr) String() string {
return a.Key + "=" + a.Value.String()
}

31
pkg/onebot/kind_string.go Normal file
View File

@ -0,0 +1,31 @@
// Code generated by "stringer -type=Kind -trimprefix=Kind"; DO NOT EDIT.
package onebot
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[KindAny-0]
_ = x[KindBool-1]
_ = x[KindDuration-2]
_ = x[KindFloat64-3]
_ = x[KindInt64-4]
_ = x[KindString-5]
_ = x[KindTime-6]
_ = x[KindUint64-7]
_ = x[KindGroup-8]
}
const _Kind_name = "AnyBoolDurationFloat64Int64StringTimeUint64Group"
var _Kind_index = [...]uint8{0, 3, 7, 15, 22, 27, 33, 37, 43, 48}
func (i Kind) String() string {
if i < 0 || i >= Kind(len(_Kind_index)-1) {
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
}

41
pkg/onebot/onebot.go Normal file
View File

@ -0,0 +1,41 @@
package onebot
// Self 机器人自身标识
//
// https://12.onebot.dev/connect/data-protocol/basic-types/#_10
type Self struct {
Platform string `json:"platform"`
UserID string `json:"user_id"`
}
// Request 动作请求是应用端为了主动向 OneBot 实现请求服务而发送的数据
//
// https://12.onebot.dev/connect/data-protocol/action-request/
type Request struct {
Action string // 动作名称
Params any // 动作参数
Echo any // 每次请求的唯一标识
}
// Response 动作响应是 OneBot 实现收到应用端的动作请求并处理完毕后,发回应用端的数据
//
// https://12.onebot.dev/connect/data-protocol/action-response/
type Response struct {
Status string `json:"status"` // 执行状态,必须是 ok、failed 中的一个
Code int64 `json:"retcode"` // 返回码
Data any `json:"data"` // 响应数据
Message string `json:"message"` // 错误信息
Echo any `json:"echo"` // 动作请求中的 echo 字段值
}
// Event 事件
//
// https://12.onebot.dev/connect/data-protocol/event/
type Event struct {
ID string
Time int64
Type string
DetailType string
SubType string
Self *Self
}

32
pkg/onebot/spec.go Normal file
View File

@ -0,0 +1,32 @@
// Package onebot defines onebot protocol struct and some spec info.
package onebot
import "fmt"
//go:generate go run ./../../cmd/api-generator -pkg onebot -path=./../../coolq/api.go,./../../coolq/api_v12.go -supported -o supported.go
// Spec OneBot Specification
type Spec struct {
Version int // must be 11 or 12
SupportedActions []string
}
// V11 OneBot V11
var V11 = &Spec{
Version: 11,
SupportedActions: supportedV11,
}
// V12 OneBot V12
var V12 = &Spec{
Version: 12,
SupportedActions: supportedV12,
}
// ConvertID 根据版本转换ID
func (s *Spec) ConvertID(id any) any {
if s.Version == 12 {
return fmt.Sprint(id)
}
return id
}

169
pkg/onebot/supported.go Normal file
View File

@ -0,0 +1,169 @@
// Code generated by cmd/api-generator. DO NOT EDIT.
package onebot
var supportedV11 = []string{
".get_word_slices",
".handle_quick_operation",
".ocr_image",
"ocr_image",
"_del_group_notice",
"_get_group_notice",
"_get_model_show",
"_send_group_notice",
"_set_model_show",
"can_send_image",
"can_send_record",
"check_url_safely",
"create_group_file_folder",
"create_guild_role",
"delete_essence_msg",
"delete_friend",
"delete_group_file",
"delete_group_folder",
"delete_guild_role",
"delete_msg",
"delete_unidirectional_friend",
"download_file",
"get_essence_msg_list",
"get_forward_msg",
"get_friend_list",
"get_group_at_all_remain",
"get_group_file_system_info",
"get_group_file_url",
"get_group_files_by_folder",
"get_group_honor_info",
"get_group_info",
"get_group_list",
"get_group_member_info",
"get_group_member_list",
"get_group_msg_history",
"get_group_root_files",
"get_group_system_msg",
"get_guild_channel_list",
"get_guild_list",
"get_guild_member_list",
"get_guild_member_profile",
"get_guild_meta_by_guest",
"get_guild_msg",
"get_guild_roles",
"get_guild_service_profile",
"get_image",
"get_login_info",
"get_msg",
"get_online_clients",
"get_status",
"get_stranger_info",
"get_supported_actions",
"get_topic_channel_feeds",
"get_unidirectional_friend_list",
"get_version_info",
"mark_msg_as_read",
"qidian_get_account_info",
"reload_event_filter",
"send_forward_msg",
"send_group_forward_msg",
"send_group_msg",
"send_group_sign",
"send_guild_channel_msg",
"send_msg",
"send_private_forward_msg",
"send_private_msg",
"set_essence_msg",
"set_friend_add_request",
"set_group_add_request",
"set_group_admin",
"set_group_anonymous",
"set_group_anonymous_ban",
"set_group_ban",
"set_group_card",
"set_group_kick",
"set_group_leave",
"set_group_name",
"set_group_portrait",
"set_group_special_title",
"set_group_whole_ban",
"set_guild_member_role",
"set_qq_profile",
"update_guild_role",
"upload_group_file",
"upload_private_file",
}
var supportedV12 = []string{
".get_word_slices",
".ocr_image",
"ocr_image",
"_del_group_notice",
"_get_group_notice",
"_get_model_show",
"_send_group_notice",
"_set_model_show",
"check_url_safely",
"create_group_file_folder",
"create_guild_role",
"delete_essence_msg",
"delete_friend",
"delete_group_file",
"delete_group_folder",
"delete_guild_role",
"delete_msg",
"delete_unidirectional_friend",
"download_file",
"get_essence_msg_list",
"get_forward_msg",
"get_friend_list",
"get_group_at_all_remain",
"get_group_file_system_info",
"get_group_file_url",
"get_group_files_by_folder",
"get_group_honor_info",
"get_group_info",
"get_group_list",
"get_group_member_info",
"get_group_member_list",
"get_group_msg_history",
"get_group_root_files",
"get_group_system_msg",
"get_guild_channel_list",
"get_guild_list",
"get_guild_member_list",
"get_guild_member_profile",
"get_guild_meta_by_guest",
"get_guild_msg",
"get_guild_roles",
"get_guild_service_profile",
"get_image",
"get_self_info",
"get_msg",
"get_online_clients",
"get_status",
"get_user_info",
"get_supported_actions",
"get_topic_channel_feeds",
"get_unidirectional_friend_list",
"mark_msg_as_read",
"qidian_get_account_info",
"reload_event_filter",
"send_group_sign",
"send_guild_channel_msg",
"set_essence_msg",
"set_friend_add_request",
"set_group_add_request",
"set_group_admin",
"set_group_anonymous",
"set_group_anonymous_ban",
"set_group_ban",
"set_group_card",
"set_group_kick",
"set_group_leave",
"set_group_name",
"set_group_portrait",
"set_group_special_title",
"set_group_whole_ban",
"set_guild_member_role",
"set_qq_profile",
"update_guild_role",
"upload_group_file",
"upload_private_file",
}

355
pkg/onebot/value.go Normal file
View File

@ -0,0 +1,355 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package onebot
import (
"fmt"
"math"
"strconv"
"time"
"unsafe"
)
// A Value can represent any Go value, but unlike type any,
// it can represent most small values without an allocation.
// The zero Value corresponds to nil.
type Value struct {
_ [0]func() // disallow ==
num uint64 // hold number value
any any // hold Kind or other value
}
type (
stringptr *byte // used in Value.any when the Value is a string
groupptr *Attr // used in Value.any when the Value is a []Attr
)
//go:generate stringer -type=Kind -trimprefix=Kind
// Kind is the kind of Value.
type Kind int
// Kind
const (
KindAny Kind = iota
KindBool
KindDuration
KindFloat64
KindInt64
KindString
KindTime
KindUint64
KindGroup
)
// Unexported version of Kind, just so we can store Kinds in Values.
// (No user-provided value has this type.)
type kind Kind
// Kind returns v's Kind.
func (v Value) Kind() Kind {
switch x := v.any.(type) {
case Kind:
return x
case stringptr:
return KindString
case timeLocation:
return KindTime
case groupptr:
return KindGroup
case kind: // a kind is just a wrapper for a Kind
return KindAny
default:
return KindAny
}
}
//////////////// Constructors
// StringValue returns a new Value for a string.
func StringValue(value string) Value {
return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
}
// IntValue returns a Value for an int.
func IntValue(v int) Value {
return Int64Value(int64(v))
}
// Int64Value returns a Value for an int64.
func Int64Value(v int64) Value {
return Value{num: uint64(v), any: KindInt64}
}
// Uint64Value returns a Value for a uint64.
func Uint64Value(v uint64) Value {
return Value{num: v, any: KindUint64}
}
// Float64Value returns a Value for a floating-point number.
func Float64Value(v float64) Value {
return Value{num: math.Float64bits(v), any: KindFloat64}
}
// BoolValue returns a Value for a bool.
func BoolValue(v bool) Value {
u := uint64(0)
if v {
u = 1
}
return Value{num: u, any: KindBool}
}
// Unexported version of *time.Location, just so we can store *time.Locations in
// Values. (No user-provided value has this type.)
type timeLocation *time.Location
// TimeValue returns a Value for a time.Time.
// It discards the monotonic portion.
func TimeValue(v time.Time) Value {
if v.IsZero() {
// UnixNano on the zero time is undefined, so represent the zero time
// with a nil *time.Location instead. time.Time.Location method never
// returns nil, so a Value with any == timeLocation(nil) cannot be
// mistaken for any other Value, time.Time or otherwise.
return Value{any: timeLocation(nil)}
}
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
}
// DurationValue returns a Value for a time.Duration.
func DurationValue(v time.Duration) Value {
return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
}
// GroupValue returns a new Value for a list of Attrs.
// The caller must not subsequently mutate the argument slice.
func GroupValue(as ...Attr) Value {
return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
}
// AnyValue returns a Value for the supplied value.
//
// If the supplied value is of type Value, it is returned
// unmodified.
//
// Given a value of one of Go's predeclared string, bool, or
// (non-complex) numeric types, AnyValue returns a Value of kind
// String, Bool, Uint64, Int64, or Float64. The width of the
// original numeric type is not preserved.
//
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
// KindTime or KindDuration. The monotonic time is not preserved.
//
// For nil, or values of all other types, including named types whose
// underlying type is numeric, AnyValue returns a value of kind KindAny.
func AnyValue(v any) Value {
switch v := v.(type) {
case string:
return StringValue(v)
case int:
return Int64Value(int64(v))
case uint:
return Uint64Value(uint64(v))
case int64:
return Int64Value(v)
case uint64:
return Uint64Value(v)
case bool:
return BoolValue(v)
case time.Duration:
return DurationValue(v)
case time.Time:
return TimeValue(v)
case uint8:
return Uint64Value(uint64(v))
case uint16:
return Uint64Value(uint64(v))
case uint32:
return Uint64Value(uint64(v))
case uintptr:
return Uint64Value(uint64(v))
case int8:
return Int64Value(int64(v))
case int16:
return Int64Value(int64(v))
case int32:
return Int64Value(int64(v))
case float64:
return Float64Value(v)
case float32:
return Float64Value(float64(v))
case []Attr:
return GroupValue(v...)
case Kind:
return Value{any: kind(v)}
case Value:
return v
default:
return Value{any: v}
}
}
//////////////// Accessors
// Any returns v's value as an any.
func (v Value) Any() any {
switch v.Kind() {
case KindAny:
if k, ok := v.any.(kind); ok {
return Kind(k)
}
return v.any
case KindGroup:
return v.group()
case KindInt64:
return int64(v.num)
case KindUint64:
return v.num
case KindFloat64:
return v.float()
case KindString:
return v.str()
case KindBool:
return v.bool()
case KindDuration:
return v.duration()
case KindTime:
return v.time()
default:
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
}
}
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
// the methods Int64, Float64, and so on, which panic if v is of the
// wrong kind, String never panics.
func (v Value) String() string {
if sp, ok := v.any.(stringptr); ok {
return unsafe.String(sp, v.num)
}
var buf []byte
return string(v.append(buf))
}
func (v Value) str() string {
return unsafe.String(v.any.(stringptr), v.num)
}
// Int64 returns v's value as an int64. It panics
// if v is not a signed integer.
func (v Value) Int64() int64 {
if g, w := v.Kind(), KindInt64; g != w {
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
}
return int64(v.num)
}
// Uint64 returns v's value as a uint64. It panics
// if v is not an unsigned integer.
func (v Value) Uint64() uint64 {
if g, w := v.Kind(), KindUint64; g != w {
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
}
return v.num
}
// Bool returns v's value as a bool. It panics
// if v is not a bool.
func (v Value) Bool() bool {
if g, w := v.Kind(), KindBool; g != w {
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
}
return v.bool()
}
func (v Value) bool() bool {
return v.num == 1
}
// Duration returns v's value as a time.Duration. It panics
// if v is not a time.Duration.
func (v Value) Duration() time.Duration {
if g, w := v.Kind(), KindDuration; g != w {
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
}
return v.duration()
}
func (v Value) duration() time.Duration {
return time.Duration(int64(v.num))
}
// Float64 returns v's value as a float64. It panics
// if v is not a float64.
func (v Value) Float64() float64 {
if g, w := v.Kind(), KindFloat64; g != w {
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
}
return v.float()
}
func (v Value) float() float64 {
return math.Float64frombits(v.num)
}
// Time returns v's value as a time.Time. It panics
// if v is not a time.Time.
func (v Value) Time() time.Time {
if g, w := v.Kind(), KindTime; g != w {
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
}
return v.time()
}
func (v Value) time() time.Time {
loc := v.any.(timeLocation)
if loc == nil {
return time.Time{}
}
return time.Unix(0, int64(v.num)).In(loc)
}
// Group returns v's value as a []Attr.
// It panics if v's Kind is not KindGroup.
func (v Value) Group() []Attr {
if sp, ok := v.any.(groupptr); ok {
return unsafe.Slice(sp, v.num)
}
panic("Group: bad kind")
}
func (v Value) group() []Attr {
return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
}
// append appends a text representation of v to dst.
// v is formatted as with fmt.Sprint.
func (v Value) append(dst []byte) []byte {
switch v.Kind() {
case KindString:
return append(dst, v.str()...)
case KindInt64:
return strconv.AppendInt(dst, int64(v.num), 10)
case KindUint64:
return strconv.AppendUint(dst, v.num, 10)
case KindFloat64:
return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
case KindBool:
return strconv.AppendBool(dst, v.bool())
case KindDuration:
return append(dst, v.duration().String()...)
case KindTime:
return append(dst, v.time().String()...)
case KindGroup:
return fmt.Append(dst, v.group())
case KindAny:
return fmt.Append(dst, v.any)
default:
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
}
}

View File

@ -1,6 +1,8 @@
#!/bin/sh #!/usr/bin/env bash
echo "Start GOCQHTTP~~~" function index.main_handler() {
echo "Start GOCQHTTP~~~"
cp -f config.yml /tmp/config.yml cp -f config.yml /tmp/config.yml
cp -f device.json /tmp/device.json cp -f device.json /tmp/device.json
./go-cqhttp -w="/tmp/" faststart ./go-cqhttp -w="/tmp/" faststart
}
index.main_handler

View File

@ -29,6 +29,7 @@ import (
"github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/modules/config" "github.com/Mrs4s/go-cqhttp/modules/config"
"github.com/Mrs4s/go-cqhttp/modules/filter" "github.com/Mrs4s/go-cqhttp/modules/filter"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
) )
// HTTPServer HTTP通信相关配置 // HTTPServer HTTP通信相关配置
@ -58,7 +59,7 @@ type httpServerPost struct {
type httpServer struct { type httpServer struct {
api *api.Caller api *api.Caller
accessToken string accessToken string
version uint16 spec *onebot.Spec // onebot spec
} }
// HTTPClient 反向HTTP上报客户端 // HTTPClient 反向HTTP上报客户端
@ -158,7 +159,7 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
switch request.Method { switch request.Method {
case http.MethodPost: case http.MethodPost:
// todo: msg pack // todo: msg pack
if s.version == 12 && strings.Contains(contentType, "application/msgpack") { if s.spec.Version == 12 && strings.Contains(contentType, "application/msgpack") {
log.Warnf("请求 %v 数据类型暂不支持: MsgPack", request.RequestURI) log.Warnf("请求 %v 数据类型暂不支持: MsgPack", request.RequestURI)
writer.WriteHeader(http.StatusUnsupportedMediaType) writer.WriteHeader(http.StatusUnsupportedMediaType)
return return
@ -204,12 +205,12 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
if request.URL.Path == "/" { if request.URL.Path == "/" {
action := strings.TrimSuffix(ctx.Get("action").Str, "_async") action := strings.TrimSuffix(ctx.Get("action").Str, "_async")
log.Debugf("HTTPServer接收到API调用: %v", action) log.Debugf("HTTPServer接收到API调用: %v", action)
response = s.api.Call(action, s.version, ctx.Get("params")) response = s.api.Call(action, s.spec, ctx.Get("params"))
} else { } else {
action := strings.TrimPrefix(request.URL.Path, "/") action := strings.TrimPrefix(request.URL.Path, "/")
action = strings.TrimSuffix(action, "_async") action = strings.TrimSuffix(action, "_async")
log.Debugf("HTTPServer接收到API调用: %v", action) log.Debugf("HTTPServer接收到API调用: %v", action)
response = s.api.Call(action, s.version, &ctx) response = s.api.Call(action, s.spec, &ctx)
} }
writer.Header().Set("Content-Type", "application/json; charset=utf-8") writer.Header().Set("Content-Type", "application/json; charset=utf-8")
@ -259,11 +260,15 @@ func runHTTP(bot *coolq.CQBot, node yaml.Node) {
case conf.Disabled: case conf.Disabled:
return return
} }
if conf.Version != 11 && conf.Version != 12 {
conf.Version = 11
}
network, addr := "tcp", conf.Address network, addr := "tcp", conf.Address
s := &httpServer{accessToken: conf.AccessToken, version: conf.Version} s := &httpServer{accessToken: conf.AccessToken}
switch conf.Version {
default:
// default v11
s.spec = onebot.V11
case 12:
s.spec = onebot.V12
}
switch { switch {
case conf.Address != "": case conf.Address != "":
uri, err := url.Parse(conf.Address) uri, err := url.Parse(conf.Address)

View File

@ -9,6 +9,7 @@ import (
"github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
@ -26,7 +27,7 @@ type MiddleWares struct {
func rateLimit(frequency float64, bucketSize int) api.Handler { func rateLimit(frequency float64, bucketSize int) api.Handler {
limiter := rate.NewLimiter(rate.Limit(frequency), bucketSize) limiter := rate.NewLimiter(rate.Limit(frequency), bucketSize)
return func(_ string, _ api.Getter) global.MSG { return func(_ string, _ *onebot.Spec, _ api.Getter) global.MSG {
_ = limiter.Wait(context.Background()) _ = limiter.Wait(context.Background())
return nil return nil
} }
@ -45,8 +46,11 @@ func longPolling(bot *coolq.CQBot, maxSize int) api.Handler {
} }
cond.Signal() cond.Signal()
}) })
return func(action string, p api.Getter) global.MSG { return func(action string, spec *onebot.Spec, p api.Getter) global.MSG {
if action != "get_updates" { switch {
case spec.Version == 11 && action == "get_updates": // ok
case spec.Version == 12 && action == "get_latest_events": // ok
default:
return nil return nil
} }
var ( var (

View File

@ -25,6 +25,7 @@ import (
"github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/modules/config" "github.com/Mrs4s/go-cqhttp/modules/config"
"github.com/Mrs4s/go-cqhttp/modules/filter" "github.com/Mrs4s/go-cqhttp/modules/filter"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
) )
type webSocketServer struct { type webSocketServer struct {
@ -476,7 +477,7 @@ func (c *wsConn) handleRequest(_ *coolq.CQBot, payload []byte) {
t := strings.TrimSuffix(j.Get("action").Str, "_async") t := strings.TrimSuffix(j.Get("action").Str, "_async")
params := j.Get("params") params := j.Get("params")
log.Debugf("WS接收到API调用: %v 参数: %v", t, params.Raw) log.Debugf("WS接收到API调用: %v 参数: %v", t, params.Raw)
ret := c.apiCaller.Call(t, 11, params) ret := c.apiCaller.Call(t, onebot.V11, params)
if j.Get("echo").Exists() { if j.Get("echo").Exists() {
ret["echo"] = j.Get("echo").Value() ret["echo"] = j.Get("echo").Value()
} }

1
winres/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
winres.json

118
winres/gen/json.go Normal file
View File

@ -0,0 +1,118 @@
// Package main generates winres.json
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"time"
"github.com/Mrs4s/go-cqhttp/internal/base"
)
const js = `{
"RT_GROUP_ICON": {
"APP": {
"0000": [
"icon.png",
"icon16.png"
]
}
},
"RT_MANIFEST": {
"#1": {
"0409": {
"identity": {
"name": "go-cqhttp",
"version": "%s"
},
"description": "",
"minimum-os": "vista",
"execution-level": "as invoker",
"ui-access": false,
"auto-elevate": false,
"dpi-awareness": "system",
"disable-theming": false,
"disable-window-filtering": false,
"high-resolution-scrolling-aware": false,
"ultra-high-resolution-scrolling-aware": false,
"long-path-aware": false,
"printer-driver-isolation": false,
"gdi-scaling": false,
"segment-heap": false,
"use-common-controls-v6": false
}
}
},
"RT_VERSION": {
"#1": {
"0000": {
"fixed": {
"file_version": "%s",
"product_version": "%s",
"timestamp": "%s"
},
"info": {
"0409": {
"Comments": "Golang implementation of cqhttp.",
"CompanyName": "Mrs4s",
"FileDescription": "https://github.com/Mrs4s/go-cqhttp",
"FileVersion": "%s",
"InternalName": "",
"LegalCopyright": "©️ 2020 - %d Mrs4s. All Rights Reserved.",
"LegalTrademarks": "",
"OriginalFilename": "GOCQHTTP.EXE",
"PrivateBuild": "",
"ProductName": "go-cqhttp",
"ProductVersion": "%s",
"SpecialBuild": ""
}
}
}
}
}
}`
const timeformat = `2006-01-02T15:04:05+08:00`
func main() {
f, err := os.Create("winres.json")
if err != nil {
panic(err)
}
defer f.Close()
v := ""
if base.Version == "(devel)" {
vartag := bytes.NewBuffer(nil)
vartagcmd := exec.Command("git", "tag", "--sort=committerdate")
vartagcmd.Stdout = vartag
err = vartagcmd.Run()
if err != nil {
panic(err)
}
s := strings.Split(vartag.String(), "\n")
v = s[len(s)-2]
} else {
v = base.Version
}
i := strings.Index(v, "-") // remove -rc / -beta
if i <= 0 {
i = len(v)
}
commitcnt := strings.Builder{}
commitcnt.WriteString(v[1:i])
commitcnt.WriteByte('.')
commitcntcmd := exec.Command("git", "rev-list", "--count", "master")
commitcntcmd.Stdout = &commitcnt
err = commitcntcmd.Run()
if err != nil {
panic(err)
}
fv := commitcnt.String()[:commitcnt.Len()-1]
_, err = fmt.Fprintf(f, js, fv, fv, v, time.Now().Format(timeformat), fv, time.Now().Year(), v)
if err != nil {
panic(err)
}
}

BIN
winres/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
winres/icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

4
winres/init.go Normal file
View File

@ -0,0 +1,4 @@
// Package winres 生成windows资源
package winres
//go:generate go run github.com/Mrs4s/go-cqhttp/winres/gen