mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-06-29 19:43:24 +00:00
Compare commits
285 Commits
v1.0.0-rc4
...
b53dcae9c8
Author | SHA1 | Date | |
---|---|---|---|
b53dcae9c8 | |||
d11b5b4ec6 | |||
ea025013d0 | |||
192b8c8692 | |||
ab371c1878 | |||
494692aa6f | |||
d89d21d0b6 | |||
7727819c92 | |||
7fdb04c902 | |||
acf77019e8 | |||
b2b98cc2d5 | |||
f9217aadb5 | |||
c70e33ead1 | |||
e7ea3f01e1 | |||
f1950e297e | |||
0edb2a76b6 | |||
7ab0de5edb | |||
cf86eab638 | |||
be805fdae0 | |||
78467f63ee | |||
5e208ed530 | |||
28a74bc961 | |||
faa0c02bd7 | |||
00220b5c8a | |||
e6906e1065 | |||
5aca41c061 | |||
17033c6084 | |||
3a8f94cbcb | |||
a518cc9850 | |||
7738611481 | |||
a5efbab29b | |||
426f8c1311 | |||
bec496c9fb | |||
b844665b29 | |||
8899038742 | |||
dc4925635e | |||
81b4bf8221 | |||
6427ee20a6 | |||
70a49f96e1 | |||
5aceb79dbc | |||
676998c1c0 | |||
c57351372b | |||
8fe525cf9a | |||
cd6954d4d3 | |||
f0e72f9130 | |||
03e1b07413 | |||
1d79458b48 | |||
23f18a0e54 | |||
99bec8dea8 | |||
603ddaabc5 | |||
8ca8f05c0e | |||
6c64ded108 | |||
62c65a45a1 | |||
926cd8778c | |||
68b069f5c5 | |||
fca88baf29 | |||
a71444d7ca | |||
9732ce3743 | |||
beb69149b3 | |||
c5d8e93cba | |||
4b42bc3446 | |||
294117639e | |||
47e64dfa64 | |||
66d913d101 | |||
b5486fe17d | |||
beda86de01 | |||
bd0fa9c4e0 | |||
022406f73b | |||
d272d10599 | |||
f297e54d29 | |||
31f4806ba6 | |||
9862860b2d | |||
b58d17ef89 | |||
7d7639d6f0 | |||
726b5616fb | |||
2d5bfc6c5f | |||
6e511dad7e | |||
f47cd4b6db | |||
54d7c05d1a | |||
7d524a7ab2 | |||
6819c45223 | |||
a5923f179b | |||
730d01c648 | |||
12e0cb4afb | |||
e06edd2412 | |||
5aca1f4500 | |||
75635739ba | |||
d25e320238 | |||
b8d622bb90 | |||
9cccd0e39b | |||
e1a4293ee6 | |||
8607542f1e | |||
5cbbcda2c2 | |||
fcf79ded58 | |||
6ac7a8f0ae | |||
bd785d3894 | |||
642c74688c | |||
517d323953 | |||
07214e396e | |||
1c34643f4f | |||
f16d72f0ca | |||
9e6d7b7650 | |||
417a0f256a | |||
77b54fca20 | |||
fd6ef4a2b8 | |||
79a194fbb0 | |||
f8354ec082 | |||
d85d697fc2 | |||
da9f03fa47 | |||
977030e814 | |||
5db03c7092 | |||
3b99a825eb | |||
94a3ff5dae | |||
0714aac1f0 | |||
ca20a3d6bf | |||
ce119b7ddf | |||
a6fd7de65a | |||
8ea182a4c3 | |||
99cdf9247a | |||
837e163ef6 | |||
fe92bb54df | |||
7cae9829a8 | |||
3992dd40c5 | |||
1bd0bb9ae2 | |||
b8527721c2 | |||
14539adcb8 | |||
1911b5d245 | |||
75ad7aa45c | |||
cffdfd8181 | |||
99e5cb6c6b | |||
09ab2169d9 | |||
2b1d9c21cb | |||
998fda54a2 | |||
5cb8548487 | |||
88f5db89a8 | |||
7c813f8579 | |||
7adbbd6f81 | |||
04cbf7b5d7 | |||
dae03784cc | |||
f466ca7a72 | |||
13215f23c5 | |||
19dd37a938 | |||
a3ad233cd9 | |||
06461960a9 | |||
aa3a5d28da | |||
a4c131e04a | |||
526391e613 | |||
2901fd14bb | |||
16a2ff050e | |||
6cf8030d3c | |||
9c1390c75c | |||
b958046a27 | |||
19906eba36 | |||
8e6e79f734 | |||
9b9ecd6a41 | |||
c8e480d12f | |||
5bf64ee743 | |||
bad3c86912 | |||
2af55d6a67 | |||
42606a825d | |||
1ed675d5bf | |||
91b4394d9b | |||
0b90074a48 | |||
55cb80dccc | |||
54995fc101 | |||
8acc9f39c2 | |||
13325634c0 | |||
7b2d1fd573 | |||
637d46f282 | |||
1e42b2c450 | |||
749cde2a6d | |||
0f0e711111 | |||
233e276d6a | |||
1ab1cba84c | |||
268ac07271 | |||
c486c254d8 | |||
6ad62a2642 | |||
9762a66ba2 | |||
43c6e3dcf5 | |||
6a17c70689 | |||
d70d66d6d7 | |||
43ff36e3e8 | |||
008d546f1a | |||
82ecf19480 | |||
588728aa62 | |||
ddfe24f6db | |||
5d492c7b38 | |||
d77dc9ef64 | |||
98c2a2218a | |||
3ccc2c6087 | |||
e6e30c0a10 | |||
1815ed769d | |||
cc4a981c90 | |||
174d99f94b | |||
dd33cd9598 | |||
4ad7da7a9a | |||
73bd3c92f3 | |||
3a60e081f2 | |||
1d0b513b96 | |||
0312f05f6e | |||
b85c2ecb07 | |||
0b106d8ef5 | |||
40e4f40525 | |||
8124879c77 | |||
3f4630b6d1 | |||
6a291840d7 | |||
a0e3291725 | |||
069f9d1335 | |||
91facb54ce | |||
414f067431 | |||
a704009484 | |||
278d6260c8 | |||
485d5c0df9 | |||
e3fd0771ae | |||
8cb8428785 | |||
95adb403e9 | |||
84dcf46ae2 | |||
bef2ba6f08 | |||
04c4446496 | |||
c3840a5988 | |||
291942357b | |||
c24aa8d8a0 | |||
07b1e6b72e | |||
377d7af2c1 | |||
d867451ef6 | |||
c4d703dc86 | |||
dbddd18e3a | |||
1b8ebf55a5 | |||
63d9ffa90b | |||
2830676e3b | |||
ddd52ca933 | |||
72173337ae | |||
9b0fae6346 | |||
4ceacc38d5 | |||
1dba273b61 | |||
cb1604a098 | |||
0c9f7a1f8f | |||
edfcd41ed6 | |||
811cfdca98 | |||
0e5f3ed555 | |||
90fa530a02 | |||
c80adf5795 | |||
debc1ed1ae | |||
43dd9aa76d | |||
9c0525b3d4 | |||
cf717ad762 | |||
6b3aabd9af | |||
59ed726c6a | |||
a7c003d404 | |||
0a4f849154 | |||
17420feeac | |||
2af671cec9 | |||
0f0ccf459f | |||
2483eb09c4 | |||
a8bed3fc03 | |||
bbef330069 | |||
d96f840d7f | |||
fc0845b16d | |||
f3da083be9 | |||
06450c66a2 | |||
4ed04443c5 | |||
0be18fb221 | |||
20c62111f5 | |||
84e061f321 | |||
4d064e145f | |||
64653a6815 | |||
4497053fb9 | |||
e050fd6885 | |||
43004e2496 | |||
7d5f1d6843 | |||
960f7ab79b | |||
4cddc5051f | |||
4a80441a5c | |||
a5b51051e6 | |||
311a254b9c | |||
008e139c27 | |||
524debbfda | |||
2a4ea28f4d | |||
4061904945 | |||
37a8901061 | |||
aec0ef66be | |||
86f5b7f5f5 | |||
672dafdb9d | |||
847ef6d415 | |||
f900fd62fb |
4
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@ -101,11 +101,13 @@ body:
|
||||
label: 使用协议
|
||||
description: 选择使用的协议
|
||||
options:
|
||||
- 0 | iPad
|
||||
- 0 | Default
|
||||
- 1 | Android Phone
|
||||
- 2 | Android Watch
|
||||
- 3 | MacOS
|
||||
- 4 | 企点
|
||||
- 5 | iPad
|
||||
- 6 | aPad
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
20
.github/workflows/build_docker_image.yml
vendored
20
.github/workflows/build_docker_image.yml
vendored
@ -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@v4
|
||||
|
||||
- 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@v3
|
||||
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,19 @@ jobs:
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v6
|
||||
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
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x
|
||||
|
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@ -24,18 +24,12 @@ jobs:
|
||||
goarch: "386"
|
||||
fail-fast: true
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Go environment
|
||||
uses: actions/setup-go@v2.1.3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
- 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') }}
|
||||
cache: true
|
||||
go-version: '1.20'
|
||||
- name: Build binary file
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
@ -44,12 +38,12 @@ jobs:
|
||||
run: |
|
||||
if [ $GOOS = "windows" ]; then export BINARY_SUFFIX="$BINARY_SUFFIX.exe"; fi
|
||||
if $IS_PR ; then echo $PR_PROMPT; fi
|
||||
export BINARY_NAME="$BINARY_PREFIX$GOOS_$GOARCH$BINARY_SUFFIX"
|
||||
export BINARY_NAME="$BINARY_PREFIX"$GOOS"_$GOARCH$BINARY_SUFFIX"
|
||||
export CGO_ENABLED=0
|
||||
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@v4
|
||||
if: ${{ !github.head_ref }}
|
||||
with:
|
||||
name: ${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
|
21
.github/workflows/close_pr.yml
vendored
Normal file
21
.github/workflows/close_pr.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Check and Close Invalid PR
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
|
||||
jobs:
|
||||
# This workflow closes invalid PR
|
||||
close_pr:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- name: Close PR if it is not pointed to dev branch
|
||||
if: github.event.pull_request.base.ref != 'dev'
|
||||
uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
# Optional. Post a issue comment just before closing a pull request.
|
||||
comment: "Invalid PR to `non-dev` branch `${{ github.event.pull_request.base.ref }}`."
|
10
.github/workflows/golint.yml
vendored
10
.github/workflows/golint.yml
vendored
@ -7,17 +7,19 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go environment
|
||||
uses: actions/setup-go@v2.1.3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: '1.20'
|
||||
cache: false
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
skip-cache: true
|
||||
|
||||
- name: Tests
|
||||
run: |
|
||||
|
19
.github/workflows/release.yml
vendored
19
.github/workflows/release.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: release
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -8,22 +8,25 @@ on:
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
run: |
|
||||
git version
|
||||
git clone "${{ github.event.repository.html_url }}" /home/runner/work/go-cqhttp/go-cqhttp
|
||||
git checkout "${{ github.ref }}"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.19'
|
||||
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
4
.gitignore
vendored
@ -12,6 +12,10 @@ internal/btree/*.db
|
||||
|
||||
# binary builds
|
||||
go-cqhttp
|
||||
*.exe
|
||||
|
||||
# macos
|
||||
.DS_Store
|
||||
|
||||
# windwos rc
|
||||
*.syso
|
||||
|
@ -1,7 +1,8 @@
|
||||
linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:.*,io/ioutil:^Read.*
|
||||
ignoretests: true
|
||||
exclude-functions:
|
||||
- fmt:.*
|
||||
- io/ioutil:^Read.*
|
||||
|
||||
goimports:
|
||||
local-prefixes: github.com/Mrs4s/go-cqhttp
|
||||
@ -51,16 +52,15 @@ linters:
|
||||
run:
|
||||
# default concurrency is a available CPU number.
|
||||
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
||||
deadline: 5m
|
||||
timeout: 5m
|
||||
issues-exit-code: 1
|
||||
skip-dirs:
|
||||
- db
|
||||
- cmd/api-generator
|
||||
tests: true
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
format: "colored-line-number"
|
||||
formats:
|
||||
- format: colored-line-number
|
||||
path: stdout
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
uniq-by-line: true
|
||||
@ -71,3 +71,7 @@ issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- "Error return value of .((os.)?std(out|err)..*|.*Close|.*Seek|.*Flush|os.Remove(All)?|.*print(f|ln)?|os.(Un)?Setenv). is not check"
|
||||
exclude-dirs:
|
||||
- db
|
||||
- cmd/api-generator
|
||||
- internal/encryption
|
||||
|
@ -1,8 +1,11 @@
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
version: 2
|
||||
|
||||
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
|
||||
@ -66,6 +69,7 @@ changelog:
|
||||
|
||||
archives:
|
||||
- id: binary
|
||||
format: tar.gz
|
||||
builds:
|
||||
- win
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
@ -73,6 +77,7 @@ archives:
|
||||
- goos: windows
|
||||
format: binary
|
||||
- id: nowin
|
||||
format: tar.gz
|
||||
builds:
|
||||
- nowin
|
||||
- win
|
||||
|
@ -1,8 +1,7 @@
|
||||
FROM golang:1.19-alpine AS builder
|
||||
FROM golang:1.20-alpine AS builder
|
||||
|
||||
RUN go env -w GO111MODULE=auto \
|
||||
&& go env -w CGO_ENABLED=0 \
|
||||
&& go env -w GOPROXY=https://goproxy.cn,direct
|
||||
&& go env -w CGO_ENABLED=0
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
@ -21,7 +20,8 @@ RUN chmod +x /docker-entrypoint.sh && \
|
||||
ffmpeg \
|
||||
coreutils \
|
||||
shadow \
|
||||
su-exec && \
|
||||
su-exec \
|
||||
tzdata && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
mkdir -p /app && \
|
||||
mkdir -p /data && \
|
||||
@ -42,3 +42,4 @@ WORKDIR /data
|
||||
VOLUME [ "/data" ]
|
||||
|
||||
ENTRYPOINT [ "/docker-entrypoint.sh" ]
|
||||
CMD [ "/app/cqhttp" ]
|
||||
|
@ -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>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
# go-cqhttp
|
||||
|
||||
_✨ 基于 [Mirai](https://github.com/mamoe/mirai) 以及 [MiraiGo](https://github.com/Mrs4s/MiraiGo) 的 [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md) Golang 原生实现 ✨_
|
||||
_✨ 基于 [Lagrange.Core](https://github.com/KonataDev/Lagrange.Core) 以及 [LagrangeGo](https://github.com/LagrangeDev/LagrangeGo) 的 [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md) Golang 原生实现 ✨_
|
||||
|
||||
|
||||
</div>
|
||||
@ -42,7 +42,6 @@ _✨ 基于 [Mirai](https://github.com/mamoe/mirai) 以及 [MiraiGo](https://git
|
||||
<a href="https://github.com/Mrs4s/go-cqhttp/blob/master/CONTRIBUTING.md">参与贡献</a>
|
||||
</p>
|
||||
|
||||
|
||||
## 兼容性
|
||||
go-cqhttp 兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) 绝大多数内容,并在其基础上做了一些扩展,详情请看 go-cqhttp 的文档。
|
||||
|
||||
|
@ -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
|
||||
@ -22,38 +28,77 @@ type Param struct {
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
Func string
|
||||
Path []string
|
||||
Params []Param
|
||||
Func string
|
||||
Path []string
|
||||
PathV11 []string // v11 only
|
||||
PathV12 []string // v12 only
|
||||
Params []Param
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
const (
|
||||
PathAll = 0
|
||||
PathV11 = 11
|
||||
PathV12 = 12
|
||||
)
|
||||
|
||||
func (g *generator) WriteString(s string) {
|
||||
io.WriteString(g.out, s)
|
||||
}
|
||||
|
||||
func (g *generator) generate(routers []Router) {
|
||||
g.WriteString("// Code generated by cmd/api-generator. DO NOT EDIT.\n\n")
|
||||
g.WriteString("package api\n\nimport (\n\n")
|
||||
g.WriteString("\"github.com/Mrs4s/go-cqhttp/coolq\"\n")
|
||||
g.WriteString("\"github.com/Mrs4s/go-cqhttp/global\"\n")
|
||||
g.WriteString(")\n\n")
|
||||
g.WriteString(`func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
switch action {
|
||||
default:
|
||||
return coolq.Failed(404, "API_NOT_FOUND", "API不存在")` + "\n")
|
||||
for _, router := range routers {
|
||||
g.router(router)
|
||||
}
|
||||
io.WriteString(g.out, ` }}`)
|
||||
func (g *generator) writef(format string, a ...any) {
|
||||
fmt.Fprintf(g.out, format, a...)
|
||||
}
|
||||
|
||||
func (g *generator) router(router Router) {
|
||||
func (g *generator) header() {
|
||||
g.WriteString("// Code generated by cmd/api-generator. DO NOT EDIT.\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(`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 {
|
||||
`, path)
|
||||
for _, router := range routers {
|
||||
g.router(router, path)
|
||||
}
|
||||
g.WriteString("}}\n")
|
||||
}
|
||||
genVer(PathV11)
|
||||
genVer(PathV12)
|
||||
// generic path
|
||||
g.WriteString("switch action {\n")
|
||||
for _, router := range routers {
|
||||
g.router(router, PathAll)
|
||||
}
|
||||
g.WriteString("}\n")
|
||||
g.WriteString("return coolq.Failed(404, \"API_NOT_FOUND\", \"API不存在\")}")
|
||||
}
|
||||
|
||||
func (g *generator) router(router Router, pathVersion int) {
|
||||
path := router.Path
|
||||
if pathVersion == PathV11 {
|
||||
path = router.PathV11
|
||||
}
|
||||
if pathVersion == PathV12 {
|
||||
path = router.PathV12
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
g.WriteString(`case `)
|
||||
for i, p := range router.Path {
|
||||
for i, p := range path {
|
||||
if i != 0 {
|
||||
g.WriteString(`, `)
|
||||
}
|
||||
@ -62,22 +107,29 @@ func (g *generator) router(router Router) {
|
||||
g.WriteString(":\n")
|
||||
|
||||
for i, p := range router.Params {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
g.WriteString("\t\treturn c.bot." + router.Func + "(")
|
||||
for i := range router.Params {
|
||||
for i, p := range router.Params {
|
||||
if i != 0 {
|
||||
g.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(g.out, "p%d", i)
|
||||
if p.Type == "*onebot.Spec" {
|
||||
g.WriteString("spec")
|
||||
continue
|
||||
}
|
||||
g.writef("p%d", i)
|
||||
}
|
||||
g.WriteString(")\n")
|
||||
}
|
||||
@ -85,8 +137,8 @@ func (g *generator) router(router Router) {
|
||||
func conv(v, t string) string {
|
||||
switch t {
|
||||
default:
|
||||
panic("unknown type: " + t)
|
||||
case "gjson.Result":
|
||||
panic("unsupported type: " + t)
|
||||
case "gjson.Result", "*onebot.Spec":
|
||||
return v
|
||||
case "int64":
|
||||
return v + ".Int()"
|
||||
@ -100,92 +152,118 @@ func conv(v, t string) string {
|
||||
return v + ".Uint()"
|
||||
case "uint32":
|
||||
return "uint32(" + v + ".Uint())"
|
||||
case "uint16":
|
||||
return "uint16(" + v + ".Uint())"
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var routers []Router
|
||||
src := flag.String("path", "", "source file")
|
||||
flag.Parse()
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, *src, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, s := range strings.Split(*src, ",") {
|
||||
file, err := parser.ParseFile(fset, s, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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" {
|
||||
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
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if !decl.Name.IsExported() || decl.Recv == nil ||
|
||||
typeName(decl.Recv.List[0].Type) != "*CQBot" {
|
||||
continue
|
||||
}
|
||||
for _, name := range p.Names {
|
||||
router.Params = append(router.Params, Param{Name: snakecase(name.Name), Type: typ})
|
||||
}
|
||||
}
|
||||
router := Router{Func: decl.Name.Name}
|
||||
|
||||
for _, comment := range decl.Doc.List {
|
||||
annotation, args := match(comment.Text)
|
||||
switch annotation {
|
||||
case "route":
|
||||
for _, route := range strings.Split(args, ",") {
|
||||
router.Path = append(router.Path, unquote(route))
|
||||
// compute params
|
||||
for _, p := range decl.Type.Params.List {
|
||||
typ := typeName(p.Type)
|
||||
for _, name := range p.Names {
|
||||
router.Params = append(router.Params, Param{Name: snakecase(name.Name), Type: typ})
|
||||
}
|
||||
case "default":
|
||||
for name, value := range parseMap(args, "=") {
|
||||
for i, p := range router.Params {
|
||||
if p.Name == name {
|
||||
router.Params[i].Default = convDefault(value, p.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "rename":
|
||||
for name, value := range parseMap(args, "->") {
|
||||
for i, p := range router.Params {
|
||||
if p.Name == name {
|
||||
router.Params[i].Name = value
|
||||
}
|
||||
|
||||
for _, comment := range decl.Doc.List {
|
||||
annotation, args := match(comment.Text)
|
||||
switch annotation {
|
||||
case "route":
|
||||
for _, route := range strings.Split(args, ",") {
|
||||
router.Path = append(router.Path, unquote(route))
|
||||
}
|
||||
case "route11":
|
||||
for _, route := range strings.Split(args, ",") {
|
||||
router.PathV11 = append(router.PathV11, unquote(route))
|
||||
}
|
||||
case "route12":
|
||||
for _, route := range strings.Split(args, ",") {
|
||||
router.PathV12 = append(router.PathV12, unquote(route))
|
||||
}
|
||||
case "default":
|
||||
for name, value := range parseMap(args, "=") {
|
||||
for i, p := range router.Params {
|
||||
if p.Name == name {
|
||||
router.Params[i].Default = convDefault(value, p.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "rename":
|
||||
for name, value := range parseMap(args, "->") {
|
||||
for i, p := range router.Params {
|
||||
if p.Name == name {
|
||||
router.Params[i].Name = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(router.Path, func(i, j int) bool {
|
||||
return router.Path[i] < router.Path[j]
|
||||
})
|
||||
sort.Slice(router.PathV11, func(i, j int) bool {
|
||||
return router.PathV11[i] < router.PathV11[j]
|
||||
})
|
||||
sort.Slice(router.PathV12, func(i, j int) bool {
|
||||
return router.PathV12[i] < router.PathV12[j]
|
||||
})
|
||||
}
|
||||
if router.Path != nil || router.PathV11 != nil || router.PathV12 != nil {
|
||||
routers = append(routers, router)
|
||||
} else {
|
||||
println(decl.Name.Name)
|
||||
}
|
||||
sort.Slice(router.Path, func(i, j int) bool {
|
||||
return router.Path[i] < router.Path[j]
|
||||
})
|
||||
}
|
||||
if router.Path != nil {
|
||||
routers = append(routers, router)
|
||||
} else {
|
||||
println(decl.Name.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(routers, func(i, j int) bool {
|
||||
return routers[i].Path[0] < routers[j].Path[0]
|
||||
path := func(r Router) string {
|
||||
if r.Path != nil {
|
||||
return r.Path[0]
|
||||
}
|
||||
if r.PathV11 != nil {
|
||||
return r.PathV11[0]
|
||||
}
|
||||
if r.PathV12 != nil {
|
||||
return r.PathV12[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return path(routers[i]) < path(routers[j])
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
@ -204,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"
|
||||
}
|
||||
@ -222,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")
|
||||
|
||||
@ -269,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())
|
||||
}
|
||||
}
|
||||
|
44
cmd/api-generator/supported.go
Normal file
44
cmd/api-generator/supported.go
Normal 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}}
|
||||
}`
|
@ -5,20 +5,21 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/client"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/auth"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/packets/wtlogin/qrcodestate"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.ilharper.com/x/isatty"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
)
|
||||
|
||||
var console = bufio.NewReader(os.Stdin)
|
||||
@ -29,7 +30,7 @@ func readLine() (str string) {
|
||||
return
|
||||
}
|
||||
|
||||
func readLineTimeout(t time.Duration, de string) (str string) {
|
||||
func readLineTimeout(t time.Duration) {
|
||||
r := make(chan string)
|
||||
go func() {
|
||||
select {
|
||||
@ -37,12 +38,10 @@ func readLineTimeout(t time.Duration, de string) (str string) {
|
||||
case <-time.After(t):
|
||||
}
|
||||
}()
|
||||
str = de
|
||||
select {
|
||||
case str = <-r:
|
||||
case <-r:
|
||||
case <-time.After(t):
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readIfTTY(de string) (str string) {
|
||||
@ -54,12 +53,13 @@ func readIfTTY(de string) (str string) {
|
||||
}
|
||||
|
||||
var cli *client.QQClient
|
||||
var device *auth.DeviceInfo
|
||||
|
||||
// ErrSMSRequestError SMS请求出错
|
||||
var ErrSMSRequestError = errors.New("sms request error")
|
||||
|
||||
func commonLogin() error {
|
||||
res, err := cli.Login()
|
||||
res, err := cli.PasswordLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -67,6 +67,52 @@ func commonLogin() error {
|
||||
}
|
||||
|
||||
func printQRCode(imgData []byte) {
|
||||
// (".", "^", " ", "@") : ("▄", "▀", " ", "█")
|
||||
const (
|
||||
bb = "█"
|
||||
wb = "▄"
|
||||
bw = "▀"
|
||||
ww = " "
|
||||
)
|
||||
img, err := png.Decode(bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
bound := img.Bounds().Max.X
|
||||
buf := make([]byte, 0, (bound+1)*(bound/2+utils.Ternary(bound%2 == 0, 0, 1)))
|
||||
|
||||
padding := 0
|
||||
lastColor := img.At(padding, padding).(color.Gray).Y
|
||||
for padding++; padding < bound; padding++ {
|
||||
if img.At(padding, padding).(color.Gray).Y != lastColor {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for y := padding; y < bound-padding; y += 2 {
|
||||
for x := padding; x < bound-padding; x++ {
|
||||
isUpWhite := img.At(x, y).(color.Gray).Y == 255
|
||||
isDownWhite := utils.Ternary(y < bound-padding, img.At(x, y+1).(color.Gray).Y == 255, false)
|
||||
|
||||
switch {
|
||||
case !isUpWhite && !isDownWhite:
|
||||
buf = append(buf, bb...)
|
||||
case isUpWhite && !isDownWhite:
|
||||
buf = append(buf, wb...)
|
||||
case !isUpWhite:
|
||||
buf = append(buf, bw...)
|
||||
default:
|
||||
buf = append(buf, ww...)
|
||||
}
|
||||
}
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
_, _ = colorable.NewColorableStdout().Write(buf)
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func printQRCodeCommon(imgData []byte) {
|
||||
const (
|
||||
black = "\033[48;5;0m \033[0m"
|
||||
white = "\033[48;5;7m \033[0m"
|
||||
@ -95,11 +141,11 @@ func printQRCode(imgData []byte) {
|
||||
}
|
||||
|
||||
func qrcodeLogin() error {
|
||||
rsp, err := cli.FetchQRCodeCustomSize(1, 2, 1)
|
||||
qrcodeData, _, err := cli.FetchQRCode(1, 2, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.WriteFile("qrcode.png", rsp.ImageData, 0o644)
|
||||
_ = os.WriteFile("qrcode.png", qrcodeData, 0o644)
|
||||
defer func() { _ = os.Remove("qrcode.png") }()
|
||||
if cli.Uin != 0 {
|
||||
log.Infof("请使用账号 %v 登录手机QQ扫描二维码 (qrcode.png) : ", cli.Uin)
|
||||
@ -107,36 +153,33 @@ func qrcodeLogin() error {
|
||||
log.Infof("请使用手机QQ扫描二维码 (qrcode.png) : ")
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
printQRCode(rsp.ImageData)
|
||||
s, err := cli.QueryQRCodeStatus(rsp.Sig)
|
||||
printQRCode(qrcodeData)
|
||||
s, err := cli.GetQRCodeResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prevState := s.State
|
||||
prevState := s
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
s, _ = cli.QueryQRCodeStatus(rsp.Sig)
|
||||
if s == nil {
|
||||
s, _ = cli.GetQRCodeResult()
|
||||
if prevState == s {
|
||||
continue
|
||||
}
|
||||
if prevState == s.State {
|
||||
continue
|
||||
}
|
||||
prevState = s.State
|
||||
switch s.State {
|
||||
case client.QRCodeCanceled:
|
||||
prevState = s
|
||||
switch s {
|
||||
case qrcodestate.Canceled:
|
||||
log.Fatalf("扫码被用户取消.")
|
||||
case client.QRCodeTimeout:
|
||||
case qrcodestate.Expired:
|
||||
log.Fatalf("二维码过期")
|
||||
case client.QRCodeWaitingForConfirm:
|
||||
case qrcodestate.WaitingForConfirm:
|
||||
log.Infof("扫码成功, 请在手机端确认登录.")
|
||||
case client.QRCodeConfirmed:
|
||||
res, err := cli.QRCodeLogin(s.LoginInfo)
|
||||
case qrcodestate.Confirmed:
|
||||
res, err := cli.QRCodeLogin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return loginResponseProcessor(res)
|
||||
case client.QRCodeImageFetch, client.QRCodeWaitingForScan:
|
||||
case qrcodestate.WaitingForScan:
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@ -151,114 +194,122 @@ func loginResponseProcessor(res *client.LoginResponse) error {
|
||||
if res.Success {
|
||||
return nil
|
||||
}
|
||||
var text string
|
||||
//var text string
|
||||
//nolint:exhaustive
|
||||
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, randStr := getTicket(res.VerifyURL)
|
||||
if ticket == "" {
|
||||
log.Infof("按 Enter 继续....")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
}
|
||||
cli.Disconnect()
|
||||
cli.Release()
|
||||
cli = client.NewClientEmpty()
|
||||
return qrcodeLogin()
|
||||
case client.NeedCaptcha:
|
||||
log.Warnf("登录需要验证码.")
|
||||
_ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
|
||||
log.Warnf("请输入验证码 (captcha.jpg): (Enter 提交)")
|
||||
text = readLine()
|
||||
global.DelFile("captcha.jpg")
|
||||
res, err = cli.SubmitCaptcha(text, res.CaptchaSign)
|
||||
res, err = cli.SubmitCaptcha(ticket, randStr, strings.Split(strings.Split(res.VerifyURL, "sid=")[1], "&")[0])
|
||||
continue
|
||||
case client.SMSNeededError:
|
||||
log.Warnf("账号已开启设备锁, 按 Enter 向手机 %v 发送短信验证码.", res.SMSPhone)
|
||||
readLine()
|
||||
if !cli.RequestSMS() {
|
||||
log.Warnf("发送验证码失败,可能是请求过于频繁.")
|
||||
return errors.WithStack(ErrSMSRequestError)
|
||||
}
|
||||
log.Warn("请输入短信验证码: (Enter 提交)")
|
||||
text = readLine()
|
||||
res, err = cli.SubmitSMS(text)
|
||||
continue
|
||||
case client.SMSOrVerifyNeededError:
|
||||
log.Warnf("账号已开启设备锁,请选择验证方式:")
|
||||
log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone)
|
||||
log.Warnf("2. 使用手机QQ扫码验证.")
|
||||
log.Warn("请输入(1 - 2):")
|
||||
text = readIfTTY("2")
|
||||
if strings.Contains(text, "1") {
|
||||
if !cli.RequestSMS() {
|
||||
log.Warnf("发送验证码失败,可能是请求过于频繁.")
|
||||
return errors.WithStack(ErrSMSRequestError)
|
||||
}
|
||||
log.Warn("请输入短信验证码: (Enter 提交)")
|
||||
text = readLine()
|
||||
res, err = cli.SubmitSMS(text)
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
//case client.NeedCaptcha:
|
||||
// log.Warnf("登录需要验证码.")
|
||||
// _ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
|
||||
// log.Warnf("请输入验证码 (captcha.jpg): (Enter 提交)")
|
||||
// text = readLine()
|
||||
// global.DelFile("captcha.jpg")
|
||||
// res, err = cli.SubmitCaptcha(text, res.CaptchaSign)
|
||||
// continue
|
||||
// TODO 短信验证码?
|
||||
//case client.SMSNeededError:
|
||||
// log.Warnf("账号已开启设备锁, 按 Enter 向手机 %v 发送短信验证码.", res.SMSPhone)
|
||||
// readLine()
|
||||
// if !cli.RequestSMS() {
|
||||
// log.Warnf("发送验证码失败,可能是请求过于频繁.")
|
||||
// return errors.WithStack(ErrSMSRequestError)
|
||||
// }
|
||||
// log.Warn("请输入短信验证码: (Enter 提交)")
|
||||
// text = readLine()
|
||||
// res, err = cli.SubmitSMS(text)
|
||||
// continue
|
||||
// TODO 设备锁?
|
||||
//case client.SMSOrVerifyNeededError:
|
||||
// log.Warnf("账号已开启设备锁,请选择验证方式:")
|
||||
// log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone)
|
||||
// log.Warnf("2. 使用手机QQ扫码验证.")
|
||||
// log.Warn("请输入(1 - 2):")
|
||||
// text = readIfTTY("2")
|
||||
// if strings.Contains(text, "1") {
|
||||
// if !cli.RequestSMS() {
|
||||
// log.Warnf("发送验证码失败,可能是请求过于频繁.")
|
||||
// return errors.WithStack(ErrSMSRequestError)
|
||||
// }
|
||||
// log.Warn("请输入短信验证码: (Enter 提交)")
|
||||
// text = readLine()
|
||||
// res, err = cli.SubmitSMS(text)
|
||||
// continue
|
||||
// }
|
||||
// fallthrough
|
||||
case client.UnsafeDeviceError:
|
||||
log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证后重启Bot.", res.VerifyUrl)
|
||||
log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证后重启Bot.", res.VerifyURL)
|
||||
log.Infof("按 Enter 或等待 5s 后继续....")
|
||||
readLineTimeout(time.Second*5, "")
|
||||
readLineTimeout(time.Second * 5)
|
||||
os.Exit(0)
|
||||
case client.OtherLoginError, client.UnknownLoginError, client.TooManySMSRequestError:
|
||||
fallthrough
|
||||
default:
|
||||
msg := res.ErrorMessage
|
||||
if strings.Contains(msg, "版本") {
|
||||
msg = "密码错误或账号被冻结"
|
||||
} else if strings.Contains(msg, "冻结") {
|
||||
log.Fatalf("账号被冻结")
|
||||
log.Warnf("登录失败: %v Code: %v", msg, res.Code)
|
||||
switch res.Code {
|
||||
case 235:
|
||||
log.Warnf("设备信息被封禁, 请删除 device.json 后重试.")
|
||||
case 237:
|
||||
log.Warnf("登录过于频繁, 请在手机QQ登录并根据提示完成认证后等一段时间重试")
|
||||
case 45:
|
||||
log.Warnf("你的账号被限制登录, 请配置 SignServer 后重试")
|
||||
}
|
||||
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) {
|
||||
id := utils.RandomString(8)
|
||||
log.Warnf("请前往该地址验证 -> %v <- 或输入手动抓取的 ticket:(Enter 提交)", 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()
|
||||
func getTicket(u string) (string, string) {
|
||||
log.Warnf("请选择提交滑块ticket方式:")
|
||||
log.Warnf("1. 自动提交")
|
||||
log.Warnf("2. 手动抓取提交")
|
||||
log.Warn("请输入(1 - 2):")
|
||||
text := readLine()
|
||||
id := utils.NewUUID()
|
||||
auto := !strings.Contains(text, "2")
|
||||
// TODO 自动获取验证码
|
||||
if auto {
|
||||
u = strings.ReplaceAll(u, "https://ti.qq.com/safe/tools/captcha/sms-verify-login?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id))
|
||||
}
|
||||
log.Warnf("请前往该地址验证 -> %v ", u)
|
||||
if !auto {
|
||||
log.Warn("请输入ticket: (Enter 提交)")
|
||||
ticket := readLine()
|
||||
log.Warn("请输入rand_str: (Enter 提交)")
|
||||
randStr := readLine()
|
||||
return ticket, randStr
|
||||
}
|
||||
|
||||
for count := 120; count > 0; count-- {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
str = fetchCaptcha(id)
|
||||
if str != "" {
|
||||
return
|
||||
}
|
||||
case str = <-manual:
|
||||
return
|
||||
ticket, randStr := fetchCaptcha(id)
|
||||
if ticket != "" && randStr != "" {
|
||||
return ticket, randStr
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
log.Warnf("验证超时")
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func fetchCaptcha(id string) string {
|
||||
func fetchCaptcha(id string) (string, string) {
|
||||
g, err := download.Request{URL: "https://captcha.go-cqhttp.org/captcha/ticket?id=" + id}.JSON()
|
||||
if err != nil {
|
||||
log.Warnf("获取 Ticket 时出现错误: %v", err)
|
||||
return ""
|
||||
log.Debugf("获取 Ticket 时出现错误: %v", err)
|
||||
return "", ""
|
||||
}
|
||||
if g.Get("ticket").Exists() {
|
||||
return g.Get("ticket").String()
|
||||
if g.Get("ticket").Exists() && g.Get("randstr").Exists() {
|
||||
return g.Get("ticket").String(), g.Get("randstr").String()
|
||||
}
|
||||
return ""
|
||||
return "", ""
|
||||
}
|
||||
|
306
cmd/gocq/main.go
306
cmd/gocq/main.go
@ -6,17 +6,23 @@ import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/LagrangeDev/LagrangeGo/client"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/auth"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/packets/pb/action"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/crypto"
|
||||
para "github.com/fumiama/go-hide-param"
|
||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/term"
|
||||
|
||||
@ -26,23 +32,19 @@ import (
|
||||
"github.com/Mrs4s/go-cqhttp/global/terminal"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/cache"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/selfdiagnosis"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/selfupdate"
|
||||
"github.com/Mrs4s/go-cqhttp/modules/servers"
|
||||
"github.com/Mrs4s/go-cqhttp/server"
|
||||
)
|
||||
|
||||
// 允许通过配置文件设置的状态列表
|
||||
var allowStatus = [...]client.UserOnlineStatus{
|
||||
client.StatusOnline, client.StatusAway, client.StatusInvisible, client.StatusBusy,
|
||||
client.StatusListening, client.StatusConstellation, client.StatusWeather, client.StatusMeetSpring,
|
||||
client.StatusTimi, client.StatusEatChicken, client.StatusLoving, client.StatusWangWang, client.StatusCookedRice,
|
||||
client.StatusStudy, client.StatusStayUp, client.StatusPlayBall, client.StatusSignal, client.StatusStudyOnline,
|
||||
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 +52,7 @@ func Main() {
|
||||
log.Errorf("遇到错误: %v", err)
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
return
|
||||
os.Exit(0)
|
||||
}
|
||||
switch {
|
||||
case base.LittleH:
|
||||
@ -65,7 +67,10 @@ func Main() {
|
||||
}
|
||||
}
|
||||
base.Init()
|
||||
}
|
||||
|
||||
// PrepareData 准备 log, 缓存, 数据库, 必须在 InitBase 之后执行
|
||||
func PrepareData() {
|
||||
rotateOptions := []rotatelogs.Option{
|
||||
rotatelogs.WithRotationTime(time.Hour * 24),
|
||||
}
|
||||
@ -94,14 +99,17 @@ func Main() {
|
||||
mkCacheDir(global.VoicePath, "语音")
|
||||
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 {
|
||||
@ -136,14 +144,15 @@ func Main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Warnf("已开启Debug模式.")
|
||||
}
|
||||
if !global.PathExists("device.json") {
|
||||
if !global.FileExists("device.json") {
|
||||
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
|
||||
client.GenRandomDevice()
|
||||
_ = os.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0o644)
|
||||
device = auth.NewDeviceInfo(int(crypto.RandU32()))
|
||||
_ = device.Save("device.json")
|
||||
log.Info("已生成设备信息并保存到 device.json 文件.")
|
||||
} else {
|
||||
log.Info("将使用 device.json 内的设备信息运行Bot.")
|
||||
if err := client.SystemDeviceInfo.ReadJson([]byte(global.ReadAllText("device.json"))); err != nil {
|
||||
var err error
|
||||
if device, err = auth.LoadOrSaveDevice("device.json"); err != nil {
|
||||
log.Fatalf("加载设备信息失败: %v", err)
|
||||
}
|
||||
}
|
||||
@ -152,101 +161,146 @@ func Main() {
|
||||
if !global.PathExists("password.encrypt") {
|
||||
if base.Account.Password == "" {
|
||||
log.Error("无法进行加密,请在配置文件中的添加密码后重新启动.")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
} else {
|
||||
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
|
||||
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
||||
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
|
||||
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
||||
}
|
||||
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
|
||||
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
||||
_ = os.WriteFile("password.encrypt", []byte(PasswordHashEncrypt(base.PasswordHash[:], byteKey)), 0o644)
|
||||
log.Info("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
} else {
|
||||
if base.Account.Password != "" {
|
||||
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if len(byteKey) == 0 {
|
||||
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
|
||||
cancel := make(chan struct{}, 1)
|
||||
state, _ := term.GetState(int(os.Stdin.Fd()))
|
||||
go func() {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case <-time.After(time.Second * 45):
|
||||
log.Infof("解密key输入超时")
|
||||
time.Sleep(3 * time.Second)
|
||||
_ = term.Restore(int(os.Stdin.Fd()), state)
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
cancel <- struct{}{}
|
||||
} else {
|
||||
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
|
||||
}
|
||||
|
||||
encrypt, _ := os.ReadFile("password.encrypt")
|
||||
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
|
||||
if err != nil {
|
||||
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
|
||||
}
|
||||
copy(base.PasswordHash[:], ph)
|
||||
}
|
||||
if base.Account.Password != "" {
|
||||
log.Error("密码已加密,为了您的账号安全,请删除配置文件中的密码后重新启动.")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
}
|
||||
if len(byteKey) == 0 {
|
||||
log.Infof("密码加密已启用, 请输入Key对密码进行解密以继续: (Enter 提交)")
|
||||
cancel := make(chan struct{}, 1)
|
||||
state, _ := term.GetState(int(os.Stdin.Fd()))
|
||||
go func() {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case <-time.After(time.Second * 45):
|
||||
log.Infof("解密key输入超时")
|
||||
time.Sleep(3 * time.Second)
|
||||
_ = term.Restore(int(os.Stdin.Fd()), state)
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
|
||||
cancel <- struct{}{}
|
||||
} else {
|
||||
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
|
||||
}
|
||||
|
||||
encrypt, _ := os.ReadFile("password.encrypt")
|
||||
ph, err := PasswordHashDecrypt(string(encrypt), byteKey)
|
||||
if err != nil {
|
||||
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
|
||||
}
|
||||
copy(base.PasswordHash[:], ph)
|
||||
} else if len(base.Account.Password) > 0 {
|
||||
base.PasswordHash = md5.Sum([]byte(base.Account.Password))
|
||||
}
|
||||
|
||||
if !base.FastStart {
|
||||
log.Info("Bot将在5秒后登录并开始信息处理, 按 Ctrl+C 取消.")
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
log.Info("开始尝试登录并同步消息...")
|
||||
log.Infof("使用协议: %s", client.SystemDeviceInfo.Protocol)
|
||||
cli = newClient()
|
||||
app := auth.AppList["linux"]["3.2.15-30366"]
|
||||
log.Infof("使用协议: %s", app.CurrentVersion)
|
||||
cli = newClient(app)
|
||||
cli.UseDevice(device)
|
||||
isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt
|
||||
isTokenLogin := false
|
||||
|
||||
// 加载本地版本信息, 一般是在上次登录时保存的
|
||||
versionFile := path.Join(global.VersionsPath, "7.json")
|
||||
if global.FileExists(versionFile) {
|
||||
b, err := os.ReadFile(versionFile)
|
||||
if err != nil {
|
||||
log.Warnf("从文件 %s 读取本地版本信息文件出错.", versionFile)
|
||||
os.Exit(0)
|
||||
}
|
||||
info, err := auth.UnmarshalAppInfo(b)
|
||||
if err != nil {
|
||||
log.Warnf("从文件 %s 解析本地版本信息出错: %v", versionFile, err)
|
||||
os.Exit(0)
|
||||
}
|
||||
cli.UseVersion(info)
|
||||
log.Infof("从文件 %s 读取协议版本 %s.", versionFile, cli.Version().CurrentVersion)
|
||||
}
|
||||
|
||||
saveToken := func() {
|
||||
base.AccountToken = cli.GenToken()
|
||||
base.AccountToken, _ = cli.Sig().Marshal()
|
||||
_ = os.WriteFile("session.token", base.AccountToken, 0o644)
|
||||
}
|
||||
if global.PathExists("session.token") {
|
||||
token, err := os.ReadFile("session.token")
|
||||
if global.FileExists("session.token") {
|
||||
token, _ := os.ReadFile("session.token")
|
||||
sig, err := auth.UnmarshalSigInfo(token, true)
|
||||
if err == nil {
|
||||
if base.Account.Uin != 0 {
|
||||
r := binary.NewReader(token)
|
||||
cu := r.ReadInt64()
|
||||
if cu != base.Account.Uin {
|
||||
log.Warnf("警告: 配置文件内的QQ号 (%v) 与缓存内的QQ号 (%v) 不相同", base.Account.Uin, cu)
|
||||
log.Warnf("1. 使用会话缓存继续.")
|
||||
log.Warnf("2. 删除会话缓存并重启.")
|
||||
log.Warnf("请选择:")
|
||||
text := readIfTTY("1")
|
||||
if text == "2" {
|
||||
_ = os.Remove("session.token")
|
||||
log.Infof("缓存已删除.")
|
||||
os.Exit(0)
|
||||
}
|
||||
if base.Account.Uin != 0 && int64(sig.Uin) != base.Account.Uin {
|
||||
log.Warnf("警告: 配置文件内的QQ号 (%v) 与缓存内的QQ号 (%v) 不相同", base.Account.Uin, int64(sig.Uin))
|
||||
log.Warnf("1. 使用会话缓存继续.")
|
||||
log.Warnf("2. 删除会话缓存并重启.")
|
||||
log.Warnf("请选择:")
|
||||
text := readIfTTY("1")
|
||||
if text == "2" {
|
||||
_ = os.Remove("session.token")
|
||||
log.Infof("缓存已删除.")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
if err = cli.TokenLogin(token); err != nil {
|
||||
cli.UseSig(sig)
|
||||
if err = cli.FastLogin(); err != nil {
|
||||
_ = os.Remove("session.token")
|
||||
log.Warnf("恢复会话失败: %v , 尝试使用正常流程登录.", err)
|
||||
time.Sleep(time.Second)
|
||||
cli.Disconnect()
|
||||
cli.Release()
|
||||
cli = newClient()
|
||||
cli = newClient(app)
|
||||
cli.UseDevice(device)
|
||||
} else {
|
||||
isTokenLogin = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if base.Account.Uin != 0 && base.PasswordHash != [16]byte{} {
|
||||
cli.Uin = base.Account.Uin
|
||||
cli.PasswordMd5 = base.PasswordHash
|
||||
cli.Uin = uint32(base.Account.Uin)
|
||||
cli.PasswordMD5 = base.PasswordHash
|
||||
}
|
||||
if !base.FastStart {
|
||||
log.Infof("正在检查协议更新...")
|
||||
currentVersionName := cli.Version().CurrentVersion
|
||||
remoteVersion, err := getRemoteLatestProtocolVersion(7)
|
||||
if err == nil {
|
||||
remoteVersionName := gjson.GetBytes(remoteVersion, "current_version").String()
|
||||
if remoteVersionName != currentVersionName {
|
||||
switch {
|
||||
case !base.UpdateProtocol:
|
||||
log.Infof("检测到协议更新: %s -> %s", currentVersionName, remoteVersionName)
|
||||
log.Infof("如果登录时出现版本过低错误, 可尝试使用 -update-protocol 参数启动")
|
||||
case !isTokenLogin:
|
||||
info, _ := auth.UnmarshalAppInfo(remoteVersion)
|
||||
cli.UseVersion(info)
|
||||
err := os.WriteFile(versionFile, remoteVersion, 0644)
|
||||
log.Infof("协议版本已更新: %s -> %s", currentVersionName, remoteVersionName)
|
||||
if err != nil {
|
||||
log.Warnln("更新协议版本缓存文件", versionFile, "失败:", err)
|
||||
}
|
||||
default:
|
||||
log.Infof("检测到协议更新: %s -> %s", currentVersionName, remoteVersionName)
|
||||
log.Infof("由于使用了会话缓存, 无法自动更新协议, 请删除缓存后重试")
|
||||
}
|
||||
}
|
||||
} else if err.Error() != "remote version unavailable" {
|
||||
log.Warnf("检查协议更新失败: %v", err)
|
||||
}
|
||||
}
|
||||
if !isTokenLogin {
|
||||
if !isQRCodeLogin {
|
||||
@ -261,7 +315,7 @@ func Main() {
|
||||
}
|
||||
var times uint = 1 // 重试次数
|
||||
var reLoginLock sync.Mutex
|
||||
cli.DisconnectedEvent.Subscribe(func(q *client.QQClient, e *client.ClientDisconnectedEvent) {
|
||||
cli.DisconnectedEvent.Subscribe(func(_ *client.QQClient, e *client.DisconnectedEvent) {
|
||||
reLoginLock.Lock()
|
||||
defer reLoginLock.Unlock()
|
||||
times = 1
|
||||
@ -290,7 +344,7 @@ func Main() {
|
||||
break
|
||||
}
|
||||
log.Warnf("尝试重连...")
|
||||
err := cli.TokenLogin(base.AccountToken)
|
||||
err := cli.FastLogin()
|
||||
if err == nil {
|
||||
saveToken()
|
||||
return
|
||||
@ -301,7 +355,7 @@ func Main() {
|
||||
}
|
||||
log.Warnf("快速重连失败, 尝试普通登录. 这可能是因为其他端强行T下线导致的.")
|
||||
time.Sleep(time.Second)
|
||||
if err := commonLogin(); err != nil {
|
||||
if err := qrcodeLogin(); err != nil {
|
||||
log.Errorf("登录时发生致命错误: %v", err)
|
||||
} else {
|
||||
saveToken()
|
||||
@ -310,23 +364,33 @@ func Main() {
|
||||
}
|
||||
})
|
||||
saveToken()
|
||||
cli.AllowSlider = true
|
||||
log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
|
||||
// cli.AllowSlider = true
|
||||
log.Infof("登录成功 欢迎使用: %v", cli.NickName())
|
||||
log.Info("开始加载好友列表...")
|
||||
global.Check(cli.ReloadFriendList(), true)
|
||||
log.Infof("共加载 %v 个好友.", len(cli.FriendList))
|
||||
global.Check(cli.RefreshFriendCache(), true)
|
||||
friendListLen := len(cli.GetCachedAllFriendsInfo())
|
||||
log.Infof("共加载 %v 个好友.", friendListLen)
|
||||
log.Infof("开始加载群列表...")
|
||||
global.Check(cli.ReloadGroupList(), true)
|
||||
log.Infof("共加载 %v 个群.", len(cli.GroupList))
|
||||
if uint(base.Account.Status) >= uint(len(allowStatus)) {
|
||||
base.Account.Status = 0
|
||||
global.Check(cli.RefreshAllGroupsInfo(), true)
|
||||
GroupListLen := len(cli.GetCachedAllGroupsInfo())
|
||||
log.Infof("共加载 %v 个群.", GroupListLen)
|
||||
if uint(base.Account.Status) >= 3000 {
|
||||
base.Account.Status = 10
|
||||
}
|
||||
cli.SetOnlineStatus(allowStatus[base.Account.Status])
|
||||
|
||||
_ = cli.SetOnlineStatus(utils.Ternary(base.Account.Status >= 1000, action.SetStatus{
|
||||
Status: 10,
|
||||
ExtStatus: uint32(base.Account.Status),
|
||||
}, action.SetStatus{Status: uint32(base.Account.Status)}))
|
||||
servers.Run(coolq.NewQQBot(cli))
|
||||
log.Info("资源初始化完成, 开始处理信息.")
|
||||
log.Info("アトリは、高性能ですから!")
|
||||
}
|
||||
|
||||
// WaitSignal 在新线程检查更新和网络并等待信号, 必须在 InitBase, PrepareData, LoginInteract 之后执行
|
||||
//
|
||||
// - 直接返回: os.Interrupt, syscall.SIGTERM
|
||||
// - dump stack: syscall.SIGQUIT, syscall.SIGUSR1
|
||||
func WaitSignal() {
|
||||
go func() {
|
||||
selfupdate.CheckUpdate()
|
||||
selfdiagnosis.NetworkDiagnosis(cli)
|
||||
@ -366,18 +430,28 @@ func PasswordHashDecrypt(encryptedPasswordHash string, key []byte) ([]byte, erro
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func newClient() *client.QQClient {
|
||||
c := client.NewClientEmpty()
|
||||
c.UseFragmentMessage = base.ForceFragmented
|
||||
c.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) bool {
|
||||
if !base.UseSSOAddress {
|
||||
log.Infof("收到服务器地址更新通知, 根据配置文件已忽略.")
|
||||
return false
|
||||
func newClient(app *auth.AppInfo) *client.QQClient {
|
||||
signUrls := make([]string, 0, len(base.SignServers))
|
||||
for _, s := range base.SignServers {
|
||||
u, err := url.Parse(s.URL)
|
||||
if err != nil || u.Hostname() == "" {
|
||||
continue
|
||||
}
|
||||
log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ")
|
||||
return true
|
||||
})
|
||||
if global.PathExists("address.txt") {
|
||||
signUrls = append(signUrls, u.String())
|
||||
}
|
||||
c := client.NewClientEmpty()
|
||||
c.UseVersion(app)
|
||||
c.AddSignServer(signUrls...)
|
||||
// TODO 服务器更新通知
|
||||
// c.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) bool {
|
||||
// if !base.UseSSOAddress {
|
||||
// log.Infof("收到服务器地址更新通知, 根据配置文件已忽略.")
|
||||
// return false
|
||||
// }
|
||||
// log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ")
|
||||
// return true
|
||||
//})
|
||||
if global.FileExists("address.txt") {
|
||||
log.Infof("检测到 address.txt 文件. 将覆盖目标IP.")
|
||||
addr := global.ReadAddrFile("address.txt")
|
||||
if len(addr) > 0 {
|
||||
@ -389,6 +463,24 @@ 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",
|
||||
7: "https://raw.githubusercontent.com/LagrangeDev/protocol-versions/refs/heads/master/LagrangeGo/latest.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://www.ghproxy.cn/" + url}.Bytes()
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type protocolLogger struct{}
|
||||
|
||||
const fromProtocol = "Protocol -> "
|
||||
|
1277
coolq/api.go
1277
coolq/api.go
File diff suppressed because it is too large
Load Diff
33
coolq/api_v12.go
Normal file
33
coolq/api_v12.go
Normal file
@ -0,0 +1,33 @@
|
||||
package coolq
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// CQGetVersion 获取版本信息 OneBotV12
|
||||
//
|
||||
// https://git.io/JtwUs
|
||||
// @route12(get_version)
|
||||
func (bot *CQBot) CQGetVersion() global.MSG {
|
||||
return OK(global.MSG{
|
||||
"impl": "go_cqhttp",
|
||||
"platform": "qq",
|
||||
"version": base.Version,
|
||||
"onebot_version": 12,
|
||||
"runtime_version": runtime.Version(),
|
||||
"runtime_os": runtime.GOOS,
|
||||
})
|
||||
}
|
||||
|
||||
// CQSendMessageV12 发送消息
|
||||
//
|
||||
// @route12(send_message)
|
||||
// @rename(m->message)
|
||||
func (bot *CQBot) CQSendMessageV12(groupID, userID, detailType string, m gjson.Result) global.MSG { // nolint
|
||||
// TODO: implement
|
||||
return OK(nil)
|
||||
}
|
413
coolq/bot.go
413
coolq/bot.go
@ -5,25 +5,30 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/RomiChan/syncx"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/segmentio/asm/base64"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/LagrangeDev/LagrangeGo/client"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/entity"
|
||||
event2 "github.com/LagrangeDev/LagrangeGo/client/event"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/sign"
|
||||
"github.com/LagrangeDev/LagrangeGo/message"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/binary"
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
"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"
|
||||
"github.com/RomiChan/syncx"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
// CQBot CQBot结构体,存储Bot实例相关配置
|
||||
@ -33,9 +38,8 @@ type CQBot struct {
|
||||
lock sync.RWMutex
|
||||
events []func(*Event)
|
||||
|
||||
friendReqCache syncx.Map[string, *client.NewFriendRequest]
|
||||
tempSessionCache syncx.Map[int64, *client.TempSessionInfo]
|
||||
nextTokenCache *utils.Cache[*guildMemberPageToken]
|
||||
friendReqCache syncx.Map[string, *event2.NewFriendRequest]
|
||||
//tempSessionCache syncx.Map[int64, *event2.]
|
||||
}
|
||||
|
||||
// Event 事件
|
||||
@ -68,8 +72,7 @@ func (e *Event) JSONString() string {
|
||||
// NewQQBot 初始化一个QQBot实例
|
||||
func NewQQBot(cli *client.QQClient) *CQBot {
|
||||
bot := &CQBot{
|
||||
Client: cli,
|
||||
nextTokenCache: utils.NewCache[*guildMemberPageToken](time.Second * 10),
|
||||
Client: cli,
|
||||
}
|
||||
bot.Client.PrivateMessageEvent.Subscribe(bot.privateMessageEvent)
|
||||
bot.Client.GroupMessageEvent.Subscribe(bot.groupMessageEvent)
|
||||
@ -78,30 +81,28 @@ func NewQQBot(cli *client.QQClient) *CQBot {
|
||||
bot.Client.SelfGroupMessageEvent.Subscribe(bot.groupMessageEvent)
|
||||
}
|
||||
bot.Client.TempMessageEvent.Subscribe(bot.tempMessageEvent)
|
||||
bot.Client.GuildService.OnGuildChannelMessage(bot.guildChannelMessageEvent)
|
||||
bot.Client.GuildService.OnGuildMessageReactionsUpdated(bot.guildMessageReactionsUpdatedEvent)
|
||||
bot.Client.GuildService.OnGuildMessageRecalled(bot.guildChannelMessageRecalledEvent)
|
||||
bot.Client.GuildService.OnGuildChannelUpdated(bot.guildChannelUpdatedEvent)
|
||||
bot.Client.GuildService.OnGuildChannelCreated(bot.guildChannelCreatedEvent)
|
||||
bot.Client.GuildService.OnGuildChannelDestroyed(bot.guildChannelDestroyedEvent)
|
||||
bot.Client.GroupMuteEvent.Subscribe(bot.groupMutedEvent)
|
||||
bot.Client.GroupMessageRecalledEvent.Subscribe(bot.groupRecallEvent)
|
||||
bot.Client.GroupRecallEvent.Subscribe(bot.groupRecallEvent)
|
||||
bot.Client.GroupNotifyEvent.Subscribe(bot.groupNotifyEvent)
|
||||
bot.Client.FriendNotifyEvent.Subscribe(bot.friendNotifyEvent)
|
||||
bot.Client.MemberSpecialTitleUpdatedEvent.Subscribe(bot.memberTitleUpdatedEvent)
|
||||
bot.Client.FriendMessageRecalledEvent.Subscribe(bot.friendRecallEvent)
|
||||
bot.Client.OfflineFileEvent.Subscribe(bot.offlineFileEvent)
|
||||
bot.Client.FriendRecallEvent.Subscribe(bot.friendRecallEvent)
|
||||
// TODO 离线文件
|
||||
//bot.Client.OfflineFileEvent.Subscribe(bot.offlineFileEvent)
|
||||
bot.Client.GroupJoinEvent.Subscribe(bot.joinGroupEvent)
|
||||
bot.Client.GroupLeaveEvent.Subscribe(bot.leaveGroupEvent)
|
||||
bot.Client.GroupMemberJoinEvent.Subscribe(bot.memberJoinEvent)
|
||||
bot.Client.GroupMemberLeaveEvent.Subscribe(bot.memberLeaveEvent)
|
||||
bot.Client.GroupMemberPermissionChangedEvent.Subscribe(bot.memberPermissionChangedEvent)
|
||||
bot.Client.MemberCardUpdatedEvent.Subscribe(bot.memberCardUpdatedEvent)
|
||||
// TODO 群成员名片更新
|
||||
//bot.Client.MemberCardUpdatedEvent.Subscribe(bot.memberCardUpdatedEvent)
|
||||
bot.Client.NewFriendRequestEvent.Subscribe(bot.friendRequestEvent)
|
||||
// TODO 成为好友
|
||||
bot.Client.NewFriendEvent.Subscribe(bot.friendAddedEvent)
|
||||
bot.Client.GroupInvitedEvent.Subscribe(bot.groupInvitedEvent)
|
||||
bot.Client.UserWantJoinGroupEvent.Subscribe(bot.groupJoinReqEvent)
|
||||
bot.Client.OtherClientStatusChangedEvent.Subscribe(bot.otherClientStatusChangedEvent)
|
||||
bot.Client.GroupMemberJoinRequestEvent.Subscribe(bot.groupJoinReqEvent)
|
||||
// TODO 客户端变更
|
||||
//bot.Client.OtherClientStatusChangedEvent.Subscribe(bot.otherClientStatusChangedEvent)
|
||||
bot.Client.GroupDigestEvent.Subscribe(bot.groupEssenceMsg)
|
||||
go func() {
|
||||
if base.HeartbeatInterval == 0 {
|
||||
@ -112,7 +113,7 @@ func NewQQBot(cli *client.QQClient) *CQBot {
|
||||
for {
|
||||
<-t.C
|
||||
bot.dispatchEvent("meta_event/heartbeat", global.MSG{
|
||||
"status": bot.CQGetStatus()["data"],
|
||||
"status": bot.CQGetStatus(onebot.V11)["data"],
|
||||
"interval": base.HeartbeatInterval.Milliseconds(),
|
||||
})
|
||||
}
|
||||
@ -143,8 +144,13 @@ func (w *worker) wait() {
|
||||
w.wg.Wait()
|
||||
}
|
||||
|
||||
// uploadLocalVoice 上传语音
|
||||
func (bot *CQBot) uploadLocalVoice(target message.Source, voice *message.VoiceElement) (message.IMessageElement, error) {
|
||||
return bot.Client.UploadRecord(target, voice)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -153,39 +159,45 @@ func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement
|
||||
defer func() { _ = f.Close() }()
|
||||
img.Stream = f
|
||||
}
|
||||
if mt, ok := mime.CheckImage(img.Stream); !ok {
|
||||
mt, ok := mime.CheckImage(img.Stream)
|
||||
if !ok {
|
||||
return nil, errors.New("image type error: " + mt)
|
||||
}
|
||||
i, err := bot.Client.UploadImage(target, img.Stream, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if mt == "image/webp" && base.ConvertWebpImage {
|
||||
img0, err := webp.Decode(img.Stream)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decode webp error")
|
||||
}
|
||||
stream := bytes.NewBuffer(nil)
|
||||
err = png.Encode(stream, img0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "encode png error")
|
||||
}
|
||||
img.Stream = bytes.NewReader(stream.Bytes())
|
||||
}
|
||||
switch i := i.(type) {
|
||||
case *message.GroupImageElement:
|
||||
i.Flash = img.Flash
|
||||
i.EffectID = img.EffectID
|
||||
case *message.FriendImageElement:
|
||||
i.Flash = img.Flash
|
||||
}
|
||||
return i, err
|
||||
return bot.Client.UploadImage(target, message.NewStreamImage(img.Stream))
|
||||
}
|
||||
|
||||
// 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, message.NewStreamVideo(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 *message.VoiceElement: // 未上传的语音消息, 也删除
|
||||
case *msg.LocalImage, *msg.LocalVideo:
|
||||
// todo 这里先不要删,语音消息暂时没有本地表示
|
||||
//case *message.VoiceElement: // 未上传的语音消息, 也删除
|
||||
// if elem.MsgInfo == nil {
|
||||
// continue
|
||||
// }
|
||||
case nil:
|
||||
default:
|
||||
if j < i {
|
||||
@ -207,14 +219,12 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
|
||||
source = "群"
|
||||
case message.SourcePrivate:
|
||||
source = "私聊"
|
||||
case message.SourceGuildChannel:
|
||||
source = "频道"
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -225,14 +235,14 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
|
||||
})
|
||||
case *message.VoiceElement:
|
||||
w.do(func() {
|
||||
m, err := bot.Client.UploadVoice(target, bytes.NewReader(e.Data))
|
||||
m, err := bot.uploadLocalVoice(target, e)
|
||||
if err != nil {
|
||||
log.Warnf(uploadFailedTemplate, source, target.PrimaryID, "语音", err)
|
||||
} else {
|
||||
*p = m
|
||||
}
|
||||
})
|
||||
case *LocalVideoElement:
|
||||
case *msg.LocalVideo:
|
||||
w.do(func() {
|
||||
m, err := bot.uploadLocalVideo(target, e)
|
||||
if err != nil {
|
||||
@ -250,7 +260,6 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
|
||||
// SendGroupMessage 发送群消息
|
||||
func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) (int32, error) {
|
||||
newElem := make([]message.IMessageElement, 0, len(m.Elements))
|
||||
group := bot.Client.FindGroup(groupID)
|
||||
source := message.Source{
|
||||
SourceType: message.SourceGroup,
|
||||
PrimaryID: groupID,
|
||||
@ -258,39 +267,50 @@ 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:
|
||||
if group != nil {
|
||||
if mem := group.FindMember(i.Target); mem != nil {
|
||||
mem.Poke()
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
case *message.MusicShareElement:
|
||||
ret, err := bot.Client.SendGroupMusicShare(groupID, i)
|
||||
if err != nil {
|
||||
log.Warnf("警告: 群 %v 富文本消息发送失败: %v", groupID, err)
|
||||
return -1, errors.Wrap(err, "send group music share error")
|
||||
}
|
||||
return bot.InsertGroupMessage(ret), nil
|
||||
case *msg.Poke:
|
||||
return 0, bot.Client.GroupPoke(uint32(groupID), uint32(i.Target))
|
||||
// TODO 发送音乐卡片
|
||||
//case *message.MusicShareElement:
|
||||
// ret, err := bot.Client.SendGroupMusicShare(groupID, i)
|
||||
// if err != nil {
|
||||
// log.Warnf("警告: 群 %v 富文本消息发送失败: %v", groupID, err)
|
||||
// return -1, errors.Wrap(err, "send group music share error")
|
||||
// }
|
||||
// return bot.InsertGroupMessage(ret, source), nil
|
||||
case *message.AtElement:
|
||||
if i.Target == 0 && group.SelfPermission() == client.Member {
|
||||
e = message.NewText("@全体成员")
|
||||
if i.TargetUin == 0 {
|
||||
self := bot.Client.GetCachedMemberInfo(bot.Client.Uin, uint32(groupID))
|
||||
if self.Permission != entity.Member {
|
||||
e = message.NewText("@全体成员")
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
member := bot.Client.GetCachedMemberInfo(i.TargetUin, uint32(groupID))
|
||||
if member != nil {
|
||||
i.TargetUID = member.UID
|
||||
i.Display = "@" + member.DisplayName()
|
||||
}
|
||||
}
|
||||
}
|
||||
newElem = append(newElem, e)
|
||||
}
|
||||
if len(newElem) == 0 {
|
||||
log.Warnf("群消息发送失败: 消息为空.")
|
||||
log.Warnf("群 %v 消息发送失败: 消息为空.", groupID)
|
||||
return -1, errors.New("empty message")
|
||||
}
|
||||
m.Elements = newElem
|
||||
bot.checkMedia(newElem, groupID)
|
||||
ret := bot.Client.SendGroupMessage(groupID, m)
|
||||
if ret == nil || ret.Id == -1 {
|
||||
log.Warnf("群消息发送失败: 账号可能被风控.")
|
||||
bot.checkMedia(newElem, source)
|
||||
ret, err := bot.Client.SendGroupMessage(uint32(groupID), m.Elements, false)
|
||||
if err != nil || ret == nil {
|
||||
if errors.Is(err, sign.ErrVersionMismatch) {
|
||||
log.Warnf("群 %v 发送消息失败: 签名与当前协议版本不对应.", groupID)
|
||||
return -1, err
|
||||
}
|
||||
log.Warnf("群 %v 发送消息失败: 账号可能被风控.", groupID)
|
||||
return -1, errors.New("send group message failed: blocked by server")
|
||||
}
|
||||
return bot.InsertGroupMessage(ret), nil
|
||||
return bot.InsertGroupMessage(ret, source), nil
|
||||
}
|
||||
|
||||
// SendPrivateMessage 发送私聊消息
|
||||
@ -302,13 +322,16 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
|
||||
}
|
||||
m.Elements = bot.uploadMedia(source, m.Elements)
|
||||
for _, e := range m.Elements {
|
||||
//nolint:gocritic
|
||||
switch i := e.(type) {
|
||||
case *PokeElement:
|
||||
bot.Client.SendFriendPoke(i.Target)
|
||||
return 0
|
||||
case *message.MusicShareElement:
|
||||
bot.Client.SendFriendMusicShare(target, i)
|
||||
case *msg.Poke:
|
||||
_ = bot.Client.FriendPoke(uint32(i.Target))
|
||||
return 0
|
||||
|
||||
// TODO 音乐卡片
|
||||
//case *message.MusicShareElement:
|
||||
// bot.Client.SendFriendMusicShare(target, i)
|
||||
// return 0
|
||||
}
|
||||
newElem = append(newElem, e)
|
||||
}
|
||||
@ -317,7 +340,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
|
||||
return -1
|
||||
}
|
||||
m.Elements = newElem
|
||||
bot.checkMedia(newElem, bot.Client.Uin)
|
||||
bot.checkMedia(newElem, source)
|
||||
|
||||
// 单向好友是否存在
|
||||
unidirectionalFriendExists := func() bool {
|
||||
@ -326,140 +349,99 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
|
||||
return false
|
||||
}
|
||||
for _, f := range list {
|
||||
if f.Uin == target {
|
||||
if f.Uin == uint32(target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
session, ok := bot.tempSessionCache.Load(target)
|
||||
//session, ok := bot.tempSessionCache.Load(target)
|
||||
var id int32 = -1
|
||||
|
||||
switch {
|
||||
case bot.Client.FindFriend(target) != nil: // 双向好友
|
||||
msg := bot.Client.SendPrivateMessage(target, m)
|
||||
case bot.Client.GetCachedFriendInfo(uint32(target)) != nil: // 双向好友
|
||||
msg, _ := bot.Client.SendPrivateMessage(uint32(target), m.Elements)
|
||||
if msg != nil {
|
||||
id = bot.InsertPrivateMessage(msg)
|
||||
id = bot.InsertPrivateMessage(msg, source)
|
||||
}
|
||||
case ok || groupID != 0: // 临时会话
|
||||
case groupID != 0: // 临时会话
|
||||
if !base.AllowTempSession {
|
||||
log.Warnf("发送临时会话消息失败: 已关闭临时会话信息发送功能")
|
||||
return -1
|
||||
}
|
||||
group := bot.Client.GetCachedGroupInfo(uint32(groupID))
|
||||
switch {
|
||||
case groupID != 0 && bot.Client.FindGroup(groupID) == nil:
|
||||
case groupID != 0 && group == nil:
|
||||
log.Errorf("错误: 找不到群(%v)", groupID)
|
||||
case groupID != 0 && !bot.Client.FindGroup(groupID).AdministratorOrOwner():
|
||||
case groupID != 0 && bot.Client.GetCachedMemberInfo(bot.Client.Uin, group.GroupUin).Permission == entity.Member:
|
||||
log.Errorf("错误: 机器人在群(%v) 为非管理员或群主, 无法主动发起临时会话", groupID)
|
||||
case groupID != 0 && bot.Client.FindGroup(groupID).FindMember(target) == nil:
|
||||
case groupID != 0 && bot.Client.GetCachedMemberInfo(uint32(target), group.GroupUin) == nil:
|
||||
log.Errorf("错误: 群员(%v) 不在 群(%v), 无法发起临时会话", target, groupID)
|
||||
default:
|
||||
if session == nil && groupID != 0 {
|
||||
msg := bot.Client.SendGroupTempMessage(groupID, target, m)
|
||||
if groupID != 0 {
|
||||
ret, err := bot.Client.SendTempMessage(uint32(groupID), uint32(target), m.Elements)
|
||||
if err != nil {
|
||||
log.Errorf("发送临时会话消息失败: %v", err)
|
||||
break
|
||||
}
|
||||
//lint:ignore SA9003 there is a todo
|
||||
if msg != nil { // nolint
|
||||
if ret != nil { // nolint
|
||||
// todo(Mrs4s)
|
||||
// id = bot.InsertTempMessage(target, msg)
|
||||
}
|
||||
break
|
||||
}
|
||||
msg, err := session.SendMessage(m)
|
||||
if err != nil {
|
||||
log.Errorf("发送临时会话消息失败: %v", err)
|
||||
break
|
||||
}
|
||||
//lint:ignore SA9003 there is a todo
|
||||
if msg != nil { // nolint
|
||||
// todo(Mrs4s)
|
||||
// id = bot.InsertTempMessage(target, msg)
|
||||
}
|
||||
}
|
||||
case unidirectionalFriendExists(): // 单向好友
|
||||
msg := bot.Client.SendPrivateMessage(target, m)
|
||||
if msg != nil {
|
||||
id = bot.InsertPrivateMessage(msg)
|
||||
msg, err := bot.Client.SendPrivateMessage(uint32(target), m.Elements)
|
||||
if err == nil {
|
||||
id = bot.InsertPrivateMessage(msg, source)
|
||||
}
|
||||
default:
|
||||
nickname := "Unknown"
|
||||
if summaryInfo, _ := bot.Client.GetSummaryInfo(target); summaryInfo != nil {
|
||||
nickname = summaryInfo.Nickname
|
||||
if info, _ := bot.Client.FetchUserInfoUin(uint32(target)); info != nil {
|
||||
nickname = info.Nickname
|
||||
}
|
||||
log.Errorf("错误: 请先添加 %v(%v) 为好友", nickname, target)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// SendGuildChannelMessage 发送频道消息
|
||||
func (bot *CQBot) SendGuildChannelMessage(guildID, channelID uint64, m *message.SendingMessage) string {
|
||||
newElem := make([]message.IMessageElement, 0, len(m.Elements))
|
||||
source := message.Source{
|
||||
SourceType: message.SourceGuildChannel,
|
||||
PrimaryID: int64(guildID),
|
||||
SecondaryID: int64(channelID),
|
||||
}
|
||||
m.Elements = bot.uploadMedia(source, m.Elements)
|
||||
for _, e := range m.Elements {
|
||||
switch i := e.(type) {
|
||||
case *message.MusicShareElement:
|
||||
bot.Client.SendGuildMusicShare(guildID, channelID, i)
|
||||
return "-1" // todo: fix this
|
||||
|
||||
case *message.VoiceElement, *PokeElement:
|
||||
log.Warnf("警告: 频道暂不支持发送 %v 消息", i.Type().String())
|
||||
continue
|
||||
}
|
||||
newElem = append(newElem, e)
|
||||
}
|
||||
if len(newElem) == 0 {
|
||||
log.Warnf("频道消息发送失败: 消息为空.")
|
||||
return ""
|
||||
}
|
||||
m.Elements = newElem
|
||||
bot.checkMedia(newElem, bot.Client.Uin)
|
||||
ret, err := bot.Client.GuildService.SendGuildChannelMessage(guildID, channelID, m)
|
||||
if err != nil {
|
||||
log.Warnf("频道消息发送失败: %v", err)
|
||||
return ""
|
||||
}
|
||||
// todo: insert db
|
||||
return fmt.Sprintf("%v-%v", ret.Id, ret.InternalId)
|
||||
}
|
||||
|
||||
// InsertGroupMessage 群聊消息入数据库
|
||||
func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
|
||||
func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage, source message.Source) int32 {
|
||||
t := &message.SendingMessage{Elements: m.Elements}
|
||||
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
|
||||
_, ok := e.(*message.ReplyElement)
|
||||
return ok
|
||||
})
|
||||
msg := &db.StoredGroupMessage{
|
||||
ID: encodeMessageID(m.GroupCode, m.Id),
|
||||
GlobalID: db.ToGlobalID(m.GroupCode, m.Id),
|
||||
ID: encodeMessageID(int64(m.GroupUin), int32(m.ID)),
|
||||
GlobalID: db.ToGlobalID(int64(m.GroupUin), int32(m.ID)),
|
||||
SubType: "normal",
|
||||
Attribute: &db.StoredMessageAttribute{
|
||||
MessageSeq: m.Id,
|
||||
InternalID: m.InternalId,
|
||||
SenderUin: m.Sender.Uin,
|
||||
SenderName: m.Sender.DisplayName(),
|
||||
MessageSeq: int32(m.ID),
|
||||
InternalID: int32(m.InternalID),
|
||||
SenderUin: int64(m.Sender.Uin),
|
||||
SenderName: m.Sender.CardName,
|
||||
Timestamp: int64(m.Time),
|
||||
},
|
||||
GroupCode: m.GroupCode,
|
||||
GroupCode: int64(m.GroupUin),
|
||||
AnonymousID: func() string {
|
||||
if m.Sender.IsAnonymous() {
|
||||
return m.Sender.AnonymousInfo.AnonymousId
|
||||
return m.Sender.AnonymousInfo.AnonymousID
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
Content: ToMessageContent(m.Elements),
|
||||
Content: ToMessageContent(m.Elements, source),
|
||||
}
|
||||
if replyElem != nil {
|
||||
reply := replyElem.(*message.ReplyElement)
|
||||
msg.SubType = "quote"
|
||||
msg.QuotedInfo = &db.QuotedInfo{
|
||||
PrevID: encodeMessageID(m.GroupCode, reply.ReplySeq),
|
||||
PrevGlobalID: db.ToGlobalID(m.GroupCode, reply.ReplySeq),
|
||||
QuotedContent: ToMessageContent(reply.Elements),
|
||||
PrevID: encodeMessageID(int64(m.GroupUin), int32(reply.ReplySeq)),
|
||||
PrevGlobalID: db.ToGlobalID(int64(m.GroupUin), int32(reply.ReplySeq)),
|
||||
QuotedContent: ToMessageContent(reply.Elements, source),
|
||||
}
|
||||
}
|
||||
if err := db.InsertGroupMessage(msg); err != nil {
|
||||
@ -470,39 +452,40 @@ func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
|
||||
}
|
||||
|
||||
// InsertPrivateMessage 私聊消息入数据库
|
||||
func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 {
|
||||
func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage, source message.Source) int32 {
|
||||
t := &message.SendingMessage{Elements: m.Elements}
|
||||
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
|
||||
_, ok := e.(*message.ReplyElement)
|
||||
return ok
|
||||
})
|
||||
msg := &db.StoredPrivateMessage{
|
||||
ID: encodeMessageID(m.Sender.Uin, m.Id),
|
||||
GlobalID: db.ToGlobalID(m.Sender.Uin, m.Id),
|
||||
ID: encodeMessageID(int64(m.Sender.Uin), int32(m.ID)),
|
||||
GlobalID: db.ToGlobalID(int64(m.Sender.Uin), int32(m.ID)),
|
||||
SubType: "normal",
|
||||
Attribute: &db.StoredMessageAttribute{
|
||||
MessageSeq: m.Id,
|
||||
InternalID: m.InternalId,
|
||||
SenderUin: m.Sender.Uin,
|
||||
SenderName: m.Sender.DisplayName(),
|
||||
MessageSeq: int32(m.ID),
|
||||
ClientSeq: int32(m.ClientSeq),
|
||||
InternalID: int32(m.InternalID),
|
||||
SenderUin: int64(m.Sender.Uin),
|
||||
SenderName: m.Sender.Nickname,
|
||||
Timestamp: int64(m.Time),
|
||||
},
|
||||
SessionUin: func() int64 {
|
||||
if m.Sender.Uin == m.Self {
|
||||
return m.Target
|
||||
return int64(m.Target)
|
||||
}
|
||||
return m.Sender.Uin
|
||||
return int64(m.Sender.Uin)
|
||||
}(),
|
||||
TargetUin: m.Target,
|
||||
Content: ToMessageContent(m.Elements),
|
||||
TargetUin: int64(m.Target),
|
||||
Content: ToMessageContent(m.Elements, source),
|
||||
}
|
||||
if replyElem != nil {
|
||||
reply := replyElem.(*message.ReplyElement)
|
||||
msg.SubType = "quote"
|
||||
msg.QuotedInfo = &db.QuotedInfo{
|
||||
PrevID: encodeMessageID(reply.Sender, reply.ReplySeq),
|
||||
PrevGlobalID: db.ToGlobalID(reply.Sender, reply.ReplySeq),
|
||||
QuotedContent: ToMessageContent(reply.Elements),
|
||||
PrevID: encodeMessageID(int64(reply.SenderUin), int32(reply.ReplySeq)),
|
||||
PrevGlobalID: db.ToGlobalID(int64(reply.SenderUin), int32(reply.ReplySeq)),
|
||||
QuotedContent: ToMessageContent(reply.Elements, source),
|
||||
}
|
||||
}
|
||||
if err := db.InsertPrivateMessage(msg); err != nil {
|
||||
@ -512,59 +495,6 @@ func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 {
|
||||
return msg.GlobalID
|
||||
}
|
||||
|
||||
/*
|
||||
// InsertTempMessage 临时消息入数据库
|
||||
func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32 {
|
||||
val := global.MSG{
|
||||
"message-id": m.Id,
|
||||
// FIXME(InsertTempMessage) InternalId missing
|
||||
"from-group": m.GroupCode,
|
||||
"group-name": m.GroupName,
|
||||
"target": target,
|
||||
"sender": m.Sender,
|
||||
"time": int32(time.Now().Unix()),
|
||||
"message": ToStringMessage(m.Elements, 0, true),
|
||||
}
|
||||
id := db.ToGlobalID(m.Sender.Uin, m.Id)
|
||||
if bot.db != nil {
|
||||
buf := global.NewBuffer()
|
||||
defer global.PutBuffer(buf)
|
||||
if err := gob.NewEncoder(buf).Encode(val); err != nil {
|
||||
log.Warnf("记录聊天数据时出现错误: %v", err)
|
||||
return -1
|
||||
}
|
||||
if err := bot.db.Put(binary.ToBytes(id), buf.Bytes(), nil); err != nil {
|
||||
log.Warnf("记录聊天数据时出现错误: %v", err)
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
*/
|
||||
|
||||
// InsertGuildChannelMessage 频道消息入数据库
|
||||
func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) string {
|
||||
id := encodeGuildMessageID(m.GuildId, m.ChannelId, m.Id, message.SourceGuildChannel)
|
||||
msg := &db.StoredGuildChannelMessage{
|
||||
ID: id,
|
||||
Attribute: &db.StoredGuildMessageAttribute{
|
||||
MessageSeq: m.Id,
|
||||
InternalID: m.InternalId,
|
||||
SenderTinyID: m.Sender.TinyId,
|
||||
SenderName: m.Sender.Nickname,
|
||||
Timestamp: m.Time,
|
||||
},
|
||||
GuildID: m.GuildId,
|
||||
ChannelID: m.ChannelId,
|
||||
Content: ToMessageContent(m.Elements),
|
||||
}
|
||||
if err := db.InsertGuildChannelMessage(msg); err != nil {
|
||||
log.Warnf("记录聊天数据时出现错误: %v", err)
|
||||
return ""
|
||||
}
|
||||
return msg.ID
|
||||
}
|
||||
|
||||
func (bot *CQBot) event(typ string, others global.MSG) *event {
|
||||
ev := new(event)
|
||||
post, detail, ok := strings.Cut(typ, "/")
|
||||
@ -576,7 +506,7 @@ func (bot *CQBot) event(typ string, others global.MSG) *event {
|
||||
ev.SubType = sub
|
||||
}
|
||||
ev.Time = time.Now().Unix()
|
||||
ev.SelfID = bot.Client.Uin
|
||||
ev.SelfID = int64(bot.Client.Uin)
|
||||
ev.Others = others
|
||||
return ev
|
||||
}
|
||||
@ -615,11 +545,11 @@ func (bot *CQBot) dispatch(ev *event) {
|
||||
}
|
||||
}
|
||||
|
||||
func formatGroupName(group *client.GroupInfo) string {
|
||||
return fmt.Sprintf("%s(%d)", group.Name, group.Code)
|
||||
func formatGroupName(group *entity.Group) string {
|
||||
return fmt.Sprintf("%s(%d)", group.GroupName, group.GroupUin)
|
||||
}
|
||||
|
||||
func formatMemberName(mem *client.GroupMemberInfo) string {
|
||||
func formatMemberName(mem *entity.GroupMember) string {
|
||||
if mem == nil {
|
||||
return "未知"
|
||||
}
|
||||
@ -628,35 +558,8 @@ func formatMemberName(mem *client.GroupMemberInfo) string {
|
||||
|
||||
// encodeMessageID 临时先这样, 暂时用不上
|
||||
func encodeMessageID(target int64, seq int32) string {
|
||||
return hex.EncodeToString(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt64(uint64(target))
|
||||
w.WriteUInt32(uint32(seq))
|
||||
return hex.EncodeToString(binary.NewWriterF(func(w *binary.Builder) {
|
||||
w.WriteU64(uint64(target))
|
||||
w.WriteU32(uint32(seq))
|
||||
}))
|
||||
}
|
||||
|
||||
// encodeGuildMessageID 将频道信息编码为字符串
|
||||
// 当信息来源为 Channel 时 primaryID 为 guildID , subID 为 channelID
|
||||
// 当信息来源为 Direct 时 primaryID 为 guildID , subID 为 tinyID
|
||||
func encodeGuildMessageID(primaryID, subID, seq uint64, source message.SourceType) string {
|
||||
return base64.StdEncoding.EncodeToString(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteByte(byte(source))
|
||||
w.WriteUInt64(primaryID)
|
||||
w.WriteUInt64(subID)
|
||||
w.WriteUInt64(seq)
|
||||
}))
|
||||
}
|
||||
|
||||
func decodeGuildMessageID(id string) (source message.Source, seq uint64) {
|
||||
b, _ := base64.StdEncoding.DecodeString(id)
|
||||
if len(b) < 25 {
|
||||
return
|
||||
}
|
||||
r := binary.NewReader(b)
|
||||
source = message.Source{
|
||||
SourceType: message.SourceType(r.ReadByte()),
|
||||
PrimaryID: r.ReadInt64(),
|
||||
SecondaryID: r.ReadInt64(),
|
||||
}
|
||||
seq = uint64(r.ReadInt64())
|
||||
return
|
||||
}
|
||||
|
@ -4,41 +4,40 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/topic"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/LagrangeDev/LagrangeGo/client/entity"
|
||||
"github.com/LagrangeDev/LagrangeGo/message"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG {
|
||||
func convertGroupMemberInfo(groupID int64, m *entity.GroupMember) global.MSG {
|
||||
sex := "unknown"
|
||||
if m.Gender == 1 { // unknown = 0xff
|
||||
sex = "female"
|
||||
} else if m.Gender == 0 {
|
||||
sex = "male"
|
||||
}
|
||||
//if m.Gender == 1 { // unknown = 0xff
|
||||
// sex = "female"
|
||||
//} else if m.Gender == 0 {
|
||||
// sex = "male"
|
||||
//}
|
||||
role := "member"
|
||||
switch m.Permission { // nolint:exhaustive
|
||||
case client.Owner:
|
||||
case entity.Owner:
|
||||
role = "owner"
|
||||
case client.Administrator:
|
||||
case entity.Admin:
|
||||
role = "admin"
|
||||
case entity.Member:
|
||||
role = "member"
|
||||
}
|
||||
return global.MSG{
|
||||
"group_id": groupID,
|
||||
"user_id": m.Uin,
|
||||
"nickname": m.Nickname,
|
||||
"card": m.CardName,
|
||||
"card": m.MemberCard,
|
||||
"sex": sex,
|
||||
"age": 0,
|
||||
"area": "",
|
||||
"join_time": m.JoinTime,
|
||||
"last_sent_time": m.LastSpeakTime,
|
||||
"shut_up_timestamp": m.ShutUpTimestamp,
|
||||
"level": strconv.FormatInt(int64(m.Level), 10),
|
||||
"last_sent_time": m.LastMsgTime,
|
||||
"shut_up_timestamp": m.ShutUpTime,
|
||||
"level": strconv.Itoa(int(m.GroupLevel)),
|
||||
"role": role,
|
||||
"unfriendly": false,
|
||||
"title": m.SpecialTitle,
|
||||
@ -47,23 +46,10 @@ func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG
|
||||
}
|
||||
}
|
||||
|
||||
func convertGuildMemberInfo(m []*client.GuildMemberInfo) (r []global.MSG) {
|
||||
for _, mem := range m {
|
||||
r = append(r, global.MSG{
|
||||
"tiny_id": fU64(mem.TinyId),
|
||||
"title": mem.Title,
|
||||
"nickname": mem.Nickname,
|
||||
"role_id": fU64(mem.Role),
|
||||
"role_name": mem.RoleName,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
|
||||
source := message.Source{
|
||||
SourceType: message.SourceGroup,
|
||||
PrimaryID: m.GroupCode,
|
||||
PrimaryID: int64(m.GroupUin),
|
||||
}
|
||||
cqm := toStringMessage(m.Elements, source)
|
||||
typ := "message/group/normal"
|
||||
@ -73,9 +59,9 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
|
||||
gm := global.MSG{
|
||||
"anonymous": nil,
|
||||
"font": 0,
|
||||
"group_id": m.GroupCode,
|
||||
"group_id": m.GroupUin,
|
||||
"message": ToFormattedMessage(m.Elements, source),
|
||||
"message_seq": m.Id,
|
||||
"message_seq": m.ID,
|
||||
"raw_message": cqm,
|
||||
"sender": global.MSG{
|
||||
"age": 0,
|
||||
@ -88,24 +74,22 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
|
||||
}
|
||||
if m.Sender.IsAnonymous() {
|
||||
gm["anonymous"] = global.MSG{
|
||||
"flag": m.Sender.AnonymousInfo.AnonymousId + "|" + m.Sender.AnonymousInfo.AnonymousNick,
|
||||
"flag": m.Sender.AnonymousInfo.AnonymousID + "|" + m.Sender.AnonymousInfo.AnonymousNick,
|
||||
"id": m.Sender.Uin,
|
||||
"name": m.Sender.AnonymousInfo.AnonymousNick,
|
||||
}
|
||||
gm["sender"].(global.MSG)["nickname"] = "匿名消息"
|
||||
gm["sub_type"] = "anonymous"
|
||||
typ = "message/group/anonymous"
|
||||
} else {
|
||||
group := bot.Client.FindGroup(m.GroupCode)
|
||||
mem := group.FindMember(m.Sender.Uin)
|
||||
mem := bot.Client.GetCachedMemberInfo(m.Sender.Uin, m.GroupUin)
|
||||
if mem == nil {
|
||||
log.Warnf("获取 %v 成员信息失败,尝试刷新成员列表", m.Sender.Uin)
|
||||
t, err := bot.Client.GetGroupMembers(group)
|
||||
err := bot.Client.RefreshGroupMembersCache(m.GroupUin)
|
||||
if err != nil {
|
||||
log.Warnf("刷新群 %v 成员列表失败: %v", group.Uin, err)
|
||||
log.Warnf("刷新群 %v 成员列表失败: %v", m.GroupUin, err)
|
||||
return nil
|
||||
}
|
||||
group.Members = t
|
||||
mem = group.FindMember(m.Sender.Uin)
|
||||
mem = bot.Client.GetCachedMemberInfo(m.Sender.Uin, m.GroupUin)
|
||||
if mem == nil {
|
||||
return nil
|
||||
}
|
||||
@ -113,102 +97,24 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
|
||||
ms := gm["sender"].(global.MSG)
|
||||
role := "member"
|
||||
switch mem.Permission { // nolint:exhaustive
|
||||
case client.Owner:
|
||||
case entity.Owner:
|
||||
role = "owner"
|
||||
case client.Administrator:
|
||||
case entity.Admin:
|
||||
role = "admin"
|
||||
case entity.Member:
|
||||
role = "member"
|
||||
}
|
||||
ms["role"] = role
|
||||
ms["nickname"] = mem.Nickname
|
||||
ms["card"] = mem.CardName
|
||||
ms["title"] = mem.SpecialTitle
|
||||
ms["nickname"] = m.Sender.Nickname
|
||||
ms["card"] = m.Sender.CardName
|
||||
// TODO 获取专属头衔
|
||||
ms["title"] = ""
|
||||
}
|
||||
ev := bot.event(typ, gm)
|
||||
ev.Time = int64(m.Time)
|
||||
return ev
|
||||
}
|
||||
|
||||
func convertChannelInfo(c *client.ChannelInfo) global.MSG {
|
||||
slowModes := make([]global.MSG, 0, len(c.Meta.SlowModes))
|
||||
for _, mode := range c.Meta.SlowModes {
|
||||
slowModes = append(slowModes, global.MSG{
|
||||
"slow_mode_key": mode.SlowModeKey,
|
||||
"slow_mode_text": mode.SlowModeText,
|
||||
"speak_frequency": mode.SpeakFrequency,
|
||||
"slow_mode_circle": mode.SlowModeCircle,
|
||||
})
|
||||
}
|
||||
return global.MSG{
|
||||
"channel_id": fU64(c.ChannelId),
|
||||
"channel_type": c.ChannelType,
|
||||
"channel_name": c.ChannelName,
|
||||
"owner_guild_id": fU64(c.Meta.GuildId),
|
||||
"creator_tiny_id": fU64(c.Meta.CreatorTinyId),
|
||||
"create_time": c.Meta.CreateTime,
|
||||
"current_slow_mode": c.Meta.CurrentSlowMode,
|
||||
"talk_permission": c.Meta.TalkPermission,
|
||||
"visible_type": c.Meta.VisibleType,
|
||||
"slow_modes": slowModes,
|
||||
}
|
||||
}
|
||||
|
||||
func convertChannelFeedInfo(f *topic.Feed) global.MSG {
|
||||
m := global.MSG{
|
||||
"id": f.Id,
|
||||
"title": f.Title,
|
||||
"sub_title": f.SubTitle,
|
||||
"create_time": f.CreateTime,
|
||||
"guild_id": fU64(f.GuildId),
|
||||
"channel_id": fU64(f.ChannelId),
|
||||
"poster_info": global.MSG{
|
||||
"tiny_id": f.Poster.TinyIdStr,
|
||||
"nickname": f.Poster.Nickname,
|
||||
"icon_url": f.Poster.IconUrl,
|
||||
},
|
||||
"contents": FeedContentsToArrayMessage(f.Contents),
|
||||
}
|
||||
images := make([]global.MSG, 0, len(f.Images))
|
||||
videos := make([]global.MSG, 0, len(f.Videos))
|
||||
for _, image := range f.Images {
|
||||
images = append(images, global.MSG{
|
||||
"file_id": image.FileId,
|
||||
"pattern_id": image.PatternId,
|
||||
"url": image.Url,
|
||||
"width": image.Width,
|
||||
"height": image.Height,
|
||||
})
|
||||
}
|
||||
for _, video := range f.Videos {
|
||||
videos = append(videos, global.MSG{
|
||||
"file_id": video.FileId,
|
||||
"pattern_id": video.PatternId,
|
||||
"url": video.Url,
|
||||
"width": video.Width,
|
||||
"height": video.Height,
|
||||
})
|
||||
}
|
||||
m["resource"] = global.MSG{
|
||||
"images": images,
|
||||
"videos": videos,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func convertReactions(reactions []*message.GuildMessageEmojiReaction) (r []global.MSG) {
|
||||
r = make([]global.MSG, len(reactions))
|
||||
for i, re := range reactions {
|
||||
r[i] = global.MSG{
|
||||
"emoji_id": re.EmojiId,
|
||||
"emoji_index": re.Face.Index,
|
||||
"emoji_type": re.EmojiType,
|
||||
"emoji_name": re.Face.Name,
|
||||
"count": re.Count,
|
||||
"clicked": re.Clicked,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toStringMessage(m []message.IMessageElement, source message.Source) string {
|
||||
elems := toElements(m, source)
|
||||
var sb strings.Builder
|
||||
@ -218,6 +124,7 @@ func toStringMessage(m []message.IMessageElement, source message.Source) string
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func fU64(v uint64) string {
|
||||
return strconv.FormatUint(v, 10)
|
||||
}
|
||||
|
1522
coolq/cqcode.go
1522
coolq/cqcode.go
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@
|
||||
package cqcode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
)
|
||||
|
||||
// Element single message
|
||||
type Element struct {
|
||||
Type string
|
||||
Data []Pair
|
||||
}
|
||||
|
||||
// Pair key value pair
|
||||
type Pair struct {
|
||||
K string
|
||||
V string
|
||||
}
|
||||
|
||||
// CQCode convert element to cqcode
|
||||
func (e *Element) CQCode() string {
|
||||
buf := strings.Builder{}
|
||||
e.WriteCQCodeTo(&buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// WriteCQCodeTo write element's cqcode into sb
|
||||
func (e *Element) WriteCQCodeTo(sb *strings.Builder) {
|
||||
if e.Type == "text" {
|
||||
sb.WriteString(EscapeText(e.Data[0].V)) // must be {"text": value}
|
||||
return
|
||||
}
|
||||
sb.WriteString("[CQ:")
|
||||
sb.WriteString(e.Type)
|
||||
for _, data := range e.Data {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteString(data.K)
|
||||
sb.WriteByte('=')
|
||||
sb.WriteString(EscapeValue(data.V))
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
}
|
||||
|
||||
// MarshalJSON see encoding/json.Marshaler
|
||||
func (e *Element) MarshalJSON() ([]byte, error) {
|
||||
return binary.NewWriterF(func(w *binary.Writer) {
|
||||
buf := (*bytes.Buffer)(w)
|
||||
// fmt.Fprintf(buf, `{"type":"%s","data":{`, e.Type)
|
||||
buf.WriteString(`{"type":"`)
|
||||
buf.WriteString(e.Type)
|
||||
buf.WriteString(`","data":{`)
|
||||
for i, data := range e.Data {
|
||||
if i != 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
// fmt.Fprintf(buf, `"%s":%q`, data.K, data.V)
|
||||
buf.WriteByte('"')
|
||||
buf.WriteString(data.K)
|
||||
buf.WriteString(`":`)
|
||||
buf.WriteString(global.Quote(data.V))
|
||||
}
|
||||
buf.WriteString(`}}`)
|
||||
}), nil
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
// Package cqcode provides CQCode util functions.
|
||||
package cqcode
|
||||
|
||||
import "strings"
|
||||
|
||||
// EscapeText 将字符串raw中部分字符转义
|
||||
//
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
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("&")
|
||||
case '[':
|
||||
b.WriteString("[")
|
||||
case ']':
|
||||
b.WriteString("]")
|
||||
}
|
||||
start = j + 1
|
||||
}
|
||||
b.WriteString(s[start:])
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// EscapeValue 将字符串value中部分字符转义
|
||||
//
|
||||
// - , -> ,
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
func EscapeValue(value string) string {
|
||||
ret := EscapeText(value)
|
||||
return strings.ReplaceAll(ret, ",", ",")
|
||||
}
|
||||
|
||||
// UnescapeText 将字符串content中部分字符反转义
|
||||
//
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
func UnescapeText(content string) string {
|
||||
ret := content
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
ret = strings.ReplaceAll(ret, "]", "]")
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
return ret
|
||||
}
|
||||
|
||||
// UnescapeValue 将字符串content中部分字符反转义
|
||||
//
|
||||
// - , -> ,
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
func UnescapeValue(content string) string {
|
||||
ret := strings.ReplaceAll(content, ",", ",")
|
||||
return UnescapeText(ret)
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package coolq
|
||||
|
||||
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) {
|
||||
fmt.Println(v)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
bench = `asdfqwerqwerqwer[CQ:face,id=115,text=111]asdfasdfasdfasdfasdfasdfasd[CQ:face,id=217]] 123 [`
|
||||
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) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bot.ConvertStringMessage(bench, message.SourcePrivate)
|
||||
}
|
||||
b.SetBytes(int64(len(bench)))
|
||||
}
|
||||
|
||||
func BenchmarkCQBot_ConvertObjectMessage(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bot.ConvertObjectMessage(benchArray, message.SourcePrivate)
|
||||
}
|
||||
}
|
||||
|
||||
const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&987654321[]&`
|
||||
|
||||
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, "&", "&")
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
strings.ReplaceAll(ret, "]", "]")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCQCodeEscapeText(t *testing.T) {
|
||||
for i := 0; i < 200; i++ {
|
||||
rs := utils.RandomStringRange(3000, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890[]&")
|
||||
ret := rs
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
ret = strings.ReplaceAll(ret, "]", "]")
|
||||
assert.Equal(t, ret, cqcode.EscapeText(rs))
|
||||
}
|
||||
}
|
649
coolq/event.go
649
coolq/event.go
@ -8,9 +8,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/LagrangeDev/LagrangeGo/client"
|
||||
"github.com/LagrangeDev/LagrangeGo/client/entity"
|
||||
event2 "github.com/LagrangeDev/LagrangeGo/client/event"
|
||||
"github.com/LagrangeDev/LagrangeGo/message"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/binary"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
@ -21,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
// ToFormattedMessage 将给定[]message.IMessageElement转换为通过coolq.SetMessageFormat所定义的消息上报格式
|
||||
func ToFormattedMessage(e []message.IMessageElement, source message.Source) (r interface{}) {
|
||||
func ToFormattedMessage(e []message.IMessageElement, source message.Source) (r any) {
|
||||
if base.PostFormat == "string" {
|
||||
r = toStringMessage(e, source)
|
||||
} else if base.PostFormat == "array" {
|
||||
@ -73,14 +75,14 @@ func (ev *event) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (bot *CQBot) privateMessageEvent(_ *client.QQClient, m *message.PrivateMessage) {
|
||||
bot.checkMedia(m.Elements, m.Sender.Uin)
|
||||
source := message.Source{
|
||||
SourceType: message.SourcePrivate,
|
||||
PrimaryID: m.Sender.Uin,
|
||||
PrimaryID: int64(m.Sender.Uin),
|
||||
}
|
||||
bot.checkMedia(m.Elements, source)
|
||||
cqm := toStringMessage(m.Elements, source)
|
||||
id := bot.InsertPrivateMessage(m)
|
||||
log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
|
||||
id := bot.InsertPrivateMessage(m, source)
|
||||
log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.Nickname, m.Sender.Uin, cqm, id)
|
||||
typ := "message/private/friend"
|
||||
if m.Sender.Uin == bot.Client.Uin {
|
||||
typ = "message_sent/private/friend"
|
||||
@ -102,32 +104,33 @@ func (bot *CQBot) privateMessageEvent(_ *client.QQClient, m *message.PrivateMess
|
||||
bot.dispatchEvent(typ, fm)
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage) {
|
||||
bot.checkMedia(m.Elements, m.GroupCode)
|
||||
for _, elem := range m.Elements {
|
||||
if file, ok := elem.(*message.GroupFileElement); ok {
|
||||
log.Infof("群 %v(%v) 内 %v(%v) 上传了文件: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, file.Name)
|
||||
bot.dispatchEvent("notice/group_upload", global.MSG{
|
||||
"group_id": m.GroupCode,
|
||||
"user_id": m.Sender.Uin,
|
||||
"file": global.MSG{
|
||||
"id": file.Path,
|
||||
"name": file.Name,
|
||||
"size": file.Size,
|
||||
"busid": file.Busid,
|
||||
"url": c.GetGroupFileUrl(m.GroupCode, file.Path, file.Busid),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
func (bot *CQBot) groupMessageEvent(_ *client.QQClient, m *message.GroupMessage) {
|
||||
source := message.Source{
|
||||
SourceType: message.SourceGroup,
|
||||
PrimaryID: m.GroupCode,
|
||||
PrimaryID: int64(m.GroupUin),
|
||||
}
|
||||
bot.checkMedia(m.Elements, source)
|
||||
// TODO 群聊文件上传
|
||||
//for _, elem := range m.Elements {
|
||||
// if file, ok := elem.(*message.GroupFileElement); ok {
|
||||
// log.Infof("群 %v(%v) 内 %v(%v) 上传了文件: %v", m.GroupName, m.GroupCode, m.Sender.CardName, m.Sender.Uin, file.Name)
|
||||
// bot.dispatchEvent("notice/group_upload", global.MSG{
|
||||
// "group_id": m.GroupCode,
|
||||
// "user_id": m.Sender.Uin,
|
||||
// "file": global.MSG{
|
||||
// "id": file.Path,
|
||||
// "name": file.Name,
|
||||
// "size": file.Size,
|
||||
// "busid": file.Busid,
|
||||
// "url": c.GetGroupFileUrl(m.GroupCode, file.Path, file.Busid),
|
||||
// },
|
||||
// })
|
||||
// // return
|
||||
// }
|
||||
//}
|
||||
cqm := toStringMessage(m.Elements, source)
|
||||
id := bot.InsertGroupMessage(m)
|
||||
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
|
||||
id := bot.InsertGroupMessage(m, source)
|
||||
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupUin, m.Sender.CardName, m.Sender.Uin, cqm, id)
|
||||
gm := bot.formatGroupMessage(m)
|
||||
if gm == nil {
|
||||
return
|
||||
@ -136,32 +139,35 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
|
||||
bot.dispatch(gm)
|
||||
}
|
||||
|
||||
func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEvent) {
|
||||
m := e.Message
|
||||
bot.checkMedia(m.Elements, m.Sender.Uin)
|
||||
func (bot *CQBot) tempMessageEvent(_ *client.QQClient, e *message.TempMessage) {
|
||||
source := message.Source{
|
||||
SourceType: message.SourcePrivate,
|
||||
PrimaryID: e.Session.Sender,
|
||||
PrimaryID: int64(e.Sender.Uin),
|
||||
}
|
||||
cqm := toStringMessage(m.Elements, source)
|
||||
bot.tempSessionCache.Store(m.Sender.Uin, e.Session)
|
||||
id := m.Id
|
||||
bot.checkMedia(e.Elements, source)
|
||||
|
||||
cqm := toStringMessage(e.Elements, source)
|
||||
//if base.AllowTempSession {
|
||||
// bot.tempSessionCache.Store(e.Sender.Uin, e.Session)
|
||||
//}
|
||||
|
||||
id := e.ID
|
||||
// todo(Mrs4s)
|
||||
// if bot.db != nil { // nolint
|
||||
// id = bot.InsertTempMessage(m.Sender.Uin, m)
|
||||
// }
|
||||
log.Infof("收到来自群 %v(%v) 内 %v(%v) 的临时会话消息: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm)
|
||||
log.Infof("收到来自群 %v(%v) 内 %v(%v) 的临时会话消息: %v", e.GroupName, e.GroupName, e.Sender.Nickname, e.Sender.Uin, cqm)
|
||||
tm := global.MSG{
|
||||
"temp_source": e.Session.Source,
|
||||
//"temp_source": e.Session.Source,
|
||||
"message_id": id,
|
||||
"user_id": m.Sender.Uin,
|
||||
"message": ToFormattedMessage(m.Elements, source),
|
||||
"user_id": e.Sender.Uin,
|
||||
"message": ToFormattedMessage(e.Elements, source),
|
||||
"raw_message": cqm,
|
||||
"font": 0,
|
||||
"sender": global.MSG{
|
||||
"user_id": m.Sender.Uin,
|
||||
"group_id": m.GroupCode,
|
||||
"nickname": m.Sender.Nickname,
|
||||
"user_id": e.Sender.Uin,
|
||||
"group_id": e.GroupUin,
|
||||
"nickname": e.Sender.Nickname,
|
||||
"sex": "unknown",
|
||||
"age": 0,
|
||||
},
|
||||
@ -169,252 +175,116 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
|
||||
bot.dispatchEvent("message/private/group", tm)
|
||||
}
|
||||
|
||||
func (bot *CQBot) guildChannelMessageEvent(c *client.QQClient, m *message.GuildChannelMessage) {
|
||||
bot.checkMedia(m.Elements, int64(m.Sender.TinyId))
|
||||
guild := c.GuildService.FindGuild(m.GuildId)
|
||||
if guild == nil {
|
||||
return
|
||||
}
|
||||
channel := guild.FindChannel(m.ChannelId)
|
||||
source := message.Source{
|
||||
SourceType: message.SourceGuildChannel,
|
||||
PrimaryID: int64(m.GuildId),
|
||||
SecondaryID: int64(m.ChannelId),
|
||||
}
|
||||
log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, toStringMessage(m.Elements, source))
|
||||
id := bot.InsertGuildChannelMessage(m)
|
||||
ev := bot.event("message/guild/channel", global.MSG{
|
||||
"guild_id": fU64(m.GuildId),
|
||||
"channel_id": fU64(m.ChannelId),
|
||||
"message_id": id,
|
||||
"user_id": fU64(m.Sender.TinyId),
|
||||
"message": ToFormattedMessage(m.Elements, source), // todo: 增加对频道消息 Reply 的支持
|
||||
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
|
||||
"sender": global.MSG{
|
||||
"user_id": m.Sender.TinyId,
|
||||
"tiny_id": fU64(m.Sender.TinyId),
|
||||
"nickname": m.Sender.Nickname,
|
||||
},
|
||||
})
|
||||
ev.Time = m.Time
|
||||
bot.dispatch(ev)
|
||||
}
|
||||
|
||||
func (bot *CQBot) guildMessageReactionsUpdatedEvent(c *client.QQClient, e *client.GuildMessageReactionsUpdatedEvent) {
|
||||
guild := c.GuildService.FindGuild(e.GuildId)
|
||||
if guild == nil {
|
||||
return
|
||||
}
|
||||
msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, message.SourceGuildChannel)
|
||||
str := fmt.Sprintf("频道 %v(%v) 消息 %v 表情贴片已更新: ", guild.GuildName, guild.GuildId, msgID)
|
||||
currentReactions := make([]global.MSG, len(e.CurrentReactions))
|
||||
for i, r := range e.CurrentReactions {
|
||||
str += fmt.Sprintf("%v*%v ", r.Face.Name, r.Count)
|
||||
currentReactions[i] = global.MSG{
|
||||
"emoji_id": r.EmojiId,
|
||||
"emoji_index": r.Face.Index,
|
||||
"emoji_type": r.EmojiType,
|
||||
"emoji_name": r.Face.Name,
|
||||
"count": r.Count,
|
||||
"clicked": r.Clicked,
|
||||
}
|
||||
}
|
||||
if len(e.CurrentReactions) == 0 {
|
||||
str += "无任何表情"
|
||||
}
|
||||
log.Infof(str)
|
||||
bot.dispatchEvent("notice/message_reactions_updated", global.MSG{
|
||||
"guild_id": fU64(e.GuildId),
|
||||
"channel_id": fU64(e.ChannelId),
|
||||
"message_id": msgID,
|
||||
"operator_id": fU64(e.OperatorId),
|
||||
"current_reactions": currentReactions,
|
||||
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
|
||||
"user_id": e.OperatorId,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) guildChannelMessageRecalledEvent(c *client.QQClient, e *client.GuildMessageRecalledEvent) {
|
||||
guild := c.GuildService.FindGuild(e.GuildId)
|
||||
if guild == nil {
|
||||
return
|
||||
}
|
||||
channel := guild.FindChannel(e.ChannelId)
|
||||
if channel == nil {
|
||||
return
|
||||
}
|
||||
operator, err := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId)
|
||||
if err != nil {
|
||||
log.Errorf("处理频道撤回事件时出现错误: 获取操作者资料时出现错误 %v", err)
|
||||
return
|
||||
}
|
||||
msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, message.SourceGuildChannel)
|
||||
log.Infof("用户 %v(%v) 撤回了频道 %v(%v) 子频道 %v(%v) 的消息 %v", operator.Nickname, operator.TinyId, guild.GuildName, guild.GuildId, channel.ChannelName, channel.ChannelId, msgID)
|
||||
bot.dispatchEvent("notice/guild_channel_recall", global.MSG{
|
||||
"guild_id": fU64(e.GuildId),
|
||||
"channel_id": fU64(e.ChannelId),
|
||||
"operator_id": fU64(e.OperatorId),
|
||||
"message_id": msgID,
|
||||
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
|
||||
"user_id": e.OperatorId,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) guildChannelUpdatedEvent(c *client.QQClient, e *client.GuildChannelUpdatedEvent) {
|
||||
guild := c.GuildService.FindGuild(e.GuildId)
|
||||
if guild == nil {
|
||||
return
|
||||
}
|
||||
log.Infof("频道 %v(%v) 子频道 %v(%v) 信息已更新", guild.GuildName, guild.GuildId, e.NewChannelInfo.ChannelName, e.NewChannelInfo.ChannelId)
|
||||
bot.dispatchEvent("notice/channel_updated", global.MSG{
|
||||
"guild_id": fU64(e.GuildId),
|
||||
"channel_id": fU64(e.ChannelId),
|
||||
"operator_id": fU64(e.OperatorId),
|
||||
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
|
||||
"user_id": e.OperatorId,
|
||||
"old_info": convertChannelInfo(e.OldChannelInfo),
|
||||
"new_info": convertChannelInfo(e.NewChannelInfo),
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) guildChannelCreatedEvent(c *client.QQClient, e *client.GuildChannelOperationEvent) {
|
||||
guild := c.GuildService.FindGuild(e.GuildId)
|
||||
if guild == nil {
|
||||
return
|
||||
}
|
||||
member, _ := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId)
|
||||
if member == nil {
|
||||
member = &client.GuildUserProfile{Nickname: "未知"}
|
||||
}
|
||||
log.Infof("频道 %v(%v) 内用户 %v(%v) 创建了子频道 %v(%v)", guild.GuildName, guild.GuildId, member.Nickname, member.TinyId, e.ChannelInfo.ChannelName, e.ChannelInfo.ChannelId)
|
||||
bot.dispatchEvent("notice/channel_created", global.MSG{
|
||||
"guild_id": fU64(e.GuildId),
|
||||
"channel_id": fU64(e.ChannelInfo.ChannelId),
|
||||
"operator_id": fU64(e.OperatorId),
|
||||
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
|
||||
"user_id": e.OperatorId,
|
||||
"channel_info": convertChannelInfo(e.ChannelInfo),
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) guildChannelDestroyedEvent(c *client.QQClient, e *client.GuildChannelOperationEvent) {
|
||||
guild := c.GuildService.FindGuild(e.GuildId)
|
||||
if guild == nil {
|
||||
return
|
||||
}
|
||||
member, _ := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId)
|
||||
if member == nil {
|
||||
member = &client.GuildUserProfile{Nickname: "未知"}
|
||||
}
|
||||
log.Infof("频道 %v(%v) 内用户 %v(%v) 删除了子频道 %v(%v)", guild.GuildName, guild.GuildId, member.Nickname, member.TinyId, e.ChannelInfo.ChannelName, e.ChannelInfo.ChannelId)
|
||||
bot.dispatchEvent("notice/channel_destroyed", global.MSG{
|
||||
"guild_id": fU64(e.GuildId),
|
||||
"channel_id": fU64(e.ChannelInfo.ChannelId),
|
||||
"operator_id": fU64(e.OperatorId),
|
||||
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
|
||||
"user_id": e.OperatorId,
|
||||
"channel_info": convertChannelInfo(e.ChannelInfo),
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupMutedEvent(c *client.QQClient, e *client.GroupMuteEvent) {
|
||||
g := c.FindGroup(e.GroupCode)
|
||||
if e.TargetUin == 0 {
|
||||
if e.Time != 0 {
|
||||
func (bot *CQBot) groupMutedEvent(c *client.QQClient, e *event2.GroupMute) {
|
||||
g := c.GetCachedGroupInfo(e.GroupUin)
|
||||
operator := c.GetCachedMemberInfo(c.GetUin(e.OperatorUID, e.GroupUin), e.GroupUin)
|
||||
target := c.GetCachedMemberInfo(c.GetUin(e.UserUID, e.GroupUin), e.GroupUin)
|
||||
if e.UserUID == "" {
|
||||
if e.Duration != 0 {
|
||||
log.Infof("群 %v 被 %v 开启全员禁言.",
|
||||
formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)))
|
||||
formatGroupName(g), formatMemberName(operator))
|
||||
} else {
|
||||
log.Infof("群 %v 被 %v 解除全员禁言.",
|
||||
formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)))
|
||||
formatGroupName(g), formatMemberName(operator))
|
||||
}
|
||||
} else {
|
||||
if e.Time > 0 {
|
||||
if e.Duration > 0 {
|
||||
log.Infof("群 %v 内 %v 被 %v 禁言了 %v 秒.",
|
||||
formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)), e.Time)
|
||||
formatGroupName(g), formatMemberName(target), formatMemberName(operator), e.Duration)
|
||||
} else {
|
||||
log.Infof("群 %v 内 %v 被 %v 解除禁言.",
|
||||
formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)))
|
||||
formatGroupName(g), formatMemberName(target), formatMemberName(operator))
|
||||
}
|
||||
}
|
||||
typ := "notice/group_ban/ban"
|
||||
if e.Time == 0 {
|
||||
if e.Duration == 0 {
|
||||
typ = "notice/group_ban/lift_ban"
|
||||
}
|
||||
var userID uint32
|
||||
if target != nil {
|
||||
userID = target.Uin
|
||||
} else {
|
||||
userID = 0
|
||||
}
|
||||
bot.dispatchEvent(typ, global.MSG{
|
||||
"duration": e.Time,
|
||||
"group_id": e.GroupCode,
|
||||
"operator_id": e.OperatorUin,
|
||||
"user_id": e.TargetUin,
|
||||
"duration": e.Duration,
|
||||
"group_id": e.GroupUin,
|
||||
"operator_id": operator.Uin,
|
||||
"user_id": userID,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupRecallEvent(c *client.QQClient, e *client.GroupMessageRecalledEvent) {
|
||||
g := c.FindGroup(e.GroupCode)
|
||||
gid := db.ToGlobalID(e.GroupCode, e.MessageId)
|
||||
func (bot *CQBot) groupRecallEvent(c *client.QQClient, e *event2.GroupRecall) {
|
||||
g := c.GetCachedGroupInfo(e.GroupUin)
|
||||
gid := db.ToGlobalID(int64(e.GroupUin), int32(e.Sequence))
|
||||
operator := c.GetCachedMemberInfo(c.GetUin(e.OperatorUID, e.GroupUin), e.GroupUin)
|
||||
Author := c.GetCachedMemberInfo(c.GetUin(e.UserUID, e.GroupUin), e.GroupUin)
|
||||
log.Infof("群 %v 内 %v 撤回了 %v 的消息: %v.",
|
||||
formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)), formatMemberName(g.FindMember(e.AuthorUin)), gid)
|
||||
formatGroupName(g), formatMemberName(operator), formatMemberName(Author), gid)
|
||||
|
||||
ev := bot.event("notice/group_recall", global.MSG{
|
||||
"group_id": e.GroupCode,
|
||||
"user_id": e.AuthorUin,
|
||||
"operator_id": e.OperatorUin,
|
||||
"group_id": e.GroupUin,
|
||||
"user_id": Author.Uin,
|
||||
"operator_id": operator.Uin,
|
||||
"message_id": gid,
|
||||
})
|
||||
ev.Time = int64(e.Time)
|
||||
bot.dispatch(ev)
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
|
||||
group := c.FindGroup(e.From())
|
||||
func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e event2.INotifyEvent) {
|
||||
group := c.GetCachedGroupInfo(e.From())
|
||||
// TODO more event
|
||||
//nolint:gocritic
|
||||
switch notify := e.(type) {
|
||||
case *client.GroupPokeNotifyEvent:
|
||||
sender := group.FindMember(notify.Sender)
|
||||
receiver := group.FindMember(notify.Receiver)
|
||||
case *event2.GroupPokeEvent:
|
||||
sender := c.GetCachedMemberInfo(notify.UserUin, e.From())
|
||||
receiver := c.GetCachedMemberInfo(notify.Receiver, e.From())
|
||||
log.Infof("群 %v 内 %v 戳了戳 %v", formatGroupName(group), formatMemberName(sender), formatMemberName(receiver))
|
||||
bot.dispatchEvent("notice/notify/poke", global.MSG{
|
||||
"group_id": group.Code,
|
||||
"user_id": notify.Sender,
|
||||
"sender_id": notify.Sender,
|
||||
"group_id": group.GroupUin,
|
||||
"user_id": notify.UserUin,
|
||||
"sender_id": notify.UserUin,
|
||||
"target_id": notify.Receiver,
|
||||
})
|
||||
case *client.GroupRedBagLuckyKingNotifyEvent:
|
||||
sender := group.FindMember(notify.Sender)
|
||||
luckyKing := group.FindMember(notify.LuckyKing)
|
||||
log.Infof("群 %v 内 %v 的红包被抢完, %v 是运气王", formatGroupName(group), formatMemberName(sender), formatMemberName(luckyKing))
|
||||
bot.dispatchEvent("notice/notify/lucky_king", global.MSG{
|
||||
"group_id": group.Code,
|
||||
"user_id": notify.Sender,
|
||||
"sender_id": notify.Sender,
|
||||
"target_id": notify.LuckyKing,
|
||||
})
|
||||
case *client.MemberHonorChangedNotifyEvent:
|
||||
log.Info(notify.Content())
|
||||
bot.dispatchEvent("notice/notify/honor", global.MSG{
|
||||
"group_id": group.Code,
|
||||
"user_id": notify.Uin,
|
||||
"honor_type": func() string {
|
||||
switch notify.Honor {
|
||||
case client.Talkative:
|
||||
return "talkative"
|
||||
case client.Performer:
|
||||
return "performer"
|
||||
case client.Emotion:
|
||||
return "emotion"
|
||||
case client.Legend:
|
||||
return "legend"
|
||||
case client.StrongNewbie:
|
||||
return "strong_newbie"
|
||||
default:
|
||||
return "ERROR"
|
||||
}
|
||||
}(),
|
||||
})
|
||||
//case *client.GroupRedBagLuckyKingNotifyEvent:
|
||||
// sender := group.FindMember(notify.Sender)
|
||||
// luckyKing := group.FindMember(notify.LuckyKing)
|
||||
// log.Infof("群 %v 内 %v 的红包被抢完, %v 是运气王", formatGroupName(group), formatMemberName(sender), formatMemberName(luckyKing))
|
||||
// bot.dispatchEvent("notice/notify/lucky_king", global.MSG{
|
||||
// "group_id": group.Code,
|
||||
// "user_id": notify.Sender,
|
||||
// "sender_id": notify.Sender,
|
||||
// "target_id": notify.LuckyKing,
|
||||
// })
|
||||
//case *client.MemberHonorChangedNotifyEvent:
|
||||
// log.Info(notify.Content())
|
||||
// bot.dispatchEvent("notice/notify/honor", global.MSG{
|
||||
// "group_id": group.Code,
|
||||
// "user_id": notify.Uin,
|
||||
// "honor_type": func() string {
|
||||
// switch notify.Honor {
|
||||
// case client.Talkative:
|
||||
// return "talkative"
|
||||
// case client.Performer:
|
||||
// return "performer"
|
||||
// case client.Emotion:
|
||||
// return "emotion"
|
||||
// case client.Legend:
|
||||
// return "legend"
|
||||
// case client.StrongNewbie:
|
||||
// return "strong_newbie"
|
||||
// default:
|
||||
// return "ERROR"
|
||||
// }
|
||||
// }(),
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *CQBot) friendNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
|
||||
friend := c.FindFriend(e.From())
|
||||
if notify, ok := e.(*client.FriendPokeNotifyEvent); ok {
|
||||
func (bot *CQBot) friendNotifyEvent(c *client.QQClient, e event2.INotifyEvent) {
|
||||
friend := c.GetCachedFriendInfo(e.From())
|
||||
if notify, ok := e.(*event2.FriendPokeEvent); ok {
|
||||
if notify.Receiver == notify.Sender {
|
||||
log.Infof("好友 %v 戳了戳自己.", friend.Nickname)
|
||||
} else {
|
||||
@ -428,34 +298,35 @@ func (bot *CQBot) friendNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *CQBot) memberTitleUpdatedEvent(c *client.QQClient, e *client.MemberSpecialTitleUpdatedEvent) {
|
||||
group := c.FindGroup(e.GroupCode)
|
||||
mem := group.FindMember(e.Uin)
|
||||
log.Infof("群 %v(%v) 内成员 %v(%v) 获得了新的头衔: %v", group.Name, group.Code, mem.DisplayName(), mem.Uin, e.NewTitle)
|
||||
func (bot *CQBot) memberTitleUpdatedEvent(c *client.QQClient, e *event2.MemberSpecialTitleUpdated) {
|
||||
group := c.GetCachedGroupInfo(e.GroupUin)
|
||||
mem := c.GetCachedMemberInfo(e.UserUin, e.GroupUin)
|
||||
log.Infof("群 %v(%v) 内成员 %v(%v) 获得了新的头衔: %v", group.GroupName, group.GroupUin, mem.MemberCard, mem.Uin, e.NewTitle)
|
||||
bot.dispatchEvent("notice/notify/title", global.MSG{
|
||||
"group_id": group.Code,
|
||||
"user_id": e.Uin,
|
||||
"group_id": group.GroupUin,
|
||||
"user_id": e.UserUin,
|
||||
"title": e.NewTitle,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) friendRecallEvent(c *client.QQClient, e *client.FriendMessageRecalledEvent) {
|
||||
f := c.FindFriend(e.FriendUin)
|
||||
gid := db.ToGlobalID(e.FriendUin, e.MessageId)
|
||||
func (bot *CQBot) friendRecallEvent(c *client.QQClient, e *event2.FriendRecall) {
|
||||
f := c.GetCachedFriendInfo(c.GetUin(e.FromUID))
|
||||
gid := db.ToGlobalID(int64(e.FromUin), int32(e.Sequence))
|
||||
if f != nil {
|
||||
log.Infof("好友 %v(%v) 撤回了消息: %v", f.Nickname, f.Uin, gid)
|
||||
} else {
|
||||
log.Infof("好友 %v 撤回了消息: %v", e.FriendUin, gid)
|
||||
log.Infof("好友 %v 撤回了消息: %v", e.FromUin, gid)
|
||||
}
|
||||
ev := bot.event("notice/friend_recall", global.MSG{
|
||||
"user_id": e.FriendUin,
|
||||
"user_id": e.FromUin,
|
||||
"message_id": gid,
|
||||
})
|
||||
ev.Time = e.Time
|
||||
ev.Time = int64(e.Time)
|
||||
bot.dispatch(ev)
|
||||
}
|
||||
|
||||
func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEvent) {
|
||||
// TODO 好友离线文件
|
||||
/*func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEvent) {
|
||||
f := c.FindFriend(e.Sender)
|
||||
if f == nil {
|
||||
return
|
||||
@ -469,81 +340,87 @@ func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEven
|
||||
"url": e.DownloadUrl,
|
||||
},
|
||||
})
|
||||
}*/
|
||||
|
||||
// TODO bot自身进群退群
|
||||
func (bot *CQBot) joinGroupEvent(c *client.QQClient, event *event2.GroupMemberIncrease) {
|
||||
log.Infof("Bot进入了群 %v.", formatGroupName(c.GetCachedGroupInfo(event.GroupUin)))
|
||||
bot.dispatch(bot.groupIncrease(int64(event.GroupUin), 0, int64(c.Uin)))
|
||||
}
|
||||
|
||||
func (bot *CQBot) joinGroupEvent(c *client.QQClient, group *client.GroupInfo) {
|
||||
log.Infof("Bot进入了群 %v.", formatGroupName(group))
|
||||
bot.dispatch(bot.groupIncrease(group.Code, 0, c.Uin))
|
||||
}
|
||||
|
||||
func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *client.GroupLeaveEvent) {
|
||||
if e.Operator != nil {
|
||||
log.Infof("Bot被 %v T出了群 %v.", formatMemberName(e.Operator), formatGroupName(e.Group))
|
||||
func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *event2.GroupMemberDecrease) {
|
||||
if e.IsKicked() {
|
||||
log.Infof("Bot被 %v T出了群 %v.", formatMemberName(c.GetCachedMemberInfo(e.OperatorUin, e.GroupUin)), formatGroupName(c.GetCachedGroupInfo(e.GroupUin)))
|
||||
} else {
|
||||
log.Infof("Bot退出了群 %v.", formatGroupName(e.Group))
|
||||
log.Infof("Bot退出了群 %v.", formatGroupName(c.GetCachedGroupInfo(e.GroupUin)))
|
||||
}
|
||||
bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
|
||||
bot.dispatch(bot.groupDecrease(int64(e.GroupUin), int64(c.Uin), c.GetCachedMemberInfo(e.OperatorUin, e.GroupUin)))
|
||||
}
|
||||
|
||||
func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.MemberPermissionChangedEvent) {
|
||||
func (bot *CQBot) memberPermissionChangedEvent(_ *client.QQClient, e *event2.GroupMemberPermissionChanged) {
|
||||
st := "unset"
|
||||
if e.NewPermission == client.Administrator {
|
||||
if e.IsAdmin {
|
||||
st = "set"
|
||||
}
|
||||
bot.dispatchEvent("notice/group_admin/"+st, global.MSG{
|
||||
"group_id": e.Group.Code,
|
||||
"user_id": e.Member.Uin,
|
||||
"group_id": e.GroupUin,
|
||||
"user_id": e.UserUin,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) memberCardUpdatedEvent(c *client.QQClient, e *client.MemberCardUpdatedEvent) {
|
||||
log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
|
||||
bot.dispatchEvent("notice/group_card", global.MSG{
|
||||
"group_id": e.Group.Code,
|
||||
"user_id": e.Member.Uin,
|
||||
"card_new": e.Member.CardName,
|
||||
"card_old": e.OldCard,
|
||||
})
|
||||
// TODO 群名片变更
|
||||
//func (bot *CQBot) memberCardUpdatedEvent(_ *client.QQClient, e *client.MemberCardUpdatedEvent) {
|
||||
// log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
|
||||
// bot.dispatchEvent("notice/group_card", global.MSG{
|
||||
// "group_id": e.Group.Code,
|
||||
// "user_id": e.Member.Uin,
|
||||
// "card_new": e.Member.CardName,
|
||||
// "card_old": e.OldCard,
|
||||
// })
|
||||
//}
|
||||
|
||||
func (bot *CQBot) memberJoinEvent(c *client.QQClient, e *event2.GroupMemberIncrease) {
|
||||
log.Infof("新成员 %v 进入了群 %v.", formatMemberName(c.GetCachedMemberInfo(e.UserUin, e.GroupUin)), formatGroupName(c.GetCachedGroupInfo(e.GroupUin)))
|
||||
bot.dispatch(bot.groupIncrease(int64(e.GroupUin), 0, int64(e.UserUin)))
|
||||
}
|
||||
|
||||
func (bot *CQBot) memberJoinEvent(_ *client.QQClient, e *client.MemberJoinGroupEvent) {
|
||||
log.Infof("新成员 %v 进入了群 %v.", formatMemberName(e.Member), formatGroupName(e.Group))
|
||||
bot.dispatch(bot.groupIncrease(e.Group.Code, 0, e.Member.Uin))
|
||||
}
|
||||
|
||||
func (bot *CQBot) memberLeaveEvent(_ *client.QQClient, e *client.MemberLeaveGroupEvent) {
|
||||
if e.Operator != nil {
|
||||
log.Infof("成员 %v 被 %v T出了群 %v.", formatMemberName(e.Member), formatMemberName(e.Operator), formatGroupName(e.Group))
|
||||
func (bot *CQBot) memberLeaveEvent(c *client.QQClient, e *event2.GroupMemberDecrease) {
|
||||
member := c.GetCachedMemberInfo(c.GetUin(e.UserUID), e.GroupUin)
|
||||
op := c.GetCachedMemberInfo(c.GetUin(e.OperatorUID), e.GroupUin)
|
||||
group := c.GetCachedGroupInfo(e.GroupUin)
|
||||
if e.IsKicked() {
|
||||
log.Infof("成员 %v 被 %v T出了群 %v.", formatMemberName(member), formatMemberName(op), formatGroupName(group))
|
||||
} else {
|
||||
log.Infof("成员 %v 离开了群 %v.", formatMemberName(e.Member), formatGroupName(e.Group))
|
||||
log.Infof("成员 %v 离开了群 %v.", formatMemberName(member), formatGroupName(group))
|
||||
}
|
||||
bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
|
||||
bot.dispatch(bot.groupDecrease(int64(e.GroupUin), int64(member.Uin), op))
|
||||
}
|
||||
|
||||
func (bot *CQBot) friendRequestEvent(c *client.QQClient, e *client.NewFriendRequest) {
|
||||
log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message)
|
||||
flag := strconv.FormatInt(e.RequestId, 10)
|
||||
func (bot *CQBot) friendRequestEvent(_ *client.QQClient, e *event2.NewFriendRequest) {
|
||||
log.Infof("收到来自 %v(%v) 的好友请求: %v", e.Source, e.SourceUin, e.Msg)
|
||||
// 就用uin当flag吧
|
||||
flag := strconv.FormatInt(int64(e.SourceUin), 10)
|
||||
bot.friendReqCache.Store(flag, e)
|
||||
bot.dispatchEvent("request/friend", global.MSG{
|
||||
"user_id": e.RequesterUin,
|
||||
"comment": e.Message,
|
||||
"user_id": e.SourceUin,
|
||||
"comment": e.Msg,
|
||||
"flag": flag,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent) {
|
||||
log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin)
|
||||
bot.tempSessionCache.Delete(e.Friend.Uin)
|
||||
func (bot *CQBot) friendAddedEvent(_ *client.QQClient, e *event2.NewFriend) {
|
||||
log.Infof("添加了新好友: %v(%v)", e.FromNick, e.FromUin)
|
||||
//bot.tempSessionCache.Delete(e.Friend.Uin)
|
||||
bot.dispatchEvent("notice/friend_add", global.MSG{
|
||||
"user_id": e.Friend.Uin,
|
||||
"user_id": e.FromUin,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRequest) {
|
||||
log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin)
|
||||
flag := strconv.FormatInt(e.RequestId, 10)
|
||||
func (bot *CQBot) groupInvitedEvent(_ *client.QQClient, e *event2.GroupInvite) {
|
||||
log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupUin, e.InvitorNick, e.InvitorUin)
|
||||
flag := strconv.FormatInt(int64(e.RequestSeq), 10)
|
||||
bot.dispatchEvent("request/group/invite", global.MSG{
|
||||
"group_id": e.GroupCode,
|
||||
"group_id": e.GroupUin,
|
||||
"user_id": e.InvitorUin,
|
||||
"invitor_id": 0,
|
||||
"comment": "",
|
||||
@ -551,51 +428,52 @@ func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRe
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupRequest) {
|
||||
log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin)
|
||||
flag := strconv.FormatInt(e.RequestId, 10)
|
||||
func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *event2.GroupMemberJoinRequest) {
|
||||
group := c.GetCachedGroupInfo(e.GroupUin)
|
||||
log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", group.GroupName, e.GroupUin, e.TargetNick, e.UserUin)
|
||||
flag := strconv.FormatInt(int64(e.RequestSeq), 10)
|
||||
bot.dispatchEvent("request/group/add", global.MSG{
|
||||
"group_id": e.GroupCode,
|
||||
"user_id": e.RequesterUin,
|
||||
"invitor_id": e.ActionUin,
|
||||
"comment": e.Message,
|
||||
"group_id": e.GroupUin,
|
||||
"user_id": e.UserUin,
|
||||
"invitor_id": e.InvitorUin,
|
||||
"comment": e.Answer,
|
||||
"flag": flag,
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.OtherClientStatusChangedEvent) {
|
||||
if e.Online {
|
||||
log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
|
||||
} else {
|
||||
log.Infof("Bot 账号在客户端 %v (%v) 登出.", e.Client.DeviceName, e.Client.DeviceKind)
|
||||
}
|
||||
bot.dispatchEvent("notice/client_status", global.MSG{
|
||||
"online": e.Online,
|
||||
"client": global.MSG{
|
||||
"app_id": e.Client.AppId,
|
||||
"device_name": e.Client.DeviceName,
|
||||
"device_kind": e.Client.DeviceKind,
|
||||
},
|
||||
})
|
||||
}
|
||||
//func (bot *CQBot) otherClientStatusChangedEvent(_ *client.QQClient, e *client.OtherClientStatusChangedEvent) {
|
||||
// if e.Online {
|
||||
// log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
|
||||
// } else {
|
||||
// log.Infof("Bot 账号在客户端 %v (%v) 登出.", e.Client.DeviceName, e.Client.DeviceKind)
|
||||
// }
|
||||
// bot.dispatchEvent("notice/client_status", global.MSG{
|
||||
// "online": e.Online,
|
||||
// "client": global.MSG{
|
||||
// "app_id": e.Client.AppId,
|
||||
// "device_name": e.Client.DeviceName,
|
||||
// "device_kind": e.Client.DeviceKind,
|
||||
// },
|
||||
// })
|
||||
//}
|
||||
|
||||
func (bot *CQBot) groupEssenceMsg(c *client.QQClient, e *client.GroupDigestEvent) {
|
||||
g := c.FindGroup(e.GroupCode)
|
||||
gid := db.ToGlobalID(e.GroupCode, e.MessageID)
|
||||
func (bot *CQBot) groupEssenceMsg(c *client.QQClient, e *event2.GroupDigestEvent) {
|
||||
g := c.GetCachedGroupInfo(e.GroupUin)
|
||||
gid := db.ToGlobalID(int64(e.GroupUin), int32(e.MessageID))
|
||||
if e.OperationType == 1 {
|
||||
log.Infof(
|
||||
"群 %v 内 %v 将 %v 的消息(%v)设为了精华消息.",
|
||||
formatGroupName(g),
|
||||
formatMemberName(g.FindMember(e.OperatorUin)),
|
||||
formatMemberName(g.FindMember(e.SenderUin)),
|
||||
formatMemberName(c.GetCachedMemberInfo(e.OperatorUin, e.GroupUin)),
|
||||
formatMemberName(c.GetCachedMemberInfo(e.UserUin, e.GroupUin)),
|
||||
gid,
|
||||
)
|
||||
} else {
|
||||
log.Infof(
|
||||
"群 %v 内 %v 将 %v 的消息(%v)移出了精华消息.",
|
||||
formatGroupName(g),
|
||||
formatMemberName(g.FindMember(e.OperatorUin)),
|
||||
formatMemberName(g.FindMember(e.SenderUin)),
|
||||
formatMemberName(c.GetCachedMemberInfo(e.OperatorUin, e.GroupUin)),
|
||||
formatMemberName(c.GetCachedMemberInfo(e.UserUin, e.GroupUin)),
|
||||
gid,
|
||||
)
|
||||
}
|
||||
@ -603,12 +481,12 @@ func (bot *CQBot) groupEssenceMsg(c *client.QQClient, e *client.GroupDigestEvent
|
||||
return
|
||||
}
|
||||
subtype := "delete"
|
||||
if e.OperationType == 1 {
|
||||
if e.IsSet() {
|
||||
subtype = "add"
|
||||
}
|
||||
bot.dispatchEvent("notice/essence/"+subtype, global.MSG{
|
||||
"group_id": e.GroupCode,
|
||||
"sender_id": e.SenderUin,
|
||||
"group_id": e.GroupUin,
|
||||
"sender_id": e.UserUin,
|
||||
"operator_id": e.OperatorUin,
|
||||
"message_id": gid,
|
||||
})
|
||||
@ -622,14 +500,14 @@ func (bot *CQBot) groupIncrease(groupCode, operatorUin, userUin int64) *event {
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.GroupMemberInfo) *event {
|
||||
func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *entity.GroupMember) *event {
|
||||
op := userUin
|
||||
if operator != nil {
|
||||
op = operator.Uin
|
||||
op = int64(operator.Uin)
|
||||
}
|
||||
subtype := "leave"
|
||||
if operator != nil {
|
||||
if userUin == bot.Client.Uin {
|
||||
if userUin == int64(bot.Client.Uin) {
|
||||
subtype = "kick_me"
|
||||
} else {
|
||||
subtype = "kick"
|
||||
@ -642,47 +520,23 @@ func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.Group
|
||||
})
|
||||
}
|
||||
|
||||
func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) {
|
||||
func (bot *CQBot) checkMedia(e []message.IMessageElement, source message.Source) {
|
||||
for _, elem := range e {
|
||||
switch i := elem.(type) {
|
||||
case *message.GroupImageElement:
|
||||
if i.Flash && sourceID != 0 {
|
||||
u, err := bot.Client.GetGroupImageDownloadUrl(i.FileId, sourceID, i.Md5)
|
||||
if err != nil {
|
||||
log.Warnf("获取闪照地址时出现错误: %v", err)
|
||||
} else {
|
||||
i.Url = u
|
||||
}
|
||||
}
|
||||
data := binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.Write(i.Md5)
|
||||
w.WriteUInt32(uint32(i.Size))
|
||||
w.WriteString(i.ImageId)
|
||||
w.WriteString(i.Url)
|
||||
})
|
||||
cache.Image.Insert(i.Md5, data)
|
||||
|
||||
case *message.GuildImageElement:
|
||||
data := binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.Write(i.Md5)
|
||||
w.WriteUInt32(uint32(i.Size))
|
||||
w.WriteString(i.DownloadIndex)
|
||||
w.WriteString(i.Url)
|
||||
})
|
||||
filename := hex.EncodeToString(i.Md5) + ".image"
|
||||
cache.Image.Insert(i.Md5, data)
|
||||
if i.Url != "" && !global.PathExists(path.Join(global.ImagePath, "guild-images", filename)) {
|
||||
r := download.Request{URL: i.Url}
|
||||
if err := r.WriteToFile(path.Join(global.ImagePath, "guild-images", filename)); err != nil {
|
||||
log.Warnf("下载频道图片时出现错误: %v", err)
|
||||
}
|
||||
}
|
||||
case *message.FriendImageElement:
|
||||
data := binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.Write(i.Md5)
|
||||
w.WriteUInt32(uint32(i.Size))
|
||||
w.WriteString(i.ImageId)
|
||||
w.WriteString(i.Url)
|
||||
case *message.ImageElement:
|
||||
// 闪照已经4了(私聊还没)
|
||||
//if i.Flash && source.PrimaryID != 0 {
|
||||
// u, err := bot.Client.GetGroupImageURL(uint32(source.PrimaryID), i.MsgInfo.MsgInfoBody[0].Index)
|
||||
// if err != nil {
|
||||
// log.Warnf("获取闪照地址时出现错误: %v", err)
|
||||
// } else {
|
||||
// i.URL = u
|
||||
// }
|
||||
//}
|
||||
data := binary.NewWriterF(func(w *binary.Builder) {
|
||||
_, _ = w.Write(i.Md5)
|
||||
w.WritePacketString(i.FileUUID, "u32", true)
|
||||
w.WritePacketString(i.ImageID, "u32", true)
|
||||
})
|
||||
cache.Image.Insert(i.Md5, data)
|
||||
|
||||
@ -690,26 +544,25 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) {
|
||||
// todo: don't download original file?
|
||||
i.Name = strings.ReplaceAll(i.Name, "{", "")
|
||||
i.Name = strings.ReplaceAll(i.Name, "}", "")
|
||||
if !global.PathExists(path.Join(global.VoicePath, i.Name)) {
|
||||
err := download.Request{URL: i.Url}.WriteToFile(path.Join(global.VoicePath, i.Name))
|
||||
if !global.FileExists(path.Join(global.VoicePath, i.Name)) {
|
||||
err := download.Request{URL: i.URL}.WriteToFile(path.Join(global.VoicePath, i.Name))
|
||||
if err != nil {
|
||||
log.Warnf("语音文件 %v 下载失败: %v", i.Name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
case *message.ShortVideoElement:
|
||||
data := binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.Write(i.Md5)
|
||||
w.Write(i.ThumbMd5)
|
||||
w.WriteUInt32(uint32(i.Size))
|
||||
w.WriteUInt32(uint32(i.ThumbSize))
|
||||
w.WriteString(i.Name)
|
||||
w.Write(i.Uuid)
|
||||
data := binary.NewWriterF(func(w *binary.Builder) {
|
||||
w.WriteBool(source.SourceType == message.SourceGroup)
|
||||
w.WriteBytes(i.Md5)
|
||||
w.WriteBytes(i.Sha1)
|
||||
w.WritePacketString(i.Name, "u32", true)
|
||||
w.WritePacketString(i.UUID, "u32", true)
|
||||
})
|
||||
filename := hex.EncodeToString(i.Md5) + ".video"
|
||||
cache.Video.Insert(i.Md5, data)
|
||||
i.URL, _ = bot.Client.GetVideoURL(source.SourceType == message.SourceGroup, i.UUID)
|
||||
i.Name = filename
|
||||
i.Url = bot.Client.GetShortVideoUrl(i.Uuid, i.Md5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
package coolq
|
||||
|
||||
import (
|
||||
"github.com/Mrs4s/MiraiGo/topic"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
)
|
||||
|
||||
// FeedContentsToArrayMessage 将话题频道帖子内容转换为 Array Message
|
||||
func FeedContentsToArrayMessage(contents []topic.IFeedRichContentElement) []global.MSG {
|
||||
r := make([]global.MSG, 0, len(contents))
|
||||
for _, e := range contents {
|
||||
var m global.MSG
|
||||
switch elem := e.(type) {
|
||||
case *topic.TextElement:
|
||||
m = global.MSG{
|
||||
"type": "text",
|
||||
"data": global.MSG{"text": elem.Content},
|
||||
}
|
||||
case *topic.AtElement:
|
||||
m = global.MSG{
|
||||
"type": "at",
|
||||
"data": global.MSG{"id": elem.Id, "qq": elem.Id},
|
||||
}
|
||||
case *topic.EmojiElement:
|
||||
m = global.MSG{
|
||||
"type": "face",
|
||||
"data": global.MSG{"id": elem.Id},
|
||||
}
|
||||
case *topic.ChannelQuoteElement:
|
||||
m = global.MSG{
|
||||
"type": "channel_quote",
|
||||
"data": global.MSG{
|
||||
"guild_id": fU64(elem.GuildId),
|
||||
"channel_id": fU64(elem.ChannelId),
|
||||
"display_text": elem.DisplayText,
|
||||
},
|
||||
}
|
||||
case *topic.UrlQuoteElement:
|
||||
m = global.MSG{
|
||||
"type": "url_quote",
|
||||
"data": global.MSG{
|
||||
"url": elem.Url,
|
||||
"display_text": elem.DisplayText,
|
||||
},
|
||||
}
|
||||
}
|
||||
if m != nil {
|
||||
r = append(r, m)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
@ -19,15 +19,11 @@ type (
|
||||
GetGroupMessageByGlobalID(int32) (*StoredGroupMessage, error)
|
||||
// GetPrivateMessageByGlobalID 通过 GlobalID 来获取私聊消息
|
||||
GetPrivateMessageByGlobalID(int32) (*StoredPrivateMessage, error)
|
||||
// GetGuildChannelMessageByID 通过 ID 来获取频道消息
|
||||
GetGuildChannelMessageByID(string) (*StoredGuildChannelMessage, error)
|
||||
|
||||
// InsertGroupMessage 向数据库写入新的群消息
|
||||
InsertGroupMessage(*StoredGroupMessage) error
|
||||
// InsertPrivateMessage 向数据库写入新的私聊消息
|
||||
InsertPrivateMessage(*StoredPrivateMessage) error
|
||||
// InsertGuildChannelMessage 向数据库写入新的频道消息
|
||||
InsertGuildChannelMessage(*StoredGuildChannelMessage) error
|
||||
}
|
||||
|
||||
StoredMessage interface {
|
||||
@ -62,34 +58,16 @@ type (
|
||||
Content []global.MSG `bson:"content" yaml:"content"`
|
||||
}
|
||||
|
||||
// StoredGuildChannelMessage 持久化频道消息
|
||||
StoredGuildChannelMessage struct {
|
||||
ID string `bson:"_id" yaml:"-"`
|
||||
Attribute *StoredGuildMessageAttribute `bson:"attribute" yaml:"-"`
|
||||
GuildID uint64 `bson:"guildId" yaml:"-"`
|
||||
ChannelID uint64 `bson:"channelId" yaml:"-"`
|
||||
QuotedInfo *QuotedInfo `bson:"quotedInfo" yaml:"-"`
|
||||
Content []global.MSG `bson:"content" yaml:"content"`
|
||||
}
|
||||
|
||||
// StoredMessageAttribute 持久化消息属性
|
||||
StoredMessageAttribute struct {
|
||||
MessageSeq int32 `bson:"messageSeq" yaml:"-"`
|
||||
ClientSeq int32 `bson:"clientSeq" yaml:"-"`
|
||||
InternalID int32 `bson:"internalId" yaml:"-"`
|
||||
SenderUin int64 `bson:"senderUin" yaml:"-"`
|
||||
SenderName string `bson:"senderName" yaml:"-"`
|
||||
Timestamp int64 `bson:"timestamp" yaml:"-"`
|
||||
}
|
||||
|
||||
// StoredGuildMessageAttribute 持久化频道消息属性
|
||||
StoredGuildMessageAttribute struct {
|
||||
MessageSeq uint64 `bson:"messageSeq" yaml:"-"`
|
||||
InternalID uint64 `bson:"internalId" yaml:"-"`
|
||||
SenderTinyID uint64 `bson:"senderTinyId" yaml:"-"`
|
||||
SenderName string `bson:"senderName" yaml:"-"`
|
||||
Timestamp int64 `bson:"timestamp" yaml:"-"`
|
||||
}
|
||||
|
||||
// QuotedInfo 引用回复
|
||||
QuotedInfo struct {
|
||||
PrevID string `bson:"prevId" yaml:"-"`
|
||||
|
@ -3,9 +3,8 @@ package leveldb
|
||||
const dataVersion = 1
|
||||
|
||||
const (
|
||||
group = 0x0
|
||||
private = 0x1
|
||||
guildChannel = 0x2
|
||||
group = 0x0
|
||||
private = 0x1
|
||||
)
|
||||
|
||||
type coder byte
|
||||
|
@ -3,14 +3,13 @@ package leveldb
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/binary"
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
)
|
||||
|
||||
type database struct {
|
||||
@ -93,28 +92,6 @@ func (ldb *database) GetPrivateMessageByGlobalID(id int32) (*db.StoredPrivateMes
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (ldb *database) GetGuildChannelMessageByID(id string) (*db.StoredGuildChannelMessage, error) {
|
||||
v, err := ldb.db.Get([]byte(id), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get value error")
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
r, err := newReader(utils.B2S(v))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch r.uvarint() {
|
||||
case guildChannel:
|
||||
return r.readStoredGuildChannelMessage(), nil
|
||||
default:
|
||||
return nil, errors.New("unknown message flag")
|
||||
}
|
||||
}
|
||||
|
||||
func (ldb *database) InsertGroupMessage(msg *db.StoredGroupMessage) error {
|
||||
w := newWriter()
|
||||
w.uvarint(group)
|
||||
@ -130,11 +107,3 @@ func (ldb *database) InsertPrivateMessage(msg *db.StoredPrivateMessage) error {
|
||||
err := ldb.db.Put(binary.ToBytes(msg.GlobalID), w.bytes(), nil)
|
||||
return errors.Wrap(err, "put data error")
|
||||
}
|
||||
|
||||
func (ldb *database) InsertGuildChannelMessage(msg *db.StoredGuildChannelMessage) error {
|
||||
w := newWriter()
|
||||
w.uvarint(guildChannel)
|
||||
w.writeStoredGuildChannelMessage(msg)
|
||||
err := ldb.db.Put(utils.S2B(msg.ID), w.bytes(), nil)
|
||||
return errors.Wrap(err, "put data error")
|
||||
}
|
||||
|
@ -6,9 +6,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type intReader struct {
|
||||
@ -84,7 +83,7 @@ func (r *reader) arrayMsg() []global.MSG {
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (r *reader) obj() interface{} {
|
||||
func (r *reader) obj() any {
|
||||
switch coder := r.coder(); coder {
|
||||
case coderNil:
|
||||
return nil
|
||||
|
@ -68,35 +68,6 @@ func (r *reader) readStoredPrivateMessage() *db.StoredPrivateMessage {
|
||||
return x
|
||||
}
|
||||
|
||||
func (w *writer) writeStoredGuildChannelMessage(x *db.StoredGuildChannelMessage) {
|
||||
if x == nil {
|
||||
w.nil()
|
||||
return
|
||||
}
|
||||
w.coder(coderStruct)
|
||||
w.string(x.ID)
|
||||
w.writeStoredGuildMessageAttribute(x.Attribute)
|
||||
w.uint64(x.GuildID)
|
||||
w.uint64(x.ChannelID)
|
||||
w.writeQuotedInfo(x.QuotedInfo)
|
||||
w.arrayMsg(x.Content)
|
||||
}
|
||||
|
||||
func (r *reader) readStoredGuildChannelMessage() *db.StoredGuildChannelMessage {
|
||||
coder := r.coder()
|
||||
if coder == coderNil {
|
||||
return nil
|
||||
}
|
||||
x := &db.StoredGuildChannelMessage{}
|
||||
x.ID = r.string()
|
||||
x.Attribute = r.readStoredGuildMessageAttribute()
|
||||
x.GuildID = r.uint64()
|
||||
x.ChannelID = r.uint64()
|
||||
x.QuotedInfo = r.readQuotedInfo()
|
||||
x.Content = r.arrayMsg()
|
||||
return x
|
||||
}
|
||||
|
||||
func (w *writer) writeStoredMessageAttribute(x *db.StoredMessageAttribute) {
|
||||
if x == nil {
|
||||
w.nil()
|
||||
@ -124,33 +95,6 @@ func (r *reader) readStoredMessageAttribute() *db.StoredMessageAttribute {
|
||||
return x
|
||||
}
|
||||
|
||||
func (w *writer) writeStoredGuildMessageAttribute(x *db.StoredGuildMessageAttribute) {
|
||||
if x == nil {
|
||||
w.nil()
|
||||
return
|
||||
}
|
||||
w.coder(coderStruct)
|
||||
w.uint64(x.MessageSeq)
|
||||
w.uint64(x.InternalID)
|
||||
w.uint64(x.SenderTinyID)
|
||||
w.string(x.SenderName)
|
||||
w.int64(x.Timestamp)
|
||||
}
|
||||
|
||||
func (r *reader) readStoredGuildMessageAttribute() *db.StoredGuildMessageAttribute {
|
||||
coder := r.coder()
|
||||
if coder == coderNil {
|
||||
return nil
|
||||
}
|
||||
x := &db.StoredGuildMessageAttribute{}
|
||||
x.MessageSeq = r.uint64()
|
||||
x.InternalID = r.uint64()
|
||||
x.SenderTinyID = r.uint64()
|
||||
x.SenderName = r.string()
|
||||
x.Timestamp = r.int64()
|
||||
return x
|
||||
}
|
||||
|
||||
func (w *writer) writeQuotedInfo(x *db.QuotedInfo) {
|
||||
if x == nil {
|
||||
w.nil()
|
||||
|
@ -96,7 +96,7 @@ func (w *writer) arrayMsg(a []global.MSG) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writer) obj(o interface{}) {
|
||||
func (w *writer) obj(o any) {
|
||||
switch x := o.(type) {
|
||||
case nil:
|
||||
w.nil()
|
||||
|
@ -3,13 +3,12 @@ package mongodb
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
"github.com/pkg/errors"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
)
|
||||
|
||||
type database struct {
|
||||
@ -26,9 +25,8 @@ type config struct {
|
||||
}
|
||||
|
||||
const (
|
||||
MongoGroupMessageCollection = "group-messages"
|
||||
MongoPrivateMessageCollection = "private-messages"
|
||||
MongoGuildChannelMessageCollection = "guild-channel-messages"
|
||||
MongoGroupMessageCollection = "group-messages"
|
||||
MongoPrivateMessageCollection = "private-messages"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -79,15 +77,6 @@ func (m *database) GetPrivateMessageByGlobalID(id int32) (*db.StoredPrivateMessa
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (m *database) GetGuildChannelMessageByID(id string) (*db.StoredGuildChannelMessage, error) {
|
||||
coll := m.mongo.Collection(MongoGuildChannelMessageCollection)
|
||||
var ret db.StoredGuildChannelMessage
|
||||
if err := coll.FindOne(context.Background(), bson.D{{"_id", id}}).Decode(&ret); err != nil {
|
||||
return nil, errors.Wrap(err, "query error")
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (m *database) InsertGroupMessage(msg *db.StoredGroupMessage) error {
|
||||
coll := m.mongo.Collection(MongoGroupMessageCollection)
|
||||
_, err := coll.UpdateOne(context.Background(), bson.D{{"_id", msg.ID}}, bson.D{{"$set", msg}}, options.Update().SetUpsert(true))
|
||||
@ -99,9 +88,3 @@ func (m *database) InsertPrivateMessage(msg *db.StoredPrivateMessage) error {
|
||||
_, err := coll.UpdateOne(context.Background(), bson.D{{"_id", msg.ID}}, bson.D{{"$set", msg}}, options.Update().SetUpsert(true))
|
||||
return errors.Wrap(err, "insert error")
|
||||
}
|
||||
|
||||
func (m *database) InsertGuildChannelMessage(msg *db.StoredGuildChannelMessage) error {
|
||||
coll := m.mongo.Collection(MongoGuildChannelMessageCollection)
|
||||
_, err := coll.UpdateOne(context.Background(), bson.D{{"_id", msg.ID}}, bson.D{{"$set", msg}}, options.Update().SetUpsert(true))
|
||||
return errors.Wrap(err, "insert error")
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
)
|
||||
|
||||
// backends 多数据库支持, 后端支持
|
||||
@ -70,13 +69,6 @@ func GetPrivateMessageByGlobalID(id int32) (*StoredPrivateMessage, error) {
|
||||
return backends[0].GetPrivateMessageByGlobalID(id)
|
||||
}
|
||||
|
||||
func GetGuildChannelMessageByID(id string) (*StoredGuildChannelMessage, error) {
|
||||
if len(backends) == 0 {
|
||||
return nil, DatabaseDisabledError
|
||||
}
|
||||
return backends[0].GetGuildChannelMessageByID(id)
|
||||
}
|
||||
|
||||
func InsertGroupMessage(m *StoredGroupMessage) error {
|
||||
for _, b := range backends {
|
||||
if err := b.InsertGroupMessage(m); err != nil {
|
||||
@ -94,12 +86,3 @@ func InsertPrivateMessage(m *StoredPrivateMessage) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InsertGuildChannelMessage(m *StoredGuildChannelMessage) error {
|
||||
for _, b := range backends {
|
||||
if err := b.InsertGuildChannelMessage(m); err != nil {
|
||||
return errors.Wrap(err, "insert message to backend error")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
package sqlite3
|
||||
|
||||
const (
|
||||
Sqlite3GroupMessageTableName = "grpmsg"
|
||||
Sqlite3MessageAttributeTableName = "msgattr"
|
||||
Sqlite3GuildMessageAttributeTableName = "gmsgattr"
|
||||
Sqlite3QuotedInfoTableName = "quoinf"
|
||||
Sqlite3PrivateMessageTableName = "privmsg"
|
||||
Sqlite3GuildChannelMessageTableName = "guildmsg"
|
||||
Sqlite3UinInfoTableName = "uininf"
|
||||
Sqlite3TinyInfoTableName = "tinyinf"
|
||||
Sqlite3GroupMessageTableName = "grpmsg"
|
||||
Sqlite3MessageAttributeTableName = "msgattr"
|
||||
Sqlite3QuotedInfoTableName = "quoinf"
|
||||
Sqlite3PrivateMessageTableName = "privmsg"
|
||||
Sqlite3UinInfoTableName = "uininf"
|
||||
Sqlite3TinyInfoTableName = "tinyinf"
|
||||
)
|
||||
|
||||
// StoredMessageAttribute 持久化消息属性
|
||||
@ -20,15 +18,6 @@ type StoredMessageAttribute struct {
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
// StoredGuildMessageAttribute 持久化频道消息属性
|
||||
type StoredGuildMessageAttribute struct {
|
||||
ID int64 // ID is the crc64 of 字段s below
|
||||
MessageSeq int64
|
||||
InternalID int64
|
||||
SenderTinyID int64 // SenderTinyID is fk to TinyInfo
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
// QuotedInfo 引用回复
|
||||
type QuotedInfo struct {
|
||||
ID int64 // ID is the crc64 of 字段s below
|
||||
@ -72,13 +61,3 @@ type StoredPrivateMessage struct {
|
||||
TargetUin int64
|
||||
Content string // Content is json of original content
|
||||
}
|
||||
|
||||
// StoredGuildChannelMessage 持久化频道消息
|
||||
type StoredGuildChannelMessage struct {
|
||||
ID string
|
||||
AttributeID int64
|
||||
GuildID int64
|
||||
ChannelID int64
|
||||
QuotedInfoID int64
|
||||
Content string // Content is json of original content
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"hash/crc64"
|
||||
"os"
|
||||
"path"
|
||||
@ -10,12 +9,12 @@ import (
|
||||
"time"
|
||||
|
||||
sql "github.com/FloatTech/sqlite"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/binary"
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type database struct {
|
||||
@ -26,8 +25,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 +37,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}
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,16 +83,6 @@ func (s *database) Open() error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert into sqlite3 table "+Sqlite3MessageAttributeTableName+" error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3GuildMessageAttributeTableName, &StoredGuildMessageAttribute{},
|
||||
"FOREIGN KEY(SenderTinyID) REFERENCES "+Sqlite3TinyInfoTableName+"(ID)",
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
err = s.db.Insert(Sqlite3GuildMessageAttributeTableName, &StoredGuildMessageAttribute{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert into sqlite3 table "+Sqlite3GuildMessageAttributeTableName+" error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3QuotedInfoTableName, &QuotedInfo{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
@ -112,13 +105,6 @@ func (s *database) Open() error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3GuildChannelMessageTableName, &StoredGuildChannelMessage{},
|
||||
"FOREIGN KEY(AttributeID) REFERENCES "+Sqlite3MessageAttributeTableName+"(ID)",
|
||||
"FOREIGN KEY(QuotedInfoID) REFERENCES "+Sqlite3QuotedInfoTableName+"(ID)",
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -233,63 +219,6 @@ func (s *database) GetPrivateMessageByGlobalID(id int32) (*db.StoredPrivateMessa
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *database) GetGuildChannelMessageByID(id string) (*db.StoredGuildChannelMessage, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "query invalid id error")
|
||||
}
|
||||
if len(b) < 25 {
|
||||
return nil, errors.New("query invalid id error: content too short")
|
||||
}
|
||||
var ret db.StoredGuildChannelMessage
|
||||
var guildmsg StoredGuildChannelMessage
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3GuildChannelMessageTableName, &guildmsg, "WHERE ID='"+id+"'")
|
||||
s.RUnlock()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "query error")
|
||||
}
|
||||
ret.ID = guildmsg.ID
|
||||
ret.GuildID = uint64(guildmsg.GuildID)
|
||||
ret.ChannelID = uint64(guildmsg.ChannelID)
|
||||
_ = yaml.Unmarshal(utils.S2B(guildmsg.Content), &ret)
|
||||
if guildmsg.AttributeID != 0 {
|
||||
var attr StoredGuildMessageAttribute
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3GuildMessageAttributeTableName, &attr, "WHERE ID="+strconv.FormatInt(guildmsg.AttributeID, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
var tiny TinyInfo
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3TinyInfoTableName, &tiny, "WHERE ID="+strconv.FormatInt(attr.SenderTinyID, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
ret.Attribute = &db.StoredGuildMessageAttribute{
|
||||
MessageSeq: uint64(attr.MessageSeq),
|
||||
InternalID: uint64(attr.InternalID),
|
||||
SenderTinyID: uint64(attr.SenderTinyID),
|
||||
SenderName: tiny.Name,
|
||||
Timestamp: attr.Timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if guildmsg.QuotedInfoID != 0 {
|
||||
var quoinf QuotedInfo
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3QuotedInfoTableName, &quoinf, "WHERE ID="+strconv.FormatInt(guildmsg.QuotedInfoID, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
ret.QuotedInfo = &db.QuotedInfo{
|
||||
PrevID: quoinf.PrevID,
|
||||
PrevGlobalID: quoinf.PrevGlobalID,
|
||||
}
|
||||
_ = yaml.Unmarshal(utils.S2B(quoinf.QuotedContent), &ret.QuotedInfo)
|
||||
}
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (s *database) InsertGroupMessage(msg *db.StoredGroupMessage) error {
|
||||
grpmsg := &StoredGroupMessage{
|
||||
GlobalID: msg.GlobalID,
|
||||
@ -300,11 +229,11 @@ func (s *database) InsertGroupMessage(msg *db.StoredGroupMessage) error {
|
||||
}
|
||||
h := crc64.New(crc64.MakeTable(crc64.ISO))
|
||||
if msg.Attribute != nil {
|
||||
h.Write(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt32(uint32(msg.Attribute.MessageSeq))
|
||||
w.WriteUInt32(uint32(msg.Attribute.InternalID))
|
||||
w.WriteUInt64(uint64(msg.Attribute.SenderUin))
|
||||
w.WriteUInt64(uint64(msg.Attribute.Timestamp))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Builder) {
|
||||
w.WriteU32(uint32(msg.Attribute.MessageSeq))
|
||||
w.WriteU32(uint32(msg.Attribute.InternalID))
|
||||
w.WriteU64(uint64(msg.Attribute.SenderUin))
|
||||
w.WriteU64(uint64(msg.Attribute.Timestamp))
|
||||
}))
|
||||
h.Write(utils.S2B(msg.Attribute.SenderName))
|
||||
id := int64(h.Sum64())
|
||||
@ -333,8 +262,8 @@ func (s *database) InsertGroupMessage(msg *db.StoredGroupMessage) error {
|
||||
}
|
||||
if msg.QuotedInfo != nil {
|
||||
h.Write(utils.S2B(msg.QuotedInfo.PrevID))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt32(uint32(msg.QuotedInfo.PrevGlobalID))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Builder) {
|
||||
w.WriteU32(uint32(msg.QuotedInfo.PrevGlobalID))
|
||||
}))
|
||||
content, err := yaml.Marshal(&msg.QuotedInfo)
|
||||
if err != nil {
|
||||
@ -381,11 +310,11 @@ func (s *database) InsertPrivateMessage(msg *db.StoredPrivateMessage) error {
|
||||
}
|
||||
h := crc64.New(crc64.MakeTable(crc64.ISO))
|
||||
if msg.Attribute != nil {
|
||||
h.Write(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt32(uint32(msg.Attribute.MessageSeq))
|
||||
w.WriteUInt32(uint32(msg.Attribute.InternalID))
|
||||
w.WriteUInt64(uint64(msg.Attribute.SenderUin))
|
||||
w.WriteUInt64(uint64(msg.Attribute.Timestamp))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Builder) {
|
||||
w.WriteU32(uint32(msg.Attribute.MessageSeq))
|
||||
w.WriteU32(uint32(msg.Attribute.InternalID))
|
||||
w.WriteU64(uint64(msg.Attribute.SenderUin))
|
||||
w.WriteU64(uint64(msg.Attribute.Timestamp))
|
||||
}))
|
||||
h.Write(utils.S2B(msg.Attribute.SenderName))
|
||||
id := int64(h.Sum64())
|
||||
@ -414,8 +343,8 @@ func (s *database) InsertPrivateMessage(msg *db.StoredPrivateMessage) error {
|
||||
}
|
||||
if msg.QuotedInfo != nil {
|
||||
h.Write(utils.S2B(msg.QuotedInfo.PrevID))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt32(uint32(msg.QuotedInfo.PrevGlobalID))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Builder) {
|
||||
w.WriteU32(uint32(msg.QuotedInfo.PrevGlobalID))
|
||||
}))
|
||||
content, err := yaml.Marshal(&msg.QuotedInfo)
|
||||
if err != nil {
|
||||
@ -451,82 +380,3 @@ func (s *database) InsertPrivateMessage(msg *db.StoredPrivateMessage) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *database) InsertGuildChannelMessage(msg *db.StoredGuildChannelMessage) error {
|
||||
guildmsg := &StoredGuildChannelMessage{
|
||||
ID: msg.ID,
|
||||
GuildID: int64(msg.GuildID),
|
||||
ChannelID: int64(msg.ChannelID),
|
||||
}
|
||||
h := crc64.New(crc64.MakeTable(crc64.ISO))
|
||||
if msg.Attribute != nil {
|
||||
h.Write(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt32(uint32(msg.Attribute.MessageSeq))
|
||||
w.WriteUInt32(uint32(msg.Attribute.InternalID))
|
||||
w.WriteUInt64(uint64(msg.Attribute.SenderTinyID))
|
||||
w.WriteUInt64(uint64(msg.Attribute.Timestamp))
|
||||
}))
|
||||
h.Write(utils.S2B(msg.Attribute.SenderName))
|
||||
id := int64(h.Sum64())
|
||||
if id == 0 {
|
||||
id++
|
||||
}
|
||||
s.Lock()
|
||||
err := s.db.Insert(Sqlite3TinyInfoTableName, &TinyInfo{
|
||||
ID: int64(msg.Attribute.SenderTinyID),
|
||||
Name: msg.Attribute.SenderName,
|
||||
})
|
||||
if err == nil {
|
||||
err = s.db.Insert(Sqlite3MessageAttributeTableName, &StoredGuildMessageAttribute{
|
||||
ID: id,
|
||||
MessageSeq: int64(msg.Attribute.MessageSeq),
|
||||
InternalID: int64(msg.Attribute.InternalID),
|
||||
SenderTinyID: int64(msg.Attribute.SenderTinyID),
|
||||
Timestamp: msg.Attribute.Timestamp,
|
||||
})
|
||||
}
|
||||
s.Unlock()
|
||||
if err == nil {
|
||||
guildmsg.AttributeID = id
|
||||
}
|
||||
h.Reset()
|
||||
}
|
||||
if msg.QuotedInfo != nil {
|
||||
h.Write(utils.S2B(msg.QuotedInfo.PrevID))
|
||||
h.Write(binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.WriteUInt32(uint32(msg.QuotedInfo.PrevGlobalID))
|
||||
}))
|
||||
content, err := yaml.Marshal(&msg.QuotedInfo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert marshal QuotedContent error")
|
||||
}
|
||||
h.Write(content)
|
||||
id := int64(h.Sum64())
|
||||
if id == 0 {
|
||||
id++
|
||||
}
|
||||
s.Lock()
|
||||
err = s.db.Insert(Sqlite3QuotedInfoTableName, &QuotedInfo{
|
||||
ID: id,
|
||||
PrevID: msg.QuotedInfo.PrevID,
|
||||
PrevGlobalID: msg.QuotedInfo.PrevGlobalID,
|
||||
QuotedContent: utils.B2S(content),
|
||||
})
|
||||
s.Unlock()
|
||||
if err == nil {
|
||||
guildmsg.QuotedInfoID = id
|
||||
}
|
||||
}
|
||||
content, err := yaml.Marshal(&msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert marshal Content error")
|
||||
}
|
||||
guildmsg.Content = utils.B2S(content)
|
||||
s.Lock()
|
||||
err = s.db.Insert(Sqlite3GuildChannelMessageTableName, guildmsg)
|
||||
s.Unlock()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -17,4 +17,4 @@ chown -R ${UID}:${GID} /app /data
|
||||
chmod +x /app/cqhttp
|
||||
|
||||
echo "Starting..."
|
||||
su-exec ${USER} /app/cqhttp
|
||||
su-exec ${USER} /app/cqhttp "$@"
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
注意: 与客户端建立连接的握手事件**不会**经过事件过滤器
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足.
|
||||
|
||||
## 示例
|
||||
|
||||
这节首先给出一些示例,演示过滤器的基本用法,下一节将给出具体语法说明。
|
||||
|
@ -1,5 +1,7 @@
|
||||
# 常见问题
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足.
|
||||
|
||||
### Q: 为什么挂一段时间后就会出现 `消息发送失败,账号可能被风控`?
|
||||
|
||||
### A: 如果你刚开始使用 go-cqhttp 建议挂机3-7天,即可解除风控
|
||||
|
@ -1,5 +1,7 @@
|
||||
# 配置
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足.
|
||||
|
||||
go-cqhttp 包含 `config.yml` 和 `device.json` 两个配置文件, 其中 `config.yml` 为运行配置 `device.json` 为虚拟设备信息.
|
||||
|
||||
## 配置信息
|
||||
|
@ -1,6 +1,8 @@
|
||||
# 拓展API
|
||||
|
||||
由于部分 api 原版 CQHTTP 并未实现,go-cqhttp 修改并增加了一些拓展 api .
|
||||
由于部分 api 原版 CQHTTP 并未实现,go-cqhttp 修改并增加了一些拓展 api
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足..
|
||||
|
||||
<details>
|
||||
<summary>目录</summary>
|
||||
|
412
docs/guild.md
412
docs/guild.md
@ -1,412 +0,0 @@
|
||||
# 频道相关API
|
||||
|
||||
> 注意: QQ频道功能目前还在测试阶段, go-cqhttp 也在适配的初期阶段, 以下 `API` `Event` 的字段名可能存在错误并均有可能在后续版本修改/添加/删除.
|
||||
> 目前仅供开发者测试以及适配使用
|
||||
|
||||
QQ频道相关功能的事件以及API
|
||||
|
||||
## 命名说明
|
||||
|
||||
API以及字段相关命名均为参考QQ官方命名或相似产品命名规则, 由于QQ频道的账号系统独立于QQ本体, 所以各个 `ID` 并不能和QQ通用.也无法通过 `tiny_id` 获取到 `QQ号`
|
||||
|
||||
下表为常见字段命名说明
|
||||
|
||||
| 命名 | 说明 |
|
||||
| ------------ | -------------------- |
|
||||
| `tiny_id` | 在频道系统中代表用户ID, 与QQ号并不通用 |
|
||||
| `guild_id` | 频道ID |
|
||||
| `channel_id` | 子频道ID |
|
||||
|
||||
> 所有频道相关事件的 `user_id` 均为 `tiny_id`
|
||||
|
||||
## 特殊说明
|
||||
|
||||
- 由于频道的限制, 目前无法通过图片摘要查询到频道图片消息的详细信息, 所以通过频道消息收到的图片均会下载完整文件到 `images/guild-images`. (群图片转发不受此限制)
|
||||
- 由于无法通过 `GlobalID` 放下频道消息的ID, 所以所有频道消息的 `message_id` 均为 `string` 类型
|
||||
- `send_msg` API将无法发送频道消息
|
||||
- `get_msg` API暂时无法获取频道消息
|
||||
- `reply` 等消息类型暂不支持解析
|
||||
- `at` 消息的 `target` 依然使用 `qq` 字段, 以保证一致性. 但内容为 `tiny_id`
|
||||
- 所有事件的 `self_id` 均为 BOT 的QQ号. `tiny_id` 将放在 `self_tiny_id` 字段
|
||||
- 遵循我们一贯的原则, 将不会支持主动加频道/主动拉人/红包相关消息类型
|
||||
- 频道相关的API仅能在 `Android Phone` 和 `iPad` 协议上使用.
|
||||
- 由于频道相关ID的数据类型均为 `uint64` , 为保证不超过某些语言的安全值范围, 在 `v1.0.0-beta8-fix3` 以后, 所有ID相关数据将转换为 `string` 类型, API调用 `uint64`
|
||||
或 `string` 均可接受.
|
||||
- 为保证一致性, 所有频道接口返回的 `用户ID` 均命名为 `tiny_id`, 所有频道相关接口的 `用户ID` 入参均命名为 `user_id`
|
||||
|
||||
## API
|
||||
|
||||
### 获取频道系统内BOT的资料
|
||||
|
||||
终结点: `/get_guild_service_profile`
|
||||
|
||||
**响应数据**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `tiny_id` | string | 自身的ID |
|
||||
| `avatar_url` | string | 头像链接 |
|
||||
|
||||
### 获取频道列表
|
||||
|
||||
终结点: `/get_guild_list`
|
||||
|
||||
**响应数据**
|
||||
|
||||
正常情况下响应 `GuildInfo` 数组, 未加入任何频道响应 `null`
|
||||
|
||||
GuildInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `guild_name` | string | 频道名称 |
|
||||
| `guild_display_id` | int64 | 频道显示ID, 公测后可能作为搜索ID使用 |
|
||||
|
||||
### 通过访客获取频道元数据
|
||||
|
||||
终结点: `/get_guild_meta_by_guest`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
|
||||
**响应数据**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `guild_name` | string | 频道名称 |
|
||||
| `guild_profile` | string | 频道简介 |
|
||||
| `create_time` | int64 | 创建时间 |
|
||||
| `max_member_count` | int64 | 频道人数上限 |
|
||||
| `max_robot_count` | int64 | 频道BOT数上限 |
|
||||
| `max_admin_count` | int64 | 频道管理员人数上限 |
|
||||
| `member_count` | int64 | 已加入人数 |
|
||||
| `owner_id` | string | 创建者ID |
|
||||
|
||||
### 获取子频道列表
|
||||
|
||||
终结点: `/get_guild_channel_list`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `no_cache` | bool | 是否无视缓存 |
|
||||
|
||||
**响应数据**
|
||||
|
||||
正常情况下响应 `ChannelInfo` 数组, 未找到任何子频道响应 `null`
|
||||
|
||||
ChannelInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `owner_guild_id` | string | 所属频道ID |
|
||||
| `channel_id` | string | 子频道ID |
|
||||
| `channel_type` | int32 | 子频道类型 |
|
||||
| `channel_name` | string | 子频道名称 |
|
||||
| `create_time` | int64 | 创建时间 |
|
||||
| `creator_tiny_id` | string | 创建者ID |
|
||||
| `talk_permission` | int32 | 发言权限类型 |
|
||||
| `visible_type` | int32 | 可视性类型 |
|
||||
| `current_slow_mode` | int32 | 当前启用的慢速模式Key |
|
||||
| `slow_modes` | []SlowModeInfo | 频道内可用慢速模式类型列表|
|
||||
|
||||
SlowModeInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `slow_mode_key` | int32 | 慢速模式Key |
|
||||
| `slow_mode_text` | string | 慢速模式说明 |
|
||||
| `speak_frequency` | int32 | 周期内发言频率限制 |
|
||||
| `slow_mode_circle` | int32 | 单位周期时间, 单位秒 |
|
||||
|
||||
已知子频道类型列表
|
||||
|
||||
| 类型 | 说明 |
|
||||
| ------------- | ---------- |
|
||||
| 1 | 文字频道 |
|
||||
| 2 | 语音频道 |
|
||||
| 5 | 直播频道 |
|
||||
| 7 | 主题频道 |
|
||||
|
||||
### 获取频道成员列表
|
||||
|
||||
终结点: `/get_guild_member_list`
|
||||
|
||||
> 由于频道人数较多(数万), 请尽量不要全量拉取成员列表, 这将会导致严重的性能问题
|
||||
>
|
||||
> 尽量使用 `get_guild_member_profile` 接口代替全量拉取
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `next_token` | string | 翻页Token |
|
||||
|
||||
> `next_token` 为空的情况下, 将返回第一页的数据, 并在返回值附带下一页的 `token`
|
||||
|
||||
**响应数据**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `members` | []GuildMemberInfo | 成员列表 |
|
||||
| `finished` | bool | 是否最终页 |
|
||||
| `next_token` | string | 翻页Token |
|
||||
|
||||
GuildMemberInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `tiny_id` | string | 成员ID |
|
||||
| `title` | string | 成员头衔 |
|
||||
| `nickname` | string | 成员昵称 |
|
||||
| `role_id` | string | 所在权限组ID |
|
||||
| `role_name` | string | 所在权限组名称 |
|
||||
|
||||
> 默认情况下频道管理员的权限组ID为 `2`, 部分频道可能会另行创建, 需手动判断
|
||||
>
|
||||
> 此接口仅展现最新的权限组, 获取用户加入的所有权限组请使用 `get_guild_member_profile` 接口
|
||||
|
||||
### 单独获取频道成员信息
|
||||
|
||||
终结点: `/get_guild_member_profile`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `user_id` | string | 用户ID |
|
||||
|
||||
**响应数据**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `tiny_id` | string | 用户ID |
|
||||
| `nickname` | string | 用户昵称 |
|
||||
| `avatar_url` | string | 头像地址 |
|
||||
| `join_time` | int64 | 加入时间 |
|
||||
| `roles` | []RoleInfo | 加入的所有权限组 |
|
||||
|
||||
RoleInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `role_id` | string | 权限组ID |
|
||||
| `role_name` | string | 权限组名称 |
|
||||
|
||||
### 发送信息到子频道
|
||||
|
||||
终结点: `/send_guild_channel_msg`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `channel_id` | string | 子频道ID |
|
||||
| `message` | Message | 消息, 与原有消息类型相同 |
|
||||
|
||||
**响应数据**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `message_id` | string | 消息ID |
|
||||
|
||||
### 获取话题频道帖子
|
||||
|
||||
终结点: `/get_topic_channel_feeds`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `channel_id` | string | 子频道ID |
|
||||
|
||||
**响应数据**
|
||||
|
||||
返回 `FeedInfo` 数组
|
||||
|
||||
FeedInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `id` | string | 帖子ID |
|
||||
| `channel_id` | string | 子频道ID |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `create_time` | int64 | 发帖时间 |
|
||||
| `title` | string | 帖子标题 |
|
||||
| `sub_title` | string | 帖子副标题 |
|
||||
| `poster_info` | PosterInfo | 发帖人信息 |
|
||||
| `resource` | ResourceInfo | 媒体资源信息 |
|
||||
| `resource.images` | []FeedMedia | 帖子附带的图片列表 |
|
||||
| `resource.videos` | []FeedMedia | 帖子附带的视频列表 |
|
||||
| `contents` | []FeedContent | 帖子内容 |
|
||||
|
||||
PosterInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `tiny_id` | string | 发帖人ID |
|
||||
| `nickname` | string | 发帖人昵称 |
|
||||
| `icon_url` | string | 发帖人头像链接 |
|
||||
|
||||
FeedMedia:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `file_id` | string | 媒体ID |
|
||||
| `pattern_id` | string | 控件ID?(不确定) |
|
||||
| `url` | string | 媒体链接 |
|
||||
| `height` | int32 | 媒体高度 |
|
||||
| `width` | int32 | 媒体宽度 |
|
||||
|
||||
FeedContent:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `type` | string | 内容类型 |
|
||||
| `data` | Data | 内容数据 |
|
||||
|
||||
#### 内容类型列表:
|
||||
|
||||
| 类型 | 说明 |
|
||||
| ----- | ---------- |
|
||||
| `text` | 文本 |
|
||||
| `face` | 表情 |
|
||||
| `at` | At |
|
||||
| `url_quote` | 链接引用 |
|
||||
| `channel_quote` | 子频道引用 |
|
||||
|
||||
#### 内容类型对应数据列表:
|
||||
|
||||
- `text`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `text` | string | 文本内容 |
|
||||
|
||||
- `face`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `id` | string | 表情ID |
|
||||
|
||||
- `at`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `id` | string | 目标ID |
|
||||
| `qq` | string | 目标ID, 为确保和 `array message` 的一致性保留 |
|
||||
|
||||
- `url_quote`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `display_text` | string | 显示文本 |
|
||||
| `url` | string | 链接 |
|
||||
|
||||
- `channel_quote`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------- | ----- | ---------- |
|
||||
| `display_text` | string | 显示文本 |
|
||||
| `guild_id` | string | 频道ID |
|
||||
| `channel_id` | string | 子频道ID |
|
||||
|
||||
## 事件
|
||||
|
||||
### 收到频道消息
|
||||
|
||||
**上报数据**
|
||||
|
||||
| 字段 | 类型 | 可能的值 | 说明 |
|
||||
| ------------- | ------ | -------------- | -------------- |
|
||||
| `post_type` | string | `message` | 上报类型 |
|
||||
| `message_type` | string | `guild` | 消息类型 |
|
||||
| `sub_type` | string | `channel` | 消息子类型 |
|
||||
| `guild_id` | string | | 频道ID |
|
||||
| `channel_id` | string | | 子频道ID |
|
||||
| `user_id` | string | | 消息发送者ID |
|
||||
| `message_id` | string | | 消息ID |
|
||||
| `sender` | Sender | | 发送者 |
|
||||
| `message` | Message | | 消息内容 |
|
||||
|
||||
> 注: 此处的 `Sender` 对象为保证一致性, `user_id` 为 `uint64` 类型, 并添加了 `string` 类型的 `tiny_id` 字段
|
||||
|
||||
### 频道消息表情贴更新
|
||||
|
||||
**上报数据**
|
||||
|
||||
| 字段 | 类型 | 可能的值 | 说明 |
|
||||
| ------------- | ------ | -------------- | -------------- |
|
||||
| `post_type` | string | `notice` | 上报类型 |
|
||||
| `notice_type` | string | `message_reactions_updated` | 消息类型 |
|
||||
| `guild_id` | string | | 频道ID |
|
||||
| `channel_id` | string | | 子频道ID |
|
||||
| `user_id` | string | | 操作者ID |
|
||||
| `message_id` | string | | 消息ID |
|
||||
| `current_reactions` | []ReactionInfo | | 当前消息被贴表情列表 |
|
||||
|
||||
ReactionInfo:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | ----- | ---- |
|
||||
| `emoji_id` | string | 表情ID |
|
||||
| `emoji_index` | int32 | 表情对应数值ID |
|
||||
| `emoji_type` | int32 | 表情类型 |
|
||||
| `emoji_name` | string | 表情名字 |
|
||||
| `count` | int32 | 当前表情被贴数量 |
|
||||
| `clicked` | bool | BOT是否点击 |
|
||||
|
||||
### 子频道信息更新
|
||||
|
||||
**上报数据**
|
||||
|
||||
| 字段 | 类型 | 可能的值 | 说明 |
|
||||
| ------------- | ------ | -------------- | -------------- |
|
||||
| `post_type` | string | `notice` | 上报类型 |
|
||||
| `notice_type` | string | `channel_updated` | 消息类型 |
|
||||
| `guild_id` | string | | 频道ID |
|
||||
| `channel_id` | string | | 子频道ID |
|
||||
| `user_id` | string | | 操作者ID |
|
||||
| `operator_id` | string | | 操作者ID |
|
||||
| `old_info` | ChannelInfo | | 更新前的频道信息 |
|
||||
| `new_info` | ChannelInfo | | 更新后的频道信息 |
|
||||
|
||||
### 子频道创建
|
||||
|
||||
**上报数据**
|
||||
|
||||
| 字段 | 类型 | 可能的值 | 说明 |
|
||||
| ------------- | ------ | -------------- | -------------- |
|
||||
| `post_type` | string | `notice` | 上报类型 |
|
||||
| `notice_type` | string | `channel_created` | 消息类型 |
|
||||
| `guild_id` | string | | 频道ID |
|
||||
| `channel_id` | string | | 子频道ID |
|
||||
| `user_id` | string | | 操作者ID |
|
||||
| `operator_id` | string | | 操作者ID |
|
||||
| `channel_info` | ChannelInfo | | 频道信息 |
|
||||
|
||||
### 子频道删除
|
||||
|
||||
**上报数据**
|
||||
|
||||
| 字段 | 类型 | 可能的值 | 说明 |
|
||||
| ------------- | ------ | -------------- | -------------- |
|
||||
| `post_type` | string | `notice` | 上报类型 |
|
||||
| `notice_type` | string | `channel_destroyed` | 消息类型 |
|
||||
| `guild_id` | string | | 频道ID |
|
||||
| `channel_id` | string | | 子频道ID |
|
||||
| `user_id` | string | | 操作者ID |
|
||||
| `operator_id` | string | | 操作者ID |
|
||||
| `channel_info` | ChannelInfo | | 频道信息 |
|
@ -2,6 +2,8 @@
|
||||
|
||||
欢迎来到 go-cqhttp 文档 目前还在咕
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足.
|
||||
|
||||
# 基础教程
|
||||
## 下载
|
||||
从[release](https://github.com/Mrs4s/go-cqhttp/releases)界面下载最新版本的go-cqhttp
|
||||
|
@ -3,15 +3,22 @@ package global
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary" // 和 MiraiGo 共用同一 buffer 池
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/binary"
|
||||
"github.com/RomiChan/syncx"
|
||||
)
|
||||
|
||||
var bufferTable syncx.Map[*bytes.Buffer, *binary.Builder]
|
||||
|
||||
// NewBuffer 从池中获取新 bytes.Buffer
|
||||
func NewBuffer() *bytes.Buffer {
|
||||
return (*bytes.Buffer)(binary.SelectWriter())
|
||||
builder := binary.SelectBuilder(nil)
|
||||
bufferTable.Store(builder.Buffer(), builder)
|
||||
return builder.Buffer()
|
||||
}
|
||||
|
||||
// PutBuffer 将 Buffer放入池中
|
||||
func PutBuffer(buf *bytes.Buffer) {
|
||||
binary.PutWriter((*binary.Writer)(buf))
|
||||
if v, ok := bufferTable.LoadAndDelete(buf); ok {
|
||||
binary.PutBuilder(v)
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func EncoderSilk(data []byte) ([]byte, error) {
|
||||
return nil, errors.Wrap(err, "calc md5 failed")
|
||||
}
|
||||
tempName := hex.EncodeToString(h.Sum(nil))
|
||||
if silkPath := path.Join("data/cache", tempName+".silk"); PathExists(silkPath) {
|
||||
if silkPath := path.Join("data/cache", tempName+".silk"); FileExists(silkPath) {
|
||||
return os.ReadFile(silkPath)
|
||||
}
|
||||
slk, err := base.EncodeSilk(data, tempName)
|
||||
@ -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")
|
||||
}
|
||||
|
24
global/fs.go
24
global/fs.go
@ -12,7 +12,8 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
|
||||
b14 "github.com/fumiama/go-base16384"
|
||||
"github.com/segmentio/asm/base64"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -27,6 +28,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使用错误转储目录
|
||||
@ -37,10 +40,16 @@ const (
|
||||
HeaderSilk = "\x02#!SILK_V3"
|
||||
)
|
||||
|
||||
// PathExists 判断给定path是否存在
|
||||
// PathExists 判断给定path是否存在且path为路径
|
||||
func PathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || errors.Is(err, os.ErrExist)
|
||||
file, err := os.Stat(path)
|
||||
return (err == nil || errors.Is(err, os.ErrExist)) && file.IsDir()
|
||||
}
|
||||
|
||||
// FileExists 判断给定path是否为存在且path为文件
|
||||
func FileExists(path string) bool {
|
||||
file, err := os.Stat(path)
|
||||
return (err == nil || errors.Is(err, os.ErrExist)) && !file.IsDir()
|
||||
}
|
||||
|
||||
// ReadAllText 读取给定path对应文件,无法读取时返回空值
|
||||
@ -61,7 +70,7 @@ func WriteAllText(path, text string) error {
|
||||
// Check 检测err是否为nil
|
||||
func Check(err error, deleteSession bool) {
|
||||
if err != nil {
|
||||
if deleteSession && PathExists("session.token") {
|
||||
if deleteSession && FileExists("session.token") {
|
||||
_ = os.Remove("session.token")
|
||||
}
|
||||
log.Fatalf("遇到错误: %v", err)
|
||||
@ -81,13 +90,14 @@ func FindFile(file, cache, p string) (data []byte, err error) {
|
||||
case strings.HasPrefix(file, "http"): // https also has prefix http
|
||||
hash := md5.Sum([]byte(file))
|
||||
cacheFile := path.Join(CachePath, hex.EncodeToString(hash[:])+".cache")
|
||||
if (cache == "" || cache == "1") && PathExists(cacheFile) {
|
||||
if (cache == "" || cache == "1") && FileExists(cacheFile) {
|
||||
return os.ReadFile(cacheFile)
|
||||
}
|
||||
err = download.Request{URL: file}.WriteToFile(cacheFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.ReadFile(cacheFile)
|
||||
case strings.HasPrefix(file, "base64"):
|
||||
data, err = base64.StdEncoding.DecodeString(strings.TrimPrefix(file, "base64://"))
|
||||
if err != nil {
|
||||
@ -112,7 +122,7 @@ func FindFile(file, cache, p string) (data []byte, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case PathExists(path.Join(p, file)):
|
||||
case FileExists(path.Join(p, file)):
|
||||
data, err = os.ReadFile(path.Join(p, file))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -108,7 +108,7 @@ func (hook *LocalHook) SetPath(path string) {
|
||||
}
|
||||
|
||||
// NewLocalHook 初始化本地日志钩子实现
|
||||
func NewLocalHook(args interface{}, consoleFormatter, fileFormatter logrus.Formatter, levels ...logrus.Level) *LocalHook {
|
||||
func NewLocalHook(args any, consoleFormatter, fileFormatter logrus.Formatter, levels ...logrus.Level) *LocalHook {
|
||||
hook := &LocalHook{
|
||||
lock: new(sync.Mutex),
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// MSG 消息Map
|
||||
type MSG = map[string]interface{}
|
||||
type MSG = map[string]any
|
||||
|
||||
// VersionNameCompare 检查版本名是否需要更新, 仅适用于 go-cqhttp 的版本命名规则
|
||||
//
|
||||
|
146
global/quote.go
146
global/quote.go
@ -1,146 +0,0 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
lowerhex = "0123456789abcdef"
|
||||
upperhex = "0123456789ABCDEF"
|
||||
)
|
||||
|
||||
// Quote returns a double-quoted Go string literal representing s. The
|
||||
// returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for
|
||||
// control characters and non-printable characters as defined by
|
||||
// IsPrint.
|
||||
func Quote(s string) string {
|
||||
return quoteWith(s, '"', false, false)
|
||||
}
|
||||
|
||||
func quoteWith(s string, quote byte, asciiOnly, graphicOnly bool) string {
|
||||
return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote, asciiOnly, graphicOnly))
|
||||
}
|
||||
|
||||
func appendQuotedWith(buf []byte, s string, quote byte, asciiOnly, graphicOnly bool) []byte {
|
||||
// Often called with big strings, so preallocate. If there's quoting,
|
||||
// this is conservative but still helps a lot.
|
||||
if cap(buf)-len(buf) < len(s) {
|
||||
nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1)
|
||||
copy(nBuf, buf)
|
||||
buf = nBuf
|
||||
}
|
||||
buf = append(buf, quote)
|
||||
for width := 0; len(s) > 0; s = s[width:] {
|
||||
r := rune(s[0])
|
||||
width = 1
|
||||
if r >= utf8.RuneSelf {
|
||||
r, width = utf8.DecodeRuneInString(s)
|
||||
}
|
||||
if width == 1 && r == utf8.RuneError {
|
||||
buf = append(buf, `\x`...)
|
||||
buf = append(buf, lowerhex[s[0]>>4])
|
||||
buf = append(buf, lowerhex[s[0]&0xF])
|
||||
continue
|
||||
}
|
||||
buf = appendEscapedRune(buf, r, quote, asciiOnly, graphicOnly)
|
||||
}
|
||||
buf = append(buf, quote)
|
||||
return buf
|
||||
}
|
||||
func appendEscapedRune(buf []byte, r rune, quote byte, asciiOnly, graphicOnly bool) []byte {
|
||||
var runeTmp [utf8.UTFMax]byte
|
||||
if r == rune(quote) || r == '\\' { // always backslashed
|
||||
buf = append(buf, '\\')
|
||||
buf = append(buf, byte(r))
|
||||
return buf
|
||||
}
|
||||
if asciiOnly {
|
||||
if r < utf8.RuneSelf && strconv.IsPrint(r) {
|
||||
buf = append(buf, byte(r))
|
||||
return buf
|
||||
}
|
||||
} else if strconv.IsPrint(r) || graphicOnly && isInGraphicList(r) {
|
||||
n := utf8.EncodeRune(runeTmp[:], r)
|
||||
buf = append(buf, runeTmp[:n]...)
|
||||
return buf
|
||||
}
|
||||
switch r {
|
||||
case '\a':
|
||||
buf = append(buf, `\a`...)
|
||||
case '\b':
|
||||
buf = append(buf, `\b`...)
|
||||
case '\f':
|
||||
buf = append(buf, `\f`...)
|
||||
case '\n':
|
||||
buf = append(buf, `\n`...)
|
||||
case '\r':
|
||||
buf = append(buf, `\r`...)
|
||||
case '\t':
|
||||
buf = append(buf, `\t`...)
|
||||
case '\v':
|
||||
buf = append(buf, `\v`...)
|
||||
default:
|
||||
switch {
|
||||
case !utf8.ValidRune(r):
|
||||
r = 0xFFFD
|
||||
fallthrough
|
||||
case r < 0x10000:
|
||||
buf = append(buf, `\u`...)
|
||||
for s := 12; s >= 0; s -= 4 {
|
||||
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||
}
|
||||
default:
|
||||
buf = append(buf, `\U`...)
|
||||
for s := 28; s >= 0; s -= 4 {
|
||||
buf = append(buf, lowerhex[r>>uint(s)&0xF])
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func isInGraphicList(r rune) bool {
|
||||
// We know r must fit in 16 bits - see makeisprint.go.
|
||||
if r > 0xFFFF {
|
||||
return false
|
||||
}
|
||||
rr := uint16(r)
|
||||
i := bsearch16(isGraphic, rr)
|
||||
return i < len(isGraphic) && rr == isGraphic[i]
|
||||
}
|
||||
|
||||
// bsearch16 returns the smallest i such that a[i] >= x.
|
||||
// If there is no such i, bsearch16 returns len(a).
|
||||
func bsearch16(a []uint16, x uint16) int {
|
||||
i, j := 0, len(a)
|
||||
for i < j {
|
||||
h := i + (j-i)>>1
|
||||
if a[h] < x {
|
||||
i = h + 1
|
||||
} else {
|
||||
j = h
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// isGraphic lists the graphic runes not matched by IsPrint.
|
||||
var isGraphic = []uint16{
|
||||
0x00a0,
|
||||
0x1680,
|
||||
0x2000,
|
||||
0x2001,
|
||||
0x2002,
|
||||
0x2003,
|
||||
0x2004,
|
||||
0x2005,
|
||||
0x2006,
|
||||
0x2007,
|
||||
0x2008,
|
||||
0x2009,
|
||||
0x200a,
|
||||
0x202f,
|
||||
0x205f,
|
||||
0x3000,
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
// Package terminal 包含用于检测在windows下是否通过双击运行go-cqhttp的函数
|
||||
// Package terminal 包含用于检测在windows下是否通过双击运行go-cqhttp, 禁用快速编辑, 启用VT100的函数
|
||||
package terminal
|
||||
|
@ -1,9 +1,8 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
// RunningByDoubleClick 检查是否通过双击直接运行,非Windows系统永远返回false
|
||||
// RunningByDoubleClick 检查是否通过双击直接运行,非Windows系统永远返回false
|
||||
func RunningByDoubleClick() bool {
|
||||
return false
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
@ -55,7 +52,7 @@ func NoMoreDoubleClick() error {
|
||||
return errors.Errorf("写入go-cqhttp.bat失败: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
boxW(0, "安全启动脚本已生成,请双击go-cqhttp.bat启动", "提示", 0x00000040|0x00000000)
|
||||
boxW(0, "安全启动脚本已生成,请双击go-cqhttp.bat启动", "提示", 0x00000040)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
13
global/terminal/quick_edit.go
Normal file
13
global/terminal/quick_edit.go
Normal 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
|
||||
}
|
44
global/terminal/quick_edit_windows.go
Normal file
44
global/terminal/quick_edit_windows.go
Normal 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
15
global/terminal/title.go
Normal 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())
|
||||
}
|
29
global/terminal/title_windows.go
Normal file
29
global/terminal/title_windows.go
Normal 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
8
global/terminal/vt100.go
Normal file
@ -0,0 +1,8 @@
|
||||
//go:build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
// EnableVT100 启用颜色、控制字符,非Windows系统永远返回nil
|
||||
func EnableVT100() error {
|
||||
return nil
|
||||
}
|
23
global/terminal/vt100_windows.go
Normal file
23
global/terminal/vt100_windows.go
Normal 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)
|
||||
}
|
64
go.mod
64
go.mod
@ -1,59 +1,61 @@
|
||||
module github.com/Mrs4s/go-cqhttp
|
||||
|
||||
go 1.19
|
||||
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-20221202060717-4658474c60dd
|
||||
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
|
||||
github.com/fumiama/go-hide-param v0.1.4
|
||||
github.com/FloatTech/sqlite v1.6.3
|
||||
github.com/LagrangeDev/LagrangeGo v0.1.3-0.20250111034447-91650c0c29cd
|
||||
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a
|
||||
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
|
||||
github.com/fumiama/go-base16384 v1.7.0
|
||||
github.com/fumiama/go-hide-param v0.2.0
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/segmentio/asm v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
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/sys v0.2.0
|
||||
golang.org/x/term v0.2.0
|
||||
golang.org/x/time v0.2.0
|
||||
gopkg.ilharper.com/x/isatty v1.1.0
|
||||
go.mongodb.org/mongo-driver v1.12.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/image v0.23.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/term v0.27.0
|
||||
golang.org/x/time v0.3.0
|
||||
gopkg.ilharper.com/x/isatty v1.1.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b // indirect
|
||||
github.com/RomiChan/protobuf v0.0.0-20220624030127-3310cba9dbc0 // indirect
|
||||
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 // indirect
|
||||
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fumiama/imgsz v0.0.2 // indirect
|
||||
github.com/fumiama/gofastTEA v0.1.2 // indirect
|
||||
github.com/fumiama/imgsz v0.0.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.3.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
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/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
@ -65,5 +67,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
|
||||
|
198
go.sum
198
go.sum
@ -1,50 +1,41 @@
|
||||
github.com/FloatTech/sqlite v1.5.7 h1:Bvo4LSojcZ6dVtbHrkqvt6z4v8e+sj0G5PSUIvdawsk=
|
||||
github.com/FloatTech/sqlite v1.5.7/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY=
|
||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJGLDNIdRX3BI546D3O7k7vrVueZw=
|
||||
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-20221202060717-4658474c60dd h1:rzAbPc++5CJ1VZDjq/eORXOWMMGsDN3DMAPMXfI7Fvs=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20221202060717-4658474c60dd/go.mod h1:lecSP26qedhinCceWn1x02dLDxGotH5nTFlpIMilmVM=
|
||||
github.com/RomiChan/protobuf v0.0.0-20220624030127-3310cba9dbc0 h1:GEwcB4dL9vc4veW1fLNt0Fby3wspVflAn5v9/HbUwDM=
|
||||
github.com/RomiChan/protobuf v0.0.0-20220624030127-3310cba9dbc0/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
|
||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
|
||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc h1:AAx50/fb/xS4lvsdQg+bFbGvqSDhyV1MF+p2PLCamZ0=
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc/go.mod h1:OMmITAib6POA37xCichWM0aRnoVpSMZO1rB/G01wrr0=
|
||||
github.com/FloatTech/sqlite v1.6.3 h1:MQkqBNlkPuCoKQQgoNLuTL/2Ci3tBTFAnVYBdD0Wy4M=
|
||||
github.com/FloatTech/sqlite v1.6.3/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY=
|
||||
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 h1:g4pTnDJUW4VbJ9NvoRfUvdjDrHz/6QhfN/LoIIpICbo=
|
||||
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
|
||||
github.com/LagrangeDev/LagrangeGo v0.1.3-0.20250111034447-91650c0c29cd h1:7YooxHVIctFD1FPsphPp3i0EDKFuPQFglgWVlxV4qSw=
|
||||
github.com/LagrangeDev/LagrangeGo v0.1.3-0.20250111034447-91650c0c29cd/go.mod h1:DaPYW9z4rtbdulFPbsWjWbFXPCV3qN727WFvgPxu5a8=
|
||||
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a h1:aU1703IHxupjzipvhu16qYKLMR03e+8WuNR+JMsKfGU=
|
||||
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a/go.mod h1:OZqLNXdYJHmx7aqq/T6wAdFEdoGm5nmIfC4kU7M8P8o=
|
||||
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-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
|
||||
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs=
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5/go.mod h1:0UcFaCkhp6vZw6l5Dpq0Dp673CoF9GdvA8lTfst0GiU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
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=
|
||||
github.com/fumiama/go-hide-param v0.1.4/go.mod h1:vJkQlJIEI56nIyp7tCQu1/2QOyKtZpudsnJkGk9U1aY=
|
||||
github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
|
||||
github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
|
||||
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
|
||||
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
|
||||
github.com/fumiama/go-hide-param v0.2.0 h1:1IuDOYJBDZVH2/wvF4gzhO8a/3zWXpfOJDYyaLiRSVQ=
|
||||
github.com/fumiama/go-hide-param v0.2.0/go.mod h1:vJkQlJIEI56nIyp7tCQu1/2QOyKtZpudsnJkGk9U1aY=
|
||||
github.com/fumiama/gofastTEA v0.1.2 h1:nMB6kAL5Fo4IwZVS4hkIsI7+4tXQtuWI0pFBM/Y1z7Q=
|
||||
github.com/fumiama/gofastTEA v0.1.2/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
|
||||
github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI=
|
||||
github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
|
||||
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
@ -52,11 +43,6 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
|
||||
@ -66,133 +52,127 @@ github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
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=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60 h1:lRKf10iIOW0VsH5WDF621ihzR+R2wEBZVtNRHuLLCb4=
|
||||
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60/go.mod h1:ecFKZPX81BaB70I6ruUgEwYcDOtuNgJGnjdK+MIl5ko=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE=
|
||||
go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
|
||||
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.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/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.ilharper.com/x/isatty v1.1.0 h1:slOK6hP9/y9mJWyCInMwnT432NExfWyYV2SsebdYOCY=
|
||||
gopkg.ilharper.com/x/isatty v1.1.0/go.mod h1:ofpv77Td5qQO6R1dmDd3oNt8TZdRo+l5gYAMxopRyS0=
|
||||
gopkg.ilharper.com/x/isatty v1.1.1 h1:RAg32Pxq/nIK4AVtdm9RBqxsxZZX1uRKRSS21E5SHMk=
|
||||
gopkg.ilharper.com/x/isatty v1.1.1/go.mod h1:ofpv77Td5qQO6R1dmDd3oNt8TZdRo+l5gYAMxopRyS0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -23,19 +23,24 @@ 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扫描
|
||||
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 // 是否更新协议
|
||||
SignServers []config.SignServer // 使用特定的服务器进行签名
|
||||
HTTPTimeout int // download 超时时间
|
||||
SignServerTimeout int // 签名服务器超时时间
|
||||
|
||||
PostFormat string // 上报格式 string or array
|
||||
Proxy string // 存储 proxy_rewrite,用于设置代理
|
||||
@ -59,6 +64,7 @@ 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.Parse()
|
||||
|
||||
if *d {
|
||||
@ -79,9 +85,13 @@ func Init() {
|
||||
ExtraReplyData = conf.Message.ExtraReplyData
|
||||
ForceFragmented = conf.Message.ForceFragment
|
||||
SkipMimeScan = conf.Message.SkipMimeScan
|
||||
ConvertWebpImage = conf.Message.ConvertWebpImage
|
||||
ReportSelfMessage = conf.Message.ReportSelfMessage
|
||||
UseSSOAddress = conf.Account.UseSSOAddress
|
||||
AllowTempSession = conf.Account.AllowTempSession
|
||||
SignServers = conf.Account.SignServers
|
||||
HTTPTimeout = conf.Message.HTTPTimeout
|
||||
SignServerTimeout = int(conf.Account.SignServerTimeout)
|
||||
}
|
||||
{ // others
|
||||
Proxy = conf.Message.ProxyRewrite
|
||||
|
@ -4,6 +4,7 @@ package download
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -12,26 +13,47 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/RomiChan/syncx"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
)
|
||||
|
||||
var client = &http.Client{
|
||||
var client = newClient(time.Second * 15)
|
||||
var clients syncx.Map[time.Duration, *http.Client]
|
||||
|
||||
var clienth2 = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: func(request *http.Request) (u *url.URL, e error) {
|
||||
Proxy: func(request *http.Request) (*url.URL, error) {
|
||||
if base.Proxy == "" {
|
||||
return http.ProxyFromEnvironment(request)
|
||||
}
|
||||
return url.Parse(base.Proxy)
|
||||
},
|
||||
ForceAttemptHTTP2: false,
|
||||
MaxConnsPerHost: 0,
|
||||
MaxIdleConns: 0,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConnsPerHost: 999,
|
||||
},
|
||||
Timeout: time.Second * 15,
|
||||
}
|
||||
|
||||
func newClient(t time.Duration) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: func(request *http.Request) (*url.URL, error) {
|
||||
if base.Proxy == "" {
|
||||
return http.ProxyFromEnvironment(request)
|
||||
}
|
||||
return url.Parse(base.Proxy)
|
||||
},
|
||||
// Disable http2
|
||||
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
|
||||
MaxIdleConnsPerHost: 999,
|
||||
},
|
||||
Timeout: t,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrOverSize 响应主体过大时返回此错误
|
||||
@ -40,15 +62,52 @@ var ErrOverSize = errors.New("oversize")
|
||||
// UserAgent HTTP请求时使用的UA
|
||||
const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
|
||||
|
||||
// WithTimeout get a download instance with timeout t
|
||||
func (r Request) WithTimeout(t time.Duration) *Request {
|
||||
if c, ok := clients.Load(t); ok {
|
||||
r.custcli = c
|
||||
} else {
|
||||
c := newClient(t)
|
||||
clients.Store(t, c)
|
||||
r.custcli = c
|
||||
}
|
||||
return &r
|
||||
}
|
||||
|
||||
// SetTimeout set internal/download client timeout
|
||||
func SetTimeout(t time.Duration) {
|
||||
if t == 0 {
|
||||
t = time.Second * 10
|
||||
}
|
||||
client.Timeout = t
|
||||
clienth2.Timeout = t
|
||||
}
|
||||
|
||||
// Request is a file download request
|
||||
type Request struct {
|
||||
URL string
|
||||
Header map[string]string
|
||||
Limit int64
|
||||
Method string
|
||||
URL string
|
||||
Header map[string]string
|
||||
Limit int64
|
||||
Body io.Reader
|
||||
custcli *http.Client
|
||||
}
|
||||
|
||||
func (r Request) client() *http.Client {
|
||||
if r.custcli != nil {
|
||||
return r.custcli
|
||||
}
|
||||
if strings.Contains(r.URL, "go-cqhttp.org") {
|
||||
return clienth2
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func (r Request) do() (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, r.URL, nil)
|
||||
if r.Method == "" {
|
||||
r.Method = http.MethodGet
|
||||
}
|
||||
req, err := http.NewRequest(r.Method, r.URL, r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -58,7 +117,7 @@ func (r Request) do() (*http.Response, error) {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return client.Do(req)
|
||||
return r.client().Do(req)
|
||||
}
|
||||
|
||||
func (r Request) body() (io.ReadCloser, error) {
|
||||
@ -79,23 +138,25 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
defer rd.Close()
|
||||
defer r.client().CloseIdleConnections()
|
||||
return io.ReadAll(rd)
|
||||
}
|
||||
|
||||
// JSON 发送GET请求, 并转换响应为JSON
|
||||
// JSON 发送请求, 并转换响应为JSON
|
||||
func (r Request) JSON() (gjson.Result, error) {
|
||||
rd, err := r.body()
|
||||
if err != nil {
|
||||
return gjson.Result{}, err
|
||||
}
|
||||
defer rd.Close()
|
||||
defer r.client().CloseIdleConnections()
|
||||
|
||||
var sb strings.Builder
|
||||
_, err = io.Copy(&sb, rd)
|
||||
@ -111,6 +172,7 @@ func writeToFile(reader io.ReadCloser, path string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
_, err = file.ReadFrom(reader)
|
||||
return err
|
||||
}
|
||||
@ -122,6 +184,7 @@ func (r Request) WriteToFile(path string) error {
|
||||
return err
|
||||
}
|
||||
defer rd.Close()
|
||||
defer r.client().CloseIdleConnections()
|
||||
return writeToFile(rd, path)
|
||||
}
|
||||
|
||||
@ -131,6 +194,7 @@ func (r Request) WriteToFileMultiThreading(path string, thread int) error {
|
||||
return r.WriteToFile(path)
|
||||
}
|
||||
|
||||
defer r.client().CloseIdleConnections()
|
||||
limit := r.Limit
|
||||
type BlockMetaData struct {
|
||||
BeginOffset int64
|
||||
|
245
internal/msg/element.go
Normal file
245
internal/msg/element.go
Normal file
@ -0,0 +1,245 @@
|
||||
// Package msg 提供了go-cqhttp消息中间表示,CQ码处理等等
|
||||
package msg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/LagrangeDev/LagrangeGo/utils/binary"
|
||||
)
|
||||
|
||||
// @@@ CQ码转义处理 @@@
|
||||
|
||||
// EscapeText 将字符串raw中部分字符转义
|
||||
//
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
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("&")
|
||||
case '[':
|
||||
b.WriteString("[")
|
||||
case ']':
|
||||
b.WriteString("]")
|
||||
}
|
||||
start = j + 1
|
||||
}
|
||||
b.WriteString(s[start:])
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// EscapeValue 将字符串value中部分字符转义
|
||||
//
|
||||
// - , -> ,
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
func EscapeValue(value string) string {
|
||||
ret := EscapeText(value)
|
||||
return strings.ReplaceAll(ret, ",", ",")
|
||||
}
|
||||
|
||||
// UnescapeText 将字符串content中部分字符反转义
|
||||
//
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
func UnescapeText(content string) string {
|
||||
ret := content
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
ret = strings.ReplaceAll(ret, "]", "]")
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
return ret
|
||||
}
|
||||
|
||||
// UnescapeValue 将字符串content中部分字符反转义
|
||||
//
|
||||
// - , -> ,
|
||||
// - & -> &
|
||||
// - [ -> [
|
||||
// - ] -> ]
|
||||
func UnescapeValue(content string) string {
|
||||
ret := strings.ReplaceAll(content, ",", ",")
|
||||
return UnescapeText(ret)
|
||||
}
|
||||
|
||||
// @@@ 消息中间表示 @@@
|
||||
|
||||
// Pair key value pair
|
||||
type Pair struct {
|
||||
K string
|
||||
V string
|
||||
}
|
||||
|
||||
// Element single message
|
||||
type Element struct {
|
||||
Type string
|
||||
Data []Pair
|
||||
}
|
||||
|
||||
// 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
|
||||
func (e *Element) CQCode() string {
|
||||
buf := strings.Builder{}
|
||||
e.WriteCQCodeTo(&buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// WriteCQCodeTo write element's cqcode into sb
|
||||
func (e *Element) WriteCQCodeTo(sb *strings.Builder) {
|
||||
if e.Type == "text" {
|
||||
sb.WriteString(EscapeText(e.Data[0].V)) // must be {"text": value}
|
||||
return
|
||||
}
|
||||
sb.WriteString("[CQ:")
|
||||
sb.WriteString(e.Type)
|
||||
for _, data := range e.Data {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteString(data.K)
|
||||
sb.WriteByte('=')
|
||||
sb.WriteString(EscapeValue(data.V))
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
}
|
||||
|
||||
// MarshalJSON see encoding/json.Marshaler
|
||||
func (e *Element) MarshalJSON() ([]byte, error) {
|
||||
return binary.NewWriterF(func(w *binary.Builder) {
|
||||
buf := w.Buffer()
|
||||
// fmt.Fprintf(buf, `{"type":"%s","data":{`, e.Type)
|
||||
buf.WriteString(`{"type":"`)
|
||||
buf.WriteString(e.Type)
|
||||
buf.WriteString(`","data":{`)
|
||||
for i, data := range e.Data {
|
||||
if i != 0 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
// fmt.Fprintf(buf, `"%s":%q`, data.K, data.V)
|
||||
buf.WriteByte('"')
|
||||
buf.WriteString(data.K)
|
||||
buf.WriteString(`":`)
|
||||
buf.WriteString(QuoteJSON(data.V))
|
||||
}
|
||||
buf.WriteString(`}}`)
|
||||
}), nil
|
||||
}
|
||||
|
||||
const hex = "0123456789abcdef"
|
||||
|
||||
// QuoteJSON 按JSON转义为字符加上双引号
|
||||
func QuoteJSON(s string) string {
|
||||
i, j := 0, 0
|
||||
var b strings.Builder
|
||||
b.WriteByte('"')
|
||||
for j < len(s) {
|
||||
c := s[j]
|
||||
|
||||
if c >= 0x20 && c <= 0x7f && c != '\\' && c != '"' {
|
||||
// fast path: most of the time, printable ascii characters are used
|
||||
j++
|
||||
continue
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '\\', '"', '\n', '\r', '\t':
|
||||
b.WriteString(s[i:j])
|
||||
b.WriteByte('\\')
|
||||
switch c {
|
||||
case '\n':
|
||||
c = 'n'
|
||||
case '\r':
|
||||
c = 'r'
|
||||
case '\t':
|
||||
c = 't'
|
||||
}
|
||||
b.WriteByte(c)
|
||||
j++
|
||||
i = j
|
||||
continue
|
||||
|
||||
case '<', '>', '&':
|
||||
b.WriteString(s[i:j])
|
||||
b.WriteString(`\u00`)
|
||||
b.WriteByte(hex[c>>4])
|
||||
b.WriteByte(hex[c&0xF])
|
||||
j++
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
|
||||
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||
if c < 0x20 {
|
||||
b.WriteString(s[i:j])
|
||||
b.WriteString(`\u00`)
|
||||
b.WriteByte(hex[c>>4])
|
||||
b.WriteByte(hex[c&0xF])
|
||||
j++
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRuneInString(s[j:])
|
||||
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
b.WriteString(s[i:j])
|
||||
b.WriteString(`\ufffd`)
|
||||
j += size
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '\u2028', '\u2029':
|
||||
// U+2028 is LINE SEPARATOR.
|
||||
// U+2029 is PARAGRAPH SEPARATOR.
|
||||
// They are both technically valid characters in JSON strings,
|
||||
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||
// and can lead to security holes there. It is valid JSON to
|
||||
// escape them, so we do so unconditionally.
|
||||
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||
b.WriteString(s[i:j])
|
||||
b.WriteString(`\u202`)
|
||||
b.WriteByte(hex[r&0xF])
|
||||
j += size
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
|
||||
j += size
|
||||
}
|
||||
|
||||
b.WriteString(s[i:])
|
||||
b.WriteByte('"')
|
||||
return b.String()
|
||||
}
|
29
internal/msg/element_test.go
Normal file
29
internal/msg/element_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func jsonMarshal(s string) string {
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func TestQuoteJSON(t *testing.T) {
|
||||
testcase := []string{
|
||||
"\u0005", // issue 1773
|
||||
"\v",
|
||||
}
|
||||
|
||||
for _, input := range testcase {
|
||||
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
44
internal/msg/local.go
Normal file
@ -0,0 +1,44 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/LagrangeDev/LagrangeGo/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
104
internal/msg/parse.go
Normal 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
|
||||
}
|
@ -19,7 +19,7 @@ import (
|
||||
// type gjson.True or gjson.False
|
||||
//
|
||||
// type string "true","yes","1" or "false","no","0" (case insensitive)
|
||||
func EnsureBool(p interface{}, defaultVal bool) bool {
|
||||
func EnsureBool(p any, defaultVal bool) bool {
|
||||
var str string
|
||||
if b, ok := p.(bool); ok {
|
||||
return b
|
||||
|
@ -2,7 +2,7 @@
|
||||
package selfdiagnosis
|
||||
|
||||
import (
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/LagrangeDev/LagrangeGo/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -12,8 +12,8 @@ func NetworkDiagnosis(c *client.QQClient) {
|
||||
qualityInfo := c.ConnectionQualityTest()
|
||||
log.Debugf("聊天服务器连接延迟: %vms", qualityInfo.ChatServerLatency)
|
||||
log.Debugf("聊天服务器丢包率: %v%%", qualityInfo.ChatServerPacketLoss*10)
|
||||
log.Debugf("长消息服务器连接延迟: %vms", qualityInfo.LongMessageServerLatency)
|
||||
log.Debugf("长消息服务器响应延迟: %vms", qualityInfo.LongMessageServerResponseLatency)
|
||||
//log.Debugf("长消息服务器连接延迟: %vms", qualityInfo.LongMessageServerLatency)
|
||||
//log.Debugf("长消息服务器响应延迟: %vms", qualityInfo.LongMessageServerResponseLatency)
|
||||
log.Debugf("媒体服务器连接延迟: %vms", qualityInfo.SrvServerLatency)
|
||||
log.Debugf("媒体服务器丢包率: %v%%", qualityInfo.SrvServerPacketLoss*10)
|
||||
|
||||
@ -35,21 +35,21 @@ func NetworkDiagnosis(c *client.QQClient) {
|
||||
log.Warnf("警告: 本地连接聊天服务器丢包率为 %v%%, %v", qualityInfo.ChatServerPacketLoss*10, chatServerErrorMessage)
|
||||
}
|
||||
|
||||
if qualityInfo.LongMessageServerLatency > 1000 {
|
||||
if qualityInfo.LongMessageServerLatency == 9999 {
|
||||
log.Errorf("错误: 长消息服务器延迟测试失败, %v 如果您使用的腾讯云服务器, 请修改DNS到114.114.114.114", longMessageServerErrorMessage)
|
||||
} else {
|
||||
log.Warnf("警告: 长消息延迟为 %vms, 大于 1000ms, %v", qualityInfo.LongMessageServerLatency, longMessageServerErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
if qualityInfo.LongMessageServerResponseLatency > 2000 {
|
||||
if qualityInfo.LongMessageServerResponseLatency == 9999 {
|
||||
log.Errorf("错误: 长消息服务器响应延迟测试失败, %v 如果您使用的腾讯云服务器, 请修改DNS到114.114.114.114", longMessageServerErrorMessage)
|
||||
} else {
|
||||
log.Warnf("警告: 长消息响应延迟为 %vms, 大于 1000ms, %v", qualityInfo.LongMessageServerResponseLatency, longMessageServerErrorMessage)
|
||||
}
|
||||
}
|
||||
//if qualityInfo.LongMessageServerLatency > 1000 {
|
||||
// if qualityInfo.LongMessageServerLatency == 9999 {
|
||||
// log.Errorf("错误: 长消息服务器延迟测试失败, %v 如果您使用的腾讯云服务器, 请修改DNS到114.114.114.114", longMessageServerErrorMessage)
|
||||
// } else {
|
||||
// log.Warnf("警告: 长消息延迟为 %vms, 大于 1000ms, %v", qualityInfo.LongMessageServerLatency, longMessageServerErrorMessage)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//if qualityInfo.LongMessageServerResponseLatency > 2000 {
|
||||
// if qualityInfo.LongMessageServerResponseLatency == 9999 {
|
||||
// log.Errorf("错误: 长消息服务器响应延迟测试失败, %v 如果您使用的腾讯云服务器, 请修改DNS到114.114.114.114", longMessageServerErrorMessage)
|
||||
// } else {
|
||||
// log.Warnf("警告: 长消息响应延迟为 %vms, 大于 1000ms, %v", qualityInfo.LongMessageServerResponseLatency, longMessageServerErrorMessage)
|
||||
// }
|
||||
//}
|
||||
|
||||
if qualityInfo.SrvServerLatency > 1000 {
|
||||
if qualityInfo.SrvServerPacketLoss == 9999 {
|
||||
@ -63,7 +63,11 @@ func NetworkDiagnosis(c *client.QQClient) {
|
||||
log.Warnf("警告: 本地连接媒体服务器丢包率为 %v%%, %v", qualityInfo.SrvServerPacketLoss*10, mediaServerErrorMessage)
|
||||
}
|
||||
|
||||
if qualityInfo.ChatServerLatency > 1000 || qualityInfo.ChatServerPacketLoss > 0 || qualityInfo.LongMessageServerLatency > 1000 || qualityInfo.SrvServerLatency > 1000 || qualityInfo.SrvServerPacketLoss > 0 {
|
||||
if qualityInfo.ChatServerLatency > 1000 ||
|
||||
qualityInfo.ChatServerPacketLoss > 0 ||
|
||||
//qualityInfo.LongMessageServerLatency > 1000 ||
|
||||
qualityInfo.SrvServerLatency > 1000 ||
|
||||
qualityInfo.SrvServerPacketLoss > 0 {
|
||||
log.Infof("网络诊断完成. 发现问题, 请检查日志.")
|
||||
} else {
|
||||
log.Infof("网络诊断完成. 未发现问题")
|
||||
|
@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package selfupdate
|
||||
|
||||
|
10
main.go
10
main.go
@ -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()
|
||||
}
|
||||
|
@ -5,19 +5,79 @@ 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, p Getter) global.MSG {
|
||||
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":
|
||||
return c.bot.CQCanSendRecord()
|
||||
case "get_login_info":
|
||||
return c.bot.CQGetLoginInfo()
|
||||
case "get_stranger_info":
|
||||
p0 := p.Get("user_id").Int()
|
||||
return c.bot.CQGetStrangerInfo(p0)
|
||||
case "get_version_info":
|
||||
return c.bot.CQGetVersionInfo()
|
||||
case "send_forward_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
p2 := p.Get("messages")
|
||||
p3 := p.Get("message_type").String()
|
||||
return c.bot.CQSendForwardMessage(p0, p1, p2, p3)
|
||||
case "send_group_forward_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("messages")
|
||||
return c.bot.CQSendGroupForwardMessage(p0, p1)
|
||||
case "send_group_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("message")
|
||||
p2 := p.Get("auto_escape").Bool()
|
||||
return c.bot.CQSendGroupMessage(p0, p1, p2)
|
||||
case "send_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
p2 := p.Get("message")
|
||||
p3 := p.Get("message_type").String()
|
||||
p4 := p.Get("auto_escape").Bool()
|
||||
return c.bot.CQSendMessage(p0, p1, p2, p3, p4)
|
||||
case "send_private_forward_msg":
|
||||
p0 := p.Get("user_id").Int()
|
||||
p1 := p.Get("messages")
|
||||
return c.bot.CQSendPrivateForwardMessage(p0, p1)
|
||||
case "send_private_msg":
|
||||
p0 := p.Get("user_id").Int()
|
||||
p1 := p.Get("group_id").Int()
|
||||
p2 := p.Get("message")
|
||||
p3 := p.Get("auto_escape").Bool()
|
||||
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 {
|
||||
default:
|
||||
return coolq.Failed(404, "API_NOT_FOUND", "API不存在")
|
||||
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)
|
||||
@ -28,22 +88,11 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
case "_get_group_notice":
|
||||
p0 := p.Get("group_id").Int()
|
||||
return c.bot.CQGetGroupMemo(p0)
|
||||
case "_get_model_show":
|
||||
p0 := p.Get("model").String()
|
||||
return c.bot.CQGetModelShow(p0)
|
||||
case "_send_group_notice":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("content").String()
|
||||
p2 := p.Get("image").String()
|
||||
return c.bot.CQSetGroupMemo(p0, p1, p2)
|
||||
case "_set_model_show":
|
||||
p0 := p.Get("model").String()
|
||||
p1 := p.Get("model_show").String()
|
||||
return c.bot.CQSetModelShow(p0, p1)
|
||||
case "can_send_image":
|
||||
return c.bot.CQCanSendImage()
|
||||
case "can_send_record":
|
||||
return c.bot.CQCanSendRecord()
|
||||
case "check_url_safely":
|
||||
p0 := p.Get("url").String()
|
||||
return c.bot.CQCheckURLSafely(p0)
|
||||
@ -52,32 +101,21 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
p1 := p.Get("parent_id").String()
|
||||
p2 := p.Get("name").String()
|
||||
return c.bot.CQGroupFileCreateFolder(p0, p1, p2)
|
||||
case "create_guild_role":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("name").String()
|
||||
p2 := uint32(p.Get("color").Uint())
|
||||
p3 := p.Get("independent").Bool()
|
||||
p4 := p.Get("initial_users")
|
||||
return c.bot.CQCreateGuildRole(p0, p1, p2, p3, p4)
|
||||
case "delete_essence_msg":
|
||||
p0 := int32(p.Get("message_id").Int())
|
||||
return c.bot.CQDeleteEssenceMessage(p0)
|
||||
case "delete_friend":
|
||||
p0 := p.Get("[user_id,id].0").Int()
|
||||
return c.bot.CQDeleteFriend(p0)
|
||||
p1 := p.Get("block").Bool()
|
||||
return c.bot.CQDeleteFriend(p0, p1)
|
||||
case "delete_group_file":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("file_id").String()
|
||||
p2 := int32(p.Get("[busid,bus_id].0").Int())
|
||||
return c.bot.CQGroupFileDeleteFile(p0, p1, p2)
|
||||
p1 := p.Get("id").String()
|
||||
return c.bot.CQGroupFileDeleteFile(p0, p1)
|
||||
case "delete_group_folder":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("folder_id").String()
|
||||
return c.bot.CQGroupFileDeleteFolder(p0, p1)
|
||||
case "delete_guild_role":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("role_id").Uint()
|
||||
return c.bot.CQDeleteGuildRole(p0, p1)
|
||||
case "delete_msg":
|
||||
p0 := int32(p.Get("message_id").Int())
|
||||
return c.bot.CQDeleteMessage(p0)
|
||||
@ -96,7 +134,7 @@ func (c *Caller) call(action string, 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()
|
||||
return c.bot.CQGetFriendList(spec)
|
||||
case "get_group_at_all_remain":
|
||||
p0 := p.Get("group_id").Int()
|
||||
return c.bot.CQGetAtAllRemain(p0)
|
||||
@ -106,8 +144,7 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
case "get_group_file_url":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("file_id").String()
|
||||
p2 := int32(p.Get("[busid,bus_id].0").Int())
|
||||
return c.bot.CQGetGroupFileURL(p0, p1, p2)
|
||||
return c.bot.CQGetGroupFileURL(p0, p1)
|
||||
case "get_group_files_by_folder":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("folder_id").String()
|
||||
@ -119,10 +156,10 @@ func (c *Caller) call(action string, 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)
|
||||
return c.bot.CQGetGroupInfo(p0, p1, spec)
|
||||
case "get_group_list":
|
||||
p0 := p.Get("no_cache").Bool()
|
||||
return c.bot.CQGetGroupList(p0)
|
||||
return c.bot.CQGetGroupList(p0, spec)
|
||||
case "get_group_member_info":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
@ -141,105 +178,27 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
return c.bot.CQGetGroupRootFiles(p0)
|
||||
case "get_group_system_msg":
|
||||
return c.bot.CQGetGroupSystemMessages()
|
||||
case "get_guild_channel_list":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("no_cache").Bool()
|
||||
return c.bot.CQGetGuildChannelList(p0, p1)
|
||||
case "get_guild_list":
|
||||
return c.bot.CQGetGuildList()
|
||||
case "get_guild_member_list":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("next_token").String()
|
||||
return c.bot.CQGetGuildMembers(p0, p1)
|
||||
case "get_guild_member_profile":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("user_id").Uint()
|
||||
return c.bot.CQGetGuildMemberProfile(p0, p1)
|
||||
case "get_guild_meta_by_guest":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
return c.bot.CQGetGuildMetaByGuest(p0)
|
||||
case "get_guild_msg":
|
||||
p0 := p.Get("message_id").String()
|
||||
p1 := p.Get("no_cache").Bool()
|
||||
return c.bot.CQGetGuildMessage(p0, p1)
|
||||
case "get_guild_roles":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
return c.bot.CQGetGuildRoles(p0)
|
||||
case "get_guild_service_profile":
|
||||
return c.bot.CQGetGuildServiceProfile()
|
||||
case "get_image":
|
||||
p0 := p.Get("file").String()
|
||||
return c.bot.CQGetImage(p0)
|
||||
case "get_login_info":
|
||||
return c.bot.CQGetLoginInfo()
|
||||
case "get_msg":
|
||||
p0 := int32(p.Get("message_id").Int())
|
||||
return c.bot.CQGetMessage(p0)
|
||||
case "get_online_clients":
|
||||
p0 := p.Get("no_cache").Bool()
|
||||
return c.bot.CQGetOnlineClients(p0)
|
||||
case "get_status":
|
||||
return c.bot.CQGetStatus()
|
||||
case "get_stranger_info":
|
||||
p0 := p.Get("user_id").Int()
|
||||
return c.bot.CQGetStrangerInfo(p0)
|
||||
case "get_topic_channel_feeds":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("channel_id").Uint()
|
||||
return c.bot.CQGetTopicChannelFeeds(p0, p1)
|
||||
return c.bot.CQGetStatus(spec)
|
||||
case "get_supported_actions":
|
||||
return c.bot.CQGetSupportedActions(spec)
|
||||
case "get_unidirectional_friend_list":
|
||||
return c.bot.CQGetUnidirectionalFriendList()
|
||||
case "get_version_info":
|
||||
return c.bot.CQGetVersionInfo()
|
||||
case "mark_msg_as_read":
|
||||
p0 := int32(p.Get("message_id").Int())
|
||||
return c.bot.CQMarkMessageAsRead(p0)
|
||||
case "qidian_get_account_info":
|
||||
return c.bot.CQGetQiDianAccountInfo()
|
||||
case "reload_event_filter":
|
||||
p0 := p.Get("file").String()
|
||||
return c.bot.CQReloadEventFilter(p0)
|
||||
case "send_forward_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
p2 := p.Get("messages")
|
||||
p3 := p.Get("message_type").String()
|
||||
return c.bot.CQSendForwardMessage(p0, p1, p2, p3)
|
||||
case "send_group_forward_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("messages")
|
||||
return c.bot.CQSendGroupForwardMessage(p0, p1)
|
||||
case "send_group_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("message")
|
||||
p2 := p.Get("auto_escape").Bool()
|
||||
return c.bot.CQSendGroupMessage(p0, p1, p2)
|
||||
case "send_group_sign":
|
||||
p0 := p.Get("group_id").Int()
|
||||
return c.bot.CQSendGroupSign(p0)
|
||||
case "send_guild_channel_msg":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("channel_id").Uint()
|
||||
p2 := p.Get("message")
|
||||
p3 := p.Get("auto_escape").Bool()
|
||||
return c.bot.CQSendGuildChannelMessage(p0, p1, p2, p3)
|
||||
case "send_msg":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
p2 := p.Get("message")
|
||||
p3 := p.Get("message_type").String()
|
||||
p4 := p.Get("auto_escape").Bool()
|
||||
return c.bot.CQSendMessage(p0, p1, p2, p3, p4)
|
||||
case "send_private_forward_msg":
|
||||
p0 := p.Get("user_id").Int()
|
||||
p1 := p.Get("messages")
|
||||
return c.bot.CQSendPrivateForwardMessage(p0, p1)
|
||||
case "send_private_msg":
|
||||
p0 := p.Get("user_id").Int()
|
||||
p1 := p.Get("group_id").Int()
|
||||
p2 := p.Get("message")
|
||||
p3 := p.Get("auto_escape").Bool()
|
||||
return c.bot.CQSendPrivateMessage(p0, p1, p2, p3)
|
||||
case "set_essence_msg":
|
||||
p0 := int32(p.Get("message_id").Int())
|
||||
return c.bot.CQSetEssenceMessage(p0)
|
||||
@ -267,11 +226,6 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
p2 = pt.Bool()
|
||||
}
|
||||
return c.bot.CQSetGroupAdmin(p0, p1, p2)
|
||||
case "set_group_anonymous_ban":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("[anonymous_flag,anonymous.flag].0").String()
|
||||
p2 := int32(p.Get("duration").Int())
|
||||
return c.bot.CQSetGroupAnonymousBan(p0, p1, p2)
|
||||
case "set_group_ban":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
@ -288,9 +242,8 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
case "set_group_kick":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("user_id").Int()
|
||||
p2 := p.Get("message").String()
|
||||
p3 := p.Get("reject_add_request").Bool()
|
||||
return c.bot.CQSetGroupKick(p0, p1, p2, p3)
|
||||
p2 := p.Get("reject_add_request").Bool()
|
||||
return c.bot.CQSetGroupKick(p0, p1, p2)
|
||||
case "set_group_leave":
|
||||
p0 := p.Get("group_id").Int()
|
||||
return c.bot.CQSetGroupLeave(p0)
|
||||
@ -315,26 +268,6 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
p1 = pt.Bool()
|
||||
}
|
||||
return c.bot.CQSetGroupWholeBan(p0, p1)
|
||||
case "set_guild_member_role":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("set").Bool()
|
||||
p2 := p.Get("role_id").Uint()
|
||||
p3 := p.Get("users")
|
||||
return c.bot.CQSetGuildMemberRole(p0, p1, p2, p3)
|
||||
case "set_qq_profile":
|
||||
p0 := p.Get("nickname")
|
||||
p1 := p.Get("company")
|
||||
p2 := p.Get("email")
|
||||
p3 := p.Get("college")
|
||||
p4 := p.Get("personal_note")
|
||||
return c.bot.CQSetQQProfile(p0, p1, p2, p3, p4)
|
||||
case "update_guild_role":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("role_id").Uint()
|
||||
p2 := p.Get("name").String()
|
||||
p3 := uint32(p.Get("color").Uint())
|
||||
p4 := p.Get("indepedent").Bool()
|
||||
return c.bot.CQModifyRoleInGuild(p0, p1, p2, p3, p4)
|
||||
case "upload_group_file":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("file").String()
|
||||
@ -347,4 +280,5 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
p2 := p.Get("name").String()
|
||||
return c.bot.CQUploadPrivateFile(p0, p1, p2)
|
||||
}
|
||||
return coolq.Failed(404, "API_NOT_FOUND", "API不存在")
|
||||
}
|
||||
|
@ -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
|
||||
//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, 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, p)
|
||||
return c.call(action, spec, p)
|
||||
}
|
||||
|
||||
// Use add handlers to the API caller
|
||||
|
@ -28,13 +28,21 @@ type Reconnect struct {
|
||||
|
||||
// Account 账号配置
|
||||
type Account struct {
|
||||
Uin int64 `yaml:"uin"`
|
||||
Password string `yaml:"password"`
|
||||
Encrypt bool `yaml:"encrypt"`
|
||||
Status int `yaml:"status"`
|
||||
ReLogin *Reconnect `yaml:"relogin"`
|
||||
UseSSOAddress bool `yaml:"use-sso-address"`
|
||||
AllowTempSession bool `yaml:"allow-temp-session"`
|
||||
Uin int64 `yaml:"uin"`
|
||||
Password string `yaml:"password"`
|
||||
Encrypt bool `yaml:"encrypt"`
|
||||
Status int `yaml:"status"`
|
||||
ReLogin *Reconnect `yaml:"relogin"`
|
||||
UseSSOAddress bool `yaml:"use-sso-address"`
|
||||
AllowTempSession bool `yaml:"allow-temp-session"`
|
||||
SignServers []SignServer `yaml:"sign-servers"`
|
||||
MaxCheckCount uint `yaml:"max-check-count"`
|
||||
SignServerTimeout uint `yaml:"sign-server-timeout"`
|
||||
}
|
||||
|
||||
// SignServer 签名服务器
|
||||
type SignServer struct {
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// Config 总配置文件
|
||||
@ -55,6 +63,8 @@ type Config struct {
|
||||
RemoveReplyAt bool `yaml:"remove-reply-at"`
|
||||
ExtraReplyData bool `yaml:"extra-reply-data"`
|
||||
SkipMimeScan bool `yaml:"skip-mime-scan"`
|
||||
ConvertWebpImage bool `yaml:"convert-webp-image"`
|
||||
HTTPTimeout int `yaml:"http-timeout"`
|
||||
} `yaml:"message"`
|
||||
|
||||
Output struct {
|
||||
|
@ -16,6 +16,26 @@ account: # 账号相关
|
||||
# 是否允许发送临时会话消息
|
||||
allow-temp-session: false
|
||||
|
||||
# 数据包的签名服务器列表,第一个作为主签名服务器,后续作为备用
|
||||
# 与android签名不兼容
|
||||
# 示例:
|
||||
# sign-servers:
|
||||
# - url: 'http://127.0.0.1:8080' # 本地签名服务器
|
||||
# - url: 'https://signserver.example.com' # 线上签名服务器
|
||||
# ...
|
||||
#
|
||||
# 服务器不提供自建
|
||||
sign-servers:
|
||||
- url: '-' # 主签名服务器地址, 必填
|
||||
- url: '-' # 备用
|
||||
|
||||
# 连续寻找可用签名服务器最大尝试次数
|
||||
# 为 0 时会在连续 3 次没有找到可用签名服务器后保持使用主签名服务器,不再尝试进行切换备用
|
||||
# 否则会在达到指定次数后 **退出** 主程序
|
||||
max-check-count: 0
|
||||
# 签名服务请求超时时间(s)
|
||||
sign-server-timeout: 60
|
||||
|
||||
heartbeat:
|
||||
# 心跳频率, 单位秒
|
||||
# -1 为关闭心跳
|
||||
@ -43,6 +63,10 @@ message:
|
||||
extra-reply-data: false
|
||||
# 跳过 Mime 扫描, 忽略错误数据
|
||||
skip-mime-scan: false
|
||||
# 是否自动转换 WebP 图片
|
||||
convert-webp-image: false
|
||||
# download 超时时间(s)
|
||||
http-timeout: 15
|
||||
|
||||
output:
|
||||
# 日志等级 trace,debug,info,warn,error
|
||||
|
@ -32,6 +32,9 @@ func encode(record []byte, tempName string) (silkWav []byte, err error) {
|
||||
// 2.转换pcm
|
||||
pcmPath := path.Join(silkCachePath, tempName+".pcm")
|
||||
cmd := exec.Command("ffmpeg", "-i", rawPath, "-f", "s16le", "-ar", "24000", "-ac", "1", pcmPath)
|
||||
if errors.Is(cmd.Err, exec.ErrDot) {
|
||||
cmd.Err = nil
|
||||
}
|
||||
if base.Debug {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
78
pkg/onebot/attr.go
Normal file
78
pkg/onebot/attr.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package onebot
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Attr is a key-value pair.
|
||||
type Attr struct {
|
||||
Key string
|
||||
Value Value
|
||||
}
|
||||
|
||||
// String returns an Attr for a string value.
|
||||
func String(key, value string) Attr {
|
||||
return Attr{key, StringValue(value)}
|
||||
}
|
||||
|
||||
// Int64 returns an Attr for an int64.
|
||||
func Int64(key string, value int64) Attr {
|
||||
return Attr{key, Int64Value(value)}
|
||||
}
|
||||
|
||||
// Int converts an int to an int64 and returns
|
||||
// an Attr with that value.
|
||||
func Int(key string, value int) Attr {
|
||||
return Int64(key, int64(value))
|
||||
}
|
||||
|
||||
// Uint64 returns an Attr for a uint64.
|
||||
func Uint64(key string, v uint64) Attr {
|
||||
return Attr{key, Uint64Value(v)}
|
||||
}
|
||||
|
||||
// Float64 returns an Attr for a floating-point number.
|
||||
func Float64(key string, v float64) Attr {
|
||||
return Attr{key, Float64Value(v)}
|
||||
}
|
||||
|
||||
// Bool returns an Attr for a bool.
|
||||
func Bool(key string, v bool) Attr {
|
||||
return Attr{key, BoolValue(v)}
|
||||
}
|
||||
|
||||
// Time returns an Attr for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func Time(key string, v time.Time) Attr {
|
||||
return Attr{key, TimeValue(v)}
|
||||
}
|
||||
|
||||
// Duration returns an Attr for a time.Duration.
|
||||
func Duration(key string, v time.Duration) Attr {
|
||||
return Attr{key, DurationValue(v)}
|
||||
}
|
||||
|
||||
// Group returns an Attr for a Group Value.
|
||||
// The caller must not subsequently mutate the
|
||||
// argument slice.
|
||||
//
|
||||
// Use Group to collect several Attrs under a single
|
||||
// key on a log line, or as the result of LogValue
|
||||
// in order to log a single value as multiple Attrs.
|
||||
func Group(key string, as ...Attr) Attr {
|
||||
return Attr{key, GroupValue(as...)}
|
||||
}
|
||||
|
||||
// Any returns an Attr for the supplied value.
|
||||
// See [Value.AnyValue] for how values are treated.
|
||||
func Any(key string, value any) Attr {
|
||||
return Attr{key, AnyValue(value)}
|
||||
}
|
||||
|
||||
func (a Attr) String() string {
|
||||
return a.Key + "=" + a.Value.String()
|
||||
}
|
31
pkg/onebot/kind_string.go
Normal file
31
pkg/onebot/kind_string.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Code generated by "stringer -type=Kind -trimprefix=Kind"; DO NOT EDIT.
|
||||
|
||||
package onebot
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[KindAny-0]
|
||||
_ = x[KindBool-1]
|
||||
_ = x[KindDuration-2]
|
||||
_ = x[KindFloat64-3]
|
||||
_ = x[KindInt64-4]
|
||||
_ = x[KindString-5]
|
||||
_ = x[KindTime-6]
|
||||
_ = x[KindUint64-7]
|
||||
_ = x[KindGroup-8]
|
||||
}
|
||||
|
||||
const _Kind_name = "AnyBoolDurationFloat64Int64StringTimeUint64Group"
|
||||
|
||||
var _Kind_index = [...]uint8{0, 3, 7, 15, 22, 27, 33, 37, 43, 48}
|
||||
|
||||
func (i Kind) String() string {
|
||||
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
|
||||
}
|
41
pkg/onebot/onebot.go
Normal file
41
pkg/onebot/onebot.go
Normal 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
32
pkg/onebot/spec.go
Normal 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
|
||||
}
|
125
pkg/onebot/supported.go
Normal file
125
pkg/onebot/supported.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Code generated by cmd/api-generator. DO NOT EDIT.
|
||||
|
||||
package onebot
|
||||
|
||||
var supportedV11 = []string{
|
||||
".handle_quick_operation",
|
||||
".ocr_image",
|
||||
"ocr_image",
|
||||
"_del_group_notice",
|
||||
"_get_group_notice",
|
||||
"_send_group_notice",
|
||||
"can_send_image",
|
||||
"can_send_record",
|
||||
"check_url_safely",
|
||||
"create_group_file_folder",
|
||||
"delete_essence_msg",
|
||||
"delete_friend",
|
||||
"delete_group_file",
|
||||
"delete_group_folder",
|
||||
"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_image",
|
||||
"get_login_info",
|
||||
"get_msg",
|
||||
"get_status",
|
||||
"get_stranger_info",
|
||||
"get_supported_actions",
|
||||
"get_unidirectional_friend_list",
|
||||
"get_version_info",
|
||||
"mark_msg_as_read",
|
||||
"reload_event_filter",
|
||||
"send_forward_msg",
|
||||
"send_group_forward_msg",
|
||||
"send_group_msg",
|
||||
"send_group_sign",
|
||||
"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_ban",
|
||||
"set_group_card",
|
||||
"set_group_kick",
|
||||
"set_group_leave",
|
||||
"set_group_name",
|
||||
"set_group_portrait",
|
||||
"set_group_special_title",
|
||||
"set_group_whole_ban",
|
||||
"upload_group_file",
|
||||
"upload_private_file",
|
||||
}
|
||||
|
||||
var supportedV12 = []string{
|
||||
".ocr_image",
|
||||
"ocr_image",
|
||||
"_del_group_notice",
|
||||
"_get_group_notice",
|
||||
"_send_group_notice",
|
||||
"check_url_safely",
|
||||
"create_group_file_folder",
|
||||
"delete_essence_msg",
|
||||
"delete_friend",
|
||||
"delete_group_file",
|
||||
"delete_group_folder",
|
||||
"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_image",
|
||||
"get_self_info",
|
||||
"get_msg",
|
||||
"get_status",
|
||||
"get_user_info",
|
||||
"get_supported_actions",
|
||||
"get_unidirectional_friend_list",
|
||||
"mark_msg_as_read",
|
||||
"reload_event_filter",
|
||||
"send_group_sign",
|
||||
"set_essence_msg",
|
||||
"set_friend_add_request",
|
||||
"set_group_add_request",
|
||||
"set_group_admin",
|
||||
"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",
|
||||
"upload_group_file",
|
||||
"upload_private_file",
|
||||
}
|
355
pkg/onebot/value.go
Normal file
355
pkg/onebot/value.go
Normal file
@ -0,0 +1,355 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package onebot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// A Value can represent any Go value, but unlike type any,
|
||||
// it can represent most small values without an allocation.
|
||||
// The zero Value corresponds to nil.
|
||||
type Value struct {
|
||||
_ [0]func() // disallow ==
|
||||
num uint64 // hold number value
|
||||
any any // hold Kind or other value
|
||||
}
|
||||
|
||||
type (
|
||||
stringptr *byte // used in Value.any when the Value is a string
|
||||
groupptr *Attr // used in Value.any when the Value is a []Attr
|
||||
)
|
||||
|
||||
//go:generate stringer -type=Kind -trimprefix=Kind
|
||||
|
||||
// Kind is the kind of Value.
|
||||
type Kind int
|
||||
|
||||
// Kind
|
||||
const (
|
||||
KindAny Kind = iota
|
||||
KindBool
|
||||
KindDuration
|
||||
KindFloat64
|
||||
KindInt64
|
||||
KindString
|
||||
KindTime
|
||||
KindUint64
|
||||
KindGroup
|
||||
)
|
||||
|
||||
// Unexported version of Kind, just so we can store Kinds in Values.
|
||||
// (No user-provided value has this type.)
|
||||
type kind Kind
|
||||
|
||||
// Kind returns v's Kind.
|
||||
func (v Value) Kind() Kind {
|
||||
switch x := v.any.(type) {
|
||||
case Kind:
|
||||
return x
|
||||
case stringptr:
|
||||
return KindString
|
||||
case timeLocation:
|
||||
return KindTime
|
||||
case groupptr:
|
||||
return KindGroup
|
||||
case kind: // a kind is just a wrapper for a Kind
|
||||
return KindAny
|
||||
default:
|
||||
return KindAny
|
||||
}
|
||||
}
|
||||
|
||||
//////////////// Constructors
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
|
||||
}
|
||||
|
||||
// IntValue returns a Value for an int.
|
||||
func IntValue(v int) Value {
|
||||
return Int64Value(int64(v))
|
||||
}
|
||||
|
||||
// Int64Value returns a Value for an int64.
|
||||
func Int64Value(v int64) Value {
|
||||
return Value{num: uint64(v), any: KindInt64}
|
||||
}
|
||||
|
||||
// Uint64Value returns a Value for a uint64.
|
||||
func Uint64Value(v uint64) Value {
|
||||
return Value{num: v, any: KindUint64}
|
||||
}
|
||||
|
||||
// Float64Value returns a Value for a floating-point number.
|
||||
func Float64Value(v float64) Value {
|
||||
return Value{num: math.Float64bits(v), any: KindFloat64}
|
||||
}
|
||||
|
||||
// BoolValue returns a Value for a bool.
|
||||
func BoolValue(v bool) Value {
|
||||
u := uint64(0)
|
||||
if v {
|
||||
u = 1
|
||||
}
|
||||
return Value{num: u, any: KindBool}
|
||||
}
|
||||
|
||||
// Unexported version of *time.Location, just so we can store *time.Locations in
|
||||
// Values. (No user-provided value has this type.)
|
||||
type timeLocation *time.Location
|
||||
|
||||
// TimeValue returns a Value for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func TimeValue(v time.Time) Value {
|
||||
if v.IsZero() {
|
||||
// UnixNano on the zero time is undefined, so represent the zero time
|
||||
// with a nil *time.Location instead. time.Time.Location method never
|
||||
// returns nil, so a Value with any == timeLocation(nil) cannot be
|
||||
// mistaken for any other Value, time.Time or otherwise.
|
||||
return Value{any: timeLocation(nil)}
|
||||
}
|
||||
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
|
||||
}
|
||||
|
||||
// DurationValue returns a Value for a time.Duration.
|
||||
func DurationValue(v time.Duration) Value {
|
||||
return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
|
||||
}
|
||||
|
||||
// AnyValue returns a Value for the supplied value.
|
||||
//
|
||||
// If the supplied value is of type Value, it is returned
|
||||
// unmodified.
|
||||
//
|
||||
// Given a value of one of Go's predeclared string, bool, or
|
||||
// (non-complex) numeric types, AnyValue returns a Value of kind
|
||||
// String, Bool, Uint64, Int64, or Float64. The width of the
|
||||
// original numeric type is not preserved.
|
||||
//
|
||||
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
||||
// KindTime or KindDuration. The monotonic time is not preserved.
|
||||
//
|
||||
// For nil, or values of all other types, including named types whose
|
||||
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
||||
func AnyValue(v any) Value {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return StringValue(v)
|
||||
case int:
|
||||
return Int64Value(int64(v))
|
||||
case uint:
|
||||
return Uint64Value(uint64(v))
|
||||
case int64:
|
||||
return Int64Value(v)
|
||||
case uint64:
|
||||
return Uint64Value(v)
|
||||
case bool:
|
||||
return BoolValue(v)
|
||||
case time.Duration:
|
||||
return DurationValue(v)
|
||||
case time.Time:
|
||||
return TimeValue(v)
|
||||
case uint8:
|
||||
return Uint64Value(uint64(v))
|
||||
case uint16:
|
||||
return Uint64Value(uint64(v))
|
||||
case uint32:
|
||||
return Uint64Value(uint64(v))
|
||||
case uintptr:
|
||||
return Uint64Value(uint64(v))
|
||||
case int8:
|
||||
return Int64Value(int64(v))
|
||||
case int16:
|
||||
return Int64Value(int64(v))
|
||||
case int32:
|
||||
return Int64Value(int64(v))
|
||||
case float64:
|
||||
return Float64Value(v)
|
||||
case float32:
|
||||
return Float64Value(float64(v))
|
||||
case []Attr:
|
||||
return GroupValue(v...)
|
||||
case Kind:
|
||||
return Value{any: kind(v)}
|
||||
case Value:
|
||||
return v
|
||||
default:
|
||||
return Value{any: v}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////// Accessors
|
||||
|
||||
// Any returns v's value as an any.
|
||||
func (v Value) Any() any {
|
||||
switch v.Kind() {
|
||||
case KindAny:
|
||||
if k, ok := v.any.(kind); ok {
|
||||
return Kind(k)
|
||||
}
|
||||
return v.any
|
||||
case KindGroup:
|
||||
return v.group()
|
||||
case KindInt64:
|
||||
return int64(v.num)
|
||||
case KindUint64:
|
||||
return v.num
|
||||
case KindFloat64:
|
||||
return v.float()
|
||||
case KindString:
|
||||
return v.str()
|
||||
case KindBool:
|
||||
return v.bool()
|
||||
case KindDuration:
|
||||
return v.duration()
|
||||
case KindTime:
|
||||
return v.time()
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
|
||||
// the methods Int64, Float64, and so on, which panic if v is of the
|
||||
// wrong kind, String never panics.
|
||||
func (v Value) String() string {
|
||||
if sp, ok := v.any.(stringptr); ok {
|
||||
return unsafe.String(sp, v.num)
|
||||
}
|
||||
var buf []byte
|
||||
return string(v.append(buf))
|
||||
}
|
||||
|
||||
func (v Value) str() string {
|
||||
return unsafe.String(v.any.(stringptr), v.num)
|
||||
}
|
||||
|
||||
// Int64 returns v's value as an int64. It panics
|
||||
// if v is not a signed integer.
|
||||
func (v Value) Int64() int64 {
|
||||
if g, w := v.Kind(), KindInt64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return int64(v.num)
|
||||
}
|
||||
|
||||
// Uint64 returns v's value as a uint64. It panics
|
||||
// if v is not an unsigned integer.
|
||||
func (v Value) Uint64() uint64 {
|
||||
if g, w := v.Kind(), KindUint64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.num
|
||||
}
|
||||
|
||||
// Bool returns v's value as a bool. It panics
|
||||
// if v is not a bool.
|
||||
func (v Value) Bool() bool {
|
||||
if g, w := v.Kind(), KindBool; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.bool()
|
||||
}
|
||||
|
||||
func (v Value) bool() bool {
|
||||
return v.num == 1
|
||||
}
|
||||
|
||||
// Duration returns v's value as a time.Duration. It panics
|
||||
// if v is not a time.Duration.
|
||||
func (v Value) Duration() time.Duration {
|
||||
if g, w := v.Kind(), KindDuration; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
|
||||
return v.duration()
|
||||
}
|
||||
|
||||
func (v Value) duration() time.Duration {
|
||||
return time.Duration(int64(v.num))
|
||||
}
|
||||
|
||||
// Float64 returns v's value as a float64. It panics
|
||||
// if v is not a float64.
|
||||
func (v Value) Float64() float64 {
|
||||
if g, w := v.Kind(), KindFloat64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
|
||||
return v.float()
|
||||
}
|
||||
|
||||
func (v Value) float() float64 {
|
||||
return math.Float64frombits(v.num)
|
||||
}
|
||||
|
||||
// Time returns v's value as a time.Time. It panics
|
||||
// if v is not a time.Time.
|
||||
func (v Value) Time() time.Time {
|
||||
if g, w := v.Kind(), KindTime; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.time()
|
||||
}
|
||||
|
||||
func (v Value) time() time.Time {
|
||||
loc := v.any.(timeLocation)
|
||||
if loc == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Unix(0, int64(v.num)).In(loc)
|
||||
}
|
||||
|
||||
// Group returns v's value as a []Attr.
|
||||
// It panics if v's Kind is not KindGroup.
|
||||
func (v Value) Group() []Attr {
|
||||
if sp, ok := v.any.(groupptr); ok {
|
||||
return unsafe.Slice(sp, v.num)
|
||||
}
|
||||
panic("Group: bad kind")
|
||||
}
|
||||
|
||||
func (v Value) group() []Attr {
|
||||
return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
|
||||
}
|
||||
|
||||
// append appends a text representation of v to dst.
|
||||
// v is formatted as with fmt.Sprint.
|
||||
func (v Value) append(dst []byte) []byte {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
return append(dst, v.str()...)
|
||||
case KindInt64:
|
||||
return strconv.AppendInt(dst, int64(v.num), 10)
|
||||
case KindUint64:
|
||||
return strconv.AppendUint(dst, v.num, 10)
|
||||
case KindFloat64:
|
||||
return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
|
||||
case KindBool:
|
||||
return strconv.AppendBool(dst, v.bool())
|
||||
case KindDuration:
|
||||
return append(dst, v.duration().String()...)
|
||||
case KindTime:
|
||||
return append(dst, v.time().String()...)
|
||||
case KindGroup:
|
||||
return fmt.Append(dst, v.group())
|
||||
case KindAny:
|
||||
return fmt.Append(dst, v.any)
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
#!/bin/sh
|
||||
echo "Start GOCQHTTP~~~"
|
||||
|
||||
cp -f config.yml /tmp/config.yml
|
||||
cp -f device.json /tmp/device.json
|
||||
./go-cqhttp -w="/tmp/" faststart
|
||||
#!/usr/bin/env bash
|
||||
function index.main_handler() {
|
||||
echo "Start GOCQHTTP~~~"
|
||||
cp -f config.yml /tmp/config.yml
|
||||
cp -f device.json /tmp/device.json
|
||||
./go-cqhttp -w="/tmp/" faststart
|
||||
}
|
||||
index.main_handler
|
||||
|
@ -19,7 +19,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"gopkg.in/yaml.v3"
|
||||
@ -29,11 +30,13 @@ 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通信相关配置
|
||||
type HTTPServer struct {
|
||||
Disabled bool `yaml:"disabled"`
|
||||
Version uint16 `yaml:"version"`
|
||||
Address string `yaml:"address"`
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
@ -57,6 +60,7 @@ type httpServerPost struct {
|
||||
type httpServer struct {
|
||||
api *api.Caller
|
||||
accessToken string
|
||||
spec *onebot.Spec // onebot spec
|
||||
}
|
||||
|
||||
// HTTPClient 反向HTTP上报客户端
|
||||
@ -81,6 +85,7 @@ type httpCtx struct {
|
||||
const httpDefault = `
|
||||
- http: # HTTP 通信设置
|
||||
address: 0.0.0.0:5700 # HTTP监听地址
|
||||
version: 11 # OneBot协议版本, 支持 11/12
|
||||
timeout: 5 # 反向 HTTP 超时时间, 单位秒,<5 时将被忽略
|
||||
long-polling: # 长轮询拓展
|
||||
enabled: false # 是否开启
|
||||
@ -104,31 +109,35 @@ func init() {
|
||||
|
||||
var joinQuery = regexp.MustCompile(`\[(.+?),(.+?)]\.0`)
|
||||
|
||||
func (h *httpCtx) get(s string, join bool) gjson.Result {
|
||||
func mayJSONParam(p string) bool {
|
||||
if strings.HasPrefix(p, "{") || strings.HasPrefix(p, "[") {
|
||||
return gjson.Valid(p)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *httpCtx) get(pattern string, join bool) gjson.Result {
|
||||
// support gjson advanced syntax:
|
||||
// h.Get("[a,b].0") see usage in http_test.go
|
||||
if join && joinQuery.MatchString(s) {
|
||||
matched := joinQuery.FindStringSubmatch(s)
|
||||
// h.Get("[a,b].0") see usage in http_test.go. See issue #1241, #1325.
|
||||
if join && strings.HasPrefix(pattern, "[") && joinQuery.MatchString(pattern) {
|
||||
matched := joinQuery.FindStringSubmatch(pattern)
|
||||
if r := h.get(matched[1], false); r.Exists() {
|
||||
return r
|
||||
}
|
||||
return h.get(matched[2], false)
|
||||
}
|
||||
|
||||
validJSONParam := func(p string) bool {
|
||||
return (strings.HasPrefix(p, "{") || strings.HasPrefix(p, "[")) && gjson.Valid(p)
|
||||
}
|
||||
if h.postForm != nil {
|
||||
if form := h.postForm.Get(s); form != "" {
|
||||
if validJSONParam(form) {
|
||||
if form := h.postForm.Get(pattern); form != "" {
|
||||
if mayJSONParam(form) {
|
||||
return gjson.Result{Type: gjson.JSON, Raw: form}
|
||||
}
|
||||
return gjson.Result{Type: gjson.String, Str: form}
|
||||
}
|
||||
}
|
||||
if h.query != nil {
|
||||
if query := h.query.Get(s); query != "" {
|
||||
if validJSONParam(query) {
|
||||
if query := h.query.Get(pattern); query != "" {
|
||||
if mayJSONParam(query) {
|
||||
return gjson.Result{Type: gjson.JSON, Raw: query}
|
||||
}
|
||||
return gjson.Result{Type: gjson.String, Str: query}
|
||||
@ -150,6 +159,13 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
|
||||
contentType := request.Header.Get("Content-Type")
|
||||
switch request.Method {
|
||||
case http.MethodPost:
|
||||
// todo: msg pack
|
||||
if s.spec.Version == 12 && strings.Contains(contentType, "application/msgpack") {
|
||||
log.Warnf("请求 %v 数据类型暂不支持: MsgPack", request.RequestURI)
|
||||
writer.WriteHeader(http.StatusUnsupportedMediaType)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(contentType, "application/json") {
|
||||
body, err := io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
@ -190,12 +206,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, 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, &ctx)
|
||||
response = s.api.Call(action, s.spec, &ctx)
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
@ -245,9 +261,15 @@ func runHTTP(bot *coolq.CQBot, node yaml.Node) {
|
||||
case conf.Disabled:
|
||||
return
|
||||
}
|
||||
|
||||
network, addr := "tcp", conf.Address
|
||||
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)
|
||||
@ -338,7 +360,7 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) {
|
||||
}
|
||||
|
||||
header := make(http.Header)
|
||||
header.Set("X-Self-ID", strconv.FormatInt(c.bot.Client.Uin, 10))
|
||||
header.Set("X-Self-ID", strconv.FormatInt(int64(c.bot.Client.Uin), 10))
|
||||
header.Set("User-Agent", "CQHttp/4.15.0")
|
||||
header.Set("Content-Type", "application/json")
|
||||
if c.secret != "" {
|
||||
|
@ -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,12 +46,15 @@ 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 (
|
||||
ch = make(chan []interface{})
|
||||
ch = make(chan []any)
|
||||
timeout = time.Duration(p.Get("timeout").Int()) * time.Second
|
||||
)
|
||||
go func() {
|
||||
@ -63,7 +67,7 @@ func longPolling(bot *coolq.CQBot, maxSize int) api.Handler {
|
||||
if limit <= 0 || queue.Len() < limit {
|
||||
limit = queue.Len()
|
||||
}
|
||||
ret := make([]interface{}, limit)
|
||||
ret := make([]any, limit)
|
||||
elem := queue.Front()
|
||||
for i := 0; i < limit; i++ {
|
||||
ret[i] = elem.Value
|
||||
@ -81,7 +85,7 @@ func longPolling(bot *coolq.CQBot, maxSize int) api.Handler {
|
||||
if timeout != 0 {
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return coolq.OK([]interface{}{})
|
||||
return coolq.OK([]any{})
|
||||
case ret := <-ch:
|
||||
return coolq.OK(ret)
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
|
@ -14,7 +14,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/LagrangeDev/LagrangeGo/utils"
|
||||
|
||||
"github.com/RomiChan/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
@ -25,6 +26,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 {
|
||||
@ -70,7 +72,7 @@ func (c *wsConn) Close() error {
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
CheckOrigin: func(_ *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
@ -191,9 +193,13 @@ func runWSClient(b *coolq.CQBot, node yaml.Node) {
|
||||
filter: conf.Filter,
|
||||
}
|
||||
filter.Add(c.filter)
|
||||
|
||||
if conf.ReconnectInterval != 0 {
|
||||
c.reconnectInterval = time.Duration(conf.ReconnectInterval) * time.Millisecond
|
||||
} else {
|
||||
c.reconnectInterval = time.Second * 5
|
||||
}
|
||||
|
||||
if conf.RateLimit.Enabled {
|
||||
c.limiter = rateLimit(conf.RateLimit.Frequency, conf.RateLimit.Bucket)
|
||||
}
|
||||
@ -234,7 +240,7 @@ func (c *websocketClient) connect(typ, addr string, conptr **wsConn) {
|
||||
log.Infof("开始尝试连接到反向WebSocket %s服务器: %v", typ, addr)
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{typ},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"X-Self-ID": []string{strconv.FormatInt(int64(c.bot.Client.Uin), 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
if c.token != "" {
|
||||
@ -463,14 +469,16 @@ func (s *webSocketServer) listenAPI(c *wsConn) {
|
||||
func (c *wsConn) handleRequest(_ *coolq.CQBot, payload []byte) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("处置WS命令时发生无法恢复的异常:%v\n%s", err, debug.Stack())
|
||||
log.Errorf("处置WS命令时发生无法恢复的异常:%v\n%s", err, debug.Stack())
|
||||
_ = c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
j := gjson.Parse(utils.B2S(payload))
|
||||
t := strings.TrimSuffix(j.Get("action").Str, "_async")
|
||||
log.Debugf("WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw)
|
||||
ret := c.apiCaller.Call(t, j.Get("params"))
|
||||
params := j.Get("params")
|
||||
log.Debugf("WS接收到API调用: %v 参数: %v", t, params.Raw)
|
||||
ret := c.apiCaller.Call(t, onebot.V11, params)
|
||||
if j.Get("echo").Exists() {
|
||||
ret["echo"] = j.Get("echo").Value()
|
||||
}
|
||||
@ -478,7 +486,11 @@ func (c *wsConn) handleRequest(_ *coolq.CQBot, payload []byte) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_ = c.conn.SetWriteDeadline(time.Now().Add(time.Second * 15))
|
||||
writer, _ := c.conn.NextWriter(websocket.TextMessage)
|
||||
writer, err := c.conn.NextWriter(websocket.TextMessage)
|
||||
if err != nil {
|
||||
log.Errorf("无法响应API调用(连接已断开?): %v", err)
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(writer).Encode(ret)
|
||||
_ = writer.Close()
|
||||
}
|
||||
|
1
winres/.gitignore
vendored
Normal file
1
winres/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
winres.json
|
118
winres/gen/json.go
Normal file
118
winres/gen/json.go
Normal 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
BIN
winres/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
BIN
winres/icon16.png
Normal file
BIN
winres/icon16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
4
winres/init.go
Normal file
4
winres/init.go
Normal file
@ -0,0 +1,4 @@
|
||||
// Package winres 生成windows资源
|
||||
package winres
|
||||
|
||||
//go:generate go run github.com/Mrs4s/go-cqhttp/winres/gen
|
Reference in New Issue
Block a user