Skip to content

Commit 400def6

Browse files
committed
first commit
0 parents  commit 400def6

16 files changed

+645
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
build/
2+
log/
3+
config.yaml
4+
*.exe

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# qqbot-demo
2+
3+
`config.example.yaml` 改为 `config.yaml` 并填入相关信息

config.example.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
bot:
2+
uin: 123 # 机器人QQ号
3+
appId: 123
4+
token: string1
5+
secret: string2
6+
7+
log:
8+
path: ./log/bot.log

config/config.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package config
2+
3+
import (
4+
"log"
5+
6+
"github.com/spf13/viper"
7+
)
8+
9+
var appConfig AppConfig
10+
11+
type AppConfig struct {
12+
Bot BotConf `yaml:"bot"`
13+
Log LogConf `yaml:"log"`
14+
}
15+
16+
type BotConf struct {
17+
Token string `yaml:"token"`
18+
Uin uint64 `yaml:"uin"`
19+
AppId uint64 `yaml:"appId"`
20+
}
21+
22+
type LogConf struct {
23+
Path string `yaml:"path"`
24+
}
25+
26+
func Init() {
27+
viper.SetConfigName("config")
28+
viper.SetConfigType("yaml")
29+
viper.AddConfigPath("./")
30+
err := viper.ReadInConfig()
31+
if err != nil {
32+
log.Fatal("read in config failed:", err)
33+
}
34+
viper.Unmarshal(&appConfig)
35+
}
36+
37+
func Bot() BotConf {
38+
return appConfig.Bot
39+
}
40+
41+
func Log() LogConf {
42+
return appConfig.Log
43+
}
44+
45+
func App() AppConfig {
46+
return appConfig
47+
}

go.mod

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module github.com/hduhelp/qqbot-demo
2+
3+
go 1.22.1
4+
5+
require (
6+
github.com/spf13/viper v1.19.0
7+
github.com/tencent-connect/botgo v0.1.6
8+
go.uber.org/zap v1.27.0
9+
)
10+
11+
require (
12+
github.com/fsnotify/fsnotify v1.7.0 // indirect
13+
github.com/go-resty/resty/v2 v2.6.0 // indirect
14+
github.com/gorilla/websocket v1.5.3 // indirect
15+
github.com/hashicorp/hcl v1.0.0 // indirect
16+
github.com/magiconair/properties v1.8.7 // indirect
17+
github.com/mitchellh/mapstructure v1.5.0 // indirect
18+
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
19+
github.com/sagikazarmark/locafero v0.6.0 // indirect
20+
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
21+
github.com/sourcegraph/conc v0.3.0 // indirect
22+
github.com/spf13/afero v1.11.0 // indirect
23+
github.com/spf13/cast v1.6.0 // indirect
24+
github.com/spf13/pflag v1.0.5 // indirect
25+
github.com/subosito/gotenv v1.6.0 // indirect
26+
github.com/tidwall/gjson v1.9.3 // indirect
27+
github.com/tidwall/match v1.1.1 // indirect
28+
github.com/tidwall/pretty v1.2.0 // indirect
29+
go.uber.org/multierr v1.11.0 // indirect
30+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
31+
golang.org/x/net v0.27.0 // indirect
32+
golang.org/x/sys v0.22.0 // indirect
33+
golang.org/x/text v0.16.0 // indirect
34+
gopkg.in/ini.v1 v1.67.0 // indirect
35+
gopkg.in/yaml.v3 v3.0.1 // indirect
36+
)

go.sum

+171
Large diffs are not rendered by default.

handler/eventType.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package handler
2+
3+
const (
4+
EventGuildCreate = "GUILD_CREATE"
5+
EventGuildUpdate = "GUILD_UPDATE"
6+
EventGuildDelete = "GUILD_DELETE"
7+
EventChannelCreate = "CHANNEL_CREATE"
8+
EventChannelUpdate = "CHANNEL_UPDATE"
9+
EventChannelDelete = "CHANNEL_DELETE"
10+
EventGuildMemberAdd = "GUILD_MEMBER_ADD"
11+
EventGuildMemberUpdate = "GUILD_MEMBER_UPDATE"
12+
EventGuildMemberRemove = "GUILD_MEMBER_REMOVE"
13+
EventMessageCreate = "MESSAGE_CREATE"
14+
EventMessageReactionAdd = "MESSAGE_REACTION_ADD"
15+
EventMessageReactionRemove = "MESSAGE_REACTION_REMOVE"
16+
EventAtMessageCreate = "AT_MESSAGE_CREATE"
17+
EventPublicMessageDelete = "PUBLIC_MESSAGE_DELETE"
18+
EventDirectMessageCreate = "DIRECT_MESSAGE_CREATE"
19+
EventDirectMessageDelete = "DIRECT_MESSAGE_DELETE"
20+
EventAudioStart = "AUDIO_START"
21+
EventAudioFinish = "AUDIO_FINISH"
22+
EventAudioOnMic = "AUDIO_ON_MIC"
23+
EventAudioOffMic = "AUDIO_OFF_MIC"
24+
EventMessageAuditPass = "MESSAGE_AUDIT_PASS"
25+
EventMessageAuditReject = "MESSAGE_AUDIT_REJECT"
26+
EventMessageDelete = "MESSAGE_DELETE"
27+
EventForumThreadCreate = "FORUM_THREAD_CREATE"
28+
EventForumThreadUpdate = "FORUM_THREAD_UPDATE"
29+
EventForumThreadDelete = "FORUM_THREAD_DELETE"
30+
EventForumPostCreate = "FORUM_POST_CREATE"
31+
EventForumPostDelete = "FORUM_POST_DELETE"
32+
EventForumReplyCreate = "FORUM_REPLY_CREATE"
33+
EventForumReplyDelete = "FORUM_REPLY_DELETE"
34+
EventForumAuditResult = "FORUM_PUBLISH_AUDIT_RESULT"
35+
EventInteractionCreate = "INTERACTION_CREATE"
36+
37+
EventC2CMessageCreate = "C2C_MESSAGE_CREATE" // 用户单聊发消息给机器人时候
38+
EventGroupAtMessageCreate = "GROUP_AT_MESSAGE_CREATE" // 用户在群里@机器人时收到的消息
39+
)

handler/handler.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package handler
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
7+
"github.com/hduhelp/qqbot-demo/logging"
8+
"github.com/hduhelp/qqbot-demo/model"
9+
"github.com/tencent-connect/botgo/dto"
10+
"github.com/tencent-connect/botgo/event"
11+
"github.com/tencent-connect/botgo/openapi"
12+
"go.uber.org/zap"
13+
)
14+
15+
var api openapi.OpenAPI
16+
17+
func RegisterApi(openapi openapi.OpenAPI) {
18+
api = openapi
19+
}
20+
21+
func Get() event.PlainEventHandler {
22+
return func(event *dto.WSPayload, data []byte) error {
23+
var g model.General
24+
if err := json.Unmarshal(data, &g); err != nil {
25+
logging.Error("failed to decode json", zap.Error(err))
26+
return nil
27+
}
28+
switch g.T {
29+
// 用户单聊发消息给机器人时候
30+
case EventC2CMessageCreate:
31+
do := ExtractPrivateChatMessage(data)
32+
msgId := do.Data.ID
33+
return SendTextMessagePrivate(do.Data.Author.UserOpenid, msgId, "不是,哥们儿")
34+
// 用户在群里@机器人时收到的消息
35+
case EventGroupAtMessageCreate:
36+
do := ExtractGroupMessage(data)
37+
msgId := do.Data.ID
38+
if strings.HasPrefix(strings.TrimSpace(do.Data.Content), "/ping") {
39+
return SendTextMessageGroup(do.Data.GroupOpenid, msgId, "pong")
40+
}
41+
return SendTextMessageGroup(do.Data.GroupOpenid, msgId, "不是,哥们儿")
42+
}
43+
return nil
44+
}
45+
}

handler/util.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package handler
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/hduhelp/qqbot-demo/logging"
9+
"github.com/hduhelp/qqbot-demo/model"
10+
"go.uber.org/zap"
11+
)
12+
13+
func SendTextMessagePrivate(userOpenId, prvMsgId, msg string) (err error) {
14+
url := fmt.Sprintf("https://sandbox.api.sgroup.qq.com/v2/users/%s/messages", userOpenId)
15+
_, err = api.Transport(context.Background(), "POST", url, SendMessage{
16+
Content: msg,
17+
MsgType: 0,
18+
MsgId: prvMsgId,
19+
})
20+
return
21+
}
22+
23+
func SendTextMessageGroup(groupId, prvMsgId, msg string) (err error) {
24+
url := fmt.Sprintf("https://sandbox.api.sgroup.qq.com/v2/groups/%s/messages", groupId)
25+
_, err = api.Transport(context.Background(), "POST", url, SendMessage{
26+
Content: msg,
27+
MsgType: 0,
28+
MsgId: prvMsgId,
29+
})
30+
return
31+
}
32+
33+
type SendMessage struct {
34+
Content string `json:"content"`
35+
MsgType int `json:"msg_type"`
36+
MsgId string `json:"msg_id"` // 带上该字段后发送消息变成被动消息,限制比主动消息少
37+
}
38+
39+
func ExtractPrivateChatMessage(data []byte) *model.PrivateChatMessage {
40+
var msg model.PrivateChatMessage
41+
if err := json.Unmarshal(data, &msg); err != nil {
42+
logging.Error("failed to unmarshal PrivateChatMessage", zap.Error(err))
43+
return nil
44+
}
45+
return &msg
46+
}
47+
func ExtractGroupMessage(data []byte) *model.GroupAtMessage {
48+
var msg model.GroupAtMessage
49+
if err := json.Unmarshal(data, &msg); err != nil {
50+
logging.Error("failed to unmarshal GroupAtMessage", zap.Error(err))
51+
return nil
52+
}
53+
return &msg
54+
}

logging/wrapper.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package logging
2+
3+
import (
4+
"go.uber.org/zap/zapcore"
5+
)
6+
7+
func Debug(msg string, fields ...zapcore.Field) {
8+
logger.Debug(msg, fields...)
9+
}
10+
func Info(msg string, fields ...zapcore.Field) {
11+
logger.Info(msg, fields...)
12+
}
13+
func Warn(msg string, fields ...zapcore.Field) {
14+
logger.Warn(msg, fields...)
15+
}
16+
func Error(msg string, fields ...zapcore.Field) {
17+
logger.Error(msg, fields...)
18+
}
19+
func DPanic(msg string, fields ...zapcore.Field) {
20+
logger.DPanic(msg, fields...)
21+
}
22+
func Fatal(msg string, fields ...zapcore.Field) {
23+
logger.Fatal(msg, fields...)
24+
}
25+
26+
func Debugf(msg string, fields ...any) {
27+
logger.Sugar().Debugf(msg, fields...)
28+
}
29+
func Infof(msg string, fields ...any) {
30+
logger.Sugar().Infof(msg, fields...)
31+
}
32+
func Warnf(msg string, fields ...any) {
33+
logger.Sugar().Warnf(msg, fields...)
34+
}
35+
func Errorf(msg string, fields ...any) {
36+
logger.Sugar().Errorf(msg, fields...)
37+
}
38+
func DPanicf(msg string, fields ...any) {
39+
logger.Sugar().DPanicf(msg, fields...)
40+
}
41+
func Fatalf(msg string, fields ...any) {
42+
logger.Sugar().Fatalf(msg, fields...)
43+
}

logging/zap.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package logging
2+
3+
import (
4+
"log"
5+
"os"
6+
7+
"github.com/hduhelp/qqbot-demo/config"
8+
"go.uber.org/zap"
9+
"go.uber.org/zap/zapcore"
10+
)
11+
12+
var logger *zap.Logger
13+
14+
func Init() {
15+
localLogPath := config.Log().Path
16+
17+
serverMode := os.Getenv("AIRP_MODE")
18+
development := serverMode == "release"
19+
20+
lv := zapcore.InfoLevel
21+
if development {
22+
lv = zapcore.DebugLevel
23+
}
24+
25+
logLevel := zap.NewAtomicLevelAt(lv)
26+
27+
var zc = zap.Config{
28+
Level: logLevel,
29+
Development: development,
30+
DisableCaller: false,
31+
DisableStacktrace: false,
32+
Sampling: nil,
33+
Encoding: "json",
34+
EncoderConfig: zapcore.EncoderConfig{
35+
MessageKey: "message",
36+
LevelKey: "level",
37+
TimeKey: "time",
38+
NameKey: "name",
39+
CallerKey: "caller",
40+
StacktraceKey: "stacktrace",
41+
LineEnding: zapcore.DefaultLineEnding,
42+
EncodeLevel: zapcore.LowercaseLevelEncoder,
43+
EncodeTime: zapcore.ISO8601TimeEncoder,
44+
EncodeDuration: zapcore.StringDurationEncoder,
45+
EncodeCaller: zapcore.ShortCallerEncoder,
46+
EncodeName: zapcore.FullNameEncoder,
47+
},
48+
OutputPaths: []string{"stdout", localLogPath},
49+
ErrorOutputPaths: []string{"stderr", localLogPath},
50+
InitialFields: map[string]any{"app": "qqbot-paper-airplane"},
51+
}
52+
53+
var err error
54+
if logger, err = zc.Build(zap.AddCallerSkip(1)); err != nil {
55+
log.Fatal("init logger failed:", err)
56+
}
57+
}
58+
59+
func Logger() *zap.Logger {
60+
return logger
61+
}

main.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/hduhelp/qqbot-demo/config"
8+
"github.com/hduhelp/qqbot-demo/handler"
9+
"github.com/hduhelp/qqbot-demo/logging"
10+
"github.com/tencent-connect/botgo"
11+
"github.com/tencent-connect/botgo/token"
12+
"github.com/tencent-connect/botgo/websocket"
13+
"go.uber.org/zap"
14+
)
15+
16+
func init() {
17+
config.Init()
18+
logging.Init()
19+
}
20+
21+
func main() {
22+
conf := config.Bot()
23+
token := token.BotToken(conf.AppId, conf.Token)
24+
api := botgo.NewSandboxOpenAPI(token).WithTimeout(3 * time.Second)
25+
handler.RegisterApi(api)
26+
27+
botgo.SetLogger(logging.Logger().Sugar())
28+
29+
ws, err := api.WS(context.Background(), nil, "")
30+
if err != nil {
31+
logging.Fatalf("%+v, error:%v", ws, err)
32+
}
33+
34+
intent := websocket.RegisterHandlers(handler.Get())
35+
intent |= 1 << 25 // 注册群和私聊消息
36+
if err := botgo.NewSessionManager().Start(ws, token, &intent); err != nil {
37+
logging.Fatal("unexpected exit", zap.Error(err))
38+
}
39+
}

0 commit comments

Comments
 (0)