1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-06-29 19:43:24 +00:00

Compare commits

...

77 Commits

Author SHA1 Message Date
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
67 changed files with 2573 additions and 1020 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, ",")))
for _, router := range routers {
g.router(router, PathV12)
`, path)
for _, router := range routers {
g.router(router, path)
}
g.WriteString("}}\n")
}
io.WriteString(g.out, `}}`)
io.WriteString(g.out, "\n")
g.WriteString(`if version == 11 {
switch action {
`)
for _, router := range routers {
g.router(router, PathV11)
}
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,38 @@ 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/Mrs4s/go-cqhttp/internal/encryption"
_ "github.com/Mrs4s/go-cqhttp/internal/encryption/t544"
"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/internal/base"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/Mrs4s/go-cqhttp/internal/download"
)
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 +64,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 +165,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") {
ticket := getTicket(res.VerifyUrl)
if ticket == "" {
os.Exit(0)
}
res, err = cli.SubmitTicket(ticket)
continue
log.Warnf("登录需要滑条验证码, 请验证后重试.")
ticket := getTicket(res.VerifyUrl)
if ticket == "" {
log.Infof("按 Enter 继续....")
readLine()
os.Exit(0)
}
cli.Disconnect()
cli.Release()
cli = client.NewClientEmpty()
return qrcodeLogin()
res, err = cli.SubmitTicket(ticket)
continue
case client.NeedCaptcha:
log.Warnf("登录需要验证码.")
_ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
@ -212,38 +217,40 @@ 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)
if res.Code == 235 {
log.Warnf("请删除 device.json 后重试.")
}
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)
if str != "" {
return
}
case str = <-manual:
return
str := fetchCaptcha(id)
if str != "" {
return str
}
time.Sleep(time.Second)
}
log.Warnf("验证超时")
return ""
@ -260,3 +267,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,10 +16,14 @@ 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"
"github.com/Mrs4s/go-cqhttp/internal/download"
"github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/db"
"github.com/Mrs4s/go-cqhttp/global"
@ -41,8 +45,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 +58,7 @@ func Main() {
log.Errorf("遇到错误: %v", err)
time.Sleep(time.Second * 5)
}
return
os.Exit(0)
}
switch {
case base.LittleH:
@ -65,7 +73,10 @@ func Main() {
}
}
base.Init()
}
// PrepareData 准备 log, 缓存, 数据库, 必须在 InitBase 之后执行
func PrepareData() {
rotateOptions := []rotatelogs.Option{
rotatelogs.WithRotationTime(time.Hour * 24),
}
@ -95,13 +106,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 +153,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)
}
}
@ -205,10 +221,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 +272,7 @@ func Main() {
cli.Disconnect()
cli.Release()
cli = newClient()
cli.UseDevice(device)
} else {
isTokenLogin = true
}
@ -248,6 +282,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 {
@ -326,7 +383,13 @@ func Main() {
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 +452,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
@ -28,5 +29,6 @@ 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 {
// 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

@ -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))
}

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"
)
@ -26,8 +28,8 @@ type database struct {
// config mongodb 相关配置
type config struct {
Enable bool `yaml:"enable"`
CacheTTL time.Duration `yaml:"cachettl"`
Enable bool `yaml:"enable"`
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

@ -23,20 +23,22 @@ var (
// config file flags
var (
Debug bool // 是否开启 debug 模式
RemoveReplyAt bool // 是否删除reply后的at
ExtraReplyData bool // 是否上报额外reply信息
IgnoreInvalidCQCode bool // 是否忽略无效CQ码
SplitURL bool // 是否分割URL
ForceFragmented bool // 是否启用强制分片
SkipMimeScan bool // 是否跳过Mime扫描
ConvertWebpImage bool // 是否转换Webp图片
ReportSelfMessage bool // 是否上报自身消息
UseSSOAddress bool // 是否使用服务器下发的新地址进行重连
LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志
LogColorful bool // 是否启用日志颜色
FastStart bool // 是否为快速启动
AllowTempSession bool // 是否允许发送临时会话信息
Debug bool // 是否开启 debug 模式
RemoveReplyAt bool // 是否删除reply后的at
ExtraReplyData bool // 是否上报额外reply信息
IgnoreInvalidCQCode bool // 是否忽略无效CQ码
SplitURL bool // 是否分割URL
ForceFragmented bool // 是否启用强制分片
SkipMimeScan bool // 是否跳过Mime扫描
ConvertWebpImage bool // 是否转换Webp图片
ReportSelfMessage bool // 是否上报自身消息
UseSSOAddress bool // 是否使用服务器下发的新地址进行重连
LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志
LogColorful bool // 是否启用日志颜色
FastStart bool // 是否为快速启动
AllowTempSession bool // 是否允许发送临时会话信息
UpdateProtocol bool // 是否更新协议
SignServerOverwrite string // 使用特定的服务器进行签名
PostFormat string // 上报格式 string or array
Proxy string // 存储 proxy_rewrite,用于设置代理
@ -60,6 +62,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 {

View File

@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
@ -32,6 +33,7 @@ var client = &http.Client{
MaxIdleConns: 0,
MaxIdleConnsPerHost: 999,
},
Timeout: time.Second * 15,
}
// ErrOverSize 响应主体过大时返回此错误
@ -42,13 +44,18 @@ const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
// 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) 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
}
@ -79,7 +86,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 +96,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{}

View File

@ -0,0 +1,10 @@
//go:build amd64
package t544
func cpuid(op uint32) (eax, ebx, ecx, edx uint32)
var canusesse2 = func() bool {
_, _, _, d := cpuid(1)
return d&(1<<26) > 0
}()

View File

@ -0,0 +1,15 @@
//go:build amd64
// +build amd64
// Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file.
// func cpuid(op uint32) (eax, ebx, ecx, edx uint32)
TEXT ·cpuid(SB), 7, $0
XORQ CX, CX
MOVL op+0(FP), AX
CPUID
MOVL AX, eax+8(FP)
MOVL BX, ebx+12(FP)
MOVL CX, ecx+16(FP)
MOVL DX, edx+20(FP)
RET

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,53 @@
//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
}()
func tencentCrc32(tab *crc32.Table, b []byte) uint32
func sub_a([]byte, []uint32)
func sub_b([]byte, []uint32)
func sub_c(*[16][16]byte, []byte)
func sub_d(*[16]byte, []byte)
func sub_e(*[256][6]byte, []byte)
func sub_f(*[16]byte, *[15]uint32, *[16][16]byte) (w [44]uint32)
func sub_aa(int, *[16][2][16][16]byte, *[16]byte, []byte) byte
// transformInner see com/tencent/mobileqq/dt/model/FEBound
func transformInner(*[0x15]byte, *[32][16]byte)
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)
}
func sub_ad([]uint32)

View File

@ -0,0 +1,637 @@
//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, $16-32
MOVQ ·t+0(FP), BX
MOVQ ·s+8(FP), SI
MOVOU (SI), X0
MOVOU X0, in-16(SP)
MOVQ SI, DI
ADDQ $15, DI
MOVB $16, CX
STD
lop:
LEAQ -1(CX), AX
XLAT
MOVBLZX in-16(SP)(AX*1), AX
STOSB
LOOP lop
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-24
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
// 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,117 @@
//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 {
for i := uint8(0); i < c.nr; i += 2 {
sub_ad(c.state[:])
}
for i := 0; i < 16; i++ {
c.state[i] += c.orgstate[i]
}
}
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,93 @@
//go:build amd64
package t544
import (
"crypto/md5"
"crypto/rc4"
"encoding/binary"
"math/rand"
"unsafe"
"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() {
if canusesse2 {
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 {
curr %= 1000000
input = append(input, []byte{byte(curr >> 24), byte(curr >> 16), byte(curr >> 8), byte(curr)}...)
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)
var crcData [0x15]byte
copy(crcData[4:4+8], (*[8]byte)(unsafe.Pointer(&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:])
copy(kt[4+32:4+32+4], (*[4]byte)(unsafe.Pointer(&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,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(t *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

@ -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

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",
}

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