mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-06-30 11:53:25 +00:00
Compare commits
201 Commits
v1.0.0-rc2
...
v1.0.0-rc5
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
49a8b9bd64 | |||
6bb98fc1df | |||
00bb185410 | |||
2c1cd57dfe | |||
c744025cc8 | |||
02aadaf63c | |||
b05a64fc1b | |||
8343db5ae3 | |||
0e08ceccdd | |||
231544d51e | |||
d5936a4064 | |||
894c047330 | |||
239fcad0c4 | |||
8f63750d7f | |||
2e16533c81 | |||
efb5e63c75 | |||
9253fb7cfd | |||
4bebd4fec8 | |||
0248c86078 | |||
cdf7638871 | |||
6f2bb1402f | |||
b981b45245 | |||
51747981dd | |||
92d78839f1 | |||
a6613d88bf | |||
1de1deb059 | |||
8da043f012 | |||
f1957e3814 | |||
2d42a968c9 | |||
0ad641aa2d | |||
bc80944f26 | |||
fc51a69ff1 | |||
cee4bccf45 | |||
e6fa400e05 | |||
481a7ce8aa | |||
3d3d19c593 | |||
fa267b6a2d | |||
fdfae87e97 | |||
5c78174d1c | |||
c84d583235 | |||
93fa36034a | |||
de44adbfa1 | |||
db64699f3c | |||
b12b7e5cb9 | |||
997cdceb7a | |||
f4117bfb70 | |||
1dd12df26a | |||
ec4b3cc3db | |||
565f8635c0 | |||
069a764a49 | |||
9b222a87fb | |||
4ff61215c6 | |||
532f55fba0 | |||
8389d9195d | |||
bc9c6c49f1 | |||
bae00b557e | |||
21df6c6d6d | |||
0377e7f803 | |||
140192c76a | |||
093605cf01 | |||
2f92146092 | |||
96e6397636 | |||
1ff5e4de12 | |||
98712bf9ca | |||
65b050c781 | |||
656dc6b103 | |||
933bdee18e | |||
202a75ee2d | |||
a73d27f40b | |||
08b0837cab | |||
bf5562b6dd | |||
93074ef4af | |||
fe19b3a9ad | |||
7a979c862f | |||
36bf579e0f | |||
551a475c1c | |||
67ea5d75ef | |||
a0fba6ad54 | |||
7e750352c4 | |||
f772996418 | |||
2402bbedb0 | |||
71ba266a8c | |||
1d859dc373 | |||
5cf5bf7a27 | |||
06b43dd666 | |||
7707e7854c | |||
aec0ef66be | |||
177ba9d8c2 | |||
2a0babad99 | |||
ae7fefad13 | |||
86f5b7f5f5 | |||
23d594be29 | |||
672dafdb9d | |||
847ef6d415 | |||
f900fd62fb | |||
7349fd4b82 | |||
7e24f8b6e6 | |||
babf35e495 | |||
a4a8e94b27 | |||
6fc3f0b649 | |||
b013f66209 | |||
13abf92b76 | |||
7d97216612 | |||
859f40db83 |
19
.github/workflows/build_docker_image.yml
vendored
19
.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@v3
|
||||
|
||||
- name: Set time zone
|
||||
uses: szenius/set-timezone@v1.0
|
||||
uses: szenius/set-timezone@v1.1
|
||||
with:
|
||||
timezoneLinux: "Asia/Shanghai"
|
||||
timezoneMacos: "Asia/Shanghai"
|
||||
@ -37,7 +38,7 @@ jobs:
|
||||
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@ -45,7 +46,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository }}
|
||||
@ -61,16 +62,18 @@ jobs:
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
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@v3
|
||||
- name: Setup Go environment
|
||||
uses: actions/setup-go@v2.1.3
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
- 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@v3
|
||||
if: ${{ !github.head_ref }}
|
||||
with:
|
||||
name: ${{ matrix.goos }}_${{ matrix.goarch }}
|
||||
|
14
.github/workflows/golint.yml
vendored
14
.github/workflows/golint.yml
vendored
@ -7,24 +7,18 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Go environment
|
||||
uses: actions/setup-go@v2.1.3
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: '1.20'
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Static Check
|
||||
uses: dominikh/staticcheck-action@v1.2.0
|
||||
with:
|
||||
install-go: false
|
||||
version: "2022.1"
|
||||
|
||||
- name: Tests
|
||||
run: |
|
||||
go test $(go list ./...)
|
||||
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -10,20 +10,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
run: |
|
||||
git version
|
||||
git clone https://github.com/Mrs4s/go-cqhttp.git /home/runner/work/go-cqhttp/go-cqhttp
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
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 }}
|
||||
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -9,3 +9,13 @@ data/
|
||||
logs/
|
||||
internal/btree/*.lock
|
||||
internal/btree/*.db
|
||||
|
||||
# binary builds
|
||||
go-cqhttp
|
||||
*.exe
|
||||
|
||||
# macos
|
||||
.DS_Store
|
||||
|
||||
# windwos rc
|
||||
*.syso
|
||||
|
@ -21,10 +21,8 @@ linters:
|
||||
disable-all: true
|
||||
fast: false
|
||||
enable:
|
||||
#- bodyclose
|
||||
#- deadcode
|
||||
#- depguard
|
||||
#- dogsled
|
||||
- bodyclose
|
||||
- durationcheck
|
||||
- gofmt
|
||||
- goimports
|
||||
- errcheck
|
||||
@ -32,18 +30,16 @@ linters:
|
||||
- exhaustive
|
||||
- bidichk
|
||||
- gocritic
|
||||
#- gosimple
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
#- nolintlint
|
||||
#- rowserrcheck
|
||||
#- staticcheck
|
||||
- structcheck
|
||||
#- stylecheck
|
||||
- staticcheck
|
||||
- stylecheck
|
||||
- unconvert
|
||||
#- unparam
|
||||
#- unused
|
||||
- varcheck
|
||||
- usestdlibvars
|
||||
- unparam
|
||||
- unused
|
||||
- whitespace
|
||||
- prealloc
|
||||
- predeclared
|
||||
|
@ -3,6 +3,9 @@ env:
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go install github.com/tc-hib/go-winres@latest
|
||||
- go generate winres/init.go
|
||||
- go-winres make
|
||||
release:
|
||||
draft: true
|
||||
discussion_category_name: General
|
||||
|
30
Dockerfile
30
Dockerfile
@ -1,4 +1,4 @@
|
||||
FROM golang:1.18-alpine AS builder
|
||||
FROM golang:1.20-alpine AS builder
|
||||
|
||||
RUN go env -w GO111MODULE=auto \
|
||||
&& go env -w CGO_ENABLED=0 \
|
||||
@ -14,11 +14,31 @@ RUN set -ex \
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache ffmpeg
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
COPY --from=builder /build/cqhttp /usr/bin/cqhttp
|
||||
RUN chmod +x /usr/bin/cqhttp
|
||||
RUN chmod +x /docker-entrypoint.sh && \
|
||||
apk add --no-cache --update \
|
||||
ffmpeg \
|
||||
coreutils \
|
||||
shadow \
|
||||
su-exec && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
mkdir -p /app && \
|
||||
mkdir -p /data && \
|
||||
mkdir -p /config && \
|
||||
useradd -d /config -s /bin/sh abc && \
|
||||
chown -R abc /config && \
|
||||
chown -R abc /data
|
||||
|
||||
ENV TZ="Asia/Shanghai"
|
||||
ENV UID=99
|
||||
ENV GID=100
|
||||
ENV UMASK=002
|
||||
|
||||
COPY --from=builder /build/cqhttp /app/
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/cqhttp" ]
|
||||
VOLUME [ "/data" ]
|
||||
|
||||
ENTRYPOINT [ "/docker-entrypoint.sh" ]
|
||||
|
110
README.md
110
README.md
@ -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>
|
||||
|
||||
@ -86,18 +86,18 @@ go-cqhttp 兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) 绝大
|
||||
| [CQ:xml] | [XML 消息] |
|
||||
| [CQ:json] | [JSON 消息] |
|
||||
|
||||
[qq 表情]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#qq-%E8%A1%A8%E6%83%85
|
||||
[语音]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E8%AF%AD%E9%9F%B3
|
||||
[短视频]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E7%9F%AD%E8%A7%86%E9%A2%91
|
||||
[@某人]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E6%9F%90%E4%BA%BA
|
||||
[链接分享]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E9%93%BE%E6%8E%A5%E5%88%86%E4%BA%AB
|
||||
[音乐分享]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E9%9F%B3%E4%B9%90%E5%88%86%E4%BA%AB-
|
||||
[音乐自定义分享]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E9%9F%B3%E4%B9%90%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%86%E4%BA%AB-
|
||||
[回复]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E5%9B%9E%E5%A4%8D
|
||||
[合并转发]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-
|
||||
[合并转发节点]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E8%8A%82%E7%82%B9-
|
||||
[xml 消息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#xml-%E6%B6%88%E6%81%AF
|
||||
[json 消息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#json-%E6%B6%88%E6%81%AF
|
||||
[qq 表情]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#qq-%E8%A1%A8%E6%83%85
|
||||
[语音]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E8%AF%AD%E9%9F%B3
|
||||
[短视频]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E7%9F%AD%E8%A7%86%E9%A2%91
|
||||
[@某人]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E6%9F%90%E4%BA%BA
|
||||
[链接分享]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E9%93%BE%E6%8E%A5%E5%88%86%E4%BA%AB
|
||||
[音乐分享]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E9%9F%B3%E4%B9%90%E5%88%86%E4%BA%AB-
|
||||
[音乐自定义分享]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E9%9F%B3%E4%B9%90%E8%87%AA%E5%AE%9A%E4%B9%89%E5%88%86%E4%BA%AB-
|
||||
[回复]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E5%9B%9E%E5%A4%8D
|
||||
[合并转发]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-
|
||||
[合并转发节点]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E8%8A%82%E7%82%B9-
|
||||
[xml 消息]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#xml-%E6%B6%88%E6%81%AF
|
||||
[json 消息]: https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#json-%E6%B6%88%E6%81%AF
|
||||
|
||||
#### 拓展 CQ 码及与 OneBot 标准有略微差异的 CQ 码
|
||||
|
||||
@ -154,33 +154,33 @@ go-cqhttp 兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) 绝大
|
||||
| /set_restart | [重启 go-cqhttp] |
|
||||
| /.handle_quick_operation | [对事件执行快速操作] |
|
||||
|
||||
[发送私聊消息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
|
||||
[发送群消息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF
|
||||
[发送消息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF
|
||||
[撤回信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF
|
||||
[群组踢人]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA
|
||||
[群组单人禁言]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
|
||||
[群组全员禁言]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80
|
||||
[群组设置管理员]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98
|
||||
[设置群名片(群备注)]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8
|
||||
[设置群名]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D
|
||||
[退出群组]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84
|
||||
[设置群组专属头衔]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94
|
||||
[处理加好友请求]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
|
||||
[处理加群请求/邀请]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
|
||||
[获取登录号信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
|
||||
[获取陌生人信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF
|
||||
[获取好友列表]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8
|
||||
[获取群信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF
|
||||
[获取群列表]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
|
||||
[获取群成员信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF
|
||||
[获取群成员列表]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
|
||||
[获取群荣誉信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
|
||||
[检查是否可以发送图片]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87
|
||||
[检查是否可以发送语音]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3
|
||||
[获取版本信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
|
||||
[重启 go-cqhttp]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0
|
||||
[对事件执行快速操作]: https://github.com/howmanybots/onebot/blob/master/v11/specs/api/hidden.md#handle_quick_operation-%E5%AF%B9%E4%BA%8B%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%93%8D%E4%BD%9C
|
||||
[发送私聊消息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
|
||||
[发送群消息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF
|
||||
[发送消息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF
|
||||
[撤回信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF
|
||||
[群组踢人]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA
|
||||
[群组单人禁言]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
|
||||
[群组全员禁言]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80
|
||||
[群组设置管理员]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98
|
||||
[设置群名片(群备注)]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8
|
||||
[设置群名]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D
|
||||
[退出群组]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84
|
||||
[设置群组专属头衔]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94
|
||||
[处理加好友请求]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
|
||||
[处理加群请求/邀请]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
|
||||
[获取登录号信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
|
||||
[获取陌生人信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF
|
||||
[获取好友列表]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8
|
||||
[获取群信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF
|
||||
[获取群列表]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
|
||||
[获取群成员信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF
|
||||
[获取群成员列表]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
|
||||
[获取群荣誉信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
|
||||
[检查是否可以发送图片]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87
|
||||
[检查是否可以发送语音]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3
|
||||
[获取版本信息]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
|
||||
[重启 go-cqhttp]: https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0
|
||||
[对事件执行快速操作]: https://github.com/botuniverse/onebot-11/blob/master/api/hidden.md#handle_quick_operation-%E5%AF%B9%E4%BA%8B%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%93%8D%E4%BD%9C
|
||||
|
||||
#### 拓展 API 及与 OneBot 标准有略微差异的 API
|
||||
|
||||
@ -239,21 +239,21 @@ go-cqhttp 兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) 绝大
|
||||
| 请求事件 | [加好友请求] |
|
||||
| 请求事件 | [加群请求/邀请] |
|
||||
|
||||
[私聊信息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
|
||||
[群消息]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#%E7%BE%A4%E6%B6%88%E6%81%AF
|
||||
[群文件上传]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0
|
||||
[群管理员变动]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E7%AE%A1%E7%90%86%E5%91%98%E5%8F%98%E5%8A%A8
|
||||
[群成员减少]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E6%88%90%E5%91%98%E5%87%8F%E5%B0%91
|
||||
[群成员增加]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E6%88%90%E5%91%98%E5%A2%9E%E5%8A%A0
|
||||
[群禁言]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E7%A6%81%E8%A8%80
|
||||
[好友添加]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E5%A5%BD%E5%8F%8B%E6%B7%BB%E5%8A%A0
|
||||
[群消息撤回]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E6%B6%88%E6%81%AF%E6%92%A4%E5%9B%9E
|
||||
[好友消息撤回]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E5%A5%BD%E5%8F%8B%E6%B6%88%E6%81%AF%E6%92%A4%E5%9B%9E
|
||||
[群内戳一戳]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E5%86%85%E6%88%B3%E4%B8%80%E6%88%B3
|
||||
[群红包运气王]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E7%BA%A2%E5%8C%85%E8%BF%90%E6%B0%94%E7%8E%8B
|
||||
[群成员荣誉变更]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md#%E7%BE%A4%E6%88%90%E5%91%98%E8%8D%A3%E8%AA%89%E5%8F%98%E6%9B%B4
|
||||
[加好友请求]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md#%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
|
||||
[加群请求/邀请]: https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md#%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
|
||||
[私聊信息]: https://github.com/botuniverse/onebot-11/blob/master/event/message.md#%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
|
||||
[群消息]: https://github.com/botuniverse/onebot-11/blob/master/event/message.md#%E7%BE%A4%E6%B6%88%E6%81%AF
|
||||
[群文件上传]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0
|
||||
[群管理员变动]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E7%AE%A1%E7%90%86%E5%91%98%E5%8F%98%E5%8A%A8
|
||||
[群成员减少]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E6%88%90%E5%91%98%E5%87%8F%E5%B0%91
|
||||
[群成员增加]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E6%88%90%E5%91%98%E5%A2%9E%E5%8A%A0
|
||||
[群禁言]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E7%A6%81%E8%A8%80
|
||||
[好友添加]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E5%A5%BD%E5%8F%8B%E6%B7%BB%E5%8A%A0
|
||||
[群消息撤回]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E6%B6%88%E6%81%AF%E6%92%A4%E5%9B%9E
|
||||
[好友消息撤回]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E5%A5%BD%E5%8F%8B%E6%B6%88%E6%81%AF%E6%92%A4%E5%9B%9E
|
||||
[群内戳一戳]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E5%86%85%E6%88%B3%E4%B8%80%E6%88%B3
|
||||
[群红包运气王]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E7%BA%A2%E5%8C%85%E8%BF%90%E6%B0%94%E7%8E%8B
|
||||
[群成员荣誉变更]: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md#%E7%BE%A4%E6%88%90%E5%91%98%E8%8D%A3%E8%AA%89%E5%8F%98%E6%9B%B4
|
||||
[加好友请求]: https://github.com/botuniverse/onebot-11/blob/master/event/request.md#%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
|
||||
[加群请求/邀请]: https://github.com/botuniverse/onebot-11/blob/master/event/request.md#%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
|
||||
|
||||
#### 拓展 Event
|
||||
|
||||
|
@ -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
|
||||
@ -24,6 +30,8 @@ type Param struct {
|
||||
type Router struct {
|
||||
Func string
|
||||
Path []string
|
||||
PathV11 []string // v11 only
|
||||
PathV12 []string // v12 only
|
||||
Params []Param
|
||||
}
|
||||
|
||||
@ -31,29 +39,66 @@ 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) 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
|
||||
}
|
||||
|
||||
func (g *generator) router(router Router) {
|
||||
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,15 +152,17 @@ 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)
|
||||
for _, s := range strings.Split(*src, ",") {
|
||||
file, err := parser.ParseFile(fset, s, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -116,23 +170,15 @@ func main() {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if !decl.Name.IsExported() || decl.Recv == nil {
|
||||
continue
|
||||
}
|
||||
if st, ok := decl.Recv.List[0].Type.(*ast.StarExpr); !ok || st.X.(*ast.Ident).Name != "CQBot" {
|
||||
if !decl.Name.IsExported() || decl.Recv == nil ||
|
||||
typeName(decl.Recv.List[0].Type) != "*CQBot" {
|
||||
continue
|
||||
}
|
||||
router := Router{Func: decl.Name.Name}
|
||||
|
||||
// compute params
|
||||
for _, p := range decl.Type.Params.List {
|
||||
var typ string
|
||||
switch t := p.Type.(type) {
|
||||
case *ast.Ident:
|
||||
typ = t.Name
|
||||
case *ast.SelectorExpr:
|
||||
typ = t.X.(*ast.Ident).Name + "." + t.Sel.Name
|
||||
}
|
||||
typ := typeName(p.Type)
|
||||
for _, name := range p.Names {
|
||||
router.Params = append(router.Params, Param{Name: snakecase(name.Name), Type: typ})
|
||||
}
|
||||
@ -145,6 +191,14 @@ func main() {
|
||||
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 {
|
||||
@ -165,27 +219,51 @@ func main() {
|
||||
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 {
|
||||
if router.Path != nil || router.PathV11 != nil || router.PathV12 != 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}}
|
||||
}`
|
@ -3,29 +3,43 @@ package gocq
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/Mrs4s/MiraiGo/wrapper"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"gopkg.ilharper.com/x/isatty"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
)
|
||||
|
||||
var console = bufio.NewReader(os.Stdin)
|
||||
|
||||
func init() {
|
||||
wrapper.DandelionEnergy = energy
|
||||
}
|
||||
|
||||
func readLine() (str string) {
|
||||
str, _ = console.ReadString('\n')
|
||||
str = strings.TrimSpace(str)
|
||||
return
|
||||
}
|
||||
|
||||
func readLineTimeout(t time.Duration, de string) (str string) {
|
||||
func readLineTimeout(t time.Duration) {
|
||||
r := make(chan string)
|
||||
go func() {
|
||||
select {
|
||||
@ -33,15 +47,22 @@ 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) {
|
||||
if isatty.Isatty(os.Stdin.Fd()) {
|
||||
return readLine()
|
||||
}
|
||||
log.Warnf("未检测到输入终端,自动选择%s.", de)
|
||||
return de
|
||||
}
|
||||
|
||||
var cli *client.QQClient
|
||||
var device *client.DeviceInfo
|
||||
|
||||
// ErrSMSRequestError SMS请求出错
|
||||
var ErrSMSRequestError = errors.New("sms request error")
|
||||
@ -142,11 +163,15 @@ func loginResponseProcessor(res *client.LoginResponse) error {
|
||||
var text string
|
||||
switch res.Error {
|
||||
case client.SliderNeededError:
|
||||
log.Warnf("登录需要滑条验证码, 请使用手机QQ扫描二维码以继续登录.")
|
||||
cli.Disconnect()
|
||||
cli.Release()
|
||||
cli = client.NewClientEmpty()
|
||||
return qrcodeLogin()
|
||||
log.Warnf("登录需要滑条验证码, 请验证后重试.")
|
||||
ticket := getTicket(res.VerifyUrl)
|
||||
if ticket == "" {
|
||||
log.Infof("按 Enter 继续....")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
}
|
||||
res, err = cli.SubmitTicket(ticket)
|
||||
continue
|
||||
case client.NeedCaptcha:
|
||||
log.Warnf("登录需要验证码.")
|
||||
_ = os.WriteFile("captcha.jpg", res.CaptchaImage, 0o644)
|
||||
@ -170,8 +195,8 @@ func loginResponseProcessor(res *client.LoginResponse) error {
|
||||
log.Warnf("账号已开启设备锁,请选择验证方式:")
|
||||
log.Warnf("1. 向手机 %v 发送短信验证码", res.SMSPhone)
|
||||
log.Warnf("2. 使用手机QQ扫码验证.")
|
||||
log.Warn("请输入(1 - 2) (将在10秒后自动选择2):")
|
||||
text = readLineTimeout(time.Second*10, "2")
|
||||
log.Warn("请输入(1 - 2):")
|
||||
text = readIfTTY("2")
|
||||
if strings.Contains(text, "1") {
|
||||
if !cli.RequestSMS() {
|
||||
log.Warnf("发送验证码失败,可能是请求过于频繁.")
|
||||
@ -186,20 +211,81 @@ func loginResponseProcessor(res *client.LoginResponse) error {
|
||||
case client.UnsafeDeviceError:
|
||||
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:
|
||||
msg := res.ErrorMessage
|
||||
if strings.Contains(msg, "版本") {
|
||||
msg = "密码错误或账号被冻结"
|
||||
log.Warnf("登录失败: %v Code: %v", msg, res.Code)
|
||||
if res.Code == 235 {
|
||||
log.Warnf("请删除 device.json 后重试.")
|
||||
}
|
||||
if strings.Contains(msg, "冻结") {
|
||||
log.Fatalf("账号被冻结")
|
||||
}
|
||||
log.Warnf("登录失败: %v", msg)
|
||||
log.Infof("按 Enter 或等待 5s 后继续....")
|
||||
readLineTimeout(time.Second*5, "")
|
||||
log.Infof("按 Enter 继续....")
|
||||
readLine()
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getTicket(u string) string {
|
||||
log.Warnf("请选择提交滑块ticket方式:")
|
||||
log.Warnf("1. 自动提交")
|
||||
log.Warnf("2. 手动抓取提交")
|
||||
log.Warn("请输入(1 - 2):")
|
||||
text := readLine()
|
||||
id := utils.RandomString(8)
|
||||
auto := !strings.Contains(text, "2")
|
||||
if auto {
|
||||
u = strings.ReplaceAll(u, "https://ssl.captcha.qq.com/template/wireless_mqq_captcha.html?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id))
|
||||
}
|
||||
log.Warnf("请前往该地址验证 -> %v ", u)
|
||||
if !auto {
|
||||
log.Warn("请输入ticket: (Enter 提交)")
|
||||
return readLine()
|
||||
}
|
||||
|
||||
for count := 120; count > 0; count-- {
|
||||
str := fetchCaptcha(id)
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
log.Warnf("验证超时")
|
||||
return ""
|
||||
}
|
||||
|
||||
func fetchCaptcha(id string) string {
|
||||
g, err := download.Request{URL: "https://captcha.go-cqhttp.org/captcha/ticket?id=" + id}.JSON()
|
||||
if err != nil {
|
||||
log.Debugf("获取 Ticket 时出现错误: %v", err)
|
||||
return ""
|
||||
}
|
||||
if g.Get("ticket").Exists() {
|
||||
return g.Get("ticket").String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func energy(uin uint64, id string, salt []byte) ([]byte, error) {
|
||||
// temporary solution
|
||||
signServer := "https://captcha.go-cqhttp.org/sdk/dandelion/energy"
|
||||
if base.SignServerOverwrite != "" {
|
||||
signServer = base.SignServerOverwrite
|
||||
}
|
||||
response, err := download.Request{
|
||||
Method: http.MethodPost,
|
||||
URL: signServer,
|
||||
Header: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
Body: bytes.NewReader([]byte(fmt.Sprintf("uin=%v&id=%s&salt=%s", uin, id, hex.EncodeToString(salt)))),
|
||||
}.Bytes()
|
||||
if err != nil {
|
||||
log.Errorf("获取T544时出现问题: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
sign, err := hex.DecodeString(gjson.GetBytes(response, "result").String())
|
||||
if err != nil {
|
||||
log.Errorf("获取T544时出现问题: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return sign, nil
|
||||
}
|
||||
|
102
cmd/gocq/main.go
102
cmd/gocq/main.go
@ -16,10 +16,13 @@ import (
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
para "github.com/fumiama/go-hide-param"
|
||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/coolq"
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
@ -41,8 +44,12 @@ var allowStatus = [...]client.UserOnlineStatus{
|
||||
client.StatusGaming, client.StatusVacationing, client.StatusWatchingTV, client.StatusFitness,
|
||||
}
|
||||
|
||||
// Main 启动主程序
|
||||
func Main() {
|
||||
// InitBase 解析参数并检测
|
||||
//
|
||||
// 如果在 windows 下双击打开了程序,程序将在此函数释出脚本后终止;
|
||||
// 如果传入 -h 参数,程序将打印帮助后终止;
|
||||
// 如果传入 -d 参数,程序将在启动 daemon 后终止。
|
||||
func InitBase() {
|
||||
base.Parse()
|
||||
if !base.FastStart && terminal.RunningByDoubleClick() {
|
||||
err := terminal.NoMoreDoubleClick()
|
||||
@ -50,18 +57,25 @@ func Main() {
|
||||
log.Errorf("遇到错误: %v", err)
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
return
|
||||
os.Exit(0)
|
||||
}
|
||||
switch {
|
||||
case base.LittleH:
|
||||
base.Help()
|
||||
case base.LittleD:
|
||||
server.Daemon()
|
||||
case base.LittleWD != "":
|
||||
base.ResetWorkingDir()
|
||||
}
|
||||
if base.LittleWD != "" {
|
||||
err := os.Chdir(base.LittleWD)
|
||||
if err != nil {
|
||||
log.Fatalf("重置工作目录时出现错误: %v", err)
|
||||
}
|
||||
}
|
||||
base.Init()
|
||||
}
|
||||
|
||||
// PrepareData 准备 log, 缓存, 数据库, 必须在 InitBase 之后执行
|
||||
func PrepareData() {
|
||||
rotateOptions := []rotatelogs.Option{
|
||||
rotatelogs.WithRotationTime(time.Hour * 24),
|
||||
}
|
||||
@ -81,7 +95,7 @@ func Main() {
|
||||
|
||||
mkCacheDir := func(path string, _type string) {
|
||||
if !global.PathExists(path) {
|
||||
if err := os.MkdirAll(path, 0o644); err != nil {
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
log.Fatalf("创建%s缓存文件夹失败: %v", _type, err)
|
||||
}
|
||||
}
|
||||
@ -91,13 +105,17 @@ func Main() {
|
||||
mkCacheDir(global.VideoPath, "视频")
|
||||
mkCacheDir(global.CachePath, "发送图片")
|
||||
mkCacheDir(path.Join(global.ImagePath, "guild-images"), "频道图片缓存")
|
||||
mkCacheDir(global.VersionsPath, "版本缓存")
|
||||
cache.Init()
|
||||
|
||||
db.Init()
|
||||
if err := db.Open(); err != nil {
|
||||
log.Fatalf("打开数据库失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// LoginInteract 登录交互, 可能需要键盘输入, 必须在 InitBase, PrepareData 之后执行
|
||||
func LoginInteract() {
|
||||
var byteKey []byte
|
||||
arg := os.Args
|
||||
if len(arg) > 1 {
|
||||
@ -131,16 +149,16 @@ func Main() {
|
||||
if base.Debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Warnf("已开启Debug模式.")
|
||||
// log.Debugf("开发交流群: 192548878")
|
||||
}
|
||||
if !global.PathExists("device.json") {
|
||||
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
|
||||
client.GenRandomDevice()
|
||||
_ = os.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0o644)
|
||||
device = client.GenRandomDevice()
|
||||
_ = os.WriteFile("device.json", device.ToJson(), 0o644)
|
||||
log.Info("已生成设备信息并保存到 device.json 文件.")
|
||||
} else {
|
||||
log.Info("将使用 device.json 内的设备信息运行Bot.")
|
||||
if err := client.SystemDeviceInfo.ReadJson([]byte(global.ReadAllText("device.json"))); err != nil {
|
||||
device = new(client.DeviceInfo)
|
||||
if err := device.ReadJson([]byte(global.ReadAllText("device.json"))); err != nil {
|
||||
log.Fatalf("加载设备信息失败: %v", err)
|
||||
}
|
||||
}
|
||||
@ -202,10 +220,27 @@ func Main() {
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
log.Info("开始尝试登录并同步消息...")
|
||||
log.Infof("使用协议: %s", client.SystemDeviceInfo.Protocol)
|
||||
log.Infof("使用协议: %s", device.Protocol.Version())
|
||||
cli = newClient()
|
||||
cli.UseDevice(device)
|
||||
isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt
|
||||
isTokenLogin := false
|
||||
|
||||
if isQRCodeLogin && cli.Device().Protocol != 2 {
|
||||
log.Warn("当前协议不支持二维码登录, 请配置账号密码登录.")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// 加载本地版本信息, 一般是在上次登录时保存的
|
||||
versionFile := path.Join(global.VersionsPath, fmt.Sprint(int(cli.Device().Protocol))+".json")
|
||||
if global.PathExists(versionFile) {
|
||||
b, err := os.ReadFile(versionFile)
|
||||
if err == nil {
|
||||
_ = cli.Device().Protocol.Version().UpdateFromJson(b)
|
||||
}
|
||||
log.Infof("从文件 %s 读取协议版本 %v.", versionFile, cli.Device().Protocol.Version())
|
||||
}
|
||||
|
||||
saveToken := func() {
|
||||
base.AccountToken = cli.GenToken()
|
||||
_ = os.WriteFile("session.token", base.AccountToken, 0o644)
|
||||
@ -220,8 +255,8 @@ func Main() {
|
||||
log.Warnf("警告: 配置文件内的QQ号 (%v) 与缓存内的QQ号 (%v) 不相同", base.Account.Uin, cu)
|
||||
log.Warnf("1. 使用会话缓存继续.")
|
||||
log.Warnf("2. 删除会话缓存并重启.")
|
||||
log.Warnf("请选择: (5秒后自动选1)")
|
||||
text := readLineTimeout(time.Second*5, "1")
|
||||
log.Warnf("请选择:")
|
||||
text := readIfTTY("1")
|
||||
if text == "2" {
|
||||
_ = os.Remove("session.token")
|
||||
log.Infof("缓存已删除.")
|
||||
@ -236,6 +271,7 @@ func Main() {
|
||||
cli.Disconnect()
|
||||
cli.Release()
|
||||
cli = newClient()
|
||||
cli.UseDevice(device)
|
||||
} else {
|
||||
isTokenLogin = true
|
||||
}
|
||||
@ -246,6 +282,23 @@ func Main() {
|
||||
cli.PasswordMd5 = base.PasswordHash
|
||||
}
|
||||
if !isTokenLogin {
|
||||
if !base.Account.DisableProtocolUpdate {
|
||||
log.Infof("正在检查协议更新...")
|
||||
oldVersionName := device.Protocol.Version().String()
|
||||
remoteVersion, err := getRemoteLatestProtocolVersion(int(device.Protocol.Version().Protocol))
|
||||
if err == nil {
|
||||
if err = device.Protocol.Version().UpdateFromJson(remoteVersion); err == nil {
|
||||
if device.Protocol.Version().String() != oldVersionName {
|
||||
log.Infof("已自动更新协议版本: %s -> %s", oldVersionName, device.Protocol.Version().String())
|
||||
} else {
|
||||
log.Infof("协议已经是最新版本")
|
||||
}
|
||||
_ = os.WriteFile(versionFile, remoteVersion, 0o644)
|
||||
}
|
||||
} else if err.Error() != "remote version unavailable" {
|
||||
log.Warnf("检查协议更新失败: %v", err)
|
||||
}
|
||||
}
|
||||
if !isQRCodeLogin {
|
||||
if err := commonLogin(); err != nil {
|
||||
log.Fatalf("登录时发生致命错误: %v", err)
|
||||
@ -323,7 +376,13 @@ func Main() {
|
||||
servers.Run(coolq.NewQQBot(cli))
|
||||
log.Info("资源初始化完成, 开始处理信息.")
|
||||
log.Info("アトリは、高性能ですから!")
|
||||
}
|
||||
|
||||
// WaitSignal 在新线程检查更新和网络并等待信号, 必须在 InitBase, PrepareData, LoginInteract 之后执行
|
||||
//
|
||||
// - 直接返回: os.Interrupt, syscall.SIGTERM
|
||||
// - dump stack: syscall.SIGQUIT, syscall.SIGUSR1
|
||||
func WaitSignal() {
|
||||
go func() {
|
||||
selfupdate.CheckUpdate()
|
||||
selfdiagnosis.NetworkDiagnosis(cli)
|
||||
@ -386,6 +445,23 @@ func newClient() *client.QQClient {
|
||||
return c
|
||||
}
|
||||
|
||||
var remoteVersions = map[int]string{
|
||||
1: "https://raw.githubusercontent.com/RomiChan/protocol-versions/master/android_phone.json",
|
||||
6: "https://raw.githubusercontent.com/RomiChan/protocol-versions/master/android_pad.json",
|
||||
}
|
||||
|
||||
func getRemoteLatestProtocolVersion(protocolType int) ([]byte, error) {
|
||||
url, ok := remoteVersions[protocolType]
|
||||
if !ok {
|
||||
return nil, errors.New("remote version unavailable")
|
||||
}
|
||||
response, err := download.Request{URL: url}.Bytes()
|
||||
if err != nil {
|
||||
return download.Request{URL: "https://ghproxy.com/" + url}.Bytes()
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type protocolLogger struct{}
|
||||
|
||||
const fromProtocol = "Protocol -> "
|
||||
|
220
coolq/api.go
220
coolq/api.go
@ -14,12 +14,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/segmentio/asm/base64"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/segmentio/asm/base64"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
@ -27,8 +26,11 @@ import (
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/cache"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/msg"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/param"
|
||||
"github.com/Mrs4s/go-cqhttp/modules/filter"
|
||||
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
|
||||
)
|
||||
|
||||
type guildMemberPageToken struct {
|
||||
@ -47,7 +49,8 @@ var defaultPageToken = guildMemberPageToken{
|
||||
// CQGetLoginInfo 获取登录号信息
|
||||
//
|
||||
// https://git.io/Jtz1I
|
||||
// @route(get_login_info)
|
||||
// @route11(get_login_info)
|
||||
// @route12(get_self_info)
|
||||
func (bot *CQBot) CQGetLoginInfo() global.MSG {
|
||||
return OK(global.MSG{"user_id": bot.Client.Uin, "nickname": bot.Client.Nickname})
|
||||
}
|
||||
@ -326,13 +329,13 @@ func (bot *CQBot) CQGetTopicChannelFeeds(guildID, channelID uint64) global.MSG {
|
||||
//
|
||||
// https://git.io/Jtz1L
|
||||
// @route(get_friend_list)
|
||||
func (bot *CQBot) CQGetFriendList() global.MSG {
|
||||
func (bot *CQBot) CQGetFriendList(spec *onebot.Spec) global.MSG {
|
||||
fs := make([]global.MSG, 0, len(bot.Client.FriendList))
|
||||
for _, f := range bot.Client.FriendList {
|
||||
fs = append(fs, global.MSG{
|
||||
"nickname": f.Nickname,
|
||||
"remark": f.Remark,
|
||||
"user_id": f.Uin,
|
||||
"user_id": spec.ConvertID(f.Uin),
|
||||
})
|
||||
}
|
||||
return OK(fs)
|
||||
@ -398,14 +401,14 @@ func (bot *CQBot) CQDeleteFriend(uin int64) global.MSG {
|
||||
//
|
||||
// https://git.io/Jtz1t
|
||||
// @route(get_group_list)
|
||||
func (bot *CQBot) CQGetGroupList(noCache bool) global.MSG {
|
||||
func (bot *CQBot) CQGetGroupList(noCache bool, spec *onebot.Spec) global.MSG {
|
||||
gs := make([]global.MSG, 0, len(bot.Client.GroupList))
|
||||
if noCache {
|
||||
_ = bot.Client.ReloadGroupList()
|
||||
}
|
||||
for _, g := range bot.Client.GroupList {
|
||||
gs = append(gs, global.MSG{
|
||||
"group_id": g.Code,
|
||||
"group_id": spec.ConvertID(g.Code),
|
||||
"group_name": g.Name,
|
||||
"group_create_time": g.GroupCreateTime,
|
||||
"group_level": g.GroupLevel,
|
||||
@ -420,7 +423,7 @@ func (bot *CQBot) CQGetGroupList(noCache bool) global.MSG {
|
||||
//
|
||||
// https://git.io/Jtz1O
|
||||
// @route(get_group_info)
|
||||
func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool) global.MSG {
|
||||
func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, spec *onebot.Spec) global.MSG {
|
||||
group := bot.Client.FindGroup(groupID)
|
||||
if group == nil || noCache {
|
||||
group, _ = bot.Client.GetGroupInfo(groupID)
|
||||
@ -434,7 +437,7 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool) global.MSG {
|
||||
for _, g := range info {
|
||||
if g.Code == groupID {
|
||||
return OK(global.MSG{
|
||||
"group_id": g.Code,
|
||||
"group_id": spec.ConvertID(g.Code),
|
||||
"group_name": g.Name,
|
||||
"group_memo": g.Memo,
|
||||
"group_create_time": 0,
|
||||
@ -446,7 +449,7 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool) global.MSG {
|
||||
}
|
||||
} else {
|
||||
return OK(global.MSG{
|
||||
"group_id": group.Code,
|
||||
"group_id": spec.ConvertID(group.Code),
|
||||
"group_name": group.Name,
|
||||
"group_create_time": group.GroupCreateTime,
|
||||
"group_level": group.GroupLevel,
|
||||
@ -601,6 +604,30 @@ func (bot *CQBot) CQUploadGroupFile(groupID int64, file, name, folder string) gl
|
||||
return OK(nil)
|
||||
}
|
||||
|
||||
// CQUploadPrivateFile 扩展API-上传私聊文件
|
||||
//
|
||||
// @route(upload_private_file)
|
||||
func (bot *CQBot) CQUploadPrivateFile(userID int64, file, name string) global.MSG {
|
||||
target := message.Source{
|
||||
SourceType: message.SourcePrivate,
|
||||
PrimaryID: userID,
|
||||
}
|
||||
fileBody, err := os.Open(file)
|
||||
if err != nil {
|
||||
log.Warnf("上传私聊文件 %v 失败: %+v", file, err)
|
||||
return Failed(100, "OPEN_FILE_ERROR", "打开文件失败")
|
||||
}
|
||||
localFile := &client.LocalFile{
|
||||
FileName: name,
|
||||
Body: fileBody,
|
||||
}
|
||||
if err := bot.Client.UploadFile(target, localFile); err != nil {
|
||||
log.Warnf("上传私聊 %v 文件 %v 失败: %+v", userID, file, err)
|
||||
return Failed(100, "FILE_SYSTEM_UPLOAD_API_ERROR", err.Error())
|
||||
}
|
||||
return OK(nil)
|
||||
}
|
||||
|
||||
// CQGroupFileCreateFolder 拓展API-创建群文件文件夹
|
||||
//
|
||||
// @route(create_group_file_folder)
|
||||
@ -668,7 +695,7 @@ func (bot *CQBot) CQGetWordSlices(content string) global.MSG {
|
||||
|
||||
// CQSendMessage 发送消息
|
||||
//
|
||||
// @route(send_msg)
|
||||
// @route11(send_msg)
|
||||
// @rename(m->message)
|
||||
func (bot *CQBot) CQSendMessage(groupID, userID int64, m gjson.Result, messageType string, autoEscape bool) global.MSG {
|
||||
switch {
|
||||
@ -686,7 +713,7 @@ func (bot *CQBot) CQSendMessage(groupID, userID int64, m gjson.Result, messageTy
|
||||
|
||||
// CQSendForwardMessage 发送合并转发消息
|
||||
//
|
||||
// @route(send_forward_msg)
|
||||
// @route11(send_forward_msg)
|
||||
// @rename(m->messages)
|
||||
func (bot *CQBot) CQSendForwardMessage(groupID, userID int64, m gjson.Result, messageType string) global.MSG {
|
||||
switch {
|
||||
@ -705,7 +732,7 @@ func (bot *CQBot) CQSendForwardMessage(groupID, userID int64, m gjson.Result, me
|
||||
// CQSendGroupMessage 发送群消息
|
||||
//
|
||||
// https://git.io/Jtz1c
|
||||
// @route(send_group_msg)
|
||||
// @route11(send_group_msg)
|
||||
// @rename(m->message)
|
||||
func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape bool) global.MSG {
|
||||
group := bot.Client.FindGroup(groupID)
|
||||
@ -727,7 +754,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b
|
||||
|
||||
var elem []message.IMessageElement
|
||||
if m.Type == gjson.JSON {
|
||||
elem = bot.ConvertObjectMessage(m, message.SourceGroup)
|
||||
elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGroup)
|
||||
} else {
|
||||
str := m.String()
|
||||
if str == "" {
|
||||
@ -737,13 +764,13 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b
|
||||
if autoEscape {
|
||||
elem = []message.IMessageElement{message.NewText(str)}
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, message.SourceGroup)
|
||||
elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGroup)
|
||||
}
|
||||
}
|
||||
fixAt(elem)
|
||||
mid := bot.SendGroupMessage(groupID, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
|
||||
mid, err := bot.SendGroupMessage(groupID, &message.SendingMessage{Elements: elem})
|
||||
if err != nil {
|
||||
return Failed(100, "SEND_MSG_API_ERROR", err.Error())
|
||||
}
|
||||
log.Infof("发送群 %v(%v) 的消息: %v (%v)", group.Name, groupID, limitedString(m.String()), mid)
|
||||
return OK(global.MSG{"message_id": mid})
|
||||
@ -781,7 +808,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
|
||||
|
||||
var elem []message.IMessageElement
|
||||
if m.Type == gjson.JSON {
|
||||
elem = bot.ConvertObjectMessage(m, message.SourceGuildChannel)
|
||||
elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGuildChannel)
|
||||
} else {
|
||||
str := m.String()
|
||||
if str == "" {
|
||||
@ -791,7 +818,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
|
||||
if autoEscape {
|
||||
elem = []message.IMessageElement{message.NewText(str)}
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, message.SourceGuildChannel)
|
||||
elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGuildChannel)
|
||||
}
|
||||
}
|
||||
fixAt(elem)
|
||||
@ -808,7 +835,12 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
||||
groupID := target
|
||||
source := message.Source{SourceType: sourceType, PrimaryID: target}
|
||||
if sourceType == message.SourcePrivate {
|
||||
groupID = 0
|
||||
// ios 设备的合并转发来源群号不能为 0
|
||||
if len(bot.Client.GroupList) == 0 {
|
||||
groupID = 1
|
||||
} else {
|
||||
groupID = bot.Client.GroupList[0].Uin
|
||||
}
|
||||
}
|
||||
builder := bot.Client.NewForwardMessageBuilder(groupID)
|
||||
|
||||
@ -820,7 +852,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
||||
for i, elem := range elems {
|
||||
p := &elems[i]
|
||||
switch o := elem.(type) {
|
||||
case *LocalVideoElement:
|
||||
case *msg.LocalVideo:
|
||||
w.do(func() {
|
||||
gm, err := bot.uploadLocalVideo(source, o)
|
||||
if err != nil {
|
||||
@ -829,7 +861,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
||||
*p = gm
|
||||
}
|
||||
})
|
||||
case *LocalImageElement:
|
||||
case *msg.LocalImage:
|
||||
w.do(func() {
|
||||
gm, err := bot.uploadLocalImage(source, o)
|
||||
if err != nil {
|
||||
@ -849,17 +881,21 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
||||
}
|
||||
if e.Get("data.id").Exists() {
|
||||
i := e.Get("data.id").Int()
|
||||
m, _ := db.GetGroupMessageByGlobalID(int32(i))
|
||||
m, _ := db.GetMessageByGlobalID(int32(i))
|
||||
if m != nil {
|
||||
msgTime := m.Attribute.Timestamp
|
||||
mSource := message.SourcePrivate
|
||||
if m.GetType() == "group" {
|
||||
mSource = message.SourceGroup
|
||||
}
|
||||
msgTime := m.GetAttribute().Timestamp
|
||||
if msgTime == 0 {
|
||||
msgTime = ts.Unix()
|
||||
}
|
||||
return &message.ForwardNode{
|
||||
SenderId: m.Attribute.SenderUin,
|
||||
SenderName: m.Attribute.SenderName,
|
||||
SenderId: m.GetAttribute().SenderUin,
|
||||
SenderName: m.GetAttribute().SenderName,
|
||||
Time: int32(msgTime),
|
||||
Message: resolveElement(bot.ConvertContentMessage(m.Content, message.SourceGroup)),
|
||||
Message: resolveElement(bot.ConvertContentMessage(m.GetContent(), mSource)),
|
||||
}
|
||||
}
|
||||
log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
|
||||
@ -892,7 +928,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
||||
}
|
||||
}
|
||||
}
|
||||
content := bot.ConvertObjectMessage(c, message.SourceGroup)
|
||||
content := bot.ConvertObjectMessage(onebot.V11, c, sourceType)
|
||||
if uin != 0 && name != "" && len(content) > 0 {
|
||||
return &message.ForwardNode{
|
||||
SenderId: uin,
|
||||
@ -928,7 +964,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
||||
// CQSendGroupForwardMessage 扩展API-发送合并转发(群)
|
||||
//
|
||||
// https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-%E7%BE%A4
|
||||
// @route(send_group_forward_msg)
|
||||
// @route11(send_group_forward_msg)
|
||||
// @rename(m->messages)
|
||||
func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) global.MSG {
|
||||
if m.Type != gjson.JSON {
|
||||
@ -944,15 +980,18 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa
|
||||
log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.")
|
||||
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
|
||||
}
|
||||
mid := bot.InsertGroupMessage(ret)
|
||||
log.Infof("发送群 %v(%v) 的合并转发消息: %v (%v)", groupID, groupID, limitedString(m.String()), mid)
|
||||
return OK(global.MSG{
|
||||
"message_id": bot.InsertGroupMessage(ret),
|
||||
"message_id": mid,
|
||||
"forward_id": fe.ResId,
|
||||
})
|
||||
}
|
||||
|
||||
// CQSendPrivateForwardMessage 扩展API-发送合并转发(好友)
|
||||
//
|
||||
// https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-%E7%BE%A4
|
||||
// @route(send_private_forward_msg)
|
||||
// @route11(send_private_forward_msg)
|
||||
// @rename(m->messages)
|
||||
func (bot *CQBot) CQSendPrivateForwardMessage(userID int64, m gjson.Result) global.MSG {
|
||||
if m.Type != gjson.JSON {
|
||||
@ -967,18 +1006,22 @@ func (bot *CQBot) CQSendPrivateForwardMessage(userID int64, m gjson.Result) glob
|
||||
log.Warnf("合并转发(好友)消息发送失败: 账号可能被风控.")
|
||||
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
|
||||
}
|
||||
return OK(global.MSG{"message_id": mid})
|
||||
log.Infof("发送好友 %v(%v) 的合并转发消息: %v (%v)", userID, userID, limitedString(m.String()), mid)
|
||||
return OK(global.MSG{
|
||||
"message_id": mid,
|
||||
"forward_id": fe.ResId,
|
||||
})
|
||||
}
|
||||
|
||||
// CQSendPrivateMessage 发送私聊消息
|
||||
//
|
||||
// https://git.io/Jtz1l
|
||||
// @route(send_private_msg)
|
||||
// @route11(send_private_msg)
|
||||
// @rename(m->message)
|
||||
func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Result, autoEscape bool) global.MSG {
|
||||
var elem []message.IMessageElement
|
||||
if m.Type == gjson.JSON {
|
||||
elem = bot.ConvertObjectMessage(m, message.SourcePrivate)
|
||||
elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourcePrivate)
|
||||
} else {
|
||||
str := m.String()
|
||||
if str == "" {
|
||||
@ -987,7 +1030,7 @@ func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Resu
|
||||
if autoEscape {
|
||||
elem = []message.IMessageElement{message.NewText(str)}
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, message.SourcePrivate)
|
||||
elem = bot.ConvertStringMessage(onebot.V11, str, message.SourcePrivate)
|
||||
}
|
||||
}
|
||||
mid := bot.SendPrivateMessage(userID, groupID, &message.SendingMessage{Elements: elem})
|
||||
@ -1066,12 +1109,12 @@ func (bot *CQBot) CQSetGroupMemo(groupID int64, msg, img string) global.MSG {
|
||||
if err != nil {
|
||||
return Failed(100, "IMAGE_NOT_FOUND", "图片未找到")
|
||||
}
|
||||
err = bot.Client.AddGroupNoticeWithPic(groupID, msg, data)
|
||||
_, err = bot.Client.AddGroupNoticeWithPic(groupID, msg, data)
|
||||
if err != nil {
|
||||
return Failed(100, "SEND_NOTICE_ERROR", err.Error())
|
||||
}
|
||||
} else {
|
||||
err := bot.Client.AddGroupNoticeSimple(groupID, msg)
|
||||
_, err := bot.Client.AddGroupNoticeSimple(groupID, msg)
|
||||
if err != nil {
|
||||
return Failed(100, "SEND_NOTICE_ERROR", err.Error())
|
||||
}
|
||||
@ -1081,6 +1124,23 @@ func (bot *CQBot) CQSetGroupMemo(groupID int64, msg, img string) global.MSG {
|
||||
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
||||
}
|
||||
|
||||
// CQDelGroupMemo 扩展API-删除群公告
|
||||
// @route(_del_group_notice)
|
||||
// @rename(fid->notice_id)
|
||||
func (bot *CQBot) CQDelGroupMemo(groupID int64, fid string) global.MSG {
|
||||
if g := bot.Client.FindGroup(groupID); g != nil {
|
||||
if g.SelfPermission() == client.Member {
|
||||
return Failed(100, "PERMISSION_DENIED", "权限不足")
|
||||
}
|
||||
err := bot.Client.DelGroupNotice(groupID, fid)
|
||||
if err != nil {
|
||||
return Failed(100, "DELETE_NOTICE_ERROR", err.Error())
|
||||
}
|
||||
return OK(nil)
|
||||
}
|
||||
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
||||
}
|
||||
|
||||
// CQSetGroupKick 群组踢人
|
||||
//
|
||||
// https://git.io/Jtz1V
|
||||
@ -1109,7 +1169,7 @@ func (bot *CQBot) CQSetGroupBan(groupID, userID int64, duration uint32) global.M
|
||||
if m := g.FindMember(userID); m != nil {
|
||||
err := m.Mute(duration)
|
||||
if err != nil {
|
||||
if duration > 2592000 {
|
||||
if duration >= 2592000 {
|
||||
return Failed(100, "DURATION_IS_NOT_IN_RANGE", "非法的禁言时长")
|
||||
}
|
||||
return Failed(100, "NOT_MANAGEABLE", "机器人权限不足")
|
||||
@ -1280,6 +1340,19 @@ func (bot *CQBot) CQSetGroupAdmin(groupID, userID int64, enable bool) global.MSG
|
||||
return OK(nil)
|
||||
}
|
||||
|
||||
// CQSetGroupAnonymous 群组匿名
|
||||
//
|
||||
// https://beautyyu.one
|
||||
// @route(set_group_anonymous)
|
||||
// @default(enable=true)
|
||||
func (bot *CQBot) CQSetGroupAnonymous(groupID int64, enable bool) global.MSG {
|
||||
if g := bot.Client.FindGroup(groupID); g != nil {
|
||||
g.SetAnonymous(enable)
|
||||
return OK(nil)
|
||||
}
|
||||
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
||||
}
|
||||
|
||||
// CQGetGroupHonorInfo 获取群荣誉信息
|
||||
//
|
||||
// https://git.io/Jtz1H
|
||||
@ -1342,7 +1415,8 @@ func (bot *CQBot) CQGetGroupHonorInfo(groupID int64, t string) global.MSG {
|
||||
// CQGetStrangerInfo 获取陌生人信息
|
||||
//
|
||||
// https://git.io/Jtz17
|
||||
// @route(get_stranger_info)
|
||||
// @route11(get_stranger_info)
|
||||
// @route12(get_user_info)
|
||||
func (bot *CQBot) CQGetStrangerInfo(userID int64) global.MSG {
|
||||
info, err := bot.Client.GetSummaryInfo(userID)
|
||||
if err != nil {
|
||||
@ -1361,16 +1435,18 @@ func (bot *CQBot) CQGetStrangerInfo(userID int64) global.MSG {
|
||||
// unknown = 0x2
|
||||
return "unknown"
|
||||
}(),
|
||||
"sign": info.Sign,
|
||||
"age": info.Age,
|
||||
"level": info.Level,
|
||||
"login_days": info.LoginDays,
|
||||
"vip_level": info.VipLevel,
|
||||
})
|
||||
}
|
||||
|
||||
// CQHandleQuickOperation 隐藏API-对事件执行快速操作
|
||||
//
|
||||
// https://git.io/Jtz15
|
||||
// @route(".handle_quick_operation")
|
||||
// @route11(".handle_quick_operation")
|
||||
func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global.MSG {
|
||||
postType := context.Get("post_type").Str
|
||||
|
||||
@ -1487,12 +1563,8 @@ func (bot *CQBot) CQGetImage(file string) global.MSG {
|
||||
}
|
||||
local := path.Join(global.CachePath, file+path.Ext(msg["filename"].(string)))
|
||||
if !global.PathExists(local) {
|
||||
if body, err := global.HTTPGetReadCloser(msg["url"].(string)); err == nil {
|
||||
f, _ := os.OpenFile(local, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o0644)
|
||||
_, _ = f.ReadFrom(body)
|
||||
_ = body.Close()
|
||||
_ = f.Close()
|
||||
} else {
|
||||
r := download.Request{URL: msg["url"].(string)}
|
||||
if err := r.WriteToFile(local); err != nil {
|
||||
log.Warnf("下载图片 %v 时出现错误: %v", msg["url"], err)
|
||||
return Failed(100, "DOWNLOAD_IMAGE_ERROR", err.Error())
|
||||
}
|
||||
@ -1535,7 +1607,8 @@ func (bot *CQBot) CQDownloadFile(url string, headers gjson.Result, threadCount i
|
||||
return Failed(100, "DELETE_FILE_ERROR", err.Error())
|
||||
}
|
||||
}
|
||||
if err := global.DownloadFileMultiThreading(url, file, 0, threadCount, h); err != nil {
|
||||
r := download.Request{URL: url, Header: h}
|
||||
if err := r.WriteToFileMultiThreading(file, threadCount); err != nil {
|
||||
log.Warnf("下载链接 %v 时出现错误: %v", url, err)
|
||||
return Failed(100, "DOWNLOAD_FILE_ERROR", err.Error())
|
||||
}
|
||||
@ -1755,7 +1828,7 @@ func (bot *CQBot) CQGetOnlineClients(noCache bool) global.MSG {
|
||||
// CQCanSendImage 检查是否可以发送图片(此处永远返回true)
|
||||
//
|
||||
// https://git.io/Jtz1N
|
||||
// @route(can_send_image)
|
||||
// @route11(can_send_image)
|
||||
func (bot *CQBot) CQCanSendImage() global.MSG {
|
||||
return OK(global.MSG{"yes": true})
|
||||
}
|
||||
@ -1763,7 +1836,7 @@ func (bot *CQBot) CQCanSendImage() global.MSG {
|
||||
// CQCanSendRecord 检查是否可以发送语音(此处永远返回true)
|
||||
//
|
||||
// https://git.io/Jtz1x
|
||||
// @route(can_send_record)
|
||||
// @route11(can_send_record)
|
||||
func (bot *CQBot) CQCanSendRecord() global.MSG {
|
||||
return OK(global.MSG{"yes": true})
|
||||
}
|
||||
@ -1774,7 +1847,11 @@ func (bot *CQBot) CQCanSendRecord() global.MSG {
|
||||
// @route(ocr_image,".ocr_image")
|
||||
// @rename(image_id->image)
|
||||
func (bot *CQBot) CQOcrImage(imageID string) global.MSG {
|
||||
img, err := bot.makeImageOrVideoElem(map[string]string{"file": imageID}, false, message.SourceGroup)
|
||||
// TODO: fix this
|
||||
var elem msg.Element
|
||||
elem.Type = "image"
|
||||
elem.Data = []msg.Pair{{K: "file", V: imageID}}
|
||||
img, err := bot.makeImageOrVideoElem(elem, false, message.SourceGroup)
|
||||
if err != nil {
|
||||
log.Warnf("load image error: %v", err)
|
||||
return Failed(100, "LOAD_FILE_ERROR", err.Error())
|
||||
@ -1831,7 +1908,8 @@ func (bot *CQBot) CQSetGroupAnonymousBan(groupID int64, flag string, duration in
|
||||
//
|
||||
// https://git.io/JtzMe
|
||||
// @route(get_status)
|
||||
func (bot *CQBot) CQGetStatus() global.MSG {
|
||||
func (bot *CQBot) CQGetStatus(spec *onebot.Spec) global.MSG {
|
||||
if spec.Version == 11 {
|
||||
return OK(global.MSG{
|
||||
"app_initialized": true,
|
||||
"app_enabled": true,
|
||||
@ -1842,6 +1920,12 @@ func (bot *CQBot) CQGetStatus() global.MSG {
|
||||
"stat": bot.Client.GetStatistics(),
|
||||
})
|
||||
}
|
||||
return OK(global.MSG{
|
||||
"online": bot.Client.Online.Load(),
|
||||
"good": bot.Client.Online.Load(),
|
||||
"stat": bot.Client.GetStatistics(),
|
||||
})
|
||||
}
|
||||
|
||||
// CQSetEssenceMessage 扩展API-设置精华消息
|
||||
//
|
||||
@ -1917,7 +2001,7 @@ func (bot *CQBot) CQCheckURLSafely(url string) global.MSG {
|
||||
// CQGetVersionInfo 获取版本信息
|
||||
//
|
||||
// https://git.io/JtwUs
|
||||
// @route(get_version_info)
|
||||
// @route11(get_version_info)
|
||||
func (bot *CQBot) CQGetVersionInfo() global.MSG {
|
||||
wd, _ := os.Getwd()
|
||||
return OK(global.MSG{
|
||||
@ -1934,22 +2018,7 @@ func (bot *CQBot) CQGetVersionInfo() global.MSG {
|
||||
"runtime_version": runtime.Version(),
|
||||
"runtime_os": runtime.GOOS,
|
||||
"version": base.Version,
|
||||
"protocol": func() int {
|
||||
switch client.SystemDeviceInfo.Protocol {
|
||||
case client.Unset, client.IPad:
|
||||
return 0
|
||||
case client.AndroidPhone:
|
||||
return 1
|
||||
case client.AndroidWatch:
|
||||
return 2
|
||||
case client.MacOS:
|
||||
return 3
|
||||
case client.QiDian:
|
||||
return 4
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}(),
|
||||
"protocol_name": bot.Client.Device().Protocol,
|
||||
})
|
||||
}
|
||||
|
||||
@ -2042,9 +2111,16 @@ func (bot *CQBot) CQReloadEventFilter(file string) global.MSG {
|
||||
return OK(nil)
|
||||
}
|
||||
|
||||
// CQGetSupportedActions 获取支持的动作列表
|
||||
//
|
||||
// @route(get_supported_actions)
|
||||
func (bot *CQBot) CQGetSupportedActions(spec *onebot.Spec) global.MSG {
|
||||
return OK(spec.SupportedActions)
|
||||
}
|
||||
|
||||
// OK 生成成功返回值
|
||||
func OK(data interface{}) global.MSG {
|
||||
return global.MSG{"data": data, "retcode": 0, "status": "ok"}
|
||||
func OK(data any) global.MSG {
|
||||
return global.MSG{"data": data, "retcode": 0, "status": "ok", "message": ""}
|
||||
}
|
||||
|
||||
// Failed 生成失败返回值
|
||||
@ -2056,7 +2132,7 @@ func Failed(code int, msg ...string) global.MSG {
|
||||
if len(msg) > 1 {
|
||||
w = msg[1]
|
||||
}
|
||||
return global.MSG{"data": nil, "retcode": code, "msg": m, "wording": w, "status": "failed"}
|
||||
return global.MSG{"data": nil, "retcode": code, "msg": m, "wording": w, "message": w, "status": "failed"}
|
||||
}
|
||||
|
||||
func limitedString(str string) string {
|
||||
|
34
coolq/api_v12.go
Normal file
34
coolq/api_v12.go
Normal file
@ -0,0 +1,34 @@
|
||||
package coolq
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// TODO: implement
|
||||
return OK(nil)
|
||||
}
|
61
coolq/bot.go
61
coolq/bot.go
@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
@ -19,10 +20,14 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/segmentio/asm/base64"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/image/webp"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CQBot CQBot结构体,存储Bot实例相关配置
|
||||
@ -111,7 +116,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,7 +148,7 @@ func (w *worker) wait() {
|
||||
}
|
||||
|
||||
// uploadLocalImage 上传本地图片
|
||||
func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement) (message.IMessageElement, error) {
|
||||
func (bot *CQBot) uploadLocalImage(target message.Source, img *msg.LocalImage) (message.IMessageElement, error) {
|
||||
if img.File != "" {
|
||||
f, err := os.Open(img.File)
|
||||
if err != nil {
|
||||
@ -152,11 +157,23 @@ func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement
|
||||
defer func() { _ = f.Close() }()
|
||||
img.Stream = f
|
||||
}
|
||||
if lawful, mime := base.IsLawfulImage(img.Stream); !lawful {
|
||||
return nil, errors.New("image type error: " + mime)
|
||||
mt, ok := mime.CheckImage(img.Stream)
|
||||
if !ok {
|
||||
return nil, errors.New("image type error: " + mt)
|
||||
}
|
||||
// todo: enable multi-thread upload, now got error code 81
|
||||
i, err := bot.Client.UploadImage(target, img.Stream, 4)
|
||||
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())
|
||||
}
|
||||
i, err := bot.Client.UploadImage(target, img.Stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -171,20 +188,20 @@ func (bot *CQBot) uploadLocalImage(target message.Source, img *LocalImageElement
|
||||
}
|
||||
|
||||
// uploadLocalVideo 上传本地短视频至群聊
|
||||
func (bot *CQBot) uploadLocalVideo(target message.Source, v *LocalVideoElement) (*message.ShortVideoElement, error) {
|
||||
func (bot *CQBot) uploadLocalVideo(target message.Source, v *msg.LocalVideo) (*message.ShortVideoElement, error) {
|
||||
video, err := os.Open(v.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = video.Close() }()
|
||||
return bot.Client.UploadShortVideo(target, video, v.thumb, 4)
|
||||
return bot.Client.UploadShortVideo(target, video, v.Thumb)
|
||||
}
|
||||
|
||||
func removeLocalElement(elements []message.IMessageElement) []message.IMessageElement {
|
||||
var j int
|
||||
for i, e := range elements {
|
||||
switch e.(type) {
|
||||
case *LocalImageElement, *LocalVideoElement:
|
||||
case *msg.LocalImage, *msg.LocalVideo:
|
||||
case *message.VoiceElement: // 未上传的语音消息, 也删除
|
||||
case nil:
|
||||
default:
|
||||
@ -214,7 +231,7 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
|
||||
for i, m := range elements {
|
||||
p := &elements[i]
|
||||
switch e := m.(type) {
|
||||
case *LocalImageElement:
|
||||
case *msg.LocalImage:
|
||||
w.do(func() {
|
||||
m, err := bot.uploadLocalImage(target, e)
|
||||
if err != nil {
|
||||
@ -232,7 +249,7 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
|
||||
*p = m
|
||||
}
|
||||
})
|
||||
case *LocalVideoElement:
|
||||
case *msg.LocalVideo:
|
||||
w.do(func() {
|
||||
m, err := bot.uploadLocalVideo(target, e)
|
||||
if err != nil {
|
||||
@ -248,7 +265,7 @@ func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessage
|
||||
}
|
||||
|
||||
// SendGroupMessage 发送群消息
|
||||
func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) int32 {
|
||||
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{
|
||||
@ -258,20 +275,20 @@ func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) int
|
||||
m.Elements = bot.uploadMedia(source, m.Elements)
|
||||
for _, e := range m.Elements {
|
||||
switch i := e.(type) {
|
||||
case *PokeElement:
|
||||
case *msg.Poke:
|
||||
if group != nil {
|
||||
if mem := group.FindMember(i.Target); mem != nil {
|
||||
mem.Poke()
|
||||
}
|
||||
}
|
||||
return 0
|
||||
return 0, nil
|
||||
case *message.MusicShareElement:
|
||||
ret, err := bot.Client.SendGroupMusicShare(groupID, i)
|
||||
if err != nil {
|
||||
log.Warnf("警告: 群 %v 富文本消息发送失败: %v", groupID, err)
|
||||
return -1
|
||||
return -1, errors.Wrap(err, "send group music share error")
|
||||
}
|
||||
return bot.InsertGroupMessage(ret)
|
||||
return bot.InsertGroupMessage(ret), nil
|
||||
case *message.AtElement:
|
||||
if i.Target == 0 && group.SelfPermission() == client.Member {
|
||||
e = message.NewText("@全体成员")
|
||||
@ -281,16 +298,16 @@ func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) int
|
||||
}
|
||||
if len(newElem) == 0 {
|
||||
log.Warnf("群消息发送失败: 消息为空.")
|
||||
return -1
|
||||
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("群消息发送失败: 账号可能被风控.")
|
||||
return -1
|
||||
return -1, errors.New("send group message failed: blocked by server")
|
||||
}
|
||||
return bot.InsertGroupMessage(ret)
|
||||
return bot.InsertGroupMessage(ret), nil
|
||||
}
|
||||
|
||||
// SendPrivateMessage 发送私聊消息
|
||||
@ -303,7 +320,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
|
||||
m.Elements = bot.uploadMedia(source, m.Elements)
|
||||
for _, e := range m.Elements {
|
||||
switch i := e.(type) {
|
||||
case *PokeElement:
|
||||
case *msg.Poke:
|
||||
bot.Client.SendFriendPoke(i.Target)
|
||||
return 0
|
||||
case *message.MusicShareElement:
|
||||
@ -405,7 +422,7 @@ func (bot *CQBot) SendGuildChannelMessage(guildID, channelID uint64, m *message.
|
||||
bot.Client.SendGuildMusicShare(guildID, channelID, i)
|
||||
return "-1" // todo: fix this
|
||||
|
||||
case *message.VoiceElement, *PokeElement:
|
||||
case *message.VoiceElement, *msg.Poke:
|
||||
log.Warnf("警告: 频道暂不支持发送 %v 消息", i.Type().String())
|
||||
continue
|
||||
}
|
||||
@ -595,10 +612,10 @@ func (bot *CQBot) dispatch(ev *event) {
|
||||
for _, f := range bot.events {
|
||||
go func(fn func(*Event)) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
if pan := recover(); pan != nil {
|
||||
log.Warnf("处理事件 %v 时出现错误: %v \n%s", event.JSONString(), pan, debug.Stack())
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
start := time.Now()
|
||||
|
@ -4,10 +4,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/topic"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/client"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/Mrs4s/MiraiGo/topic"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
@ -75,7 +74,6 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
|
||||
"font": 0,
|
||||
"group_id": m.GroupCode,
|
||||
"message": ToFormattedMessage(m.Elements, source),
|
||||
"message_type": "group",
|
||||
"message_seq": m.Id,
|
||||
"raw_message": cqm,
|
||||
"sender": global.MSG{
|
||||
|
960
coolq/cqcode.go
960
coolq/cqcode.go
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@
|
||||
package cqcode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
)
|
||||
|
||||
// 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(strconv.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)
|
||||
}
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -18,10 +17,11 @@ import (
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/cache"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
)
|
||||
|
||||
// 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" {
|
||||
@ -61,7 +61,11 @@ func (ev *event) MarshalJSON() ([]byte, error) {
|
||||
fmt.Fprintf(buf, `,"sub_type":"%s"`, ev.SubType)
|
||||
}
|
||||
for k, v := range ev.Others {
|
||||
v, _ := json.Marshal(v)
|
||||
v, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.Warnf("marshal message payload error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
fmt.Fprintf(buf, `,"%s":%s`, k, v)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
@ -140,7 +144,10 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
|
||||
PrimaryID: e.Session.Sender,
|
||||
}
|
||||
cqm := toStringMessage(m.Elements, source)
|
||||
if base.AllowTempSession {
|
||||
bot.tempSessionCache.Store(m.Sender.Uin, e.Session)
|
||||
}
|
||||
|
||||
id := m.Id
|
||||
// todo(Mrs4s)
|
||||
// if bot.db != nil { // nolint
|
||||
@ -541,6 +548,7 @@ func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRe
|
||||
bot.dispatchEvent("request/group/invite", global.MSG{
|
||||
"group_id": e.GroupCode,
|
||||
"user_id": e.InvitorUin,
|
||||
"invitor_id": 0,
|
||||
"comment": "",
|
||||
"flag": flag,
|
||||
})
|
||||
@ -552,6 +560,7 @@ func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupR
|
||||
bot.dispatchEvent("request/group/add", global.MSG{
|
||||
"group_id": e.GroupCode,
|
||||
"user_id": e.RequesterUin,
|
||||
"invitor_id": e.ActionUin,
|
||||
"comment": e.Message,
|
||||
"flag": flag,
|
||||
})
|
||||
@ -666,7 +675,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) {
|
||||
filename := hex.EncodeToString(i.Md5) + ".image"
|
||||
cache.Image.Insert(i.Md5, data)
|
||||
if i.Url != "" && !global.PathExists(path.Join(global.ImagePath, "guild-images", filename)) {
|
||||
if err := global.DownloadFile(i.Url, path.Join(global.ImagePath, "guild-images", filename), -1, nil); err != nil {
|
||||
r := download.Request{URL: i.Url}
|
||||
if err := r.WriteToFile(path.Join(global.ImagePath, "guild-images", filename)); err != nil {
|
||||
log.Warnf("下载频道图片时出现错误: %v", err)
|
||||
}
|
||||
}
|
||||
@ -684,12 +694,11 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) {
|
||||
i.Name = strings.ReplaceAll(i.Name, "{", "")
|
||||
i.Name = strings.ReplaceAll(i.Name, "}", "")
|
||||
if !global.PathExists(path.Join(global.VoicePath, i.Name)) {
|
||||
b, err := global.GetBytes(i.Url)
|
||||
err := download.Request{URL: i.Url}.WriteToFile(path.Join(global.VoicePath, i.Name))
|
||||
if err != nil {
|
||||
log.Warnf("语音文件 %v 下载失败: %v", i.Name, err)
|
||||
continue
|
||||
}
|
||||
_ = os.WriteFile(path.Join(global.VoicePath, i.Name), b, 0o644)
|
||||
}
|
||||
case *message.ShortVideoElement:
|
||||
data := binary.NewWriterF(func(w *binary.Writer) {
|
||||
|
@ -40,61 +40,61 @@ type (
|
||||
|
||||
// StoredGroupMessage 持久化群消息
|
||||
StoredGroupMessage struct {
|
||||
ID string `bson:"_id"`
|
||||
GlobalID int32 `bson:"globalId"`
|
||||
Attribute *StoredMessageAttribute `bson:"attribute"`
|
||||
SubType string `bson:"subType"`
|
||||
QuotedInfo *QuotedInfo `bson:"quotedInfo"`
|
||||
GroupCode int64 `bson:"groupCode"`
|
||||
AnonymousID string `bson:"anonymousId"`
|
||||
Content []global.MSG `bson:"content"`
|
||||
ID string `bson:"_id" yaml:"-"`
|
||||
GlobalID int32 `bson:"globalId" yaml:"-"`
|
||||
Attribute *StoredMessageAttribute `bson:"attribute" yaml:"-"`
|
||||
SubType string `bson:"subType" yaml:"-"`
|
||||
QuotedInfo *QuotedInfo `bson:"quotedInfo" yaml:"-"`
|
||||
GroupCode int64 `bson:"groupCode" yaml:"-"`
|
||||
AnonymousID string `bson:"anonymousId" yaml:"-"`
|
||||
Content []global.MSG `bson:"content" yaml:"content"`
|
||||
}
|
||||
|
||||
// StoredPrivateMessage 持久化私聊消息
|
||||
StoredPrivateMessage struct {
|
||||
ID string `bson:"_id"`
|
||||
GlobalID int32 `bson:"globalId"`
|
||||
Attribute *StoredMessageAttribute `bson:"attribute"`
|
||||
SubType string `bson:"subType"`
|
||||
QuotedInfo *QuotedInfo `bson:"quotedInfo"`
|
||||
SessionUin int64 `bson:"sessionUin"`
|
||||
TargetUin int64 `bson:"targetUin"`
|
||||
Content []global.MSG `bson:"content"`
|
||||
ID string `bson:"_id" yaml:"-"`
|
||||
GlobalID int32 `bson:"globalId" yaml:"-"`
|
||||
Attribute *StoredMessageAttribute `bson:"attribute" yaml:"-"`
|
||||
SubType string `bson:"subType" yaml:"-"`
|
||||
QuotedInfo *QuotedInfo `bson:"quotedInfo" yaml:"-"`
|
||||
SessionUin int64 `bson:"sessionUin" yaml:"-"`
|
||||
TargetUin int64 `bson:"targetUin" yaml:"-"`
|
||||
Content []global.MSG `bson:"content" yaml:"content"`
|
||||
}
|
||||
|
||||
// StoredGuildChannelMessage 持久化频道消息
|
||||
StoredGuildChannelMessage struct {
|
||||
ID string `bson:"_id"`
|
||||
Attribute *StoredGuildMessageAttribute `bson:"attribute"`
|
||||
GuildID uint64 `bson:"guildId"`
|
||||
ChannelID uint64 `bson:"channelId"`
|
||||
QuotedInfo *QuotedInfo `bson:"quotedInfo"`
|
||||
Content []global.MSG `bson:"content"`
|
||||
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"`
|
||||
InternalID int32 `bson:"internalId"`
|
||||
SenderUin int64 `bson:"senderUin"`
|
||||
SenderName string `bson:"senderName"`
|
||||
Timestamp int64 `bson:"timestamp"`
|
||||
MessageSeq int32 `bson:"messageSeq" 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"`
|
||||
InternalID uint64 `bson:"internalId"`
|
||||
SenderTinyID uint64 `bson:"senderTinyId"`
|
||||
SenderName string `bson:"senderName"`
|
||||
Timestamp int64 `bson:"timestamp"`
|
||||
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"`
|
||||
PrevGlobalID int32 `bson:"prevGlobalId"`
|
||||
QuotedContent []global.MSG `bson:"quotedContent"`
|
||||
PrevID string `bson:"prevId" yaml:"-"`
|
||||
PrevGlobalID int32 `bson:"prevGlobalId" yaml:"-"`
|
||||
QuotedContent []global.MSG `bson:"quotedContent" yaml:"quoted_content"`
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -84,7 +84,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
|
||||
|
@ -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()
|
||||
|
84
db/sqlite3/model.go
Normal file
84
db/sqlite3/model.go
Normal file
@ -0,0 +1,84 @@
|
||||
package sqlite3
|
||||
|
||||
const (
|
||||
Sqlite3GroupMessageTableName = "grpmsg"
|
||||
Sqlite3MessageAttributeTableName = "msgattr"
|
||||
Sqlite3GuildMessageAttributeTableName = "gmsgattr"
|
||||
Sqlite3QuotedInfoTableName = "quoinf"
|
||||
Sqlite3PrivateMessageTableName = "privmsg"
|
||||
Sqlite3GuildChannelMessageTableName = "guildmsg"
|
||||
Sqlite3UinInfoTableName = "uininf"
|
||||
Sqlite3TinyInfoTableName = "tinyinf"
|
||||
)
|
||||
|
||||
// StoredMessageAttribute 持久化消息属性
|
||||
type StoredMessageAttribute struct {
|
||||
ID int64 // ID is the crc64 of 字段s below
|
||||
MessageSeq int32
|
||||
InternalID int32
|
||||
SenderUin int64 // SenderUin is fk to UinInfo
|
||||
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
|
||||
PrevID string
|
||||
PrevGlobalID int32
|
||||
QuotedContent string // QuotedContent is json of original content
|
||||
}
|
||||
|
||||
// UinInfo QQ 与 昵称
|
||||
type UinInfo struct {
|
||||
Uin int64
|
||||
Name string
|
||||
}
|
||||
|
||||
// TinyInfo Tiny 与 昵称
|
||||
type TinyInfo struct {
|
||||
ID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
// StoredGroupMessage 持久化群消息
|
||||
type StoredGroupMessage struct {
|
||||
GlobalID int32
|
||||
ID string
|
||||
AttributeID int64
|
||||
SubType string
|
||||
QuotedInfoID int64
|
||||
GroupCode int64
|
||||
AnonymousID string
|
||||
Content string // Content is json of original content
|
||||
}
|
||||
|
||||
// StoredPrivateMessage 持久化私聊消息
|
||||
type StoredPrivateMessage struct {
|
||||
GlobalID int32
|
||||
ID string
|
||||
AttributeID int64
|
||||
SubType string
|
||||
QuotedInfoID int64
|
||||
SessionUin int64
|
||||
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
|
||||
}
|
538
db/sqlite3/sqlite3.go
Normal file
538
db/sqlite3/sqlite3.go
Normal file
@ -0,0 +1,538 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"hash/crc64"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
sql "github.com/FloatTech/sqlite"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/db"
|
||||
)
|
||||
|
||||
type database struct {
|
||||
sync.RWMutex
|
||||
db *sql.Sqlite
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// config mongodb 相关配置
|
||||
type config struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
CacheTTL string `yaml:"cachettl"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
sql.DriverName = "sqlite"
|
||||
db.Register("sqlite3", func(node yaml.Node) db.Database {
|
||||
conf := new(config)
|
||||
_ = node.Decode(conf)
|
||||
if !conf.Enable {
|
||||
return nil
|
||||
}
|
||||
duration, err := time.ParseDuration(conf.CacheTTL)
|
||||
if err != nil {
|
||||
log.Fatalf("illegal ttl config: %v", err)
|
||||
}
|
||||
return &database{db: new(sql.Sqlite), ttl: duration}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *database) Open() error {
|
||||
s.db.DBPath = path.Join("data", "sqlite3")
|
||||
_ = os.MkdirAll(s.db.DBPath, 0755)
|
||||
s.db.DBPath += "/msg.db"
|
||||
err := s.db.Open(s.ttl)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "open sqlite3 error")
|
||||
}
|
||||
_, err = s.db.DB.Exec("PRAGMA foreign_keys = ON;")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "enable foreign_keys error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3UinInfoTableName, &UinInfo{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
err = s.db.Insert(Sqlite3UinInfoTableName, &UinInfo{Name: "null"})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert into sqlite3 table "+Sqlite3UinInfoTableName+" error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3TinyInfoTableName, &TinyInfo{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
err = s.db.Insert(Sqlite3TinyInfoTableName, &TinyInfo{Name: "null"})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert into sqlite3 table "+Sqlite3TinyInfoTableName+" error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3MessageAttributeTableName, &StoredMessageAttribute{},
|
||||
"FOREIGN KEY(SenderUin) REFERENCES "+Sqlite3UinInfoTableName+"(Uin)",
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
err = s.db.Insert(Sqlite3MessageAttributeTableName, &StoredMessageAttribute{})
|
||||
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")
|
||||
}
|
||||
err = s.db.Insert(Sqlite3QuotedInfoTableName, &QuotedInfo{QuotedContent: "null"})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert into sqlite3 table "+Sqlite3QuotedInfoTableName+" error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3GroupMessageTableName, &StoredGroupMessage{},
|
||||
"FOREIGN KEY(AttributeID) REFERENCES "+Sqlite3MessageAttributeTableName+"(ID)",
|
||||
"FOREIGN KEY(QuotedInfoID) REFERENCES "+Sqlite3QuotedInfoTableName+"(ID)",
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create sqlite3 table error")
|
||||
}
|
||||
err = s.db.Create(Sqlite3PrivateMessageTableName, &StoredPrivateMessage{},
|
||||
"FOREIGN KEY(AttributeID) REFERENCES "+Sqlite3MessageAttributeTableName+"(ID)",
|
||||
"FOREIGN KEY(QuotedInfoID) REFERENCES "+Sqlite3QuotedInfoTableName+"(ID)",
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
func (s *database) GetMessageByGlobalID(id int32) (db.StoredMessage, error) {
|
||||
if r, err := s.GetGroupMessageByGlobalID(id); err == nil {
|
||||
return r, nil
|
||||
}
|
||||
return s.GetPrivateMessageByGlobalID(id)
|
||||
}
|
||||
|
||||
func (s *database) GetGroupMessageByGlobalID(id int32) (*db.StoredGroupMessage, error) {
|
||||
var ret db.StoredGroupMessage
|
||||
var grpmsg StoredGroupMessage
|
||||
s.RLock()
|
||||
err := s.db.Find(Sqlite3GroupMessageTableName, &grpmsg, "WHERE GlobalID="+strconv.Itoa(int(id)))
|
||||
s.RUnlock()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "query error")
|
||||
}
|
||||
ret.ID = grpmsg.ID
|
||||
ret.GlobalID = grpmsg.GlobalID
|
||||
ret.SubType = grpmsg.SubType
|
||||
ret.GroupCode = grpmsg.GroupCode
|
||||
ret.AnonymousID = grpmsg.AnonymousID
|
||||
_ = yaml.Unmarshal(utils.S2B(grpmsg.Content), &ret)
|
||||
if grpmsg.AttributeID != 0 {
|
||||
var attr StoredMessageAttribute
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3MessageAttributeTableName, &attr, "WHERE ID="+strconv.FormatInt(grpmsg.AttributeID, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
var uin UinInfo
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3UinInfoTableName, &uin, "WHERE Uin="+strconv.FormatInt(attr.SenderUin, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
ret.Attribute = &db.StoredMessageAttribute{
|
||||
MessageSeq: attr.MessageSeq,
|
||||
InternalID: attr.InternalID,
|
||||
SenderUin: attr.SenderUin,
|
||||
SenderName: uin.Name,
|
||||
Timestamp: attr.Timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if grpmsg.QuotedInfoID != 0 {
|
||||
var quoinf QuotedInfo
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3QuotedInfoTableName, &quoinf, "WHERE ID="+strconv.FormatInt(grpmsg.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) GetPrivateMessageByGlobalID(id int32) (*db.StoredPrivateMessage, error) {
|
||||
var ret db.StoredPrivateMessage
|
||||
var privmsg StoredPrivateMessage
|
||||
s.RLock()
|
||||
err := s.db.Find(Sqlite3PrivateMessageTableName, &privmsg, "WHERE GlobalID="+strconv.Itoa(int(id)))
|
||||
s.RUnlock()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "query error")
|
||||
}
|
||||
ret.ID = privmsg.ID
|
||||
ret.GlobalID = privmsg.GlobalID
|
||||
ret.SubType = privmsg.SubType
|
||||
ret.SessionUin = privmsg.SessionUin
|
||||
ret.TargetUin = privmsg.TargetUin
|
||||
_ = yaml.Unmarshal(utils.S2B(privmsg.Content), &ret)
|
||||
if privmsg.AttributeID != 0 {
|
||||
var attr StoredMessageAttribute
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3MessageAttributeTableName, &attr, "WHERE ID="+strconv.FormatInt(privmsg.AttributeID, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
var uin UinInfo
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3UinInfoTableName, &uin, "WHERE Uin="+strconv.FormatInt(attr.SenderUin, 10))
|
||||
s.RUnlock()
|
||||
if err == nil {
|
||||
ret.Attribute = &db.StoredMessageAttribute{
|
||||
MessageSeq: attr.MessageSeq,
|
||||
InternalID: attr.InternalID,
|
||||
SenderUin: attr.SenderUin,
|
||||
SenderName: uin.Name,
|
||||
Timestamp: attr.Timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if privmsg.QuotedInfoID != 0 {
|
||||
var quoinf QuotedInfo
|
||||
s.RLock()
|
||||
err = s.db.Find(Sqlite3QuotedInfoTableName, &quoinf, "WHERE ID="+strconv.FormatInt(privmsg.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) 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,
|
||||
ID: msg.ID,
|
||||
SubType: msg.SubType,
|
||||
GroupCode: msg.GroupCode,
|
||||
AnonymousID: msg.AnonymousID,
|
||||
}
|
||||
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(utils.S2B(msg.Attribute.SenderName))
|
||||
id := int64(h.Sum64())
|
||||
if id == 0 {
|
||||
id++
|
||||
}
|
||||
s.Lock()
|
||||
err := s.db.Insert(Sqlite3UinInfoTableName, &UinInfo{
|
||||
Uin: msg.Attribute.SenderUin,
|
||||
Name: msg.Attribute.SenderName,
|
||||
})
|
||||
if err == nil {
|
||||
err = s.db.Insert(Sqlite3MessageAttributeTableName, &StoredMessageAttribute{
|
||||
ID: id,
|
||||
MessageSeq: msg.Attribute.MessageSeq,
|
||||
InternalID: msg.Attribute.InternalID,
|
||||
SenderUin: msg.Attribute.SenderUin,
|
||||
Timestamp: msg.Attribute.Timestamp,
|
||||
})
|
||||
}
|
||||
s.Unlock()
|
||||
if err == nil {
|
||||
grpmsg.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 {
|
||||
grpmsg.QuotedInfoID = id
|
||||
}
|
||||
}
|
||||
content, err := yaml.Marshal(&msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert marshal Content error")
|
||||
}
|
||||
grpmsg.Content = utils.B2S(content)
|
||||
s.Lock()
|
||||
err = s.db.Insert(Sqlite3GroupMessageTableName, grpmsg)
|
||||
s.Unlock()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *database) InsertPrivateMessage(msg *db.StoredPrivateMessage) error {
|
||||
privmsg := &StoredPrivateMessage{
|
||||
GlobalID: msg.GlobalID,
|
||||
ID: msg.ID,
|
||||
SubType: msg.SubType,
|
||||
SessionUin: msg.SessionUin,
|
||||
TargetUin: msg.TargetUin,
|
||||
}
|
||||
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(utils.S2B(msg.Attribute.SenderName))
|
||||
id := int64(h.Sum64())
|
||||
if id == 0 {
|
||||
id++
|
||||
}
|
||||
s.Lock()
|
||||
err := s.db.Insert(Sqlite3UinInfoTableName, &UinInfo{
|
||||
Uin: msg.Attribute.SenderUin,
|
||||
Name: msg.Attribute.SenderName,
|
||||
})
|
||||
if err == nil {
|
||||
err = s.db.Insert(Sqlite3MessageAttributeTableName, &StoredMessageAttribute{
|
||||
ID: id,
|
||||
MessageSeq: msg.Attribute.MessageSeq,
|
||||
InternalID: msg.Attribute.InternalID,
|
||||
SenderUin: msg.Attribute.SenderUin,
|
||||
Timestamp: msg.Attribute.Timestamp,
|
||||
})
|
||||
}
|
||||
s.Unlock()
|
||||
if err == nil {
|
||||
privmsg.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 {
|
||||
privmsg.QuotedInfoID = id
|
||||
}
|
||||
}
|
||||
content, err := yaml.Marshal(&msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert marshal Content error")
|
||||
}
|
||||
privmsg.Content = utils.B2S(content)
|
||||
s.Lock()
|
||||
err = s.db.Insert(Sqlite3PrivateMessageTableName, privmsg)
|
||||
s.Unlock()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "insert 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
|
||||
}
|
20
docker-entrypoint.sh
Normal file
20
docker-entrypoint.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
USER=abc
|
||||
|
||||
echo "---Setup Timezone to ${TZ}---"
|
||||
echo "${TZ}" > /etc/timezone
|
||||
echo "---Checking if UID: ${UID} matches user---"
|
||||
usermod -o -u ${UID} ${USER}
|
||||
echo "---Checking if GID: ${GID} matches user---"
|
||||
groupmod -o -g ${GID} ${USER} > /dev/null 2>&1 ||:
|
||||
usermod -g ${GID} ${USER}
|
||||
echo "---Setting umask to ${UMASK}---"
|
||||
umask ${UMASK}
|
||||
|
||||
echo "---Taking ownership of data...---"
|
||||
chown -R ${UID}:${GID} /app /data
|
||||
chmod +x /app/cqhttp
|
||||
|
||||
echo "Starting..."
|
||||
su-exec ${USER} /app/cqhttp
|
@ -5,6 +5,8 @@
|
||||
|
||||
注意: 与客户端建立连接的握手事件**不会**经过事件过滤器
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足.
|
||||
|
||||
## 示例
|
||||
|
||||
这节首先给出一些示例,演示过滤器的基本用法,下一节将给出具体语法说明。
|
||||
@ -163,9 +165,9 @@
|
||||
|
||||
## 过滤时的事件数据对象
|
||||
|
||||
过滤器在go-cqhttp构建好事件数据后运行,各事件的数据字段见[OneBot标准]( https://github.com/howmanybots/onebot/blob/master/v11/specs/event/README.md )。
|
||||
过滤器在go-cqhttp构建好事件数据后运行,各事件的数据字段见[OneBot标准]( https://github.com/botuniverse/onebot-11/blob/master/event/README.md )。
|
||||
|
||||
这里有几点需要注意:
|
||||
|
||||
- `message` 字段在运行过滤器时和上报信息类型相同(见 [消息格式]( https://github.com/howmanybots/onebot/blob/master/v11/specs/message/array.md ))
|
||||
- `message` 字段在运行过滤器时和上报信息类型相同(见 [消息格式]( https://github.com/botuniverse/onebot-11/blob/master/message/array.md ))
|
||||
- `raw_message` 字段为未经**CQ码**处理的原始消息字符串,这意味着其中可能会出现形如 `[CQ:face,id=123]` 的 CQ 码
|
||||
|
@ -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` 为虚拟设备信息.
|
||||
|
||||
## 配置信息
|
||||
@ -84,9 +86,8 @@ servers:
|
||||
# HTTP 通信设置
|
||||
- http:
|
||||
# 服务端监听地址
|
||||
host: 127.0.0.1
|
||||
# 服务端监听端口
|
||||
port: 5700
|
||||
# 如需指定监听ipv4, 可使用 `address: tcp4://0.0.0.0:5700` (ipv6同理)
|
||||
address: 0.0.0.0:5700
|
||||
# 反向HTTP超时时间, 单位秒
|
||||
# 最小值为5,小于5将会忽略本项设置
|
||||
timeout: 5
|
||||
@ -102,9 +103,8 @@ servers:
|
||||
# 正向WS设置
|
||||
- ws:
|
||||
# 正向WS服务器监听地址
|
||||
host: 127.0.0.1
|
||||
# 正向WS服务器监听端口
|
||||
port: 6700
|
||||
# 如需指定监听ipv4, 可使用 `address: tcp4://0.0.0.0:6700` (ipv6同理)
|
||||
address: 0.0.0.0:6700
|
||||
middlewares:
|
||||
<<: *default # 引用默认中间件
|
||||
|
||||
|
112
docs/cqhttp.md
112
docs/cqhttp.md
@ -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>
|
||||
@ -42,6 +44,8 @@
|
||||
- [设置群名](#设置群名)
|
||||
- [获取用户VIP信息](#获取用户vip信息)
|
||||
- [发送群公告](#发送群公告)
|
||||
- [获取群公告](#获取群公告)
|
||||
- [删除群公告](#删除群公告)
|
||||
- [设置精华消息](#设置精华消息)
|
||||
- [移出精华消息](#移出精华消息)
|
||||
- [获取精华消息列表](#获取精华消息列表)
|
||||
@ -244,7 +248,8 @@ Type: `node`
|
||||
| `seq` | message | 具体消息 | 用于自定义消息 |
|
||||
|
||||
特殊说明: **需要使用单独的API `/send_group_forward_msg` 发送,并且由于消息段较为复杂,仅支持Array形式入参。 如果引用消息和自定义消息同时出现,实际查看顺序将取消息段顺序.
|
||||
另外按 [Onebot v11](https://github.com/botuniverse/onebot-11/blob/master/message/array.md) 文档说明, `data` 应全为字符串, 但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃**
|
||||
另外按 [Onebot v11](https://github.com/botuniverse/onebot-11/blob/master/message/array.md) 文档说明, `data` 应全为字符串,
|
||||
但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃**
|
||||
|
||||
示例:
|
||||
|
||||
@ -491,6 +496,18 @@ Type: `tts`
|
||||
|
||||
示例: `[CQ:tts,text=这是一条测试消息]`
|
||||
|
||||
### 猜拳消息
|
||||
|
||||
Type: `rps`
|
||||
|
||||
参数:
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|---------|-----|------------------|
|
||||
| `value` | int | 0:石头, 1:剪刀, 2:布 |
|
||||
|
||||
示例: `[CQ:rps,value=0]`
|
||||
|
||||
## API
|
||||
|
||||
### 设置群名
|
||||
@ -613,15 +630,16 @@ Type: `tts`
|
||||
}
|
||||
````
|
||||
|
||||
### 发送合并转发(群)
|
||||
### 发送合并转发(群/私聊)
|
||||
|
||||
终结点: `/send_group_forward_msg`
|
||||
终结点: `/send_group_forward_msg`, `send_private_forward_msg`, `send_forward_msg`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------- | -------------- | ---------------------------- |
|
||||
|------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `group_id` | int64 | 群号 |
|
||||
| `user_id` | int64 | 私聊QQ号 |
|
||||
| `messages` | forward node[] | 自定义转发消息, 具体看 [CQCode](https://github.com/Mrs4s/go-cqhttp/blob/master/docs/cqhttp.md#%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF%E8%8A%82%E7%82%B9) |
|
||||
|
||||
响应数据
|
||||
@ -883,6 +901,36 @@ Type: `tts`
|
||||
> 在不提供 `folder` 参数的情况下默认上传到根目录
|
||||
> 只能上传本地文件, 需要上传 `http` 文件的话请先调用 `download_file` API下载
|
||||
|
||||
### 上传私聊文件
|
||||
|
||||
终结点: `/upload_private_file`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|-----------|--------|--------|
|
||||
| `user_id` | int64 | 接收者id |
|
||||
| `file` | string | 本地文件路径 |
|
||||
| `name` | string | 储存名称 |
|
||||
|
||||
> 只能上传本地文件, 需要上传 `http` 文件的话请先调用 `download_file` API下载
|
||||
|
||||
### 设置 QQ 个人资料
|
||||
|
||||
终结点: `/set_qq_profile`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|-----------------|--------|------|
|
||||
| `nickname` | int64 | 昵称 |
|
||||
| `company` | string | 公司 |
|
||||
| `email` | string | 邮箱 |
|
||||
| `college` | string | 大学 |
|
||||
| `personal_note` | string | 个人签名 |
|
||||
|
||||
> 所有参数字段都为可选。
|
||||
|
||||
### 获取状态
|
||||
|
||||
终结点: `/get_status`
|
||||
@ -1065,6 +1113,60 @@ JSON数组:
|
||||
|
||||
`该 API 没有响应数据`
|
||||
|
||||
### 获取群公告
|
||||
|
||||
终结点: `/_get_group_notice`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | -------- |
|
||||
| `group_id` | int64 | | 群号 |
|
||||
|
||||
**响应数据**
|
||||
|
||||
数组信息:
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
|----------------|--------| ------ |-------|
|
||||
| `notice_id` | string | | 公告id |
|
||||
| `sender_id` | string | | 发布者id |
|
||||
| `publish_time` | string | | 发布时间 |
|
||||
| `message` | GroupNoticeMessage | | 公告id |
|
||||
|
||||
响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"notice_id": "8850de2e00000000cc6bbd628a150c00",
|
||||
"sender_id": 1111111,
|
||||
"publish_time": 1656581068,
|
||||
"message": {
|
||||
"text": "这是一条公告",
|
||||
"images": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"retcode": 0,
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
### 删除群公告
|
||||
|
||||
终结点: `/_del_group_notice`
|
||||
|
||||
**参数**
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
|-------------| -------- | ------ |------|
|
||||
| `group_id` | int64 | | 群号 |
|
||||
| `notice_id` | string | | 公告id |
|
||||
|
||||
`该 API 没有响应数据`
|
||||
|
||||
### 获取单向好友列表
|
||||
|
||||
终结点: `/get_unidirectional_friend_list`
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
QQ频道相关功能的事件以及API
|
||||
|
||||
> 注意, 最新文档已经移动到 [go-cqhttp-docs](https://github.com/ishkong/go-cqhttp-docs), 当前文档只做兼容性保留, 所以内容可能有不足.
|
||||
|
||||
## 命名说明
|
||||
|
||||
API以及字段相关命名均为参考QQ官方命名或相似产品命名规则, 由于QQ频道的账号系统独立于QQ本体, 所以各个 `ID` 并不能和QQ通用.也无法通过 `tiny_id` 获取到 `QQ号`
|
||||
|
@ -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
|
||||
|
18
global/fs.go
18
global/fs.go
@ -13,9 +13,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
b14 "github.com/fumiama/go-base16384"
|
||||
"github.com/segmentio/asm/base64"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/param"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,6 +27,8 @@ const (
|
||||
VoicePath = "data/voices"
|
||||
// VideoPath go-cqhttp使用的视频缓存目录
|
||||
VideoPath = "data/videos"
|
||||
// VersionsPath go-cqhttp使用的版本信息目录
|
||||
VersionsPath = "data/versions"
|
||||
// CachePath go-cqhttp使用的缓存目录
|
||||
CachePath = "data/cache"
|
||||
// DumpsPath go-cqhttp使用错误转储目录
|
||||
@ -82,16 +86,22 @@ func FindFile(file, cache, p string) (data []byte, err error) {
|
||||
if (cache == "" || cache == "1") && PathExists(cacheFile) {
|
||||
return os.ReadFile(cacheFile)
|
||||
}
|
||||
data, err = GetBytes(file)
|
||||
_ = os.WriteFile(cacheFile, data, 0o644)
|
||||
err = download.Request{URL: file}.WriteToFile(cacheFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.ReadFile(cacheFile)
|
||||
case strings.HasPrefix(file, "base64"):
|
||||
data, err = param.Base64DecodeString(strings.TrimPrefix(file, "base64://"))
|
||||
data, err = base64.StdEncoding.DecodeString(strings.TrimPrefix(file, "base64://"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case strings.HasPrefix(file, "base16384"):
|
||||
data, err = b14.UTF82UTF16BE(utils.S2B(strings.TrimPrefix(file, "base16384://")))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = b14.Decode(data)
|
||||
case strings.HasPrefix(file, "file"):
|
||||
var fu *url.URL
|
||||
fu, err = url.Parse(file)
|
||||
|
@ -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),
|
||||
}
|
||||
|
290
global/net.go
290
global/net.go
@ -1,307 +1,27 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
)
|
||||
|
||||
var (
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: func(request *http.Request) (u *url.URL, e error) {
|
||||
if base.Proxy == "" {
|
||||
return http.ProxyFromEnvironment(request)
|
||||
}
|
||||
return url.Parse(base.Proxy)
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxConnsPerHost: 0,
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: 999,
|
||||
},
|
||||
}
|
||||
|
||||
// ErrOverSize 响应主体过大时返回此错误
|
||||
ErrOverSize = errors.New("oversize")
|
||||
|
||||
// UserAgent HTTP请求时使用的UA
|
||||
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"
|
||||
)
|
||||
|
||||
// GetBytes 对给定URL发送Get请求,返回响应主体
|
||||
func GetBytes(url string) ([]byte, error) {
|
||||
reader, err := HTTPGetReadCloser(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
}()
|
||||
return ioutil.ReadAll(reader)
|
||||
}
|
||||
|
||||
// DownloadFile 将给定URL对应的文件下载至给定Path
|
||||
func DownloadFile(url, path string, limit int64, headers map[string]string) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if limit > 0 && resp.ContentLength > limit {
|
||||
return ErrOverSize
|
||||
}
|
||||
_, err = file.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadFileMultiThreading 使用threadCount个线程将给定URL对应的文件下载至给定Path
|
||||
func DownloadFileMultiThreading(url, path string, limit int64, threadCount int, headers map[string]string) error {
|
||||
if threadCount < 2 {
|
||||
return DownloadFile(url, path, limit, headers)
|
||||
}
|
||||
type BlockMetaData struct {
|
||||
BeginOffset int64
|
||||
EndOffset int64
|
||||
DownloadedSize int64
|
||||
}
|
||||
var blocks []*BlockMetaData
|
||||
var contentLength int64
|
||||
errUnsupportedMultiThreading := errors.New("unsupported multi-threading")
|
||||
// 初始化分块或直接下载
|
||||
initOrDownload := func() error {
|
||||
copyStream := func(s io.ReadCloser) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.ReadFrom(s); err != nil {
|
||||
return err
|
||||
}
|
||||
return errUnsupportedMultiThreading
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
if _, ok := headers["User-Agent"]; !ok {
|
||||
req.Header["User-Agent"] = []string{UserAgent}
|
||||
}
|
||||
req.Header.Set("range", "bytes=0-")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return errors.New("response status unsuccessful: " + strconv.FormatInt(int64(resp.StatusCode), 10))
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
if limit > 0 && resp.ContentLength > limit {
|
||||
return ErrOverSize
|
||||
}
|
||||
return copyStream(resp.Body)
|
||||
}
|
||||
if resp.StatusCode == 206 {
|
||||
contentLength = resp.ContentLength
|
||||
if limit > 0 && resp.ContentLength > limit {
|
||||
return ErrOverSize
|
||||
}
|
||||
blockSize := func() int64 {
|
||||
if contentLength > 1024*1024 {
|
||||
return (contentLength / int64(threadCount)) - 10
|
||||
}
|
||||
return contentLength
|
||||
}()
|
||||
if blockSize == contentLength {
|
||||
return copyStream(resp.Body)
|
||||
}
|
||||
var tmp int64
|
||||
for tmp+blockSize < contentLength {
|
||||
blocks = append(blocks, &BlockMetaData{
|
||||
BeginOffset: tmp,
|
||||
EndOffset: tmp + blockSize - 1,
|
||||
})
|
||||
tmp += blockSize
|
||||
}
|
||||
blocks = append(blocks, &BlockMetaData{
|
||||
BeginOffset: tmp,
|
||||
EndOffset: contentLength - 1,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return errors.New("unknown status code")
|
||||
}
|
||||
// 下载分块
|
||||
downloadBlock := func(block *BlockMetaData) error {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, _ = file.Seek(block.BeginOffset, io.SeekStart)
|
||||
writer := bufio.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
if _, ok := headers["User-Agent"]; ok {
|
||||
req.Header["User-Agent"] = []string{UserAgent}
|
||||
}
|
||||
req.Header.Set("range", "bytes="+strconv.FormatInt(block.BeginOffset, 10)+"-"+strconv.FormatInt(block.EndOffset, 10))
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return errors.New("response status unsuccessful: " + strconv.FormatInt(int64(resp.StatusCode), 10))
|
||||
}
|
||||
buffer := make([]byte, 1024)
|
||||
i, err := resp.Body.Read(buffer)
|
||||
for {
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
i64 := int64(len(buffer[:i]))
|
||||
needSize := block.EndOffset + 1 - block.BeginOffset
|
||||
if i64 > needSize {
|
||||
i64 = needSize
|
||||
err = io.EOF
|
||||
}
|
||||
_, e := writer.Write(buffer[:i64])
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
block.BeginOffset += i64
|
||||
block.DownloadedSize += i64
|
||||
if err == io.EOF || block.BeginOffset > block.EndOffset {
|
||||
break
|
||||
}
|
||||
i, err = resp.Body.Read(buffer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := initOrDownload(); err != nil {
|
||||
if err == errUnsupportedMultiThreading {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(blocks))
|
||||
var lastErr error
|
||||
for i := range blocks {
|
||||
go func(b *BlockMetaData) {
|
||||
defer wg.Done()
|
||||
if err := downloadBlock(b); err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}(blocks[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// QQMusicSongInfo 通过给定id在QQ音乐上查找曲目信息
|
||||
func QQMusicSongInfo(id string) (gjson.Result, error) {
|
||||
d, err := GetBytes(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:` + id + `},%22module%22:%22music.pf_song_detail_svr%22}}`)
|
||||
d, err := download.Request{URL: `https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:` + id + `},%22module%22:%22music.pf_song_detail_svr%22}}`}.JSON()
|
||||
if err != nil {
|
||||
return gjson.Result{}, err
|
||||
}
|
||||
return gjson.ParseBytes(d).Get("songinfo.data"), nil
|
||||
return d.Get("songinfo.data"), nil
|
||||
}
|
||||
|
||||
// NeteaseMusicSongInfo 通过给定id在wdd音乐上查找曲目信息
|
||||
func NeteaseMusicSongInfo(id string) (gjson.Result, error) {
|
||||
d, err := GetBytes(fmt.Sprintf("http://music.163.com/api/song/detail/?id=%s&ids=%%5B%s%%5D", id, id))
|
||||
d, err := download.Request{URL: fmt.Sprintf("http://music.163.com/api/song/detail/?id=%s&ids=%%5B%s%%5D", id, id)}.JSON()
|
||||
if err != nil {
|
||||
return gjson.Result{}, err
|
||||
}
|
||||
return gjson.ParseBytes(d).Get("songs.0"), nil
|
||||
}
|
||||
|
||||
type gzipCloser struct {
|
||||
f io.Closer
|
||||
r *gzip.Reader
|
||||
}
|
||||
|
||||
// NewGzipReadCloser 从 io.ReadCloser 创建 gunzip io.ReadCloser
|
||||
func NewGzipReadCloser(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
gzipReader, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gzipCloser{
|
||||
f: reader,
|
||||
r: gzipReader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read impls io.Reader
|
||||
func (g *gzipCloser) Read(p []byte) (n int, err error) {
|
||||
return g.r.Read(p)
|
||||
}
|
||||
|
||||
// Close impls io.Closer
|
||||
func (g *gzipCloser) Close() error {
|
||||
_ = g.f.Close()
|
||||
return g.r.Close()
|
||||
}
|
||||
|
||||
// HTTPGetReadCloser 从 Http url 获取 io.ReadCloser
|
||||
func HTTPGetReadCloser(url string) (io.ReadCloser, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header["User-Agent"] = []string{UserAgent}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") {
|
||||
return NewGzipReadCloser(resp.Body)
|
||||
}
|
||||
return resp.Body, err
|
||||
return d.Get("songs.0"), nil
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// MSG 消息Map
|
||||
type MSG map[string]interface{}
|
||||
type MSG = map[string]any
|
||||
|
||||
// VersionNameCompare 检查版本名是否需要更新, 仅适用于 go-cqhttp 的版本命名规则
|
||||
//
|
||||
|
@ -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,20 +1,18 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RunningByDoubleClick 检查是否通过双击直接运行
|
||||
func RunningByDoubleClick() bool {
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
lp := kernel32.NewProc("GetConsoleProcessList")
|
||||
if lp != nil {
|
||||
var ids [2]uint32
|
||||
@ -29,7 +27,8 @@ func RunningByDoubleClick() bool {
|
||||
|
||||
// NoMoreDoubleClick 提示用户不要双击运行,并生成安全启动脚本
|
||||
func NoMoreDoubleClick() error {
|
||||
r := boxW(0, "请勿通过双击直接运行本程序, 这将导致一些非预料的后果.\n请在shell中运行./go-cqhttp.exe\n点击确认将释出安全启动脚本,点击取消则关闭程序", "警告", 0x00000030|0x00000001)
|
||||
toHighDPI()
|
||||
r := boxW(getConsoleWindows(), "请勿通过双击直接运行本程序, 这将导致一些非预料的后果.\n请在shell中运行./go-cqhttp.exe\n点击确认将释出安全启动脚本,点击取消则关闭程序", "警告", 0x00000030|0x00000001)
|
||||
if r == 2 {
|
||||
return nil
|
||||
}
|
||||
@ -59,9 +58,10 @@ func NoMoreDoubleClick() error {
|
||||
|
||||
// BoxW of Win32 API. Check https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw for more detail.
|
||||
func boxW(hwnd uintptr, caption, title string, flags uint) int {
|
||||
captionPtr, _ := syscall.UTF16PtrFromString(caption)
|
||||
titlePtr, _ := syscall.UTF16PtrFromString(title)
|
||||
ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
|
||||
captionPtr, _ := windows.UTF16PtrFromString(caption)
|
||||
titlePtr, _ := windows.UTF16PtrFromString(title)
|
||||
u32 := windows.NewLazySystemDLL("user32.dll")
|
||||
ret, _, _ := u32.NewProc("MessageBoxW").Call(
|
||||
hwnd,
|
||||
uintptr(unsafe.Pointer(captionPtr)),
|
||||
uintptr(unsafe.Pointer(titlePtr)),
|
||||
@ -69,3 +69,23 @@ func boxW(hwnd uintptr, caption, title string, flags uint) int {
|
||||
|
||||
return int(ret)
|
||||
}
|
||||
|
||||
// GetConsoleWindows retrieves the window handle used by the console associated with the calling process.
|
||||
func getConsoleWindows() (hWnd uintptr) {
|
||||
hWnd, _, _ = windows.NewLazySystemDLL("kernel32.dll").NewProc("GetConsoleWindow").Call()
|
||||
return
|
||||
}
|
||||
|
||||
// toHighDPI tries to raise DPI awareness context to DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED
|
||||
func toHighDPI() {
|
||||
systemAware := ^uintptr(2) + 1
|
||||
unawareGDIScaled := ^uintptr(5) + 1
|
||||
u32 := windows.NewLazySystemDLL("user32.dll")
|
||||
proc := u32.NewProc("SetThreadDpiAwarenessContext")
|
||||
if proc.Find() != nil {
|
||||
return
|
||||
}
|
||||
for i := unawareGDIScaled; i <= systemAware; i++ {
|
||||
_, _, _ = u32.NewProc("SetThreadDpiAwarenessContext").Call(i)
|
||||
}
|
||||
}
|
||||
|
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)
|
||||
}
|
75
go.mod
75
go.mod
@ -1,55 +1,68 @@
|
||||
module github.com/Mrs4s/go-cqhttp
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.5.1
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20220605085305-ae33763fe10a
|
||||
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c
|
||||
github.com/FloatTech/sqlite v1.5.7
|
||||
github.com/Microsoft/go-winio v0.6.0
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20230317162854-fd83d24f6794
|
||||
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/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||
github.com/mattn/go-colorable v0.1.12
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/segmentio/asm v1.1.3
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/segmentio/asm v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
github.com/tidwall/gjson v1.14.0
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60
|
||||
go.mongodb.org/mongo-driver v1.8.3
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
|
||||
go.mongodb.org/mongo-driver v1.11.0
|
||||
golang.org/x/crypto v0.3.0
|
||||
golang.org/x/image v0.5.0
|
||||
golang.org/x/sys v0.2.0
|
||||
golang.org/x/term v0.2.0
|
||||
golang.org/x/time v0.2.0
|
||||
gopkg.ilharper.com/x/isatty v1.1.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/RomiChan/protobuf v0.1.1-0.20220602121309-9e3b8cbefd7a // indirect
|
||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b // 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/go-stack/stack v1.8.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // 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.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.11 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // 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/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.0.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.2 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
modernc.org/libc v1.8.1 // indirect
|
||||
modernc.org/mathutil v1.2.2 // indirect
|
||||
modernc.org/memory v1.0.4 // 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.7.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.21.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/sqlite v1.20.0 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
)
|
||||
|
183
go.sum
183
go.sum
@ -1,25 +1,30 @@
|
||||
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20220605085305-ae33763fe10a h1:DVrEVvLNKb53wTTduRdC+hZ80S/5G9qFmKYmCmIMVws=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20220605085305-ae33763fe10a/go.mod h1:mZp8Lt7uqLCUwSLouB2yuiP467Cwl4mnG9IMAaXUKA0=
|
||||
github.com/RomiChan/protobuf v0.1.1-0.20220602121309-9e3b8cbefd7a h1:WIfEWYj82oEuPtm5pqlyQmCJCoiw00C6ugZFqHA0cC8=
|
||||
github.com/RomiChan/protobuf v0.1.1-0.20220602121309-9e3b8cbefd7a/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
|
||||
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c h1:cNPOdTNiVwxLpROLjXCgbIPvdkE+BwvxDvgmdYmWx6Q=
|
||||
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c/go.mod h1:KqZzu7slNKROh3TSYEH/IUMG6f4M+1qubZ5e52QypsE=
|
||||
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-20230317162854-fd83d24f6794 h1:V2hkbdJhTGX6tfwEsCg53rUCx/skTGBfwRMHB5/hy7E=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20230317162854-fd83d24f6794/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
|
||||
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d h1:/Xuj3fIiMY2ls1TwvPKmaqQrtJsPY+c9s+0lOScVHd8=
|
||||
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
|
||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
|
||||
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/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/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/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
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=
|
||||
@ -34,11 +39,15 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
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.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
|
||||
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@ -50,13 +59,15 @@ github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2t
|
||||
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=
|
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
|
||||
github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE=
|
||||
github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
|
||||
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
|
||||
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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
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-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=
|
||||
@ -67,30 +78,33 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k
|
||||
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.11 h1:LVs17FAZJFOjgmJXl9Tf13WfLUvZq7/RjfEJrnwZ9OE=
|
||||
github.com/pierrec/lz4/v4 v4.1.11/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
|
||||
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/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=
|
||||
@ -100,63 +114,79 @@ github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60 h1:lRKf10iIOW0V
|
||||
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.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
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/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.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
|
||||
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE=
|
||||
go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
||||
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-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
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-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI=
|
||||
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.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/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-20191026070338-33540a1f6037/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
||||
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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
|
||||
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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=
|
||||
@ -166,9 +196,11 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
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.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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
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/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=
|
||||
@ -178,10 +210,31 @@ gopkg.in/yaml.v2 v2.3.0/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=
|
||||
modernc.org/libc v1.8.1 h1:y9oPIhwcaFXxX7kMp6Qb2ZLKzr0mDkikWN3CV5GS63o=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.8.1/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI=
|
||||
modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.20.0 h1:80zmD3BGkm8BZ5fUi/4lwJQHiO3GXgIUvZRXpoIfROY=
|
||||
modernc.org/sqlite v1.20.0/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
|
@ -1,8 +1,6 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -19,13 +17,3 @@ func encodeSilk(_ []byte, _ string) ([]byte, error) {
|
||||
func resampleSilk(data []byte) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// Mime scan feature
|
||||
var (
|
||||
IsLawfulImage = nocheck // 检查图片MIME
|
||||
IsLawfulAudio = nocheck // 检查音频MIME
|
||||
)
|
||||
|
||||
func nocheck(_ io.ReadSeeker) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
@ -5,13 +5,8 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@ -35,12 +30,14 @@ var (
|
||||
SplitURL bool // 是否分割URL
|
||||
ForceFragmented bool // 是否启用强制分片
|
||||
SkipMimeScan bool // 是否跳过Mime扫描
|
||||
ConvertWebpImage bool // 是否转换Webp图片
|
||||
ReportSelfMessage bool // 是否上报自身消息
|
||||
UseSSOAddress bool // 是否使用服务器下发的新地址进行重连
|
||||
LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志
|
||||
LogColorful bool // 是否启用日志颜色
|
||||
FastStart bool // 是否为快速启动
|
||||
AllowTempSession bool // 是否允许发送临时会话信息
|
||||
SignServerOverwrite string // 使用特定的服务器进行签名
|
||||
|
||||
PostFormat string // 上报格式 string or array
|
||||
Proxy string // 存储 proxy_rewrite,用于设置代理
|
||||
@ -58,14 +55,13 @@ var (
|
||||
|
||||
// Parse parse flags
|
||||
func Parse() {
|
||||
wd, _ := os.Getwd()
|
||||
dc := path.Join(wd, "config.yml")
|
||||
flag.StringVar(&LittleC, "c", dc, "configuration filename")
|
||||
flag.StringVar(&LittleC, "c", "config.yml", "configuration filename")
|
||||
flag.BoolVar(&LittleD, "d", false, "running as a daemon")
|
||||
flag.BoolVar(&LittleH, "h", false, "this Help")
|
||||
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.StringVar(&SignServerOverwrite, "sign-server", "", "use special server to sign tlv")
|
||||
flag.Parse()
|
||||
|
||||
if *d {
|
||||
@ -86,6 +82,7 @@ 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
|
||||
@ -128,31 +125,3 @@ Options:
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// ResetWorkingDir 重设工作路径
|
||||
func ResetWorkingDir() {
|
||||
wd := LittleWD
|
||||
args := make([]string, 0, len(os.Args))
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
if os.Args[i] == "-w" {
|
||||
i++ // skip value field
|
||||
} else if !strings.HasPrefix(os.Args[i], "-w") {
|
||||
args = append(args, os.Args[i])
|
||||
}
|
||||
}
|
||||
p, _ := filepath.Abs(os.Args[0])
|
||||
_, err := os.Stat(p)
|
||||
if !(err == nil || errors.Is(err, os.ErrExist)) {
|
||||
log.Fatalf("重置工作目录时出现错误: 无法找到路径 %v", p)
|
||||
}
|
||||
proc := exec.Command(p, args...)
|
||||
proc.Stdin = os.Stdin
|
||||
proc.Stdout = os.Stdout
|
||||
proc.Stderr = os.Stderr
|
||||
proc.Dir = wd
|
||||
err = proc.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
@ -1,571 +0,0 @@
|
||||
// Package btree provide a disk-based btree
|
||||
package btree
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
hashSize = 16 // md5 hash
|
||||
tableSize = (1024 - 1) / int(unsafe.Sizeof(item{}))
|
||||
cacheSlots = 11 // prime
|
||||
superSize = int(unsafe.Sizeof(super{}))
|
||||
tableStructSize = int(unsafe.Sizeof(table{}))
|
||||
)
|
||||
|
||||
type fileLock interface {
|
||||
release() error
|
||||
}
|
||||
|
||||
type item struct {
|
||||
hash [hashSize]byte
|
||||
offset int64
|
||||
child int64
|
||||
}
|
||||
|
||||
type table struct {
|
||||
items [tableSize]item
|
||||
size int
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
table *table
|
||||
offset int64
|
||||
}
|
||||
|
||||
type super struct {
|
||||
top int64
|
||||
freeTop int64
|
||||
alloc int64
|
||||
}
|
||||
|
||||
// DB ...
|
||||
type DB struct {
|
||||
fd *os.File
|
||||
top int64
|
||||
freeTop int64
|
||||
alloc int64
|
||||
cache [cacheSlots]cache
|
||||
|
||||
flock fileLock
|
||||
inAllocator bool
|
||||
deleteLarger bool
|
||||
fqueue [freeQueueLen]chunk
|
||||
fqueueLen int
|
||||
}
|
||||
|
||||
func (d *DB) get(offset int64) *table {
|
||||
assert(offset != 0)
|
||||
|
||||
// take from cache
|
||||
slot := &d.cache[offset%cacheSlots]
|
||||
if slot.offset == offset {
|
||||
return slot.table
|
||||
}
|
||||
|
||||
table := new(table)
|
||||
|
||||
d.fd.Seek(offset, io.SeekStart)
|
||||
err := readTable(d.fd, table)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "btree I/O error"))
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
func (d *DB) put(t *table, offset int64) {
|
||||
assert(offset != 0)
|
||||
|
||||
// overwrite cache
|
||||
slot := &d.cache[offset%cacheSlots]
|
||||
slot.table = t
|
||||
slot.offset = offset
|
||||
}
|
||||
|
||||
func (d *DB) flush(t *table, offset int64) {
|
||||
assert(offset != 0)
|
||||
|
||||
d.fd.Seek(offset, io.SeekStart)
|
||||
err := writeTable(d.fd, t)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "btree I/O error"))
|
||||
}
|
||||
d.put(t, offset)
|
||||
}
|
||||
|
||||
func (d *DB) flushSuper() {
|
||||
d.fd.Seek(0, io.SeekStart)
|
||||
super := super{
|
||||
top: d.top,
|
||||
freeTop: d.freeTop,
|
||||
alloc: d.alloc,
|
||||
}
|
||||
err := writeSuper(d.fd, &super)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "btree I/O error"))
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens an existed btree file
|
||||
func Open(name string) (*DB, error) {
|
||||
lock, err := newFileLock(name + ".lock")
|
||||
if err != nil {
|
||||
return nil, errors.New("文件被其他进程占用")
|
||||
}
|
||||
btree := new(DB)
|
||||
fd, err := os.OpenFile(name, os.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "btree open file failed")
|
||||
}
|
||||
btree.fd = fd
|
||||
|
||||
super := super{}
|
||||
err = readSuper(fd, &super)
|
||||
btree.top = super.top
|
||||
btree.freeTop = super.freeTop
|
||||
btree.alloc = super.alloc
|
||||
btree.flock = lock
|
||||
return btree, errors.Wrap(err, "btree read meta info failed")
|
||||
}
|
||||
|
||||
// Create creates a database
|
||||
func Create(name string) (*DB, error) {
|
||||
lock, err := newFileLock(name + ".lock")
|
||||
if err != nil {
|
||||
return nil, errors.New("文件被其他进程占用")
|
||||
}
|
||||
btree := new(DB)
|
||||
fd, err := os.OpenFile(name, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "btree open file failed")
|
||||
}
|
||||
|
||||
btree.flock = lock
|
||||
btree.fd = fd
|
||||
btree.alloc = int64(superSize)
|
||||
btree.flushSuper()
|
||||
return btree, nil
|
||||
}
|
||||
|
||||
// Close closes the database
|
||||
func (d *DB) Close() error {
|
||||
_ = d.fd.Sync()
|
||||
if err := d.flock.release(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := d.fd.Close()
|
||||
for i := 0; i < cacheSlots; i++ {
|
||||
d.cache[i] = cache{}
|
||||
}
|
||||
return errors.Wrap(err, "btree close failed")
|
||||
}
|
||||
|
||||
func collapse(bt *DB, offset int64) int64 {
|
||||
table := bt.get(offset)
|
||||
if table.size != 0 {
|
||||
/* unable to collapse */
|
||||
bt.put(table, offset)
|
||||
return offset
|
||||
}
|
||||
ret := table.items[0].child
|
||||
bt.put(table, offset)
|
||||
|
||||
/*
|
||||
* WARNING: this is dangerous as the chunk is added to allocation tree
|
||||
* before the references to it are removed!
|
||||
*/
|
||||
bt.freeChunk(offset, int(unsafe.Sizeof(table)))
|
||||
return ret
|
||||
}
|
||||
|
||||
// split a table. The pivot item is stored to 'hash' and 'offset'.
|
||||
// Returns offset to the new table.
|
||||
func (d *DB) split(t *table, hash *byte, offset *int64) int64 {
|
||||
copyhash(hash, &t.items[tableSize/2].hash[0])
|
||||
*offset = t.items[tableSize/2].offset
|
||||
|
||||
ntable := new(table)
|
||||
ntable.size = t.size - tableSize/2 - 1
|
||||
|
||||
t.size = tableSize / 2
|
||||
|
||||
copy(ntable.items[:ntable.size+1], t.items[tableSize/2+1:])
|
||||
|
||||
noff := d.allocChunk(tableStructSize)
|
||||
d.flush(ntable, noff)
|
||||
|
||||
// make sure data is written before a reference is added to it
|
||||
_ = d.fd.Sync()
|
||||
return noff
|
||||
}
|
||||
|
||||
// takeSmallest find and remove the smallest item from the given table. The key of the item
|
||||
// is stored to 'hash'. Returns offset to the item
|
||||
func (d *DB) takeSmallest(toff int64, sha1 *byte) int64 {
|
||||
table := d.get(toff)
|
||||
assert(table.size > 0)
|
||||
|
||||
var off int64
|
||||
child := table.items[0].child
|
||||
if child == 0 {
|
||||
off = d.remove(table, 0, sha1)
|
||||
} else {
|
||||
/* recursion */
|
||||
off = d.takeSmallest(child, sha1)
|
||||
table.items[0].child = collapse(d, child)
|
||||
}
|
||||
d.flush(table, toff)
|
||||
|
||||
// make sure data is written before a reference is added to it
|
||||
_ = d.fd.Sync()
|
||||
return off
|
||||
}
|
||||
|
||||
// takeLargest find and remove the largest item from the given table. The key of the item
|
||||
// is stored to 'hash'. Returns offset to the item
|
||||
func (d *DB) takeLargest(toff int64, hash *byte) int64 {
|
||||
table := d.get(toff)
|
||||
assert(table.size > 0)
|
||||
|
||||
var off int64
|
||||
child := table.items[table.size].child
|
||||
if child == 0 {
|
||||
off = d.remove(table, table.size-1, hash)
|
||||
} else {
|
||||
/* recursion */
|
||||
off = d.takeLargest(child, hash)
|
||||
table.items[table.size].child = collapse(d, child)
|
||||
}
|
||||
d.flush(table, toff)
|
||||
|
||||
// make sure data is written before a reference is added to it
|
||||
_ = d.fd.Sync()
|
||||
return off
|
||||
}
|
||||
|
||||
// remove an item in position 'i' from the given table. The key of the
|
||||
// removed item is stored to 'hash'. Returns offset to the item.
|
||||
func (d *DB) remove(t *table, i int, hash *byte) int64 {
|
||||
assert(i < t.size)
|
||||
|
||||
if hash != nil {
|
||||
copyhash(hash, &t.items[i].hash[0])
|
||||
}
|
||||
|
||||
offset := t.items[i].offset
|
||||
lc := t.items[i].child
|
||||
rc := t.items[i+1].child
|
||||
|
||||
if lc != 0 && rc != 0 {
|
||||
/* replace the removed item by taking an item from one of the
|
||||
child tables */
|
||||
var noff int64
|
||||
if rand.Int()&1 != 0 {
|
||||
noff = d.takeLargest(lc, &t.items[i].hash[0])
|
||||
t.items[i].child = collapse(d, lc)
|
||||
} else {
|
||||
noff = d.takeSmallest(rc, &t.items[i].hash[0])
|
||||
t.items[i+1].child = collapse(d, rc)
|
||||
}
|
||||
t.items[i].child = noff
|
||||
} else {
|
||||
// memmove(&table->items[i], &table->items[i + 1],
|
||||
// (table->size - i) * sizeof(struct btree_item));
|
||||
copy(t.items[i:], t.items[i+1:])
|
||||
t.size--
|
||||
|
||||
if lc != 0 {
|
||||
t.items[i].child = lc
|
||||
} else {
|
||||
t.items[i].child = rc
|
||||
}
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func (d *DB) insert(toff int64, hash *byte, data []byte, size int) int64 {
|
||||
table := d.get(toff)
|
||||
assert(table.size < tableSize-1)
|
||||
|
||||
left, right := 0, table.size
|
||||
for left < right {
|
||||
mid := (right-left)>>1 + left
|
||||
switch cmp := cmp(hash, &table.items[mid].hash[0]); {
|
||||
case cmp == 0:
|
||||
// already in the table
|
||||
ret := table.items[mid].offset
|
||||
d.put(table, toff)
|
||||
return ret
|
||||
case cmp < 0:
|
||||
right = mid
|
||||
default:
|
||||
left = mid + 1
|
||||
}
|
||||
}
|
||||
i := left
|
||||
|
||||
var off, rc, ret int64
|
||||
lc := table.items[i].child
|
||||
if lc != 0 {
|
||||
/* recursion */
|
||||
ret = d.insert(lc, hash, data, size)
|
||||
|
||||
/* check if we need to split */
|
||||
child := d.get(lc)
|
||||
if child.size < tableSize-1 {
|
||||
/* nothing to do */
|
||||
d.put(table, toff)
|
||||
d.put(child, lc)
|
||||
return ret
|
||||
}
|
||||
/* overwrites SHA-1 */
|
||||
rc = d.split(child, hash, &off)
|
||||
/* flush just in case changes happened */
|
||||
d.flush(child, lc)
|
||||
|
||||
// make sure data is written before a reference is added to it
|
||||
_ = d.fd.Sync()
|
||||
} else {
|
||||
off = d.insertData(data, size)
|
||||
ret = off
|
||||
}
|
||||
|
||||
table.size++
|
||||
// memmove(&table->items[i + 1], &table->items[i],
|
||||
// (table->size - i) * sizeof(struct btree_item));
|
||||
copy(table.items[i+1:], table.items[i:])
|
||||
copyhash(&table.items[i].hash[0], hash)
|
||||
table.items[i].offset = off
|
||||
table.items[i].child = lc
|
||||
table.items[i+1].child = rc
|
||||
|
||||
d.flush(table, toff)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d *DB) insertData(data []byte, size int) int64 {
|
||||
if data == nil {
|
||||
return int64(size)
|
||||
}
|
||||
assert(len(data) == size)
|
||||
|
||||
offset := d.allocChunk(4 + len(data))
|
||||
|
||||
d.fd.Seek(offset, io.SeekStart)
|
||||
err := write32(d.fd, int32(len(data)))
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "btree I/O error"))
|
||||
}
|
||||
_, err = d.fd.Write(data)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "btree I/O error"))
|
||||
}
|
||||
|
||||
// make sure data is written before a reference is added to it
|
||||
_ = d.fd.Sync()
|
||||
return offset
|
||||
}
|
||||
|
||||
// delete remove an item with key 'hash' from the given table. The offset to the
|
||||
// removed item is returned.
|
||||
// Please note that 'hash' is overwritten when called inside the allocator.
|
||||
func (d *DB) delete(offset int64, hash *byte) int64 {
|
||||
if offset == 0 {
|
||||
return 0
|
||||
}
|
||||
table := d.get(offset)
|
||||
|
||||
left, right := 0, table.size
|
||||
for left < right {
|
||||
i := (right-left)>>1 + left
|
||||
switch cmp := cmp(hash, &table.items[i].hash[0]); {
|
||||
case cmp == 0:
|
||||
// found
|
||||
ret := d.remove(table, i, hash)
|
||||
d.flush(table, offset)
|
||||
return ret
|
||||
case cmp < 0:
|
||||
right = i
|
||||
default:
|
||||
left = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// not found - recursion
|
||||
i := left
|
||||
child := table.items[i].child
|
||||
ret := d.delete(child, hash)
|
||||
if ret != 0 {
|
||||
table.items[i].child = collapse(d, child)
|
||||
}
|
||||
|
||||
if ret == 0 && d.deleteLarger && i < table.size {
|
||||
ret = d.remove(table, i, hash)
|
||||
}
|
||||
if ret != 0 {
|
||||
/* flush just in case changes happened */
|
||||
d.flush(table, offset)
|
||||
} else {
|
||||
d.put(table, offset)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d *DB) insertTopLevel(toff *int64, hash *byte, data []byte, size int) int64 { // nolint:unparam
|
||||
var off, ret, rc int64
|
||||
if *toff != 0 {
|
||||
ret = d.insert(*toff, hash, data, size)
|
||||
|
||||
/* check if we need to split */
|
||||
table := d.get(*toff)
|
||||
if table.size < tableSize-1 {
|
||||
/* nothing to do */
|
||||
d.put(table, *toff)
|
||||
return ret
|
||||
}
|
||||
rc = d.split(table, hash, &off)
|
||||
d.flush(table, *toff)
|
||||
} else {
|
||||
off = d.insertData(data, size)
|
||||
ret = off
|
||||
}
|
||||
|
||||
/* create new top level table */
|
||||
t := new(table)
|
||||
t.size = 1
|
||||
copyhash(&t.items[0].hash[0], hash)
|
||||
t.items[0].offset = off
|
||||
t.items[0].child = *toff
|
||||
t.items[1].child = rc
|
||||
|
||||
ntoff := d.allocChunk(tableStructSize)
|
||||
d.flush(t, ntoff)
|
||||
|
||||
*toff = ntoff
|
||||
|
||||
// make sure data is written before a reference is added to it
|
||||
_ = d.fd.Sync()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d *DB) lookup(toff int64, hash *byte) int64 {
|
||||
if toff == 0 {
|
||||
return 0
|
||||
}
|
||||
table := d.get(toff)
|
||||
|
||||
left, right := 0, table.size
|
||||
for left < right {
|
||||
mid := (right-left)>>1 + left
|
||||
switch cmp := cmp(hash, &table.items[mid].hash[0]); {
|
||||
case cmp == 0:
|
||||
// found
|
||||
ret := table.items[mid].offset
|
||||
d.put(table, toff)
|
||||
return ret
|
||||
case cmp < 0:
|
||||
right = mid
|
||||
default:
|
||||
left = mid + 1
|
||||
}
|
||||
}
|
||||
|
||||
i := left
|
||||
child := table.items[i].child
|
||||
d.put(table, toff)
|
||||
return d.lookup(child, hash)
|
||||
}
|
||||
|
||||
// Insert a new item with key 'hash' with the contents in 'data' to the
|
||||
// database file.
|
||||
func (d *DB) Insert(chash *byte, data []byte) {
|
||||
/* SHA-1 must be in writable memory */
|
||||
var hash [hashSize]byte
|
||||
copyhash(&hash[0], chash)
|
||||
|
||||
_ = d.insertTopLevel(&d.top, &hash[0], data, len(data))
|
||||
freeQueued(d)
|
||||
d.flushSuper()
|
||||
}
|
||||
|
||||
func (d *DB) readValue(off int64) []byte {
|
||||
d.fd.Seek(off, io.SeekStart)
|
||||
length, err := read32(d.fd)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
data := make([]byte, length)
|
||||
n, err := io.ReadFull(d.fd, data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data[:n]
|
||||
}
|
||||
|
||||
// Get look up item with the given key 'hash' in the database file. Length of the
|
||||
// item is stored in 'len'. Returns a pointer to the contents of the item.
|
||||
// The returned pointer should be released with free() after use.
|
||||
func (d *DB) Get(hash *byte) []byte {
|
||||
off := d.lookup(d.top, hash)
|
||||
if off == 0 {
|
||||
return nil
|
||||
}
|
||||
return d.readValue(off)
|
||||
}
|
||||
|
||||
// Delete remove item with the given key 'hash' from the database file.
|
||||
func (d *DB) Delete(hash *byte) error {
|
||||
var h [hashSize]byte
|
||||
copyhash(&h[0], hash)
|
||||
|
||||
off := d.delete(d.top, &h[0])
|
||||
if off == 0 {
|
||||
return nil // not found key
|
||||
}
|
||||
|
||||
d.top = collapse(d, d.top)
|
||||
freeQueued(d)
|
||||
d.flushSuper()
|
||||
|
||||
d.fd.Seek(off, io.SeekStart)
|
||||
length, err := read32(d.fd) // len: 0
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "btree I/O error")
|
||||
}
|
||||
|
||||
d.freeChunk(off, int(length+4))
|
||||
freeQueued(d)
|
||||
d.flushSuper()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Foreach iterates over all items in the database file.
|
||||
func (d *DB) Foreach(iter func(key [16]byte, value []byte)) {
|
||||
if d.top != 0 {
|
||||
top := d.get(d.top)
|
||||
d.iterate(top, iter)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DB) iterate(table *table, iter func(key [16]byte, value []byte)) {
|
||||
for i := 0; i < table.size; i++ {
|
||||
item := table.items[i]
|
||||
offset := item.offset
|
||||
iter(item.hash, d.readValue(offset))
|
||||
|
||||
if item.child != 0 {
|
||||
child := d.get(item.child)
|
||||
d.iterate(child, iter)
|
||||
}
|
||||
}
|
||||
item := table.items[table.size]
|
||||
if item.child != 0 {
|
||||
child := d.get(item.child)
|
||||
d.iterate(child, iter)
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package btree
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
assert2 "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func tempfile(t *testing.T) string {
|
||||
temp, err := os.CreateTemp(".", "temp.*.db")
|
||||
assert2.NoError(t, temp.Close())
|
||||
assert2.NoError(t, err)
|
||||
return temp.Name()
|
||||
}
|
||||
|
||||
func removedb(name string) {
|
||||
os.Remove(name)
|
||||
os.Remove(name + ".lock")
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
f := tempfile(t)
|
||||
_, err := Create(f)
|
||||
assert2.NoError(t, err)
|
||||
defer removedb(f)
|
||||
}
|
||||
|
||||
func TestBtree(t *testing.T) {
|
||||
f := tempfile(t)
|
||||
defer removedb(f)
|
||||
bt, err := Create(f)
|
||||
assert := assert2.New(t)
|
||||
assert.NoError(err)
|
||||
|
||||
tests := []string{
|
||||
"hello world",
|
||||
"123",
|
||||
"We are met on a great battle-field of that war.",
|
||||
"Abraham Lincoln, November 19, 1863, Gettysburg, Pennsylvania",
|
||||
}
|
||||
sha := make([]*byte, len(tests))
|
||||
for i, tt := range tests {
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(tt))
|
||||
sha[i] = &hash.Sum(nil)[0]
|
||||
bt.Insert(sha[i], []byte(tt))
|
||||
}
|
||||
assert.NoError(bt.Close())
|
||||
|
||||
bt, err = Open(f)
|
||||
assert.NoError(err)
|
||||
var ss []string
|
||||
bt.Foreach(func(key [16]byte, value []byte) {
|
||||
ss = append(ss, string(value))
|
||||
})
|
||||
assert.ElementsMatch(tests, ss)
|
||||
|
||||
for i, tt := range tests {
|
||||
assert.Equal([]byte(tt), bt.Get(sha[i]))
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
assert.NoError(bt.Delete(sha[i]))
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
assert.Equal([]byte(nil), bt.Get(sha[i]))
|
||||
}
|
||||
assert.NoError(bt.Close())
|
||||
}
|
||||
|
||||
func testForeach(t *testing.T, elemSize int) {
|
||||
expected := make([]string, elemSize)
|
||||
for i := 0; i < elemSize; i++ {
|
||||
expected[i] = utils.RandomString(20)
|
||||
}
|
||||
f := tempfile(t)
|
||||
defer removedb(f)
|
||||
bt, err := Create(f)
|
||||
assert2.NoError(t, err)
|
||||
for _, v := range expected {
|
||||
hash := sha1.New()
|
||||
hash.Write([]byte(v))
|
||||
bt.Insert(&hash.Sum(nil)[0], []byte(v))
|
||||
}
|
||||
var got []string
|
||||
bt.Foreach(func(key [16]byte, value []byte) {
|
||||
got = append(got, string(value))
|
||||
})
|
||||
assert2.ElementsMatch(t, expected, got)
|
||||
assert2.NoError(t, bt.Close())
|
||||
}
|
||||
|
||||
func TestDB_Foreach(t *testing.T) {
|
||||
elemSizes := []int{0, 5, 100, 200}
|
||||
for _, size := range elemSizes {
|
||||
testForeach(t, size)
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
package btree
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type chunk struct {
|
||||
offset int64
|
||||
len int
|
||||
}
|
||||
|
||||
const freeQueueLen = 64
|
||||
|
||||
func freeQueued(bt *DB) {
|
||||
for i := 0; i < bt.fqueueLen; i++ {
|
||||
chunk := &bt.fqueue[i]
|
||||
bt.freeChunk(chunk.offset, chunk.len)
|
||||
}
|
||||
bt.fqueueLen = 0
|
||||
}
|
||||
|
||||
func (d *DB) allocChunk(size int) int64 {
|
||||
assert(size > 0)
|
||||
|
||||
size = power2(size)
|
||||
|
||||
var offset int64
|
||||
if d.inAllocator {
|
||||
const i32s = unsafe.Sizeof(int32(0))
|
||||
|
||||
/* create fake size SHA-1 */
|
||||
var sha1 [hashSize]byte
|
||||
p := unsafe.Pointer(&sha1[0])
|
||||
*(*int32)(p) = -1 // *(uint32_t *) hash = -1;
|
||||
*(*uint32)(unsafe.Add(p, i32s)) = uint32(size) // ((__be32 *) hash)[1] = to_be32(size);
|
||||
|
||||
/* find free chunk with the larger or the same size/SHA-1 */
|
||||
d.inAllocator = true
|
||||
d.deleteLarger = true
|
||||
offset = d.delete(d.freeTop, &sha1[0])
|
||||
d.deleteLarger = false
|
||||
if offset != 0 {
|
||||
assert(*(*int32)(p) == -1) // assert(*(uint32_t *) hash == (uint32_t) -1)
|
||||
flen := int(*(*uint32)(unsafe.Add(p, i32s))) // size_t free_len = from_be32(((__be32 *) hash)[1])
|
||||
assert(power2(flen) == flen)
|
||||
assert(flen >= size)
|
||||
|
||||
/* delete buddy information */
|
||||
resethash(&sha1[0])
|
||||
*(*int64)(p) = offset
|
||||
buddyLen := d.delete(d.freeTop, &sha1[0])
|
||||
assert(buddyLen == int64(size))
|
||||
|
||||
d.freeTop = collapse(d, d.freeTop)
|
||||
|
||||
d.inAllocator = false
|
||||
|
||||
/* free extra space at the end of the chunk */
|
||||
for flen > size {
|
||||
flen >>= 1
|
||||
d.freeChunk(offset+int64(flen), flen)
|
||||
}
|
||||
} else {
|
||||
d.inAllocator = false
|
||||
}
|
||||
}
|
||||
if offset == 0 {
|
||||
/* not found, allocate from the end of the file */
|
||||
offset = d.alloc
|
||||
/* TODO: this wastes memory.. */
|
||||
if offset&int64(size-1) != 0 {
|
||||
offset += int64(size) - (offset & (int64(size) - 1))
|
||||
}
|
||||
d.alloc = offset + int64(size)
|
||||
}
|
||||
d.flushSuper()
|
||||
|
||||
// make sure the allocation tree is up-to-date before using the chunk
|
||||
_ = d.fd.Sync()
|
||||
return offset
|
||||
}
|
||||
|
||||
/* Mark a chunk as unused in the database file */
|
||||
func (d *DB) freeChunk(offset int64, size int) {
|
||||
assert(size > 0)
|
||||
assert(offset != 0)
|
||||
size = power2(size)
|
||||
assert(offset&int64(size-1) == 0)
|
||||
|
||||
if d.inAllocator {
|
||||
chunk := &d.fqueue[d.fqueueLen]
|
||||
d.fqueueLen++
|
||||
chunk.offset = offset
|
||||
chunk.len = size
|
||||
return
|
||||
}
|
||||
|
||||
/* create fake offset SHA-1 for buddy allocation */
|
||||
var sha1 [hashSize]byte
|
||||
p := unsafe.Pointer(&sha1[0])
|
||||
d.inAllocator = true
|
||||
|
||||
const i32s = unsafe.Sizeof(int32(0))
|
||||
|
||||
/* add buddy information */
|
||||
resethash(&sha1[0])
|
||||
*(*int32)(p) = -1 // *(uint32_t *) hash = -1;
|
||||
*(*uint32)(unsafe.Add(p, i32s)) = uint32(size) // ((__be32 *) hash)[1] = to_be32(size);
|
||||
*(*uint32)(unsafe.Add(p, i32s*2)) = rand.Uint32() /* to make SHA-1 unique */
|
||||
*(*uint32)(unsafe.Add(p, i32s*3)) = rand.Uint32()
|
||||
|
||||
// insert_toplevel(btree, &btree->free_top, hash, NULL, offset);
|
||||
_ = d.insertTopLevel(&d.freeTop, &sha1[0], nil, int(offset))
|
||||
d.inAllocator = false
|
||||
|
||||
d.flushSuper()
|
||||
|
||||
// make sure the allocation tree is up-to-date before removing
|
||||
// references to the chunk
|
||||
_ = d.fd.Sync()
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd
|
||||
|
||||
package btree
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type unixFileLock struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (fl *unixFileLock) release() error {
|
||||
if err := setFileLock(fl.f, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return fl.f.Close()
|
||||
}
|
||||
|
||||
func newFileLock(path string) (fl fileLock, err error) {
|
||||
flag := os.O_RDWR
|
||||
f, err := os.OpenFile(path, flag, 0)
|
||||
if os.IsNotExist(err) {
|
||||
f, err = os.OpenFile(path, flag|os.O_CREATE, 0644)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = setFileLock(f, true)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
fl = &unixFileLock{f: f}
|
||||
return
|
||||
}
|
||||
|
||||
func setFileLock(f *os.File, lock bool) error {
|
||||
how := syscall.LOCK_UN
|
||||
if lock {
|
||||
how = syscall.LOCK_EX
|
||||
}
|
||||
return syscall.Flock(int(f.Fd()), how|syscall.LOCK_NB)
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package btree
|
||||
|
||||
import "syscall"
|
||||
|
||||
type windowsFileLock struct {
|
||||
fd syscall.Handle
|
||||
}
|
||||
|
||||
func (fl *windowsFileLock) release() error {
|
||||
return syscall.Close(fl.fd)
|
||||
}
|
||||
|
||||
func newFileLock(path string) (fileLock, error) {
|
||||
pathp, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const access uint32 = syscall.GENERIC_READ | syscall.GENERIC_WRITE
|
||||
fd, err := syscall.CreateFile(pathp, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
if err == syscall.ERROR_FILE_NOT_FOUND {
|
||||
fd, err = syscall.CreateFile(pathp, access, 0, nil, syscall.OPEN_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &windowsFileLock{fd: fd}, nil
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package btree
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func assert(cond bool) {
|
||||
if !cond {
|
||||
panic("assert failed!")
|
||||
}
|
||||
}
|
||||
|
||||
// power2 returns a value that is greater or equal to 'val' and is power-of-two.
|
||||
func power2(val int) int {
|
||||
i := 1
|
||||
for i < val {
|
||||
i <<= 1
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// helpers for hash
|
||||
|
||||
func cmp(a, b *byte) int64 {
|
||||
pa, pb := unsafe.Pointer(a), unsafe.Pointer(b)
|
||||
if *(*uint64)(pa) != *(*uint64)(pb) {
|
||||
return int64(*(*uint64)(pa) - *(*uint64)(pb))
|
||||
}
|
||||
pa, pb = unsafe.Add(pa, 8), unsafe.Add(pb, 8)
|
||||
return int64(*(*uint64)(pa) - *(*uint64)(pb))
|
||||
}
|
||||
|
||||
func copyhash(dst *byte, src *byte) {
|
||||
pa, pb := unsafe.Pointer(dst), unsafe.Pointer(src)
|
||||
*(*[hashSize]byte)(pa) = *(*[hashSize]byte)(pb)
|
||||
}
|
||||
|
||||
func resethash(sha1 *byte) {
|
||||
p := unsafe.Pointer(sha1)
|
||||
*(*[hashSize]byte)(p) = [hashSize]byte{}
|
||||
}
|
||||
|
||||
// reading table
|
||||
|
||||
func read32(r io.Reader) (int32, error) {
|
||||
b := make([]byte, 4)
|
||||
_, err := r.Read(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return *(*int32)(unsafe.Pointer(&b[0])), nil
|
||||
}
|
||||
|
||||
func readTable(r io.Reader, t *table) error {
|
||||
buf := make([]byte, tableStructSize)
|
||||
_, err := r.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = *(*table)(unsafe.Pointer(&buf[0]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func readSuper(r io.Reader, s *super) error {
|
||||
buf := make([]byte, superSize)
|
||||
_, err := r.Read(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = *(*super)(unsafe.Pointer(&buf[0]))
|
||||
return nil
|
||||
}
|
||||
|
||||
// write table
|
||||
|
||||
func write32(w io.Writer, t int32) error {
|
||||
var p []byte
|
||||
ph := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
ph.Data = uintptr(unsafe.Pointer(&t))
|
||||
ph.Len = 4
|
||||
ph.Cap = 4
|
||||
_, err := w.Write(p)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeTable(w io.Writer, t *table) error {
|
||||
var p []byte
|
||||
ph := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
ph.Data = uintptr(unsafe.Pointer(t))
|
||||
ph.Len = tableStructSize
|
||||
ph.Cap = tableStructSize
|
||||
_, err := w.Write(p)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeSuper(w io.Writer, s *super) error {
|
||||
var p []byte
|
||||
ph := (*reflect.SliceHeader)(unsafe.Pointer(&p))
|
||||
ph.Data = uintptr(unsafe.Pointer(s))
|
||||
ph.Len = superSize
|
||||
ph.Cap = superSize
|
||||
_, err := w.Write(p)
|
||||
return err
|
||||
}
|
69
internal/cache/cache.go
vendored
69
internal/cache/cache.go
vendored
@ -2,14 +2,9 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/btree"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
)
|
||||
|
||||
// Media Cache DBs
|
||||
@ -21,70 +16,36 @@ var (
|
||||
|
||||
// Cache wraps the btree.DB for concurrent safe
|
||||
type Cache struct {
|
||||
lock sync.RWMutex
|
||||
db *btree.DB
|
||||
ldb *leveldb.DB
|
||||
}
|
||||
|
||||
// Insert 添加媒体缓存
|
||||
func (c *Cache) Insert(md5, data []byte) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var hash [16]byte
|
||||
copy(hash[:], md5)
|
||||
c.db.Insert(&hash[0], data)
|
||||
_ = c.ldb.Put(md5, data, nil)
|
||||
}
|
||||
|
||||
// Get 获取缓存信息
|
||||
func (c *Cache) Get(md5 []byte) []byte {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
var hash [16]byte
|
||||
copy(hash[:], md5)
|
||||
return c.db.Get(&hash[0])
|
||||
got, _ := c.ldb.Get(md5, nil)
|
||||
return got
|
||||
}
|
||||
|
||||
// Delete 删除指定缓存
|
||||
func (c *Cache) Delete(md5 []byte) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var hash [16]byte
|
||||
copy(hash[:], md5)
|
||||
_ = c.db.Delete(&hash[0])
|
||||
_ = c.ldb.Delete(md5, nil)
|
||||
}
|
||||
|
||||
// Init 初始化 Cache
|
||||
func Init() {
|
||||
node, ok := base.Database["cache"]
|
||||
var conf map[string]string
|
||||
if ok {
|
||||
err := node.Decode(&conf)
|
||||
open := func(typ, path string, cache *Cache) {
|
||||
ldb, err := leveldb.OpenFile(path, &opt.Options{
|
||||
WriteBuffer: 4 * opt.KiB,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read cache config: %v", err)
|
||||
log.Fatalf("open cache %s db failed: %v", typ, err)
|
||||
}
|
||||
cache.ldb = ldb
|
||||
}
|
||||
|
||||
open := func(typ string, cache *Cache) {
|
||||
file := conf[typ]
|
||||
if file == "" {
|
||||
file = fmt.Sprintf("data/%s.db", typ)
|
||||
}
|
||||
if global.PathExists(file) {
|
||||
db, err := btree.Open(file)
|
||||
if err != nil {
|
||||
log.Fatalf("open %s cache failed: %v", typ, err)
|
||||
}
|
||||
cache.db = db
|
||||
} else {
|
||||
db, err := btree.Create(file)
|
||||
if err != nil {
|
||||
log.Fatalf("create %s cache failed: %v", typ, err)
|
||||
}
|
||||
cache.db = db
|
||||
}
|
||||
}
|
||||
open("image", &Image)
|
||||
open("video", &Video)
|
||||
open("image", "data/images", &Image)
|
||||
open("video", "data/videos", &Video)
|
||||
}
|
||||
|
308
internal/download/download.go
Normal file
308
internal/download/download.go
Normal file
@ -0,0 +1,308 @@
|
||||
// Package download provide download utility functions
|
||||
package download
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
)
|
||||
|
||||
var client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: func(request *http.Request) (u *url.URL, e error) {
|
||||
if base.Proxy == "" {
|
||||
return http.ProxyFromEnvironment(request)
|
||||
}
|
||||
return url.Parse(base.Proxy)
|
||||
},
|
||||
ForceAttemptHTTP2: false,
|
||||
MaxConnsPerHost: 0,
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: 999,
|
||||
},
|
||||
Timeout: time.Second * 5,
|
||||
}
|
||||
|
||||
// ErrOverSize 响应主体过大时返回此错误
|
||||
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"
|
||||
|
||||
// Request is a file download request
|
||||
type Request struct {
|
||||
Method string
|
||||
URL string
|
||||
Header map[string]string
|
||||
Limit int64
|
||||
Body io.Reader
|
||||
}
|
||||
|
||||
func (r Request) do() (*http.Response, error) {
|
||||
if r.Method == "" {
|
||||
r.Method = http.MethodGet
|
||||
}
|
||||
req, err := http.NewRequest(r.Method, r.URL, r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header["User-Agent"] = []string{UserAgent}
|
||||
for k, v := range r.Header {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func (r Request) body() (io.ReadCloser, error) {
|
||||
resp, err := r.do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
limit := r.Limit // check file size limit
|
||||
if limit > 0 && resp.ContentLength > limit {
|
||||
_ = resp.Body.Close()
|
||||
return nil, ErrOverSize
|
||||
}
|
||||
|
||||
if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") {
|
||||
return gzipReadCloser(resp.Body)
|
||||
}
|
||||
return resp.Body, err
|
||||
}
|
||||
|
||||
// Bytes 对给定URL发送请求,返回响应主体
|
||||
func (r Request) Bytes() ([]byte, error) {
|
||||
rd, err := r.body()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rd.Close()
|
||||
return io.ReadAll(rd)
|
||||
}
|
||||
|
||||
// JSON 发送请求, 并转换响应为JSON
|
||||
func (r Request) JSON() (gjson.Result, error) {
|
||||
rd, err := r.body()
|
||||
if err != nil {
|
||||
return gjson.Result{}, err
|
||||
}
|
||||
defer rd.Close()
|
||||
|
||||
var sb strings.Builder
|
||||
_, err = io.Copy(&sb, rd)
|
||||
if err != nil {
|
||||
return gjson.Result{}, err
|
||||
}
|
||||
|
||||
return gjson.Parse(sb.String()), nil
|
||||
}
|
||||
|
||||
func writeToFile(reader io.ReadCloser, path string) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
_, err = file.ReadFrom(reader)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteToFile 下载到制定目录
|
||||
func (r Request) WriteToFile(path string) error {
|
||||
rd, err := r.body()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rd.Close()
|
||||
return writeToFile(rd, path)
|
||||
}
|
||||
|
||||
// WriteToFileMultiThreading 多线程下载到制定目录
|
||||
func (r Request) WriteToFileMultiThreading(path string, thread int) error {
|
||||
if thread < 2 {
|
||||
return r.WriteToFile(path)
|
||||
}
|
||||
|
||||
limit := r.Limit
|
||||
type BlockMetaData struct {
|
||||
BeginOffset int64
|
||||
EndOffset int64
|
||||
DownloadedSize int64
|
||||
}
|
||||
var blocks []*BlockMetaData
|
||||
var contentLength int64
|
||||
errUnsupportedMultiThreading := errors.New("unsupported multi-threading")
|
||||
// 初始化分块或直接下载
|
||||
initOrDownload := func() error {
|
||||
header := make(map[string]string, len(r.Header))
|
||||
for k, v := range r.Header { // copy headers
|
||||
header[k] = v
|
||||
}
|
||||
header["range"] = "bytes=0-"
|
||||
req := Request{
|
||||
URL: r.URL,
|
||||
Header: header,
|
||||
}
|
||||
resp, err := req.do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return errors.New("response status unsuccessful: " + strconv.FormatInt(int64(resp.StatusCode), 10))
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
if limit > 0 && resp.ContentLength > limit {
|
||||
return ErrOverSize
|
||||
}
|
||||
if err = writeToFile(resp.Body, path); err != nil {
|
||||
return err
|
||||
}
|
||||
return errUnsupportedMultiThreading
|
||||
}
|
||||
if resp.StatusCode == http.StatusPartialContent {
|
||||
contentLength = resp.ContentLength
|
||||
if limit > 0 && resp.ContentLength > limit {
|
||||
return ErrOverSize
|
||||
}
|
||||
blockSize := contentLength
|
||||
if contentLength > 1024*1024 {
|
||||
blockSize = (contentLength / int64(thread)) - 10
|
||||
}
|
||||
if blockSize == contentLength {
|
||||
return writeToFile(resp.Body, path)
|
||||
}
|
||||
var tmp int64
|
||||
for tmp+blockSize < contentLength {
|
||||
blocks = append(blocks, &BlockMetaData{
|
||||
BeginOffset: tmp,
|
||||
EndOffset: tmp + blockSize - 1,
|
||||
})
|
||||
tmp += blockSize
|
||||
}
|
||||
blocks = append(blocks, &BlockMetaData{
|
||||
BeginOffset: tmp,
|
||||
EndOffset: contentLength - 1,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return errors.New("unknown status code")
|
||||
}
|
||||
// 下载分块
|
||||
downloadBlock := func(block *BlockMetaData) error {
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, _ = file.Seek(block.BeginOffset, io.SeekStart)
|
||||
writer := bufio.NewWriter(file)
|
||||
defer writer.Flush()
|
||||
|
||||
header := make(map[string]string, len(r.Header))
|
||||
for k, v := range r.Header { // copy headers
|
||||
header[k] = v
|
||||
}
|
||||
header["range"] = fmt.Sprintf("bytes=%d-%d", block.BeginOffset, block.EndOffset)
|
||||
req := Request{
|
||||
URL: r.URL,
|
||||
Header: header,
|
||||
}
|
||||
resp, err := req.do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return errors.New("response status unsuccessful: " + strconv.FormatInt(int64(resp.StatusCode), 10))
|
||||
}
|
||||
buffer := make([]byte, 1024)
|
||||
i, err := resp.Body.Read(buffer)
|
||||
for {
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
i64 := int64(len(buffer[:i]))
|
||||
needSize := block.EndOffset + 1 - block.BeginOffset
|
||||
if i64 > needSize {
|
||||
i64 = needSize
|
||||
err = io.EOF
|
||||
}
|
||||
_, e := writer.Write(buffer[:i64])
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
block.BeginOffset += i64
|
||||
block.DownloadedSize += i64
|
||||
if err == io.EOF || block.BeginOffset > block.EndOffset {
|
||||
break
|
||||
}
|
||||
i, err = resp.Body.Read(buffer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := initOrDownload(); err != nil {
|
||||
if err == errUnsupportedMultiThreading {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(blocks))
|
||||
var lastErr error
|
||||
for i := range blocks {
|
||||
go func(b *BlockMetaData) {
|
||||
defer wg.Done()
|
||||
if err := downloadBlock(b); err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}(blocks[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
type gzipCloser struct {
|
||||
f io.Closer
|
||||
r *gzip.Reader
|
||||
}
|
||||
|
||||
// gzipReadCloser 从 io.ReadCloser 创建 gunzip io.ReadCloser
|
||||
func gzipReadCloser(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
gzipReader, err := gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gzipCloser{
|
||||
f: reader,
|
||||
r: gzipReader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read impls io.Reader
|
||||
func (g *gzipCloser) Read(p []byte) (n int, err error) {
|
||||
return g.r.Read(p)
|
||||
}
|
||||
|
||||
// Close impls io.Closer
|
||||
func (g *gzipCloser) Close() error {
|
||||
_ = g.f.Close()
|
||||
return g.r.Close()
|
||||
}
|
@ -9,11 +9,6 @@ import (
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
)
|
||||
|
||||
func init() {
|
||||
base.IsLawfulImage = checkImage
|
||||
base.IsLawfulAudio = checkAudio
|
||||
}
|
||||
|
||||
const limit = 4 * 1024
|
||||
|
||||
func scan(r io.ReadSeeker) string {
|
||||
@ -24,12 +19,15 @@ func scan(r io.ReadSeeker) string {
|
||||
return http.DetectContentType(in)
|
||||
}
|
||||
|
||||
// checkImage 判断给定流是否为合法图片
|
||||
// CheckImage 判断给定流是否为合法图片
|
||||
// 返回 是否合法, 实际Mime
|
||||
// 判断后会自动将 Stream Seek 至 0
|
||||
func checkImage(r io.ReadSeeker) (ok bool, t string) {
|
||||
func CheckImage(r io.ReadSeeker) (t string, ok bool) {
|
||||
if base.SkipMimeScan {
|
||||
return true, ""
|
||||
return "", true
|
||||
}
|
||||
if r == nil {
|
||||
return "image/nil-stream", false
|
||||
}
|
||||
t = scan(r)
|
||||
switch t {
|
||||
@ -39,15 +37,15 @@ func checkImage(r io.ReadSeeker) (ok bool, t string) {
|
||||
return
|
||||
}
|
||||
|
||||
// checkImage 判断给定流是否为合法音频
|
||||
func checkAudio(r io.ReadSeeker) (bool, string) {
|
||||
// CheckAudio 判断给定流是否为合法音频
|
||||
func CheckAudio(r io.ReadSeeker) (string, bool) {
|
||||
if base.SkipMimeScan {
|
||||
return true, ""
|
||||
return "", true
|
||||
}
|
||||
t := scan(r)
|
||||
// std mime type detection is not full supported for audio
|
||||
if strings.Contains(t, "text") || strings.Contains(t, "image") {
|
||||
return false, t
|
||||
return t, false
|
||||
}
|
||||
return true, t
|
||||
return t, true
|
||||
}
|
246
internal/msg/element.go
Normal file
246
internal/msg/element.go
Normal file
@ -0,0 +1,246 @@
|
||||
// Package msg 提供了go-cqhttp消息中间表示,CQ码处理等等
|
||||
package msg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/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.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(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/Mrs4s/MiraiGo/message"
|
||||
)
|
||||
|
||||
// Poke 拍一拍
|
||||
type Poke struct {
|
||||
Target int64
|
||||
}
|
||||
|
||||
// Type 获取元素类型ID
|
||||
func (e *Poke) Type() message.ElementType {
|
||||
// Make message.IMessageElement Happy
|
||||
return message.At
|
||||
}
|
||||
|
||||
// LocalImage 本地图片
|
||||
type LocalImage struct {
|
||||
Stream io.ReadSeeker
|
||||
File string
|
||||
URL string
|
||||
|
||||
Flash bool
|
||||
EffectID int32
|
||||
}
|
||||
|
||||
// Type implements the message.IMessageElement.
|
||||
func (e *LocalImage) Type() message.ElementType {
|
||||
return message.Image
|
||||
}
|
||||
|
||||
// LocalVideo 本地视频
|
||||
type LocalVideo struct {
|
||||
File string
|
||||
Thumb io.ReadSeeker
|
||||
}
|
||||
|
||||
// Type impl message.IMessageElement
|
||||
func (e *LocalVideo) Type() message.ElementType {
|
||||
return message.Video
|
||||
}
|
104
internal/msg/parse.go
Normal file
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
|
||||
}
|
@ -1,22 +1,18 @@
|
||||
package coolq
|
||||
package msg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/coolq/cqcode"
|
||||
)
|
||||
|
||||
var bot = CQBot{}
|
||||
|
||||
func TestCQBot_ConvertStringMessage(t *testing.T) {
|
||||
for _, v := range bot.ConvertStringMessage(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`, message.SourcePrivate) {
|
||||
func TestParseString(t *testing.T) {
|
||||
// TODO: add more text
|
||||
for _, v := range ParseString(`[CQ:face,id=115,text=111][CQ:face,id=217]] [CQ:text,text=123] [`) {
|
||||
fmt.Println(v)
|
||||
}
|
||||
}
|
||||
@ -26,17 +22,18 @@ var (
|
||||
benchArray = gjson.Parse(`[{"type":"text","data":{"text":"asdfqwerqwerqwer"}},{"type":"face","data":{"id":"115","text":"111"}},{"type":"text","data":{"text":"asdfasdfasdfasdfasdfasdfasd"}},{"type":"face","data":{"id":"217"}},{"type":"text","data":{"text":"] "}},{"type":"text","data":{"text":"123"}},{"type":"text","data":{"text":" ["}}]`)
|
||||
)
|
||||
|
||||
func BenchmarkCQBot_ConvertStringMessage(b *testing.B) {
|
||||
func BenchmarkParseString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bot.ConvertStringMessage(bench, message.SourcePrivate)
|
||||
ParseString(bench)
|
||||
}
|
||||
b.SetBytes(int64(len(bench)))
|
||||
}
|
||||
|
||||
func BenchmarkCQBot_ConvertObjectMessage(b *testing.B) {
|
||||
func BenchmarkParseObject(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
bot.ConvertObjectMessage(benchArray, message.SourcePrivate)
|
||||
ParseObject(benchArray)
|
||||
}
|
||||
b.SetBytes(int64(len(benchArray.Raw)))
|
||||
}
|
||||
|
||||
const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&987654321[]&`
|
||||
@ -44,16 +41,7 @@ const bText = `123456789[]&987654321[]&987654321[]&987654321[]&987654321[]&98765
|
||||
func BenchmarkCQCodeEscapeText(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ret := bText
|
||||
cqcode.EscapeText(ret)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCQCodeEscapeTextBefore(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ret := bText
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
strings.ReplaceAll(ret, "]", "]")
|
||||
EscapeText(ret)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +52,6 @@ func TestCQCodeEscapeText(t *testing.T) {
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
ret = strings.ReplaceAll(ret, "]", "]")
|
||||
assert.Equal(t, ret, cqcode.EscapeText(rs))
|
||||
assert.Equal(t, ret, EscapeText(rs))
|
||||
}
|
||||
}
|
@ -7,8 +7,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/utils"
|
||||
"github.com/segmentio/asm/base64"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
@ -21,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
|
||||
@ -83,13 +81,3 @@ func SplitURL(s string) []string {
|
||||
result = append(result, s[last:])
|
||||
return result
|
||||
}
|
||||
|
||||
// Base64DecodeString decode base64 with avx2
|
||||
// see https://github.com/segmentio/asm/issues/50
|
||||
// avoid incorrect unsafe usage in origin library
|
||||
func Base64DecodeString(s string) ([]byte, error) {
|
||||
e := base64.StdEncoding
|
||||
dst := make([]byte, e.DecodedLen(len(s)))
|
||||
n, err := e.Decode(dst, utils.S2B(s))
|
||||
return dst[:n], err
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package selfupdate
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
@ -14,10 +15,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||
)
|
||||
|
||||
func readLine() (str string) {
|
||||
@ -28,11 +29,11 @@ func readLine() (str string) {
|
||||
}
|
||||
|
||||
func lastVersion() (string, error) {
|
||||
r, err := global.GetBytes("https://api.github.com/repos/Mrs4s/go-cqhttp/releases/latest")
|
||||
r, err := download.Request{URL: "https://api.github.com/repos/Mrs4s/go-cqhttp/releases/latest"}.JSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gjson.GetBytes(r, "tag_name").Str, nil
|
||||
return r.Get("tag_name").Str, nil
|
||||
}
|
||||
|
||||
// CheckUpdate 检查更新
|
||||
@ -69,12 +70,12 @@ func binaryName() string {
|
||||
|
||||
func checksum(github, version string) []byte {
|
||||
sumURL := fmt.Sprintf("%v/Mrs4s/go-cqhttp/releases/download/%v/go-cqhttp_checksums.txt", github, version)
|
||||
closer, err := global.HTTPGetReadCloser(sumURL)
|
||||
sum, err := download.Request{URL: sumURL}.Bytes()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rd := bufio.NewReader(closer)
|
||||
rd := bufio.NewReader(bytes.NewReader(sum))
|
||||
for {
|
||||
str, err := rd.ReadString('\n')
|
||||
if err != nil {
|
||||
|
@ -1,5 +1,4 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package selfupdate
|
||||
|
||||
|
15
main.go
15
main.go
@ -1,16 +1,25 @@
|
||||
// Package main
|
||||
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/mime" // mime检查模块
|
||||
_ "github.com/Mrs4s/go-cqhttp/db/leveldb" // leveldb 数据库支持
|
||||
_ "github.com/Mrs4s/go-cqhttp/modules/silk" // silk编码模块
|
||||
// 其他模块
|
||||
// _ "github.com/Mrs4s/go-cqhttp/db/sqlite3" // sqlite3 数据库支持
|
||||
// _ "github.com/Mrs4s/go-cqhttp/db/mongodb" // mongodb 数据库支持
|
||||
// _ "github.com/Mrs4s/go-cqhttp/modules/pprof" // pprof 性能分析
|
||||
)
|
||||
|
||||
func main() {
|
||||
gocq.Main()
|
||||
terminal.SetTitle()
|
||||
gocq.InitBase()
|
||||
gocq.PrepareData()
|
||||
gocq.LoginInteract()
|
||||
_ = terminal.DisableQuickEdit()
|
||||
_ = terminal.EnableVT100()
|
||||
gocq.WaitSignal()
|
||||
_ = terminal.RestoreInputMode()
|
||||
}
|
||||
|
@ -5,22 +5,89 @@ 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 {
|
||||
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 "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 {
|
||||
case ".get_word_slices":
|
||||
p0 := p.Get("content").String()
|
||||
return c.bot.CQGetWordSlices(p0)
|
||||
case ".ocr_image", "ocr_image":
|
||||
p0 := p.Get("image").String()
|
||||
return c.bot.CQOcrImage(p0)
|
||||
case "_del_group_notice":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("notice_id").String()
|
||||
return c.bot.CQDelGroupMemo(p0, p1)
|
||||
case "_get_group_notice":
|
||||
p0 := p.Get("group_id").Int()
|
||||
return c.bot.CQGetGroupMemo(p0)
|
||||
@ -36,10 +103,6 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
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)
|
||||
@ -92,7 +155,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)
|
||||
@ -115,10 +178,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()
|
||||
@ -166,8 +229,6 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
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)
|
||||
@ -175,18 +236,15 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
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)
|
||||
return c.bot.CQGetStatus(spec)
|
||||
case "get_supported_actions":
|
||||
return c.bot.CQGetSupportedActions(spec)
|
||||
case "get_topic_channel_feeds":
|
||||
p0 := p.Get("guild_id").Uint()
|
||||
p1 := p.Get("channel_id").Uint()
|
||||
return c.bot.CQGetTopicChannelFeeds(p0, p1)
|
||||
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)
|
||||
@ -195,21 +253,6 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
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)
|
||||
@ -219,23 +262,6 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
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)
|
||||
@ -263,6 +289,13 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
p2 = pt.Bool()
|
||||
}
|
||||
return c.bot.CQSetGroupAdmin(p0, p1, p2)
|
||||
case "set_group_anonymous":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := true
|
||||
if pt := p.Get("enable"); pt.Exists() {
|
||||
p1 = pt.Bool()
|
||||
}
|
||||
return c.bot.CQSetGroupAnonymous(p0, p1)
|
||||
case "set_group_anonymous_ban":
|
||||
p0 := p.Get("group_id").Int()
|
||||
p1 := p.Get("[anonymous_flag,anonymous.flag].0").String()
|
||||
@ -337,5 +370,11 @@ func (c *Caller) call(action string, p Getter) global.MSG {
|
||||
p2 := p.Get("name").String()
|
||||
p3 := p.Get("folder").String()
|
||||
return c.bot.CQUploadGroupFile(p0, p1, p2, p3)
|
||||
case "upload_private_file":
|
||||
p0 := p.Get("user_id").Int()
|
||||
p1 := p.Get("file").String()
|
||||
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
|
||||
|
@ -35,6 +35,7 @@ type Account struct {
|
||||
ReLogin *Reconnect `yaml:"relogin"`
|
||||
UseSSOAddress bool `yaml:"use-sso-address"`
|
||||
AllowTempSession bool `yaml:"allow-temp-session"`
|
||||
DisableProtocolUpdate bool `yaml:"disable-protocol-update"`
|
||||
}
|
||||
|
||||
// Config 总配置文件
|
||||
@ -55,6 +56,7 @@ 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"`
|
||||
} `yaml:"message"`
|
||||
|
||||
Output struct {
|
||||
|
@ -15,6 +15,8 @@ account: # 账号相关
|
||||
use-sso-address: true
|
||||
# 是否允许发送临时会话消息
|
||||
allow-temp-session: false
|
||||
# 是否禁用协议更新
|
||||
disable-protocol-update: false
|
||||
|
||||
heartbeat:
|
||||
# 心跳频率, 单位秒
|
||||
@ -43,6 +45,8 @@ message:
|
||||
extra-reply-data: false
|
||||
# 跳过 Mime 扫描, 忽略错误数据
|
||||
skip-mime-scan: false
|
||||
# 是否自动转换 WebP 图片
|
||||
convert-webp-image: false
|
||||
|
||||
output:
|
||||
# 日志等级 trace,debug,info,warn,error
|
||||
@ -78,11 +82,12 @@ database: # 数据库相关设置
|
||||
# 启用将会增加10-20MB的内存占用和一定的磁盘空间
|
||||
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
|
||||
enable: true
|
||||
|
||||
# 媒体文件缓存, 删除此项则使用缓存文件(旧版行为)
|
||||
cache:
|
||||
image: data/image.db
|
||||
video: data/video.db
|
||||
sqlite3:
|
||||
# 是否启用内置sqlite3数据库
|
||||
# 启用将会增加一定的内存占用和一定的磁盘空间
|
||||
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
|
||||
enable: false
|
||||
cachettl: 3600000000000 # 1h
|
||||
|
||||
# 连接服务列表
|
||||
servers:
|
||||
|
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
|
||||
}
|
169
pkg/onebot/supported.go
Normal file
169
pkg/onebot/supported.go
Normal file
@ -0,0 +1,169 @@
|
||||
// Code generated by cmd/api-generator. DO NOT EDIT.
|
||||
|
||||
package onebot
|
||||
|
||||
var supportedV11 = []string{
|
||||
".get_word_slices",
|
||||
".handle_quick_operation",
|
||||
".ocr_image",
|
||||
"ocr_image",
|
||||
"_del_group_notice",
|
||||
"_get_group_notice",
|
||||
"_get_model_show",
|
||||
"_send_group_notice",
|
||||
"_set_model_show",
|
||||
"can_send_image",
|
||||
"can_send_record",
|
||||
"check_url_safely",
|
||||
"create_group_file_folder",
|
||||
"create_guild_role",
|
||||
"delete_essence_msg",
|
||||
"delete_friend",
|
||||
"delete_group_file",
|
||||
"delete_group_folder",
|
||||
"delete_guild_role",
|
||||
"delete_msg",
|
||||
"delete_unidirectional_friend",
|
||||
"download_file",
|
||||
"get_essence_msg_list",
|
||||
"get_forward_msg",
|
||||
"get_friend_list",
|
||||
"get_group_at_all_remain",
|
||||
"get_group_file_system_info",
|
||||
"get_group_file_url",
|
||||
"get_group_files_by_folder",
|
||||
"get_group_honor_info",
|
||||
"get_group_info",
|
||||
"get_group_list",
|
||||
"get_group_member_info",
|
||||
"get_group_member_list",
|
||||
"get_group_msg_history",
|
||||
"get_group_root_files",
|
||||
"get_group_system_msg",
|
||||
"get_guild_channel_list",
|
||||
"get_guild_list",
|
||||
"get_guild_member_list",
|
||||
"get_guild_member_profile",
|
||||
"get_guild_meta_by_guest",
|
||||
"get_guild_msg",
|
||||
"get_guild_roles",
|
||||
"get_guild_service_profile",
|
||||
"get_image",
|
||||
"get_login_info",
|
||||
"get_msg",
|
||||
"get_online_clients",
|
||||
"get_status",
|
||||
"get_stranger_info",
|
||||
"get_supported_actions",
|
||||
"get_topic_channel_feeds",
|
||||
"get_unidirectional_friend_list",
|
||||
"get_version_info",
|
||||
"mark_msg_as_read",
|
||||
"qidian_get_account_info",
|
||||
"reload_event_filter",
|
||||
"send_forward_msg",
|
||||
"send_group_forward_msg",
|
||||
"send_group_msg",
|
||||
"send_group_sign",
|
||||
"send_guild_channel_msg",
|
||||
"send_msg",
|
||||
"send_private_forward_msg",
|
||||
"send_private_msg",
|
||||
"set_essence_msg",
|
||||
"set_friend_add_request",
|
||||
"set_group_add_request",
|
||||
"set_group_admin",
|
||||
"set_group_anonymous",
|
||||
"set_group_anonymous_ban",
|
||||
"set_group_ban",
|
||||
"set_group_card",
|
||||
"set_group_kick",
|
||||
"set_group_leave",
|
||||
"set_group_name",
|
||||
"set_group_portrait",
|
||||
"set_group_special_title",
|
||||
"set_group_whole_ban",
|
||||
"set_guild_member_role",
|
||||
"set_qq_profile",
|
||||
"update_guild_role",
|
||||
"upload_group_file",
|
||||
"upload_private_file",
|
||||
}
|
||||
|
||||
var supportedV12 = []string{
|
||||
".get_word_slices",
|
||||
".ocr_image",
|
||||
"ocr_image",
|
||||
"_del_group_notice",
|
||||
"_get_group_notice",
|
||||
"_get_model_show",
|
||||
"_send_group_notice",
|
||||
"_set_model_show",
|
||||
"check_url_safely",
|
||||
"create_group_file_folder",
|
||||
"create_guild_role",
|
||||
"delete_essence_msg",
|
||||
"delete_friend",
|
||||
"delete_group_file",
|
||||
"delete_group_folder",
|
||||
"delete_guild_role",
|
||||
"delete_msg",
|
||||
"delete_unidirectional_friend",
|
||||
"download_file",
|
||||
"get_essence_msg_list",
|
||||
"get_forward_msg",
|
||||
"get_friend_list",
|
||||
"get_group_at_all_remain",
|
||||
"get_group_file_system_info",
|
||||
"get_group_file_url",
|
||||
"get_group_files_by_folder",
|
||||
"get_group_honor_info",
|
||||
"get_group_info",
|
||||
"get_group_list",
|
||||
"get_group_member_info",
|
||||
"get_group_member_list",
|
||||
"get_group_msg_history",
|
||||
"get_group_root_files",
|
||||
"get_group_system_msg",
|
||||
"get_guild_channel_list",
|
||||
"get_guild_list",
|
||||
"get_guild_member_list",
|
||||
"get_guild_member_profile",
|
||||
"get_guild_meta_by_guest",
|
||||
"get_guild_msg",
|
||||
"get_guild_roles",
|
||||
"get_guild_service_profile",
|
||||
"get_image",
|
||||
"get_self_info",
|
||||
"get_msg",
|
||||
"get_online_clients",
|
||||
"get_status",
|
||||
"get_user_info",
|
||||
"get_supported_actions",
|
||||
"get_topic_channel_feeds",
|
||||
"get_unidirectional_friend_list",
|
||||
"mark_msg_as_read",
|
||||
"qidian_get_account_info",
|
||||
"reload_event_filter",
|
||||
"send_group_sign",
|
||||
"send_guild_channel_msg",
|
||||
"set_essence_msg",
|
||||
"set_friend_add_request",
|
||||
"set_group_add_request",
|
||||
"set_group_admin",
|
||||
"set_group_anonymous",
|
||||
"set_group_anonymous_ban",
|
||||
"set_group_ban",
|
||||
"set_group_card",
|
||||
"set_group_kick",
|
||||
"set_group_leave",
|
||||
"set_group_name",
|
||||
"set_group_portrait",
|
||||
"set_group_special_title",
|
||||
"set_group_whole_ban",
|
||||
"set_guild_member_role",
|
||||
"set_qq_profile",
|
||||
"update_guild_role",
|
||||
"upload_group_file",
|
||||
"upload_private_file",
|
||||
}
|
@ -5,6 +5,7 @@ package server
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -28,7 +29,9 @@ func Daemon() {
|
||||
execArgs = append(execArgs, args[i])
|
||||
}
|
||||
|
||||
proc := exec.Command(os.Args[0], execArgs...)
|
||||
ex, _ := os.Executable()
|
||||
p, _ := filepath.Abs(ex)
|
||||
proc := exec.Command(p, execArgs...)
|
||||
err := proc.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -29,11 +29,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 +59,7 @@ type httpServerPost struct {
|
||||
type httpServer struct {
|
||||
api *api.Caller
|
||||
accessToken string
|
||||
spec *onebot.Spec // onebot spec
|
||||
}
|
||||
|
||||
// HTTPClient 反向HTTP上报客户端
|
||||
@ -81,6 +84,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 +108,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 +158,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 +205,12 @@ func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request
|
||||
if request.URL.Path == "/" {
|
||||
action := strings.TrimSuffix(ctx.Get("action").Str, "_async")
|
||||
log.Debugf("HTTPServer接收到API调用: %v", action)
|
||||
response = s.api.Call(action, 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 +260,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)
|
||||
@ -356,13 +377,13 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) {
|
||||
for i := uint64(0); i <= c.MaxRetries; i++ {
|
||||
// see https://stackoverflow.com/questions/31337891/net-http-http-contentlength-222-with-body-length-0
|
||||
// we should create a new request for every single post trial
|
||||
req, err = http.NewRequest("POST", c.addr, bytes.NewReader(e.JSONBytes()))
|
||||
req, err = http.NewRequest(http.MethodPost, c.addr, bytes.NewReader(e.JSONBytes()))
|
||||
if err != nil {
|
||||
log.Warnf("上报 Event 数据到 %v 时创建请求失败: %v", c.addr, err)
|
||||
return
|
||||
}
|
||||
req.Header = header
|
||||
res, err = c.client.Do(req)
|
||||
res, err = c.client.Do(req) // nolint:bodyclose
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func (l *lambdaResponseWriter) flush() error {
|
||||
Body: body,
|
||||
})
|
||||
|
||||
r, _ := http.NewRequest("POST", cli.responseURL, buffer)
|
||||
r, _ := http.NewRequest(http.MethodPost, cli.responseURL, buffer)
|
||||
do, err := cli.client.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/Mrs4s/go-cqhttp/modules/api"
|
||||
"github.com/Mrs4s/go-cqhttp/modules/config"
|
||||
"github.com/Mrs4s/go-cqhttp/modules/filter"
|
||||
"github.com/Mrs4s/go-cqhttp/pkg/onebot"
|
||||
)
|
||||
|
||||
type webSocketServer struct {
|
||||
@ -191,9 +192,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)
|
||||
}
|
||||
@ -463,14 +468,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 +485,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