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

Compare commits

...

89 Commits

Author SHA1 Message Date
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
70 changed files with 3161 additions and 1076 deletions

View File

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

View File

@ -24,18 +24,12 @@ jobs:
goarch: "386"
fail-fast: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Go environment
uses: actions/setup-go@v2.1.3
uses: actions/setup-go@v3
with:
cache: true
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
env:
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}"
go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" .
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: ${{ !github.head_ref }}
with:
name: ${{ matrix.goos }}_${{ matrix.goarch }}

View File

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

View File

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

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<p align="center">
<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>
</p>

View File

@ -10,11 +10,17 @@ import (
"go/token"
"io"
"os"
"reflect"
"sort"
"strconv"
"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 {
Name string
Type string
@ -23,7 +29,6 @@ type Param struct {
type Router struct {
Func string
Version []uint16
Path []string
PathV11 []string // v11 only
PathV12 []string // v12 only
@ -44,70 +49,43 @@ func (g *generator) WriteString(s string) {
io.WriteString(g.out, s)
}
func (g *generator) generate(routers []Router) {
var actions []string // for onebot v12 get_supported_actions
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] + `"`
func (g *generator) writef(format string, a ...any) {
fmt.Fprintf(g.out, format, a...)
}
// 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("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/global\"\n")
g.WriteString("\"github.com/Mrs4s/go-cqhttp/pkg/onebot\"\n")
g.WriteString(")\n\n")
g.WriteString(fmt.Sprintf(`func (c *Caller) call(action string, version uint16, p Getter) global.MSG {
var converter coolq.IDConverter = func(id any) any {
return coolq.ConvertIDWithVersion(id,version)
}
if version == 12 {
if action == "get_supported_actions" {
return coolq.OK([]string{%v})
}
g.WriteString(`func (c *Caller) call(action string, spec *onebot.Spec, p Getter) global.MSG {`)
genVer := func(path int) {
g.writef(`if spec.Version == %d {
switch action {
`, strings.Join(actions, ",")))
`, path)
for _, router := range routers {
g.router(router, PathV12)
g.router(router, path)
}
io.WriteString(g.out, `}}`)
io.WriteString(g.out, "\n")
g.WriteString(`if version == 11 {
switch action {
`)
for _, router := range routers {
g.router(router, PathV11)
g.WriteString("}}\n")
}
io.WriteString(g.out, `}}`)
io.WriteString(g.out, "\n")
io.WriteString(g.out, "switch action {\n")
genVer(PathV11)
genVer(PathV12)
// generic path
g.WriteString("switch action {\n")
for _, router := range routers {
g.router(router, PathAll)
}
io.WriteString(g.out, `}`)
io.WriteString(g.out, "\n")
io.WriteString(g.out, "return coolq.Failed(404, \"API_NOT_FOUND\", \"API不存在\")}")
g.WriteString("}\n")
g.WriteString("return coolq.Failed(404, \"API_NOT_FOUND\", \"API不存在\")}")
}
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
if pathVersion == PathV11 {
path = router.PathV11
@ -128,26 +106,17 @@ func (g *generator) router(router Router, pathVersion int) {
}
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 {
if p.Name == "version" || p.Name == "converter" {
if p.Type == "*onebot.Spec" {
continue
}
if p.Default == "" {
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 {
fmt.Fprintf(g.out, "p%d := %s\n", i, p.Default)
fmt.Fprintf(g.out, "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", i, p.Default)
g.writef("if pt := p.Get(%s); pt.Exists() {\n", strconv.Quote(p.Name))
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 {
g.WriteString(", ")
}
if p.Name == "version" {
fmt.Fprintf(g.out, "version")
if p.Type == "*onebot.Spec" {
g.WriteString("spec")
continue
}
if p.Name == "converter" {
fmt.Fprintf(g.out, "converter")
continue
}
fmt.Fprintf(g.out, "p%d", i)
g.writef("p%d", i)
}
g.WriteString(")\n")
}
@ -172,8 +137,8 @@ func (g *generator) router(router Router, pathVersion int) {
func conv(v, t string) string {
switch t {
default:
panic("unknown type: " + t)
case "gjson.Result", "IDConverter":
panic("unsupported type: " + t)
case "gjson.Result", "*onebot.Spec":
return v
case "int64":
return v + ".Int()"
@ -194,7 +159,6 @@ func conv(v, t string) string {
func main() {
var routers []Router
src := flag.String("path", "", "source file")
flag.Parse()
fset := token.NewFileSet()
for _, s := range strings.Split(*src, ",") {
@ -206,23 +170,15 @@ func main() {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
if !decl.Name.IsExported() || decl.Recv == nil {
continue
}
if st, ok := decl.Recv.List[0].Type.(*ast.StarExpr); !ok || st.X.(*ast.Ident).Name != "CQBot" {
if !decl.Name.IsExported() || decl.Recv == nil ||
typeName(decl.Recv.List[0].Type) != "*CQBot" {
continue
}
router := Router{Func: decl.Name.Name}
// compute params
for _, p := range decl.Type.Params.List {
var typ string
switch t := p.Type.(type) {
case *ast.Ident:
typ = t.Name
case *ast.SelectorExpr:
typ = t.X.(*ast.Ident).Name + "." + t.Sel.Name
}
typ := typeName(p.Type)
for _, name := range p.Names {
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 {
return router.Path[i] < router.Path[j]
@ -304,12 +253,17 @@ func main() {
out := new(bytes.Buffer)
g := &generator{out: out}
g.generate(routers)
g.header()
if *supported {
g.genSupported(routers)
} else {
g.genRouter(routers)
}
source, err := format.Source(out.Bytes())
if err != nil {
panic(err)
}
err = os.WriteFile("api.go", source, 0o644)
err = os.WriteFile(*output, source, 0o644)
if err != nil {
panic(err)
}
@ -328,7 +282,7 @@ func unquote(s string) string {
func parseMap(input string, sep string) map[string]string {
out := make(map[string]string)
for _, arg := range strings.Split(input, ",") {
k, v, ok := cut(arg, sep)
k, v, ok := strings.Cut(arg, sep)
if !ok {
out[k] = "true"
}
@ -346,20 +300,13 @@ func match(text string) (string, string) {
return "", ""
}
text = strings.Trim(text, "@)")
cmd, args, ok := cut(text, "(")
cmd, args, ok := strings.Cut(text, "(")
if !ok {
return "", ""
}
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
var replacer = strings.NewReplacer("ID", "Id")
@ -393,3 +340,16 @@ func convDefault(s string, t string) string {
}
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,26 +3,37 @@ package gocq
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"image"
"image/png"
"net/http"
"os"
"strings"
"time"
"github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/Mrs4s/MiraiGo/wrapper"
"github.com/mattn/go-colorable"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"gopkg.ilharper.com/x/isatty"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/Mrs4s/go-cqhttp/internal/download"
"github.com/Mrs4s/go-cqhttp/internal/encryption"
_ "github.com/Mrs4s/go-cqhttp/internal/encryption/t544" // side effect
)
var console = bufio.NewReader(os.Stdin)
func init() {
wrapper.DandelionEnergy = energy
}
func readLine() (str string) {
str, _ = console.ReadString('\n')
str = strings.TrimSpace(str)
@ -52,6 +63,7 @@ func readIfTTY(de string) (str string) {
}
var cli *client.QQClient
var device *client.DeviceInfo
// ErrSMSRequestError SMS请求出错
var ErrSMSRequestError = errors.New("sms request error")
@ -152,23 +164,15 @@ func loginResponseProcessor(res *client.LoginResponse) error {
var text string
switch res.Error {
case client.SliderNeededError:
log.Warnf("登录需要滑条验证码, 请选择验证方式: ")
log.Warnf("1. 使用浏览器抓取滑条并登录")
log.Warnf("2. 使用手机QQ扫码验证 (需要手Q和gocq在同一网络下).")
log.Warn("请输入(1 - 2)")
text = readIfTTY("1")
if strings.Contains(text, "1") {
log.Warnf("登录需要滑条验证码, 请验证后重试.")
ticket := getTicket(res.VerifyUrl)
if ticket == "" {
log.Infof("按 Enter 继续....")
readLine()
os.Exit(0)
}
res, err = cli.SubmitTicket(ticket)
continue
}
cli.Disconnect()
cli.Release()
cli = client.NewClientEmpty()
return qrcodeLogin()
case client.NeedCaptcha:
log.Warnf("登录需要验证码.")
_ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
@ -212,38 +216,46 @@ func loginResponseProcessor(res *client.LoginResponse) error {
os.Exit(0)
case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError:
msg := res.ErrorMessage
if strings.Contains(msg, "版本") {
msg = "密码错误或账号被冻结"
} else if strings.Contains(msg, "冻结") {
log.Fatalf("账号被冻结")
log.Warnf("登录失败: %v Code: %v", msg, res.Code)
switch res.Code {
case 235:
log.Warnf("设备信息被封禁, 请删除 device.json 后重试.")
case 237:
log.Warnf("登录过于频繁, 请在手机QQ登录并根据提示完成认证后等一段时间重试")
case 45: // 在提供 t544 后还是出现45错误是需要强行升级到最新客户端或被限制非常用设备
log.Warnf("你的账号涉嫌违规被限制在非常用设备登录, 请在手机QQ登录并根据提示完成认证")
log.Warnf("或使用 -update-protocol 升级到最新协议后重试")
}
log.Warnf("登录失败: %v", msg)
log.Infof("按 Enter 或等待 5s 后继续....")
readLineTimeout(time.Second * 5)
log.Infof("按 Enter 继续....")
readLine()
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)
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)))
manual := make(chan string, 1)
go func() {
manual <- readLine()
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
auto := !strings.Contains(text, "2")
if auto {
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))
}
log.Warnf("请前往该地址验证 -> %v ", u)
if !auto {
log.Warn("请输入ticket (Enter 提交)")
return readLine()
}
for count := 120; count > 0; count-- {
select {
case <-ticker.C:
str = fetchCaptcha(id)
str := fetchCaptcha(id)
if str != "" {
return
}
case str = <-manual:
return
return str
}
time.Sleep(time.Second)
}
log.Warnf("验证超时")
return ""
@ -260,3 +272,34 @@ func fetchCaptcha(id string) string {
}
return ""
}
func energy(uin uint64, id string, appVersion string, salt []byte) ([]byte, error) {
if localSigner, ok := encryption.T544Signer[appVersion]; ok {
log.Debugf("use local T544Signer v%s", appVersion)
result := localSigner(time.Now().UnixMicro(), salt)
log.Debugf("t544 sign result: %x", result)
return result, nil
}
log.Debugf("fallback to remote T544Signer v%s", appVersion)
signServer := "https://captcha.go-cqhttp.org/sdk/dandelion/energy"
if base.SignServerOverwrite != "" {
signServer = base.SignServerOverwrite
}
response, err := download.Request{
Method: http.MethodPost,
URL: signServer,
Header: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
Body: bytes.NewReader([]byte(fmt.Sprintf("uin=%v&id=%s&salt=%s&version=%s", uin, id, hex.EncodeToString(salt), appVersion))),
}.Bytes()
if err != nil {
log.Errorf("获取T544时出现问题: %v", err)
return nil, err
}
sign, err := hex.DecodeString(gjson.GetBytes(response, "result").String())
if err != nil || len(sign) == 0 {
log.Errorf("获取T544时出现问题: %v", err)
return nil, err
}
log.Debugf("t544 sign result: %x", sign)
return sign, nil
}

View File

@ -16,7 +16,9 @@ import (
"github.com/Mrs4s/MiraiGo/client"
para "github.com/fumiama/go-hide-param"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/term"
@ -26,6 +28,7 @@ import (
"github.com/Mrs4s/go-cqhttp/global/terminal"
"github.com/Mrs4s/go-cqhttp/internal/base"
"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/selfupdate"
"github.com/Mrs4s/go-cqhttp/modules/servers"
@ -41,8 +44,12 @@ var allowStatus = [...]client.UserOnlineStatus{
client.StatusGaming, client.StatusVacationing, client.StatusWatchingTV, client.StatusFitness,
}
// Main 启动主程序
func Main() {
// InitBase 解析参数并检测
//
// 如果在 windows 下双击打开了程序,程序将在此函数释出脚本后终止;
// 如果传入 -h 参数,程序将打印帮助后终止;
// 如果传入 -d 参数,程序将在启动 daemon 后终止。
func InitBase() {
base.Parse()
if !base.FastStart && terminal.RunningByDoubleClick() {
err := terminal.NoMoreDoubleClick()
@ -50,7 +57,7 @@ func Main() {
log.Errorf("遇到错误: %v", err)
time.Sleep(time.Second * 5)
}
return
os.Exit(0)
}
switch {
case base.LittleH:
@ -65,7 +72,10 @@ func Main() {
}
}
base.Init()
}
// PrepareData 准备 log, 缓存, 数据库, 必须在 InitBase 之后执行
func PrepareData() {
rotateOptions := []rotatelogs.Option{
rotatelogs.WithRotationTime(time.Hour * 24),
}
@ -95,13 +105,17 @@ func Main() {
mkCacheDir(global.VideoPath, "视频")
mkCacheDir(global.CachePath, "发送图片")
mkCacheDir(path.Join(global.ImagePath, "guild-images"), "频道图片缓存")
mkCacheDir(global.VersionsPath, "版本缓存")
cache.Init()
db.Init()
if err := db.Open(); err != nil {
log.Fatalf("打开数据库失败: %v", err)
}
}
// LoginInteract 登录交互, 可能需要键盘输入, 必须在 InitBase, PrepareData 之后执行
func LoginInteract() {
var byteKey []byte
arg := os.Args
if len(arg) > 1 {
@ -138,12 +152,13 @@ func Main() {
}
if !global.PathExists("device.json") {
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
client.GenRandomDevice()
_ = os.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0o644)
device = client.GenRandomDevice()
_ = os.WriteFile("device.json", device.ToJson(), 0o644)
log.Info("已生成设备信息并保存到 device.json 文件.")
} else {
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)
}
}
@ -152,23 +167,21 @@ func Main() {
if !global.PathExists("password.encrypt") {
if base.Account.Password == "" {
log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.")
readLine()
os.Exit(0)
}
} else {
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()
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)
@ -196,7 +209,6 @@ func Main() {
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
}
copy(base.PasswordHash[:], ph)
}
} else if len(base.Account.Password) > 0 {
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
}
@ -205,10 +217,27 @@ func Main() {
time.Sleep(time.Second * 5)
}
log.Info("开始尝试登录并同步消息...")
log.Infof("使用协议: %s", client.SystemDeviceInfo.Protocol)
log.Infof("使用协议: %s", device.Protocol.Version())
cli = newClient()
cli.UseDevice(device)
isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt
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() {
base.AccountToken = cli.GenToken()
_ = os.WriteFile("session.token", base.AccountToken, 0o644)
@ -239,6 +268,7 @@ func Main() {
cli.Disconnect()
cli.Release()
cli = newClient()
cli.UseDevice(device)
} else {
isTokenLogin = true
}
@ -248,6 +278,29 @@ func Main() {
cli.Uin = base.Account.Uin
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 !isQRCodeLogin {
if err := commonLogin(); err != nil {
@ -311,6 +364,7 @@ func Main() {
})
saveToken()
cli.AllowSlider = true
download.SetTimeout(time.Duration(base.HTTPTimeout) * time.Second) // 在登录完成后设置, 防止在堵塞协议更新
log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
log.Info("开始加载好友列表...")
global.Check(cli.ReloadFriendList(), true)
@ -322,11 +376,16 @@ func Main() {
base.Account.Status = 0
}
cli.SetOnlineStatus(allowStatus[base.Account.Status])
servers.Run(coolq.NewQQBot(cli))
log.Info("资源初始化完成, 开始处理信息.")
log.Info("アトリは、高性能ですから!")
}
// WaitSignal 在新线程检查更新和网络并等待信号, 必须在 InitBase, PrepareData, LoginInteract 之后执行
//
// - 直接返回: os.Interrupt, syscall.SIGTERM
// - dump stack: syscall.SIGQUIT, syscall.SIGUSR1
func WaitSignal() {
go func() {
selfupdate.CheckUpdate()
selfdiagnosis.NetworkDiagnosis(cli)
@ -389,6 +448,23 @@ func newClient() *client.QQClient {
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{}
const fromProtocol = "Protocol -> "

View File

@ -27,8 +27,10 @@ import (
"github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/Mrs4s/go-cqhttp/internal/cache"
"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/modules/filter"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
)
type guildMemberPageToken struct {
@ -327,13 +329,13 @@ func (bot *CQBot) CQGetTopicChannelFeeds(guildID, channelID uint64) global.MSG {
//
// https://git.io/Jtz1L
// @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))
for _, f := range bot.Client.FriendList {
fs = append(fs, global.MSG{
"nickname": f.Nickname,
"remark": f.Remark,
"user_id": ConvertIDWithVersion(f.Uin, version),
"user_id": spec.ConvertID(f.Uin),
})
}
return OK(fs)
@ -399,14 +401,14 @@ func (bot *CQBot) CQDeleteFriend(uin int64) global.MSG {
//
// https://git.io/Jtz1t
// @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))
if noCache {
_ = bot.Client.ReloadGroupList()
}
for _, g := range bot.Client.GroupList {
gs = append(gs, global.MSG{
"group_id": converter(g.Code),
"group_id": spec.ConvertID(g.Code),
"group_name": g.Name,
"group_create_time": g.GroupCreateTime,
"group_level": g.GroupLevel,
@ -421,7 +423,7 @@ func (bot *CQBot) CQGetGroupList(noCache bool, converter IDConverter) global.MSG
//
// https://git.io/Jtz1O
// @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)
if group == nil || noCache {
group, _ = bot.Client.GetGroupInfo(groupID)
@ -435,7 +437,7 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, converter IDConver
for _, g := range info {
if g.Code == groupID {
return OK(global.MSG{
"group_id": converter(g.Code),
"group_id": spec.ConvertID(g.Code),
"group_name": g.Name,
"group_memo": g.Memo,
"group_create_time": 0,
@ -447,7 +449,7 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, converter IDConver
}
} else {
return OK(global.MSG{
"group_id": converter(group.Code),
"group_id": spec.ConvertID(group.Code),
"group_name": group.Name,
"group_create_time": group.GroupCreateTime,
"group_level": group.GroupLevel,
@ -752,7 +754,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b
var elem []message.IMessageElement
if m.Type == gjson.JSON {
elem = bot.ConvertObjectMessage(m, message.SourceGroup)
elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGroup)
} else {
str := m.String()
if str == "" {
@ -762,7 +764,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b
if autoEscape {
elem = []message.IMessageElement{message.NewText(str)}
} else {
elem = bot.ConvertStringMessage(str, message.SourceGroup)
elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGroup)
}
}
fixAt(elem)
@ -806,7 +808,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
var elem []message.IMessageElement
if m.Type == gjson.JSON {
elem = bot.ConvertObjectMessage(m, message.SourceGuildChannel)
elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGuildChannel)
} else {
str := m.String()
if str == "" {
@ -816,7 +818,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
if autoEscape {
elem = []message.IMessageElement{message.NewText(str)}
} else {
elem = bot.ConvertStringMessage(str, message.SourceGuildChannel)
elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGuildChannel)
}
}
fixAt(elem)
@ -850,7 +852,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
for i, elem := range elems {
p := &elems[i]
switch o := elem.(type) {
case *LocalVideoElement:
case *msg.LocalVideo:
w.do(func() {
gm, err := bot.uploadLocalVideo(source, o)
if err != nil {
@ -859,7 +861,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
*p = gm
}
})
case *LocalImageElement:
case *msg.LocalImage:
w.do(func() {
gm, err := bot.uploadLocalImage(source, o)
if err != nil {
@ -926,7 +928,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 {
return &message.ForwardNode{
SenderId: uin,
@ -1019,7 +1021,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 {
var elem []message.IMessageElement
if m.Type == gjson.JSON {
elem = bot.ConvertObjectMessage(m, message.SourcePrivate)
elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourcePrivate)
} else {
str := m.String()
if str == "" {
@ -1028,7 +1030,7 @@ func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Resu
if autoEscape {
elem = []message.IMessageElement{message.NewText(str)}
} else {
elem = bot.ConvertStringMessage(str, message.SourcePrivate)
elem = bot.ConvertStringMessage(onebot.V11, str, message.SourcePrivate)
}
}
mid := bot.SendPrivateMessage(userID, groupID, &message.SendingMessage{Elements: elem})
@ -1444,7 +1446,7 @@ func (bot *CQBot) CQGetStrangerInfo(userID int64) global.MSG {
// CQHandleQuickOperation 隐藏API-对事件执行快速操作
//
// https://git.io/Jtz15
// @route(".handle_quick_operation")
// @route11(".handle_quick_operation")
func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global.MSG {
postType := context.Get("post_type").Str
@ -1845,7 +1847,11 @@ func (bot *CQBot) CQCanSendRecord() global.MSG {
// @route(ocr_image,".ocr_image")
// @rename(image_id->image)
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 {
log.Warnf("load image error: %v", err)
return Failed(100, "LOAD_FILE_ERROR", err.Error())
@ -1902,8 +1908,8 @@ func (bot *CQBot) CQSetGroupAnonymousBan(groupID int64, flag string, duration in
//
// https://git.io/JtzMe
// @route(get_status)
func (bot *CQBot) CQGetStatus(version uint16) global.MSG {
if version == 11 {
func (bot *CQBot) CQGetStatus(spec *onebot.Spec) global.MSG {
if spec.Version == 11 {
return OK(global.MSG{
"app_initialized": true,
"app_enabled": true,
@ -2012,7 +2018,7 @@ func (bot *CQBot) CQGetVersionInfo() global.MSG {
"runtime_version": runtime.Version(),
"runtime_os": runtime.GOOS,
"version": base.Version,
"protocol_name": client.SystemDeviceInfo.Protocol,
"protocol_name": bot.Client.Device().Protocol,
})
}
@ -2105,6 +2111,13 @@ func (bot *CQBot) CQReloadEventFilter(file string) global.MSG {
return OK(nil)
}
// CQGetSupportedActions 获取支持的动作列表
//
// @route(get_supported_actions)
func (bot *CQBot) CQGetSupportedActions(spec *onebot.Spec) global.MSG {
return OK(spec.SupportedActions)
}
// OK 生成成功返回值
func OK(data any) global.MSG {
return global.MSG{"data": data, "retcode": 0, "status": "ok", "message": ""}

View File

@ -3,9 +3,10 @@ package coolq
import (
"runtime"
"github.com/tidwall/gjson"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/base"
"github.com/tidwall/gjson"
)
// CQGetVersion 获取版本信息 OneBotV12
@ -27,6 +28,7 @@ func (bot *CQBot) CQGetVersion() global.MSG {
//
// @route12(send_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)
}

View File

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

View File

@ -1,7 +1,6 @@
package coolq
import (
"fmt"
"strconv"
"strings"
@ -13,8 +12,6 @@ import (
"github.com/Mrs4s/go-cqhttp/global"
)
type IDConverter func(id any) any
func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG {
sex := "unknown"
if m.Gender == 1 { // unknown = 0xff
@ -223,10 +220,3 @@ func toStringMessage(m []message.IMessageElement, source message.Source) string
func fU64(v uint64) string {
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)
}
func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEvent) {
func (bot *CQBot) tempMessageEvent(_ *client.QQClient, e *client.TempMessageEvent) {
m := e.Message
bot.checkMedia(m.Elements, m.Sender.Uin)
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) {
if group == nil {
return
}
log.Infof("Bot进入了群 %v.", formatGroupName(group))
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))
}
func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.MemberPermissionChangedEvent) {
func (bot *CQBot) memberPermissionChangedEvent(_ *client.QQClient, e *client.MemberPermissionChangedEvent) {
st := "unset"
if e.NewPermission == client.Administrator {
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)
bot.dispatchEvent("notice/group_card", global.MSG{
"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))
}
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)
flag := strconv.FormatInt(e.RequestId, 10)
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)
bot.tempSessionCache.Delete(e.Friend.Uin)
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)
flag := strconv.FormatInt(e.RequestId, 10)
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)
flag := strconv.FormatInt(e.RequestId, 10)
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 {
log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
} else {

View File

@ -11,10 +11,12 @@ import (
sql "github.com/FloatTech/sqlite"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/Mrs4s/go-cqhttp/db"
)
@ -27,7 +29,7 @@ type database struct {
// config mongodb 相关配置
type config struct {
Enable bool `yaml:"enable"`
CacheTTL time.Duration `yaml:"cachettl"`
CacheTTL string `yaml:"cachettl"`
}
func init() {
@ -38,7 +40,11 @@ func init() {
if !conf.Enable {
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

@ -33,9 +33,15 @@ func EncoderSilk(data []byte) ([]byte, error) {
// EncodeMP4 将给定视频文件编码为MP4
func EncodeMP4(src string, dst string) error { // -y 覆盖文件
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()
if err != nil {
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 err
@ -44,5 +50,8 @@ func EncodeMP4(src string, dst string) error { // -y 覆盖文件
// ExtractCover 获取给定视频文件的Cover
func ExtractCover(src string, target string) error {
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")
}

View File

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

View File

@ -14,9 +14,9 @@ import (
func SetupMainSignalHandler() <-chan struct{} {
mainOnce.Do(func() {
mainStopCh = make(chan struct{})
mc := make(chan os.Signal, 3)
mc := make(chan os.Signal, 4)
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() {
for {
switch <-mc {
@ -24,7 +24,7 @@ func SetupMainSignalHandler() <-chan struct{} {
closeOnce.Do(func() {
close(mainStopCh)
})
case syscall.SIGUSR1:
case syscall.SIGQUIT, syscall.SIGUSR1:
dumpStack()
}
}

View File

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

View File

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

View File

@ -1,6 +1,3 @@
//go:build windows
// +build windows
package terminal
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 (
github.com/FloatTech/sqlite v1.5.7
github.com/Microsoft/go-winio v0.6.0
github.com/Mrs4s/MiraiGo v0.0.0-20230213132655-3ff1fee1b645
github.com/Mrs4s/MiraiGo v0.0.0-20230401072048-f8d9841755b5
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc
github.com/fumiama/go-base16384 v1.6.1
@ -21,7 +21,7 @@ require (
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60
go.mongodb.org/mongo-driver v1.11.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/term 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/pierrec/lz4/v4 v4.1.15 // 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/pretty v1.2.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
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/text v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.1.12 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
@ -66,5 +66,3 @@ require (
modernc.org/strutil v1.1.3 // 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/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/Mrs4s/MiraiGo v0.0.0-20230213132655-3ff1fee1b645 h1:KHWuWmhF2nacb2mKqA3OJorerCEo9n6BNizMuBACa38=
github.com/Mrs4s/MiraiGo v0.0.0-20230213132655-3ff1fee1b645/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
github.com/Mrs4s/MiraiGo v0.0.0-20230401072048-f8d9841755b5 h1:E4fIQ0l/LNZK44NjdViRb/hx4cIeHXyQFPzzkx7cjVE=
github.com/Mrs4s/MiraiGo v0.0.0-20230401072048-f8d9841755b5/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/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
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/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
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.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
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.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
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/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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.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.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -37,6 +37,9 @@ var (
LogColorful bool // 是否启用日志颜色
FastStart bool // 是否为快速启动
AllowTempSession bool // 是否允许发送临时会话信息
UpdateProtocol bool // 是否更新协议
SignServerOverwrite string // 使用特定的服务器进行签名
HTTPTimeout int
PostFormat string // 上报格式 string or array
Proxy string // 存储 proxy_rewrite,用于设置代理
@ -60,6 +63,8 @@ func Parse() {
flag.StringVar(&LittleWD, "w", "", "cover the working directory")
d := flag.Bool("D", false, "debug mode")
flag.BoolVar(&FastStart, "faststart", false, "skip waiting 5 seconds")
flag.BoolVar(&UpdateProtocol, "update-protocol", false, "update protocol")
flag.StringVar(&SignServerOverwrite, "sign-server", "", "use special server to sign tlv")
flag.Parse()
if *d {
@ -84,6 +89,7 @@ func Init() {
ReportSelfMessage = conf.Message.ReportSelfMessage
UseSSOAddress = conf.Account.UseSSOAddress
AllowTempSession = conf.Account.AllowTempSession
HTTPTimeout = conf.Message.HTTPTimeout
}
{ // others
Proxy = conf.Message.ProxyRewrite

View File

@ -4,6 +4,7 @@ package download
import (
"bufio"
"compress/gzip"
"crypto/tls"
"fmt"
"io"
"net/http"
@ -12,6 +13,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
@ -21,17 +23,31 @@ import (
var client = &http.Client{
Transport: &http.Transport{
Proxy: func(request *http.Request) (u *url.URL, e error) {
Proxy: func(request *http.Request) (*url.URL, error) {
if base.Proxy == "" {
return http.ProxyFromEnvironment(request)
}
return url.Parse(base.Proxy)
},
ForceAttemptHTTP2: false,
MaxConnsPerHost: 0,
MaxIdleConns: 0,
// Disable http2
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
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 响应主体过大时返回此错误
@ -40,15 +56,36 @@ var ErrOverSize = errors.New("oversize")
// 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"
// 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
type Request struct {
Method string
URL string
Header map[string]string
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) {
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 {
return nil, err
}
@ -58,7 +95,7 @@ func (r Request) do() (*http.Response, error) {
req.Header.Set(k, v)
}
return client.Do(req)
return r.client().Do(req)
}
func (r Request) body() (io.ReadCloser, error) {
@ -79,7 +116,7 @@ func (r Request) body() (io.ReadCloser, error) {
return resp.Body, err
}
// Bytes 对给定URL发送Get请求,返回响应主体
// Bytes 对给定URL发送请求返回响应主体
func (r Request) Bytes() ([]byte, error) {
rd, err := r.body()
if err != nil {
@ -89,7 +126,7 @@ func (r Request) Bytes() ([]byte, error) {
return io.ReadAll(rd)
}
// JSON 发送GET请求, 并转换响应为JSON
// JSON 发送请求, 并转换响应为JSON
func (r Request) JSON() (gjson.Result, error) {
rd, err := r.body()
if err != nil {

View File

@ -0,0 +1,3 @@
package encryption
var T544Signer = map[string]func(int64, []byte) []byte{}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
//go:build amd64
package t544
import (
"encoding/binary"
"hash/crc32"
"io"
)
var crc32Table = func() (tab crc32.Table) {
f, err := cryptoZip.Open("crc32.bin")
if err != nil {
panic(err)
}
data, err := io.ReadAll(f)
if err != nil {
panic(err)
}
for i := range tab {
tab[i] = binary.LittleEndian.Uint32(data[i*4 : (i+1)*4])
}
return
}()
//go:noescape
func tencentCrc32(tab *crc32.Table, b []byte) uint32
//go:noescape
func sub_a([]byte, []uint32)
//go:noescape
func sub_b([]byte, []uint32)
//go:noescape
func sub_c(*[16][16]byte, []byte)
//go:noescape
func sub_d(*[16]byte, []byte)
//go:noescape
func sub_e(*[256][6]byte, []byte)
//go:noescape
func sub_f(*[16]byte, *[15]uint32, *[16][16]byte) (w [44]uint32)
//go:noescape
func sub_aa(int, *[16][2][16][16]byte, *[16]byte, []byte) byte
// transformInner see com/tencent/mobileqq/dt/model/FEBound
//
//go:noescape
func transformInner(*[0x15]byte, *[32][16]byte)
//go:noescape
func initState(*state, []byte, []byte, uint64)
func (c *state) init(key []byte, data []byte, counter uint64, nr uint8) {
c.nr = nr
c.p = 0
initState(c, key, data, counter)
}
//go:noescape
func refreshState(c *state)

View File

@ -0,0 +1,669 @@
//go:build amd64
// +build amd64
#include "textflag.h"
DATA LC0<>+0(SB)/4, $1634760805
DATA LC0<>+4(SB)/4, $857760878
DATA LC0<>+8(SB)/4, $2036477234
DATA LC0<>+12(SB)/4, $1797285236
GLOBL LC0<>(SB), NOPTR, $16
TEXT ·sub_a(SB), NOSPLIT, $0-48
MOVQ a+0(FP), DI
MOVQ b+24(FP), CX
MOVQ CX, DX
MOVBLZX 3(CX), CX
XORB CX, (DI)
MOVBLZX 2(DX), CX
XORB CX, 1(DI)
MOVBLZX 1(DX), CX
XORB CX, 2(DI)
MOVBLZX (DX), CX
XORB CX, 3(DI)
MOVBLZX 7(DX), CX
XORB CX, 4(DI)
MOVBLZX 6(DX), CX
XORB CX, 5(DI)
MOVBLZX 5(DX), CX
XORB CX, 6(DI)
MOVBLZX 4(DX), CX
XORB CX, 7(DI)
MOVBLZX 11(DX),CX
XORB CX, 8(DI)
MOVBLZX 10(DX),CX
XORB CX, 9(DI)
MOVBLZX 9(DX), CX
XORB CX,10(DI)
MOVBLZX 8(DX), CX
XORB CX,11(DI)
MOVBLZX 15(DX),CX
XORB CX,12(DI)
MOVBLZX 14(DX),CX
XORB CX,13(DI)
MOVBLZX 13(DX),CX
XORB CX,14(DI)
MOVBLZX 12(DX),DX
XORB DL,15(DI)
RET
TEXT ·sub_b(SB), NOSPLIT, $0-48
MOVQ a+0(FP), DI
MOVQ b+24(FP), CX
MOVQ CX, DX
MOVBLZX 3(CX), CX
XORB CX, (DI)
MOVBLZX 6(DX), CX
XORB CX, 1(DI)
MOVBLZX 9(DX), CX
XORB CX, 2(DI)
MOVBLZX 12(DX),CX
XORB CX, 3(DI)
MOVBLZX 7(DX), CX
XORB CX, 4(DI)
MOVBLZX 10(DX),CX
XORB CX, 5(DI)
MOVBLZX 13(DX),CX
XORB CX, 6(DI)
MOVBLZX (DX), CX
XORB CX,7(DI)
MOVBLZX 11(DX),CX
XORB CX,8(DI)
MOVBLZX 14(DX),CX
XORB CX,9(DI)
MOVBLZX 1(DX), CX
XORB CX,10(DI)
MOVBLZX 4(DX), CX
XORB CX,11(DI)
MOVBLZX 15(DX),CX
XORB CX,12(DI)
MOVBLZX 2(DX), CX
XORB CX,13(DI)
MOVBLZX 5(DX), CX
XORB CX,14(DI)
MOVBLZX 8(DX), DX
XORB DL,15(DI)
RET
TEXT ·sub_c(SB), NOSPLIT, $0-32
MOVQ a+0(FP), DI
MOVQ b+8(FP), SI
MOVQ SI, AX
MOVBLZX (SI), SI
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 1(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, (AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 2(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 1(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 3(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 2(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 4(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 3(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 5(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 4(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 6(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 5(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 7(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 6(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 8(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 7(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 9(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 8(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 10(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 9(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 11(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 10(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 12(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 11(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 13(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 12(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 14(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 13(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVBLZX 15(AX), SI
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 14(AX)
MOVL SI, CX
ANDL $15, SI
SHRL $4, CX
SHLL $4, CX
ADDL SI, CX
MOVLQSX CX, CX
MOVBLZX (DI)(CX*1), CX
MOVB CX, 15(AX)
RET
TEXT ·sub_d(SB), NOSPLIT, $24-32
MOVQ t+0(FP), BX
MOVQ s+8(FP), DI
MOVOU (DI), X0
MOVOU X0, in-16(SP)
MOVQ $16, CX
ADDQ $15, DI
PUSHFQ
STD
lop:
LEAQ -1(CX), AX
XLAT
LEAQ in-16(SP)(AX*1), SI
MOVSB
LOOP lop
POPFQ
RET
TEXT ·sub_e(SB), NOSPLIT, $0-32
MOVQ a+0(FP), DI
MOVQ n+8(FP), SI
MOVQ $4, AX
lop:
MOVBQZX -4(SI)(AX*4), DX
MOVBQZX -3(SI)(AX*4), CX
MOVBQZX -2(SI)(AX*4), R10
MOVBQZX -1(SI)(AX*4), R8
LEAQ (DX)(DX*2), R9
LEAQ (R9*2), R9
LEAQ (CX)(CX*2), R11
LEAQ (R11*2), R11
LEAQ (R10)(R10*2), BX
LEAQ (BX*2), BX
MOVB DX, R13
XORB CX, DX
XORB R10, CX
MOVB (DI)(R9*1), R12
XORB 1(DI)(R11*1), R12
XORB R8, R10
XORB R12, R10
MOVB R10, -4(SI)(AX*4)
MOVB (DI)(R11*1), R10
XORB 1(DI)(BX*1), R10
XORB R8, R13
XORB R10, R13
MOVB R13, -3(SI)(AX*4)
MOVB (DI)(BX*1), R10
LEAQ (R8)(R8*2), R8
LEAQ (R8*2), R8
XORB 1(DI)(R8*1), R10
XORB R10, DX
MOVB DX, -2(SI)(AX*4)
MOVB 1(DI)(R9*1), DX
XORB (DI)(R8*1), DX
XORB DX, CX
MOVB CX, -1(SI)(AX*4)
DECB AX
JNZ lop
RET
TEXT sub_ab<>(SB), NOSPLIT, $0-24
MOVQ s+0(FP), DI
MOVQ w+8(FP), SI
MOVL SI, AX
MOVL SI, CX
MOVL SI, DX
SHRL $28, AX
SHRL $24, CX
ANDL $15, CX
SALL $4, AX
ADDL CX, AX
MOVBLZX SI, CX
MOVBLZX (DI)(AX*1), AX
MOVBLZX (DI)(CX*1), CX
SALL $24, AX
ORL CX, AX
MOVL SI, CX
SHRL $8, SI
SHRL $8, CX
ANDL $15, SI
ANDL $240, CX
ADDL SI, CX
MOVBLZX (DI)(CX*1), CX
SALL $8, CX
ORL CX, AX
MOVL DX, CX
SHRL $16, DX
SHRL $16, CX
ANDL $15, DX
ANDL $240, CX
ADDL CX, DX
MOVBLZX (DI)(DX*1), DX
SALL $16, DX
ORL DX, AX
MOVQ AX, retval+16(FP)
RET
TEXT ·sub_f(SB), NOSPLIT, $24-68
MOVQ k+0(FP), DI
MOVQ r+8(FP), SI
MOVQ s+16(FP), DX
MOVQ $w+24(FP), CX
MOVQ CX, R10
MOVQ SI, R9
MOVQ DX, R8
MOVL $4, BX
MOVL (DI), AX
BSWAPL AX
MOVL AX, (CX)
MOVL 4(DI), AX
BSWAPL AX
MOVL AX, 4(CX)
MOVL 8(DI), AX
BSWAPL AX
MOVL AX, 8(CX)
MOVL 12(DI), AX
BSWAPL AX
MOVL AX, 12(CX)
JMP inner
for:
XORL -16(R10)(BX*4), AX
MOVL AX, (R10)(BX*4)
ADDQ $1, BX
CMPQ BX, $44
JE end
inner:
MOVL -4(R10)(BX*4), AX
TESTB $3, BX
JNE for
ROLL $8, AX
MOVQ R8, 0(SP)
MOVL AX, 8(SP)
CALL sub_ab<>(SB)
MOVQ 16(SP), AX
LEAL -1(BX), DX
SARL $2, DX
MOVLQSX DX, DX
XORL (R9)(DX*4), AX
JMP for
end:
RET
TEXT ·sub_aa(SB), NOSPLIT, $0-56
MOVQ i+0(FP), DI
MOVQ t+8(FP), SI
MOVQ b+16(FP), DX
MOVQ m+24(FP), CX
MOVL DI, AX
MOVLQSX DI, DI
MOVQ SI, R8
MOVQ DX, SI
MOVBLZX (CX)(DI*1), CX
ANDL $15, AX
MOVBLZX (SI)(AX*1), SI
MOVQ AX, DX
MOVL CX, AX
SALQ $9, DX
ANDL $15, CX
SHRB $4, AX
MOVL SI, DI
ADDQ R8, DX
SALQ $4, CX
ANDL $15, AX
SHRB $4, DI
ANDL $15, SI
SALQ $4, AX
ANDL $15, DI
ADDQ DX, AX
ADDQ CX, DX
MOVBLZX (AX)(DI*1), AX
SALL $4, AX
ORB 256(SI)(DX*1), AX
MOVQ AX, retval+48(FP)
RET
// func transformInner(x *[0x15]byte, tab *[32][16]byte)
TEXT ·transformInner(SB), NOSPLIT, $0-16
MOVQ x+0(FP), DI
MOVQ tab+8(FP), SI
MOVQ DI, AX
MOVL $1, CX
MOVQ SI, DI
MOVQ AX, SI
lop:
MOVBLZX (SI), R8
LEAL -1(CX), AX
ADDQ $1, SI
ANDL $31, AX
MOVL R8, DX
SALL $4, AX
ANDL $15, R8
SHRB $4, DX
MOVBLZX DX, DX
ADDL DX, AX
CDQE
MOVBLSX (DI)(AX*1), AX
SALL $4, AX
MOVL AX, DX
MOVL CX, AX
ADDL $2, CX
ANDL $31, AX
SALL $4, AX
ADDL R8, AX
CDQE
ORB (DI)(AX*1), DX
MOVB DX, -1(SI)
CMPL CX, $43
JNE lop
RET
TEXT ·initState(SB), NOSPLIT, $0-64
MOVQ c+0(FP), DI
MOVQ key+8(FP), SI
MOVQ data+32(FP), R8
MOVQ counter+56(FP), AX
MOVOA LC0<>(SB), X0
MOVUPS X0, (DI)
MOVOU (SI), X1
MOVOU (DI), X3
MOVUPS X1, 16(DI)
MOVOU 16(SI), X2
MOVQ AX, 48(DI)
MOVUPS X2, 32(DI)
MOVQ (R8), AX
MOVUPS X3, 64(DI)
MOVQ AX, 56(DI)
MOVQ 48(DI), AX
MOVUPS X1, 80(DI)
MOVUPS X2, 96(DI)
MOVUPS X6,112(DI)
RET
TEXT sub_ad<>(SB), NOSPLIT, $8-8
MOVQ a+0(FP), DI
MOVQ DI, AX
MOVL 40(DI), R10
MOVL 12(DI), R12
MOVL 44(DI), BP
MOVL 16(DI), DX
MOVL (DI), R15
MOVL 48(DI), R9
MOVL 20(DI), SI
MOVL 32(DI), R11
ADDL DX, R15
MOVL 4(DI), R14
MOVL 52(DI), R8
XORL R15, R9
MOVL 24(DI), CX
MOVL 8(DI), R13
ROLL $16, R9
ADDL SI, R14
MOVL 36(DI), BX
MOVL 56(DI), DI
ADDL R9, R11
XORL R14, R8
ADDL CX, R13
XORL R11, DX
ROLL $16, R8
XORL R13, DI
ROLL $12, DX
ADDL R8, BX
ROLL $16, DI
ADDL DX, R15
XORL BX, SI
ADDL DI, R10
XORL R15, R9
ROLL $12, SI
XORL R10, CX
ROLL $8, R9
ADDL SI, R14
ROLL $12, CX
ADDL R9, R11
XORL R14, R8
ADDL CX, R13
XORL R11, DX
ROLL $8, R8
XORL R13, DI
ROLL $7, DX
LEAL (BX)(R8*1), BX
ROLL $8, DI
MOVL DX, tmp0-8(SP)
MOVL 28(AX), DX
XORL BX, SI
MOVL BX, tmp1-4(SP)
MOVL R10, BX
MOVL 60(AX), R10
ROLL $7, SI
ADDL DI, BX
ADDL DX, R12
ADDL SI, R15
XORL R12, R10
XORL BX, CX
ROLL $16, R10
ROLL $7, CX
ADDL R10, BP
ADDL CX, R14
XORL BP, DX
XORL R14, R9
ROLL $12, DX
ROLL $16, R9
ADDL DX, R12
XORL R12, R10
ROLL $8, R10
ADDL R10, BP
XORL R15, R10
ROLL $16, R10
XORL BP, DX
ADDL R9, BP
ADDL R10, BX
ROLL $7, DX
XORL BP, CX
XORL BX, SI
ROLL $12, SI
ADDL SI, R15
XORL R15, R10
MOVL R15, (AX)
ROLL $8, R10
ADDL R10, BX
MOVL R10, 60(AX)
XORL BX, SI
MOVD BX, X1
ROLL $7, SI
ROLL $12, CX
ADDL DX, R13
XORL R13, R8
ADDL CX, R14
MOVL SI, 20(AX)
ROLL $16, R8
XORL R14, R9
MOVL R14, 4(AX)
ADDL R8, R11
ROLL $8, R9
XORL R11, DX
ADDL R9, BP
MOVL R9, 48(AX)
ROLL $12, DX
XORL BP, CX
MOVD BP, X2
ADDL DX, R13
ROLL $7, CX
PUNPCKLLQ X2, X1
XORL R13, R8
MOVL CX, 24(AX)
ROLL $8, R8
MOVL R13, 8(AX)
ADDL R8, R11
XORL R11, DX
MOVD R11, X0
ROLL $7, DX
MOVL DX, 28(AX)
MOVL R8, 52(AX)
MOVL tmp0-8(SP), SI
MOVL tmp1-4(SP), CX
ADDL SI, R12
XORL R12, DI
ROLL $16, DI
ADDL DI, CX
XORL CX, SI
MOVL SI, DX
ROLL $12, DX
ADDL DX, R12
XORL R12, DI
MOVL R12, 12(AX)
ROLL $8, DI
ADDL DI, CX
MOVL DI, 56(AX)
MOVD CX, X3
XORL CX, DX
PUNPCKLLQ X3, X0
ROLL $7, DX
PUNPCKLQDQ X1, X0
MOVL DX, 16(AX)
MOVUPS X0, 32(AX)
RET
TEXT ·refreshState(SB), NOSPLIT, $16-8
MOVQ i+0(FP), BX
MOVB 128(BX), CX
JE ad
SHRQ $1, CX
fr:
MOVQ BX, 0(SP)
MOVQ CX, c-8(SP)
CALL sub_ad<>(SB)
MOVQ c-8(SP), CX
MOVQ i+0(FP), BX
LOOP fr
ad:
MOVOU (BX), X0
MOVOU 64(BX), X1
MOVOU 80(BX), X2
MOVOU 96(BX), X3
PADDD X1, X0
MOVOU 48(BX), X4
MOVUPS X0, (BX)
MOVOU 16(BX), X0
PADDD X2, X0
MOVUPS X0, 16(BX)
MOVOU 32(BX), X0
PADDD X3, X0
MOVUPS X0, 32(BX)
MOVOU 112(BX), X0
PADDD X4, X0
MOVUPS X0, 48(BX)
RET
// func tencentCrc32(tab *crc32.Table, b []byte) uint32
TEXT ·tencentCrc32(SB), NOSPLIT, $0-40
MOVQ tab+0(FP), DI
MOVQ bptr+8(FP), SI
MOVQ bngas+16(FP), DX
TESTQ DX, DX
JE quickend
ADDQ SI, DX
MOVL $-1, AX
lop:
MOVBLZX (SI), CX
ADDQ $1, SI
XORL AX, CX
SHRL $8, AX
MOVBLZX CX, CX
XORL (DI)(CX*4), AX
CMPQ SI, DX
JNE lop
NOTL AX
MOVQ AX, bngas+32(FP)
RET
quickend:
XORL AX, AX
RET

View File

@ -0,0 +1,112 @@
//go:build amd64
package t544
import (
"encoding/binary"
"io"
)
type encryptionData struct {
tableA [16][2][16][16]byte
tableB [16][16]byte
tableC [256][6]byte
tableD [16]byte
tableE [16]byte
tableF [15]uint32
}
type state struct {
state [16]uint32 // 16
orgstate [16]uint32 // 16
nr uint8
p uint8
}
var crypto = encryptionData{
tableA: readData[[16][2][16][16]byte]("table_a.bin"),
tableB: readData[[16][16]byte]("table_b.bin"),
tableC: readData[[256][6]byte]("table_c.bin"),
tableD: readData[[16]byte]("table_d.bin"),
tableE: readData[[16]byte]("table_e.bin"),
tableF: func() (tab [15]uint32) {
f, err := cryptoZip.Open("table_f.bin")
if err != nil {
panic(err)
}
data, err := io.ReadAll(f)
if err != nil {
panic(err)
}
for i := range tab {
tab[i] = binary.LittleEndian.Uint32(data[i*4 : (i+1)*4])
}
return
}(),
}
func (e *encryptionData) tencentEncryptB(p1 []byte, p2 []uint32) {
const c = 10
for r := 0; r < 9; r++ {
sub_d(&e.tableD, p1)
sub_b(p1, p2[r*4:(r+1)*4])
sub_c(&e.tableB, p1)
sub_e(&e.tableC, p1)
}
sub_d(&e.tableD, p1)
sub_b(p1, p2[(c-1)*4:c*4])
sub_c(&e.tableB, p1)
sub_a(p1, p2[c*4:(c+1)*4])
}
func (e *encryptionData) tencentEncryptionB(c []byte, m []byte) (out [0x15]byte) {
var buf [16]byte
w := sub_f(&e.tableE, &e.tableF, &e.tableB)
for i := range out {
if (i & 0xf) == 0 {
copy(buf[:], c)
e.tencentEncryptB(buf[:], w[:])
for j := 15; j >= 0; j-- {
c[j]++
if c[j] != 0 {
break
}
}
}
out[i] = sub_aa(i, &e.tableA, &buf, m)
}
return
}
func tencentEncryptionA(input, key, data []byte) {
var s state
s.init(key, data, 0, 20)
s.encrypt(input)
}
func (c *state) encrypt(data []byte) {
bp := 0
dataLen := uint32(len(data))
for dataLen > 0 {
if c.p == 0 {
refreshState(c)
}
var sb [16 * 4]byte
for i, v := range c.state {
binary.LittleEndian.PutUint32(sb[i*4:(i+1)*4], v)
}
for c.p != 64 && dataLen != 0 {
data[bp] ^= sb[c.p]
c.p++
bp++
dataLen--
}
if c.p >= 64 {
c.p = 0
c.orgstate[12]++
c.state = c.orgstate
}
}
}

View File

@ -0,0 +1,91 @@
//go:build amd64
package t544
import (
"crypto/md5"
"crypto/rc4"
"encoding/binary"
"math/rand"
"github.com/Mrs4s/go-cqhttp/internal/encryption"
)
const (
keyTable = "$%&()+,-456789:?ABCDEEFGHIJabcdefghijkopqrstuvwxyz"
table2 = "!#$%&)+.0123456789:=>?@ABCDEFGKMNabcdefghijkopqrst"
)
var (
magic = uint64(0x6EEDCF0DC4675540)
key1 = [8]byte{'a', '$', '(', 'e', 'T', '7', '*', '@'}
key2 = [8]byte{'&', 'O', '9', '!', '>', '6', 'X', ')'}
)
func init() {
encryption.T544Signer["8.9.35.10440"] = sign
encryption.T544Signer["8.9.38.10545"] = sign
}
// sign t544 algorithm
// special thanks to the anonymous contributor who provided the algorithm
func sign(curr int64, input []byte) []byte {
var crcData [0x15]byte
curr %= 1000000
binary.BigEndian.PutUint32(crcData[:4], uint32(curr))
input = append(input, crcData[:4]...)
var kt [4 + 32 + 4]byte
r := rand.New(rand.NewSource(curr))
for i := 0; i < 2; i++ {
kt[i] = keyTable[r.Int()%0x32] + 50
}
kt[2] = kt[1] + 20
kt[3] = kt[2] + 20
key3 := kt[4 : 4+10]
k3calc := key3[2:10]
copy(k3calc, key1[:4])
for i := 0; i < 4; i++ {
k3calc[4+i] = key2[i] ^ kt[i]
}
key3[0], key3[1] = k3calc[6], k3calc[7]
key3 = key3[:8]
k3calc[6], k3calc[7] = 0, 0
rc4Cipher, _ := rc4.NewCipher(key3)
rc4Cipher.XORKeyStream(key3, key3)
binary.LittleEndian.PutUint64(crcData[4:4+8], magic)
tencentEncryptionA(input, kt[4:4+32], crcData[4:4+8])
result := md5.Sum(input)
crcData[2] = 1
crcData[4] = 1
copy(crcData[5:9], kt[:4])
binary.BigEndian.PutUint32(crcData[9:13], uint32(curr))
copy(crcData[13:], result[:8])
calcCrc := tencentCrc32(&crc32Table, crcData[2:])
binary.LittleEndian.PutUint32(kt[4+32:4+32+4], calcCrc)
crcData[0] = kt[4+32]
crcData[1] = kt[4+32+3]
nonce := uint32(r.Int() ^ r.Int() ^ r.Int())
on := kt[:16]
binary.BigEndian.PutUint32(on[:4], nonce)
copy(on[4:8], on[:4])
copy(on[8:16], on[:8])
ts.transformEncode(&crcData)
encryptedData := crypto.tencentEncryptionB(on, crcData[:])
ts.transformDecode(&encryptedData)
output := kt[:39]
output[0] = 0x0C
output[1] = 0x05
binary.BigEndian.PutUint32(output[2:6], nonce)
copy(output[6:27], encryptedData[:])
binary.LittleEndian.PutUint32(output[27:31], 0)
output[31] = table2[r.Int()%0x32]
output[32] = table2[r.Int()%0x32]
addition := r.Int() % 9
for addition&1 == 0 {
addition = r.Int() % 9
}
output[33] = output[31] + byte(addition)
output[34] = output[32] + byte(9-addition) + 1
binary.LittleEndian.PutUint32(output[35:39], 0)
return output
}

View File

@ -0,0 +1,7 @@
//go:build !amd64
package t544
func init() {
}

View File

@ -0,0 +1,22 @@
package t544
import (
"crypto/rand"
"encoding/hex"
"testing"
)
func TestT544(t *testing.T) {
r := hex.EncodeToString(sign(0, []byte{}))
if r != "0c05d28b405bce1595c70ffa694ff163d4b600f229482e07de32c8000000003525382c00000000" {
t.Fatal(r)
}
}
func TestCrash(t *testing.T) {
brand := make([]byte, 4096)
for i := 1; i <= 1024; i++ {
rand.Reader.Read(brand)
sign(123, brand)
}
}

View File

@ -0,0 +1,21 @@
//go:build amd64
package t544
type transformer struct {
encode [32][16]byte
decode [32][16]byte
}
func (ts *transformer) transformEncode(bArr *[0x15]byte) {
transformInner(bArr, &ts.encode)
}
func (ts *transformer) transformDecode(bArr *[0x15]byte) {
transformInner(bArr, &ts.decode)
}
var ts = transformer{
encode: readData[[32][16]byte]("encode.bin"),
decode: readData[[32][16]byte]("decode.bin"),
}

View File

@ -1,4 +1,5 @@
package cqcode
// Package msg 提供了go-cqhttp消息中间表示CQ码处理等等
package msg
import (
"bytes"
@ -8,16 +9,105 @@ import (
"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
type Element struct {
Type string
Data []Pair
}
// Pair key value pair
type Pair struct {
K string
V string
// Get 获取指定值
func (e *Element) Get(k string) string {
for _, datum := range e.Data {
if datum.K == k {
return datum.V
}
}
return ""
}
// CQCode convert element to cqcode
@ -60,7 +150,7 @@ func (e *Element) MarshalJSON() ([]byte, error) {
buf.WriteByte('"')
buf.WriteString(data.K)
buf.WriteString(`":`)
writeQuote(buf, data.V)
buf.WriteString(QuoteJSON(data.V))
}
buf.WriteString(`}}`)
}), nil
@ -68,9 +158,10 @@ func (e *Element) MarshalJSON() ([]byte, error) {
const hex = "0123456789abcdef"
func writeQuote(b *bytes.Buffer, s string) {
// QuoteJSON 按JSON转义为字符加上双引号
func QuoteJSON(s string) string {
i, j := 0, 0
var b strings.Builder
b.WriteByte('"')
for j < len(s) {
c := s[j]
@ -151,4 +242,5 @@ func writeQuote(b *bytes.Buffer, s string) {
b.WriteString(s[i:])
b.WriteByte('"')
return b.String()
}

View File

@ -1,7 +1,6 @@
package cqcode
package msg
import (
"bytes"
"encoding/json"
"testing"
)
@ -14,16 +13,14 @@ func jsonMarshal(s string) string {
return string(b)
}
func Test_quote(t *testing.T) {
func TestQuoteJSON(t *testing.T) {
testcase := []string{
"\u0005", // issue 1773
"\v",
}
for _, input := range testcase {
var b bytes.Buffer
writeQuote(&b, input)
got := b.String()
got := QuoteJSON(input)
expected := jsonMarshal(input)
if got != expected {
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 (
"fmt"
"strings"
"testing"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
"github.com/Mrs4s/go-cqhttp/coolq/cqcode"
)
var bot = CQBot{}
func TestCQBot_ConvertStringMessage(t *testing.T) {
for _, v := range bot.ConvertStringMessage(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`, message.SourcePrivate) {
func TestParseString(_ *testing.T) {
// TODO: add more text
for _, v := range ParseString(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`) {
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":" ["}}]`)
)
func BenchmarkCQBot_ConvertStringMessage(b *testing.B) {
func BenchmarkParseString(b *testing.B) {
for i := 0; i < b.N; i++ {
bot.ConvertStringMessage(bench, message.SourcePrivate)
ParseString(bench)
}
b.SetBytes(int64(len(bench)))
}
func BenchmarkCQBot_ConvertObjectMessage(b *testing.B) {
func BenchmarkParseObject(b *testing.B) {
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[]&`
@ -44,16 +41,7 @@ const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&98765
func BenchmarkCQCodeEscapeText(b *testing.B) {
for i := 0; i < b.N; i++ {
ret := bText
cqcode.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;")
EscapeText(ret)
}
}
@ -64,6 +52,6 @@ func TestCQCodeEscapeText(t *testing.T) {
ret = strings.ReplaceAll(ret, "&", "&amp;")
ret = strings.ReplaceAll(ret, "[", "&#91;")
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
// +build !windows
package selfupdate

10
main.go
View File

@ -3,6 +3,7 @@ package main
import (
"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/modules/silk" // silk编码模块
@ -13,5 +14,12 @@ import (
)
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 (
"github.com/Mrs4s/go-cqhttp/coolq"
"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 {
var converter coolq.IDConverter = func(id any) any {
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 {
func (c *Caller) call(action string, spec *onebot.Spec, p Getter) global.MSG {
if spec.Version == 11 {
switch action {
case ".handle_quick_operation":
p0 := p.Get("context")
p1 := p.Get("operation")
return c.bot.CQHandleQuickOperation(p0, p1)
case "can_send_image":
return c.bot.CQCanSendImage()
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)
}
}
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 {
case ".get_word_slices":
p0 := p.Get("content").String()
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":
p0 := p.Get("image").String()
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()
return c.bot.CQGetForwardMessage(p0)
case "get_friend_list":
return c.bot.CQGetFriendList(version)
return c.bot.CQGetFriendList(spec)
case "get_group_at_all_remain":
p0 := p.Get("group_id").Int()
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":
p0 := p.Get("group_id").Int()
p1 := p.Get("no_cache").Bool()
return c.bot.CQGetGroupInfo(p0, p1, converter)
return c.bot.CQGetGroupInfo(p0, p1, spec)
case "get_group_list":
p0 := p.Get("no_cache").Bool()
return c.bot.CQGetGroupList(p0, converter)
return c.bot.CQGetGroupList(p0, spec)
case "get_group_member_info":
p0 := p.Get("group_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()
return c.bot.CQGetOnlineClients(p0)
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":
p0 := p.Get("guild_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/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 参数获取
type Getter interface {
@ -16,7 +17,7 @@ type Getter interface {
}
// 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
type Caller struct {
@ -25,13 +26,13 @@ type Caller struct {
}
// 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 {
if ret := fn(action, p); ret != nil {
if ret := fn(action, spec, p); ret != nil {
return ret
}
}
return c.call(action, version, p)
return c.call(action, spec, p)
}
// Use add handlers to the API caller

View File

@ -56,6 +56,7 @@ type Config struct {
ExtraReplyData bool `yaml:"extra-reply-data"`
SkipMimeScan bool `yaml:"skip-mime-scan"`
ConvertWebpImage bool `yaml:"convert-webp-image"`
HTTPTimeout int `yaml:"http-timeout"`
} `yaml:"message"`
Output struct {

View File

@ -32,6 +32,9 @@ func encode(record []byte, tempName string) (silkWav []byte, err error) {
// 2.转换pcm
pcmPath := path.Join(silkCachePath, tempName+".pcm")
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 {
cmd.Stdout = os.Stdout
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

@ -29,6 +29,7 @@ import (
"github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/modules/config"
"github.com/Mrs4s/go-cqhttp/modules/filter"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
)
// HTTPServer HTTP通信相关配置
@ -58,7 +59,7 @@ type httpServerPost struct {
type httpServer struct {
api *api.Caller
accessToken string
version uint16
spec *onebot.Spec // onebot spec
}
// HTTPClient 反向HTTP上报客户端
@ -158,7 +159,7 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
switch request.Method {
case http.MethodPost:
// 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)
writer.WriteHeader(http.StatusUnsupportedMediaType)
return
@ -204,12 +205,12 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
if request.URL.Path == "/" {
action := strings.TrimSuffix(ctx.Get("action").Str, "_async")
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 {
action := strings.TrimPrefix(request.URL.Path, "/")
action = strings.TrimSuffix(action, "_async")
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")
@ -259,11 +260,15 @@ func runHTTP(bot *coolq.CQBot, node yaml.Node) {
case conf.Disabled:
return
}
if conf.Version != 11 && conf.Version != 12 {
conf.Version = 11
}
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 {
case 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/global"
"github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
"golang.org/x/time/rate"
)
@ -26,7 +27,7 @@ type MiddleWares struct {
func rateLimit(frequency float64, bucketSize int) api.Handler {
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())
return nil
}
@ -45,8 +46,11 @@ func longPolling(bot *coolq.CQBot, maxSize int) api.Handler {
}
cond.Signal()
})
return func(action string, p api.Getter) global.MSG {
if action != "get_updates" {
return func(action string, spec *onebot.Spec, p api.Getter) global.MSG {
switch {
case spec.Version == 11 && action == "get_updates": // ok
case spec.Version == 12 && action == "get_latest_events": // ok
default:
return nil
}
var (

View File

@ -25,6 +25,7 @@ import (
"github.com/Mrs4s/go-cqhttp/modules/api"
"github.com/Mrs4s/go-cqhttp/modules/config"
"github.com/Mrs4s/go-cqhttp/modules/filter"
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
)
type webSocketServer struct {
@ -476,7 +477,7 @@ func (c *wsConn) handleRequest(_ *coolq.CQBot, payload []byte) {
t := strings.TrimSuffix(j.Get("action").Str, "_async")
params := j.Get("params")
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() {
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