From 4836098abf3a218b3f153a0c5b49569c8a7b130f Mon Sep 17 00:00:00 2001 From: Miuzarte <982809597@qq.com> Date: Tue, 24 Oct 2023 00:39:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=98=E6=96=B9AI=E6=80=BB=E7=BB=93,?= =?UTF-8?q?=20improve:=20=E5=8D=A1=E7=89=87=E9=93=BE=E6=8E=A5=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E5=90=88=E5=B9=B6=E8=BD=AC=E5=8F=91=E5=8F=91=E9=80=81?= =?UTF-8?q?,=20=E5=88=AB=E7=9A=84=E6=87=92=E5=BE=97=E6=89=BE=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AIReply2077.go | 6 +- EasyBot/EasyBot.go | 159 ++++++++++++++++++++++++++++++++---- apiForBertVITS2/api.py | 177 +++++++++++++++++++++++++--------------- at.go | 6 +- bertVits2.go | 179 ++++++++++++++++++++++------------------- bilibili.go | 122 +++++++++++++++++++--------- bilibiliWbi.go | 116 ++++++++++++++++++++++++++ bot.go | 10 +-- cardParse.go | 12 ++- context.go | 72 +++++++++++++++++ corpus.go | 6 +- default_config.yml | 21 +++-- doLua.go | 108 +++++++++++++++++++++++++ go.mod | 33 +++++--- go.sum | 52 ++++++++++++ info.go | 4 +- main.go | 124 ++++++++++++++++++++++++++-- pixiv.go | 4 +- push.go | 4 +- recall.go | 6 +- search.go | 18 ++--- setu.go | 4 +- watcher.go | 165 +++++++++++++++++++++++++++++++++++++ 23 files changed, 1139 insertions(+), 269 deletions(-) create mode 100644 bilibiliWbi.go create mode 100644 context.go create mode 100644 doLua.go create mode 100644 watcher.go diff --git a/AIReply2077.go b/AIReply2077.go index 7133e87..5819a9e 100644 --- a/AIReply2077.go +++ b/AIReply2077.go @@ -6,11 +6,11 @@ import ( ) var ( - replacer = strings.NewReplacer("吗", "", "你", "我", "?", "!", "?", "!") + SuperAiReplacer = strings.NewReplacer("吗", "", "你", "我", "是不是", "是", "?", "!", "?", "!") ) func checkAIReply2077(ctx *EasyBot.CQMessage) { - if matches := ctx.RegexpMustCompile(`[吗?\?]\s*$`); ctx.IsToMe() && len(matches) > 0 { - ctx.SendMsgReply(replacer.Replace(ctx.GetRawMessageOrMessage())) + if matches := ctx.RegexpFindAllStringSubmatch(`[吗?\?]\s*$|是不是`); ctx.IsToMe() && len(matches) > 0 { + ctx.SendMsgReply(ctx.StringsReplace(SuperAiReplacer)) } } diff --git a/EasyBot/EasyBot.go b/EasyBot/EasyBot.go index d582821..ba3d8f3 100644 --- a/EasyBot/EasyBot.go +++ b/EasyBot/EasyBot.go @@ -462,6 +462,7 @@ var ( noSU error noConnect error needEcho error + wrongType error }{ general: errors.New("OCCURRED ERROR"), noEcho: errors.New("CANT GET ECHO"), @@ -469,6 +470,7 @@ var ( noSU: errors.New("AT LEAST ONE SU IS REQUIRED"), noConnect: errors.New("DID NOT CONNECT TO GO-CQHTTP"), needEcho: errors.New("API CALLING MUST BE WITH ECHO"), + wrongType: errors.New("WRONG INPUT TYPE"), } ) @@ -924,7 +926,10 @@ retryLoop: } func (bot *CQBot) initSelfInfo() { + callTime := time.Now() selfID, selfNickName, err := bot.GetLoginInfo() + usedTime := time.Since(callTime) + bot.log.Debug("[EasyBot] 初始化用时: ", usedTime) if err != nil { bot.log.Fatal("[EasyBot] 初始化账号信息失败, err: ", err) } @@ -1447,7 +1452,7 @@ func (bot *CQBot) saveMsg(msg *CQMessage) { // @的人列表 func (msg *CQMessage) collectAt() (atWho []int) { - matches := msg.RegexpMustCompile(`\[CQ:reply,id=(-?[0-9]*)]`) //回复也算@ + matches := msg.RegexpFindAllStringSubmatch(`\[CQ:reply,id=(-?[0-9]*)]`) //回复也算@ if len(matches) > 0 { replyid, _ := strconv.Atoi(matches[0][1]) switch msg.MessageType { @@ -1465,7 +1470,7 @@ func (msg *CQMessage) collectAt() (atWho []int) { msg.Bot.MTPMutex.Unlock() } } - matches = msg.RegexpMustCompile(`\[CQ:at,qq=(\d+)]`) + matches = msg.RegexpFindAllStringSubmatch(`\[CQ:at,qq=(\d+)]`) if len(matches) > 0 { for _, match := range matches { atId, _ := strconv.Atoi(match[1]) @@ -1487,7 +1492,7 @@ func (msg *CQMessage) collectAt() (atWho []int) { // 具体化回复,go-cqhttp.extra-reply-data: true时不必要,但是开了那玩意又会导致回复带上原文又触发一遍机器人 func (msg *CQMessage) entityReply() (message string) { message = msg.GetRawMessageOrMessage() - match := msg.RegexpMustCompile(`\[CQ:reply,id=(-?[0-9]*)]`) + match := msg.RegexpFindAllStringSubmatch(`\[CQ:reply,id=(-?[0-9]*)]`) if len(match) > 0 { replyIdS := match[0][1] replyId, _ := strconv.Atoi(replyIdS) @@ -1550,6 +1555,9 @@ func (bot *CQBot) frMark(fr *CQNoticeFriendRecall) { recalledMsg.Extra.Recalled = true recalledMsg.Extra.OperatorID = fr.UserID bot.MTPMutex.Lock() + if bot.MessageTablePrivate[fr.UserID] == nil { + bot.MessageTablePrivate[fr.UserID] = make(map[int]*CQMessage) + } bot.MessageTablePrivate[fr.UserID][fr.MessageID] = recalledMsg bot.MTPMutex.Unlock() } @@ -1578,6 +1586,9 @@ func (bot *CQBot) grMark(gr *CQNoticeGroupRecall) { recalledMsg.Extra.Recalled = true recalledMsg.Extra.OperatorID = gr.OperatorID bot.MTGMutex.Lock() + if bot.MessageTableGroup[gr.GroupID] == nil { + bot.MessageTableGroup[gr.GroupID] = make(map[int]*CQMessage) + } bot.MessageTableGroup[gr.GroupID][gr.MessageID] = recalledMsg bot.MTGMutex.Unlock() } @@ -1812,6 +1823,66 @@ func (bot *CQBot) GetMsg(message_id int) (msg *CQMessage, err error) { return } +type downloadFile struct { + File string `json:"file"` +} + +func toStringArrHeaders(origin map[string]string) (to []string) { + for k, v := range origin { + to = append(to, fmt.Sprint(k+"="+v)) + } + return to +} + +/* +下载文件到gocq本地, 返回的路径可直接塞进CQ码里发送 + +headers 可以为 []string 或 map[string]string + +(最终都会转为 json 数组) +*/ +func (bot *CQBot) DownloadFile(url string, thread_count int, headers any) (path string, err error) { + switch v := headers.(type) { + case map[string]string: + headers = toStringArrHeaders(v) + case []string: + default: + return "", e.wrongType + } + + action := "download_file" + echo := genEcho(action) + p := bot.newApiCalling(action, echo) + + params := map[string]any{ + "url": url, + "thread_count": thread_count, + "headers": headers, + } + p.Raw["params"] = params + + resp, err := bot.CallApiAndListenEcho(p, echo) + if err != nil { + return "", err + } + respByte, err := json.Marshal(resp.Data) + if err != nil { + bot.log.Warn("[EasyBot] 序列化出错(json.Marshal(resp.Data)), err: ", err, + "\n resp.Data: ", resp.Data, + "\n Marshal by gson: ", gson.New(resp.Data).JSON("", "")) + return "", err + } + file := &downloadFile{} + err = json.Unmarshal(respByte, file) + if err != nil { + bot.log.Warn("[EasyBot] 反序列化出错(json.Unmarshal(respByte, msg)), err: ", err, + "\n respByte: ", string(respByte), + "\n Unmarshal by gson: ", gson.New(respByte).JSON("", "")) + return "", err + } + return file.File, nil +} + /* 发送私聊消息 @@ -1912,7 +1983,19 @@ func (bot *CQBot) SendGroupMsgs(group_ids []int, message any, otherParams ...any } /* -创建合并转发消息节点 +直接引用消息合并转发 +*/ +func NewMsgForwardNode(msgId any) CQForwardNode { + return CQForwardNode{ + "type": "node", + "data": map[string]any{ + "id": msgId, + }, + } +} + +/* +创建自定义合并转发消息节点 type nodeData struct { //标准的gocq合并转发消息节点 name string //消息发送者名字 @@ -1922,7 +2005,7 @@ func (bot *CQBot) SendGroupMsgs(group_ids []int, message any, otherParams ...any seq int64 //起始消息序号(为0时不上报) } */ -func NewForwardNode(name string, uin int, content string, timestamp, seq int64) CQForwardNode { +func NewCustomForwardNode(name string, uin int, content string, timestamp, seq int64) CQForwardNode { if timestamp == 0 { timestamp = time.Now().Unix() } @@ -1980,7 +2063,7 @@ func FastNewForwardMsg(name string, uin int, timestamp, seq int64, content ...st } for _, content_ := range content { forwardMsg = AppendForwardMsg(forwardMsg, - NewForwardNode(name, uin, content_, timestamp, seq)) + NewCustomForwardNode(name, uin, content_, timestamp, seq)) } return forwardMsg } @@ -2064,10 +2147,19 @@ func (ctx *CQMessage) GetRawMessageOrMessage() string { 正则完全匹配 */ -func (ctx *CQMessage) RegexpMustCompile(exp string) [][]string { +func (ctx *CQMessage) RegexpFindAllStringSubmatch(exp string) [][]string { return regexp.MustCompile(exp).FindAllStringSubmatch(ctx.GetRawMessageOrMessage(), -1) } +/* + return regexp.MustCompile(exp).ReplaceAllString(ctx.GetRawMessageOrMessage(), replaceTo) + +正则完全匹配替换 +*/ +func (ctx *CQMessage) RegexpReplaceAll(exp, replaceTo string) string { + return regexp.MustCompile(exp).ReplaceAllString(ctx.GetRawMessageOrMessage(), replaceTo) +} + /* return strings.Contains(ctx.GetRawMessageOrMessage(), substr) @@ -2077,6 +2169,15 @@ func (ctx *CQMessage) StringsContains(substr string) bool { return strings.Contains(ctx.GetRawMessageOrMessage(), substr) } +/* + return strings.Replace(ctx.GetRawMessageOrMessage()) + +字符串替换 +*/ +func (ctx *CQMessage) StringsReplace(replacer *strings.Replacer) string { + return replacer.Replace(ctx.GetRawMessageOrMessage()) +} + // 匹配超级用户 func (ctx *CQMessage) IsSU() bool { for _, su := range ctx.Bot.superUsers { @@ -2143,7 +2244,7 @@ func (ctx *CQMessage) IsToMe() bool { }() isAtMe := func() bool { - match := ctx.RegexpMustCompile(fmt.Sprintf(`\[CQ:at,qq=%d]`, ctx.Bot.selfID)) + match := ctx.RegexpFindAllStringSubmatch(fmt.Sprintf(`\[CQ:at,qq=%d]`, ctx.Bot.selfID)) return len(match) > 0 }() @@ -2196,7 +2297,7 @@ func (ctx *CQMessage) GetCardOrNickname() string { // 获取回复的消息 func (ctx *CQMessage) GetReplyedMsg() (replyedMsg *CQMessage, err error) { - matches := ctx.RegexpMustCompile(`\[CQ:reply,id=(-?[0-9]*)]`) + matches := ctx.RegexpFindAllStringSubmatch(`\[CQ:reply,id=(-?[0-9]*)]`) if len(matches) == 0 { return nil, errors.New("NO REPLY MESSAGE") } @@ -2269,7 +2370,7 @@ func (f *formater) Reply(id int) string { return fmt.Sprintf("[CQ:reply,id=%d]", id) } -// 编码自定义回复至CQcode +// 编码自定义回复至 CQcode func (f *formater) CustomReply(text string, qq int, timestamp int, seq int) string { if text == "" { text = "<内部错误:未指定自定义回复内容>" @@ -2298,16 +2399,42 @@ func (f *formater) ImageLocal(path string) string { return f.Image(data) } -// 将音频数据以base64的方式编码至CQcode +// 将图片数据以 base64 的方式编码至 CQcode func (f *formater) Image(data []byte) string { imageB64 := base64.StdEncoding.EncodeToString(data) return "[CQ:image,file=base64://" + imageB64 + "]" } +// fmt.Sprintf("[CQ:video,file=%s]", path) +func (f *formater) Video(path string) string { + return fmt.Sprintf("[CQ:video,file=%s,cover=/root/Miuzarte/go-cqhttp/data/0.00.jpeg]", path) +} + +/* +将音频 base64 编码至 CQcode, + +sendDirectly 为 true 时: 不调用 ffmpeg 转换至 amr +*/ +func (f *formater) VocalBase64(audioB64 string, sendDirectly bool) string { + if sendDirectly { + return "[CQ:record,file=base64://" + audioB64 + "]" + } + + data, err := base64.StdEncoding.DecodeString(audioB64) + if err != nil { + return "<内部错误:转换amr时base64解码失败: " + err.Error() + ">" + } + amrData, err := f.utils.Ffmpeg2amr(data) + if err != nil { + return "<内部错误:调用ffmpeg转换amr失败: " + err.Error() + ">" + } + return "[CQ:record,file=base64://" + base64.StdEncoding.EncodeToString(amrData) + "]" +} + /* -读取文件并将音频数据以base64的方式编码至CQcode, +读取文件并将音频数据以 base64 的方式编码至 CQcode, -sendDirectly为true时: 不调用ffmpeg转换至amr +sendDirectly 为 true 时: 不调用 ffmpeg 转换至 amr */ func (f *formater) VocalLocal(path string, sendDirectly bool) string { audioData, err := os.ReadFile(path) @@ -2318,9 +2445,9 @@ func (f *formater) VocalLocal(path string, sendDirectly bool) string { } /* -将音频数据以base64的方式编码至CQcode, +将音频数据以 base64 的方式编码至 CQcode, -sendDirectly为true时: 不调用ffmpeg转换至amr +sendDirectly 为 true 时: 不调用 ffmpeg 转换至 amr */ func (f *formater) Vocal(data []byte, sendDirectly bool) string { if sendDirectly { @@ -2334,7 +2461,7 @@ func (f *formater) Vocal(data []byte, sendDirectly bool) string { return "[CQ:record,file=base64://" + base64.StdEncoding.EncodeToString(amrData) + "]" } -// 调用path中的ffmpeg转换音频至amr格式 +// 调用 path 中的 ffmpeg 转换音频至 amr 格式 func (u *utilsFunc) Ffmpeg2amr(wav []byte) (amr []byte, err error) { cmd := exec.Command("ffmpeg", "-f", "wav", "-i", "pipe:0", "-ar", "8000", "-ac", "1", "-f", "amr", "pipe:1") cmd.Stdin = strings.NewReader(string(wav)) diff --git a/apiForBertVITS2/api.py b/apiForBertVITS2/api.py index b9d4ccd..e1063fa 100644 --- a/apiForBertVITS2/api.py +++ b/apiForBertVITS2/api.py @@ -1,52 +1,41 @@ from fastapi import FastAPI, Request import torch -import argparse import commons import utils import uvicorn import json -import datetime from models import SynthesizerTrn from text.symbols import symbols from text import cleaned_text_to_sequence, get_bert from text.cleaner import clean_text -import torchaudio import os import sys import signal -import asyncio - +from io import BytesIO +from av import open as avopen +from scipy.io import wavfile +import base64 from numba.core.errors import NumbaWarning import warnings -warnings.simplefilter('ignore', category=NumbaWarning) - +warnings.simplefilter("ignore", category=NumbaWarning) import logging -logging.getLogger('numba').setLevel(logging.WARNING) +logging.getLogger("numba").setLevel(logging.WARNING) current_dir = os.path.dirname(os.path.abspath(__file__)) app = FastAPI() -# device = "cpu" +# Load Generator +hps = utils.get_hparams_from_file("./configs/config.json") device = "cuda:0" if torch.cuda.is_available() else "cpu" -hps = None -net_g = None -speakers = [] - -def load_model_and_config(model_path, config_path): - global net_g, hps, speakers - hps = utils.get_hparams_from_file(config_path) - - net_g = SynthesizerTrn( +net_g = SynthesizerTrn( len(symbols), hps.data.filter_length // 2 + 1, hps.train.segment_size // hps.data.hop_length, n_speakers=hps.data.n_speakers, - **hps.model).to(device) - _ = net_g.eval() - _ = utils.load_checkpoint(model_path, net_g, None, skip_optimizer=True) - - speaker_ids = hps.data.spk2id - speakers = list(speaker_ids.keys()) + **hps.model, +).to(device) +_ = net_g.eval() +_ = utils.load_checkpoint("logs/as/G_57000.pth", net_g, None, skip_optimizer=True) def get_text(text, language_str, hps): norm_text, phone, tone, word2ph = clean_text(text, language_str) @@ -59,67 +48,127 @@ def get_text(text, language_str, hps): for i in range(len(word2ph)): word2ph[i] = word2ph[i] * 2 word2ph[0] += 1 - bert = get_bert(norm_text, word2ph, language_str) - - assert bert.shape[-1] == len(phone) - + bert = get_bert(norm_text, word2ph, language_str, device) + del word2ph + assert bert.shape[-1] == len(phone), phone + + if language_str == "ZH": + bert = bert + ja_bert = torch.zeros(768, len(phone)) + elif language_str == "JA": + ja_bert = bert + bert = torch.zeros(1024, len(phone)) + else: + bert = torch.zeros(1024, len(phone)) + ja_bert = torch.zeros(768, len(phone)) + assert bert.shape[-1] == len( + phone + ), f"Bert seq len {bert.shape[-1]} != {len(phone)}" phone = torch.LongTensor(phone) tone = torch.LongTensor(tone) language = torch.LongTensor(language) + return bert, ja_bert, phone, tone, language - return bert, phone, tone, language - -def infer(text, sdp_ratio, noise_scale, noise_scale_w, length_scale, sid): - bert, phones, tones, lang_ids = get_text(text, "ZH", hps) +def infer(text, sdp_ratio, noise_scale, noise_scale_w, length_scale, sid, language): + bert, ja_bert, phones, tones, lang_ids = get_text(text, language, hps) with torch.no_grad(): x_tst = phones.to(device).unsqueeze(0) tones = tones.to(device).unsqueeze(0) lang_ids = lang_ids.to(device).unsqueeze(0) bert = bert.to(device).unsqueeze(0) + ja_bert = ja_bert.to(device).unsqueeze(0) x_tst_lengths = torch.LongTensor([phones.size(0)]).to(device) speakers = torch.LongTensor([hps.data.spk2id[sid]]).to(device) - audio = net_g.infer(x_tst, x_tst_lengths, speakers, tones, lang_ids, bert, sdp_ratio=sdp_ratio, - noise_scale=noise_scale, noise_scale_w=noise_scale_w, length_scale=length_scale)[0][0, 0].data.cpu().float().numpy() + audio = ( + net_g.infer( + x_tst, + x_tst_lengths, + speakers, + tones, + lang_ids, + bert, + ja_bert, + sdp_ratio=sdp_ratio, + noise_scale=noise_scale, + noise_scale_w=noise_scale_w, + length_scale=length_scale, + )[0][0, 0] + .data.cpu() + .float() + .numpy() + ) return audio +def replace_punctuation(text, i=2): + punctuation = ",。?!" + for char in punctuation: + text = text.replace(char, char * i) + return text + + +def wav2(i, o, format): + inp = avopen(i, "rb") + out = avopen(o, "wb", format=format) + if format == "ogg": + format = "libvorbis" + + ostream = out.add_stream(format) + + for frame in inp.decode(audio=0): + for p in ostream.encode(frame): + out.mux(p) + + for p in ostream.encode(None): + out.mux(p) + + out.close() + inp.close() + def restart(): - python = sys.executable - os.execl(python, python, * sys.argv) + python = sys.executable + os.execl(python, python, * sys.argv) @app.post("/") async def tts_endpoint(request: Request): global net_g, hps, speakers json_post_raw = await request.json() - command = json_post_raw.get('command') - text = json_post_raw.get('text') - speaker = json_post_raw.get('speaker') - #speaker = "suijiSUI" - sdp_ratio = json_post_raw.get('sdp_ratio', 0.2) - noise_scale = json_post_raw.get('noise_scale', 0.6) - noise_scale_w = json_post_raw.get('noise_scale_w', 0.8) - length_scale = json_post_raw.get('length_scale', 1.0) + command = json_post_raw.get("command") + text = json_post_raw.get("text").replace("/n", "") + speaker = json_post_raw.get("speaker", "suijiSUI") + language = json_post_raw.get("language", "ZH") + sdp_ratio = json_post_raw.get("sdp_ratio", 0.2) + noise_scale = json_post_raw.get("noise_scale", 0.5) + noise_scale_w = json_post_raw.get("noise_scale_w", 0.6) + length_scale = json_post_raw.get("length_scale", 1.2) + + try: + if command == "/unload": + restart() + elif command == "/exit": + os.kill(os.getpid(), signal.SIGTERM) + if text == "": + return {"code": 400, "error": "Empty text"} + if speaker == "": + return {"code": 400, "error": "No speaker"} + if language not in ("JP", "ZH"): + return "Invalid language" + if length_scale >= 2: + return {"code": 400, "error": "Too big length_scale"} + if len(text) >= 250: + return {"code": 400, "error": "Too long text(len(text)>=250)"} + except: + return {"code": 400, "error": "Invalid parameter"} - if command == "/refresh": - restart() - elif command == "/exit": - os.kill(os.getpid(), signal.SIGTERM) - - if text == "": - return {"code": 400, "output": "", "error": "输入不可为空"} + with torch.no_grad(): + audio = infer(text, sdp_ratio, noise_scale, noise_scale_w, length_scale, speaker, language) - audio_output = infer(text, sdp_ratio, noise_scale, noise_scale_w, length_scale, speaker) - output_file_name = "output.wav" - torchaudio.save(output_file_name, torch.tensor(audio_output).unsqueeze(0), hps.data.sampling_rate) + wav_byte = None + with BytesIO() as wav: + wavfile.write(wav, hps.data.sampling_rate, audio) + torch.cuda.empty_cache() + wav_byte = wav.getvalue() - return {"code": 0, "output": (current_dir + "\\" + output_file_name), "error": ""} + return {"code": 0, "output": base64.b64encode(wav_byte).decode("utf-8"), "error": ""} if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-m", "--model", default="./logs/as/G_22000.pth", help="path of your model") - parser.add_argument("-c", "--config", default="./configs/config.json", help="path of your config file") - - args = parser.parse_args() - - load_model_and_config(args.model, args.config) - - uvicorn.run(app, host='0.0.0.0', port=9876, workers=1) + uvicorn.run(app, host="0.0.0.0", port=9876, workers=1) diff --git a/at.go b/at.go index 64fb7c3..2dd4b15 100644 --- a/at.go +++ b/at.go @@ -54,7 +54,7 @@ func (w *whoAtMe) format() (forwardMsg EasyBot.CQForwardMsg) { if atListLen > 99 { //超过100条合并转发放不下, 标题占1条 atListLen = 99 } - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( //标题 + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( //标题 "NothingBot", bot.GetSelfID(), func() string { @@ -96,7 +96,7 @@ func (w *whoAtMe) format() (forwardMsg EasyBot.CQForwardMsg) { return bot.GetSelfID() }() content := strings.ReplaceAll(atMsg.Extra.MessageWithReply, "CQ:at,", "CQ:at,​") //插入零宽空格阻止CQ码解析 - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( name, uin, content, 0, 0)) } return @@ -104,7 +104,7 @@ func (w *whoAtMe) format() (forwardMsg EasyBot.CQForwardMsg) { // 谁at我 func checkWhoAtMe(ctx *EasyBot.CQMessage) { - match := ctx.RegexpMustCompile(`^谁(@|[aA艾][tT特])(我|(\s*\[CQ:at,qq=)?([0-9]{1,11})?(]\s*))$`) + match := ctx.RegexpFindAllStringSubmatch(`^谁(@|[aA艾][tT特])(我|(\s*\[CQ:at,qq=)?([0-9]{1,11})?(]\s*))$`) if len(match) > 0 { var atId int if match[0][2] == "我" { diff --git a/bertVits2.go b/bertVits2.go index abaa934..4cae7e0 100644 --- a/bertVits2.go +++ b/bertVits2.go @@ -5,20 +5,19 @@ import ( "bufio" "encoding/json" "errors" - "fmt" "os" "strings" "time" "github.com/moxcomic/ihttp" log "github.com/sirupsen/logrus" - "github.com/spf13/viper" ) type bertVitsPost struct { Command string `json:"command"` Text string `json:"text"` Speaker string `json:"speaker"` + Lang string `json:"language"` SDP float32 `json:"sdp_ratio,omitempty"` NS float32 `json:"noise_scale,omitempty"` NSW float32 `json:"noise_scale_w,omitempty"` @@ -27,7 +26,7 @@ type bertVitsPost struct { type bertVitsResp struct { Code int `json:"code"` - Output string `json:"output"` + Output string `json:"output"` // 音频base64, 直接发 Error string `json:"error"` } @@ -40,7 +39,7 @@ func (p *bertVitsPost) post() (*bertVitsResp, error) { Post().ToString() if err != nil { log.Error("[BertVITS2] ihttp error: ", err) - return nil, errors.New("[BertVITS2] BertVITS2后端未运行") + return nil, errors.New("BertVITS2后端未运行") } log.Debug("[BertVITS2] resp: ", resp) r := &bertVitsResp{} @@ -48,44 +47,50 @@ func (p *bertVitsPost) post() (*bertVitsResp, error) { return r, nil } -func bertVits2TTS(intput string) (output string, err error) { - speaker := "suijiSUI" - p := &bertVitsPost{ - Text: intput, - Speaker: speaker, - } - nowTime := time.Now().Format(timeLayout.L24) - recordHist := func(stat string) { // 记录历史 - if err = appendToFile("./tts_history.txt", - fmt.Sprintf("%s (%s)\n%s: %s\n\n", - nowTime, stat, - speaker, intput)); err != nil { - log.Warn("[BertVITS2] 历史写入失败") - } +func bertVits2TTS(ctx *EasyBot.CQMessage, text, speaker, lang string) (outputB64 string, err error) { + if text == "" { + return "", errors.New("empty text") } - resp, err := p.post() - if err != nil { - s := "后端未运行" - recordHist("Failed: " + s) - return "", errors.New(s) - } - if resp.Error != "" { - recordHist("Failed: " + resp.Error) - return "", errors.New(resp.Error) + if speaker == "" { + speaker = "suijiSUI" } - if resp.Code != 0 { - s := "TTS FAILED" - recordHist("Failed: " + s) - return "", errors.New(s) + if lang == "" { + lang = "ZH" } - if resp.Output == "" { - s := "OUTPUT IS EMPTY" - recordHist("Failed: " + s) - return "", errors.New(s) + p := &bertVitsPost{ + Text: text, + Speaker: speaker, + Lang: lang, } + resp, err := p.post() + var state string + switch { + case err != nil: + state = "后端未运行" + case resp.Error != "": + state = "Failed: " + resp.Error + err = errors.New(state) + case resp.Code != 0: + state = "Code 0 failed" + err = errors.New(state) + case resp.Output == "": + state = "Empty output" + err = errors.New(state) + default: + state = "Success" + } + p.recordHist(ctx.GroupID, ctx.UserID, ctx.GetCardOrNickname(), state) + return resp.Output, err +} - recordHist("Success") - return resp.Output, nil +// 记录历史 +func (p *bertVitsPost) recordHist(groupId, userId int, userName, state string) { + nowTime := time.Now().Format(timeLayout.L24) + if err := appendToFile("tts_history.csv", + toCsv(nowTime, groupId, userId, userName, state, p.Speaker, p.Lang, p.Text), + ); err != nil { + log.Warn("[BertVITS2] 历史写入失败") + } } // 追加文本 @@ -109,9 +114,20 @@ func appendToFile(filePath, content string) error { return nil } +var ( + langMap = map[string]string{ + "中文": "ZH", + "汉语": "ZH", + "ZH": "ZH", + "日文": "JP", + "日语": "JP", + "JP": "JP", + } +) + func checkBertVITS2(ctx *EasyBot.CQMessage) { //后端控制 - matches := ctx.RegexpMustCompile(`^(unload|refresh|exit|卸载|清理|退出).*(模型|model)$`) + matches := ctx.RegexpFindAllStringSubmatch(`^(unload|refresh|exit|卸载|清理|退出).*(模型|model)$`) if len(matches) > 0 && ctx.IsPrivateSU() { p := &bertVitsPost{} switch matches[0][1] { @@ -128,74 +144,71 @@ func checkBertVITS2(ctx *EasyBot.CQMessage) { } return } - matches = ctx.RegexpMustCompile(`(?s)让岁己(说|复述)\s*(.*)`) + matches = ctx.RegexpFindAllStringSubmatch(`(?s)让岁己(用(中文|汉语|ZH|日文|日语|JP))?(说|复述)\s*(.+)`) if len(matches) > 0 { - isInWhite := func() (is bool) { - - var v *viper.Viper - - for i := 0; i < len(v.GetStringSlice("bertVits.whiteList.group")); i++ { //群聊黑名单 - if ctx.GroupID == v.GetInt(fmt.Sprint("bertVits.whiteList.group.", i)) { - return true - } - } - for i := 0; i < len(v.GetStringSlice("bertVits.whiteList.private")); i++ { //私聊黑名单 - if ctx.UserID == v.GetInt(fmt.Sprint("bertVits.whiteList.private.", i)) { - return true - } - } - return false - }() - if !ctx.IsSU() && !isInWhite { - ctx.SendMsg("[BertVITS2] Permission Denied") - return + // isInWhite := func() (is bool) { + // var v *viper.Viper + // for i := 0; i < len(v.GetStringSlice("bertVits.whiteList.group")); i++ { //群聊白名单 + // if ctx.GroupID == v.GetInt(fmt.Sprint("bertVits.whiteList.group.", i)) { + // return true + // } + // } + // for i := 0; i < len(v.GetStringSlice("bertVits.whiteList.private")); i++ { //私聊白名单 + // if ctx.UserID == v.GetInt(fmt.Sprint("bertVits.whiteList.private.", i)) { + // return true + // } + // } + // return false + // }() + // if !ctx.IsSU() && !isInWhite { + // ctx.SendMsg("[BertVITS2] Permission Denied") + // return + // } + lang := "ZH" + if l, ok := langMap[matches[0][2]]; ok { + lang = l } - text := trimOuterQuotes(matches[0][2]) + text := trimOuterQuotes(matches[0][4]) replyMsg, err := ctx.GetReplyedMsg() if replyMsg != nil && err == nil { //复述回复时无视内容 text = trimOuterQuotes(replyMsg.RawMessage) } - sendVitsMsg(ctx, text) + sendVitsMsg(ctx, text, lang) } } -func sendVitsMsg(ctx *EasyBot.CQMessage, text string) { - log.Debug("text: ", text) +// 发送vits消息 +func sendVitsMsg(ctx *EasyBot.CQMessage, text, lang string) { + log.Debug("[BertVITS2] Vits text: ", text) if len(strings.TrimSpace(text)) == 0 { ctx.SendMsgReply("[BertVITS2] 文本输入不可为空!") return } - output, err := bertVits2TTS(text) - if err != nil { - log.Error("[BertVITS2] 出现错误(1): ", err) - ctx.SendMsgReply("[BertVITS2] 出现错误(1):", err.Error()) - return - } - wavData, err := os.ReadFile(output) + outputB64, err := bertVits2TTS(ctx, text, "", lang) if err != nil { - log.Error("[BertVITS2] 出现错误(2): ", err) - ctx.SendMsgReply("[BertVITS2] 出现错误(2):", err.Error()) + log.Error("[BertVITS2] 发生错误: ", err) + ctx.SendMsgReply("[BertVITS2] 发生错误:", err.Error()) return } - ctx.SendMsg(bot.Utils.Format.Vocal(wavData, false)) + ctx.SendMsg(bot.Utils.Format.VocalBase64(outputB64, false)) } // 去除最外层一对互相匹配的引号 func trimOuterQuotes(s string) string { - r := []rune(s) - if len(r) < 2 { + runeArr := []rune(s) + if len(runeArr) < 2 { return s } - f := r[0] - l := r[(len(r) - 1)] + left := runeArr[0] + right := runeArr[(len(runeArr) - 1)] - if (f == '\'' && l == '\'') || - (f == '`' && l == '`') || - (f == '"' && l == '"') || - (f == '“' && l == '”') || - (f == '”' && l == '“') { - r = r[1 : len(r)-1] + if (left == '\'' && right == '\'') || + (left == '`' && right == '`') || + (left == '"' && right == '"') || + (left == '“' && right == '”') || + (left == '”' && right == '“') { + runeArr = runeArr[1 : len(runeArr)-1] } - return string(r) + return string(runeArr) } diff --git a/bilibili.go b/bilibili.go index 8bfc8fb..96ea252 100644 --- a/bilibili.go +++ b/bilibili.go @@ -104,9 +104,11 @@ var everyBiliLinkRegexp = func() (everyBiliLinkRegexp string) { return }() +const standardLength = len("BV1vh4y1U71j") + // bv转av func bv2av(bv string) (av int) { - if length, standard := len(bv), len("BV1vh4y1U71j"); length < standard { + if length := len(bv); length < standardLength { log.Warn("[bv2av] 输入了错误的bv号: ", bv, " (len: ", length, ")") return 0 } @@ -161,7 +163,7 @@ func getVoteJson(voteid int) gson.JSON { func formatDynamic(g gson.JSON) string { dynamic := g.Get("modules.module_dynamic") //动态主体 id := g.Get("id_str").Str() //动态id - uid := g.Get("modules.module_author.name").Int() //发布者uid + uid := g.Get("modules.module_author.mid").Int() //发布者uid name := g.Get("modules.module_author.name").Str() //发布者用户名 action := g.Get("modules.module_author.pub_action").Str() //"投稿了视频"/"发布了动态视频"/"投稿了文章"/"直播了" topic := func(exist bool) (topic string) { //话题 @@ -369,6 +371,35 @@ func formatDynamic(g gson.JSON) string { } } +// 获取官方AI总结 +func getArchiveSummary(aid int) (summary string, err error) { + cid := getCid(aid) + signedUrl := SignURL(fmt.Sprintf("https://api.bilibili.com/x/web-interface/view/conclusion/get?aid=%d&cid=%d", aid, cid)) + videoSummary, err := ihttp.New().WithUrl(signedUrl).WithHeaders(iheaders).Get().ToGson() + if err != nil { + return + } + // 大总结 + summary = videoSummary.Get("data.model_result.summary").Str() + // 大纲 + outlines := videoSummary.Get("data.model_result.outline").Arr() + if summary == "" && len(outlines) == 0 { + return "", nil + } + for _, outline := range outlines { + summary += "\n● " + outline.Get("title").Str() + // 小节 + for _, partOutline := range outline.Get("part_outline").Arr() { + timestamp := partOutline.Get("timestamp").Int() + content := partOutline.Get("content").Str() + summary += fmt.Sprintf("\n%s: %s", + formatTimeSimple(int64(timestamp)), content, + ) + } + } + return +} + // av号获取视频数据.Get("data")) func getArchiveJson(aid int) (archiveJson gson.JSON, stateJson gson.JSON) { archiveJson, err := ihttp.New().WithUrl("https://api.bilibili.com/x/web-interface/view"). @@ -443,7 +474,9 @@ func initCache() { as := &archiveSubtitle{} err = json.Unmarshal(fileData, as) if err != nil { - log.Error("[bilibili] as unmarshal cache err: ", err) + log.Error("[NothingBot] 反序列化出错(json.Unmarshal(fileData, as)), err: ", err, + "\n respByte: ", string(fileData), + "\n Unmarshal by gson: ", gson.New(fileData).JSON("", "")) break } as.marshal() @@ -463,7 +496,9 @@ func initCache() { at := &articleText{} err = json.Unmarshal(fileData, at) if err != nil { - log.Error("[bilibili] at unmarshal cache err: ", err) + log.Error("[NothingBot] 反序列化出错(json.Unmarshal(fileData, at)), err: ", err, + "\n respByte: ", string(fileData), + "\n Unmarshal by gson: ", gson.New(fileData).JSON("", "")) break } at.marshal() @@ -515,10 +550,10 @@ var videoQual = struct { } type archiveVideo struct { - aid int - cid int - hasAudio bool //是否带音频 - localPath string + aid int + cid int + hasAudio bool //是否带音频 + path string } // 获取视频流(mp4) @@ -529,23 +564,39 @@ func getVideoMp4(aid int, qual int) *archiveVideo { checkDir(tempDir) cid := getCid(aid) url := getVideoUrlMp4(aid, cid, qual) - fileName := fmt.Sprint("av", aid, "_c", cid, "_qn", qual, ".mp4") - localPath := tempDir + fileName - videoByte, err := ihttp.New().WithUrl(url). - WithHeaders(iheaders). - Get().ToBytes() - if err != nil { - log.Error("[bilibili] 视频(mp4)下载失败 err: ", err) - return nil - } else { - log.Debug("[bilibili] len(videoByte): ", len(videoByte)) + path := "" + + if lor := gocqIsLocalOrRemote(); lor == "local" { //gocq在本地时通过bot下载 + fileName := fmt.Sprint("av", aid, "_c", cid, "_qn", qual, ".mp4") + path := tempDir + fileName + videoByte, err := ihttp.New().WithUrl(url). + WithHeaders(iheaders). + Get().ToBytes() + if err != nil { + log.Error("[bilibili] 视频(mp4)下载失败 err: ", err) + return nil + } + err = os.WriteFile(path, videoByte, 0664) + if err != nil { + log.Error("[bilibili] 视频(mp4)写入本地失败 err: ", err) + } + log.Debug("[bilibili] local path: ", path, " len(videoByte): ", len(videoByte)) + } else if lor == "remote" { //否则调用远程下载 + p, err := bot.DownloadFile(url, 1, iheaders) + path = p + if err != nil { + log.Error("[bilibili] 远程视频(mp4)下载失败 err: ", err) + return nil + } else { + log.Debug("[bilibili] remote path: ", path) + } } - os.WriteFile(localPath, videoByte, 0664) + return &archiveVideo{ - aid: aid, - cid: cid, - hasAudio: true, - localPath: localPath, + aid: aid, + cid: cid, + hasAudio: true, + path: path, } } @@ -1141,7 +1192,7 @@ au%d cover, sid, title, - timeFormat(int64(duration)), + formatTime(int64(duration)), stuffs, tags, intro, @@ -1394,7 +1445,7 @@ type dynamicContent struct { } // 内容解析并格式化 -func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id string, kind string, summary bool, tts bool, upload bool) (content string) { +func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id, kind string, summary, tts, upload bool) (content string) { var op bool if ctx != nil { op = isBiliLinkOverParse(ctx, id, kind) @@ -1426,7 +1477,7 @@ func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id string, kind string, summ s := dc.summary() ctx.SendMsg(s) if tts { - sendVitsMsg(ctx, rmTitle.Replace(s)) //不念“概述”、“要点” + sendVitsMsg(ctx, rmTitle.Replace(s), "ZH") //不念“概述”、“要点” } }() } @@ -1438,6 +1489,12 @@ func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id string, kind string, summ content = fmt.Sprintf("[NothingBot] [ERROR] [parse] 视频av%s信息获取错误: code%d", id, g.Get("code").Int()) } else { content = formatArchive(g.Get("data"), h.Get("data")) + sum, err := getArchiveSummary(aid) + if err != nil { + log.Error("[NothingBot] 总结获取错误 err: ", err) + } else if sum != "" { + content += "\n\n哔哩哔哩AI总结:\n" + sum + } if summary { go func() { var as *archiveSubtitle @@ -1455,14 +1512,13 @@ func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id string, kind string, summ s := as.summary() ctx.SendMsg(s) if tts { - sendVitsMsg(ctx, rmTitle.Replace(s)) //不念“概述”、“要点” + sendVitsMsg(ctx, rmTitle.Replace(s), "ZH") //不念“概述”、“要点” } } else { ctx.SendMsgReply("[NothingBot] [Error] 字幕转录失败力") } }() } - upload = false if upload { go func() { var av *archiveVideo @@ -1473,13 +1529,7 @@ func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id string, kind string, summ } if av != nil { archiveVideoTable[aid] = av //缓存视频 - videoData, err := os.ReadFile(av.localPath) - if err == nil { - _ = videoData - //ctx.sendVideo(videoData) - } else { - log.Error("[NothingBot] [Error] 视频读取失败") - } + ctx.SendMsg(bot.Utils.Format.Video(av.path)) } else { ctx.SendMsgReply("[NothingBot] [Error] 视频获取失败力") } @@ -1506,7 +1556,7 @@ func parseAndFormatBiliLink(ctx *EasyBot.CQMessage, id string, kind string, summ s := at.summary() ctx.SendMsg(s) if tts { - sendVitsMsg(ctx, rmTitle.Replace(s)) //不念“概述”、“要点” + sendVitsMsg(ctx, rmTitle.Replace(s), "ZH") //不念“概述”、“要点” } } else { ctx.SendMsgReply("[NothingBot] 文章正文获取失败力") diff --git a/bilibiliWbi.go b/bilibiliWbi.go new file mode 100644 index 0000000..cdc1c7a --- /dev/null +++ b/bilibiliWbi.go @@ -0,0 +1,116 @@ +package main + +import ( + "crypto/md5" + "encoding/hex" + "net/url" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/moxcomic/ihttp" +) + +type wbiCache struct { + imgKey string + subKey string +} + +var ( + mixinKeyEncTab = []int{ + 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, + 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, + 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, + 36, 20, 34, 44, 52, + } + cache wbiCache + lastUpdateTime time.Time + replacements = [...]string{"!", "'", "(", ")", "*"} +) + +// SignURL wbi签名包装 https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/wbi.md +func SignURL(urlStr string) string { + urlObj, _ := url.Parse(urlStr) + imgKey, subKey := getWbiKeysCached() + query := urlObj.Query() + params := map[string]string{} + for k, v := range query { + if len(v) > 0 { + params[k] = v[0] + } + } + newParams := wbiSign(params, imgKey, subKey) + for k, v := range newParams { + query.Set(k, v) + } + urlObj.RawQuery = query.Encode() + newURL := urlObj.String() + return newURL +} + +func getMixinKey(orig string) string { + var str strings.Builder + t := 0 + for _, v := range mixinKeyEncTab { + if v < len(orig) { + str.WriteByte(orig[v]) + t++ + } + if t > 31 { + break + } + } + return str.String() +} + +func wbiSign(params map[string]string, imgKey string, subKey string) map[string]string { + mixinKey := getMixinKey(imgKey + subKey) + currTime := strconv.FormatInt(time.Now().Unix(), 10) + params["wts"] = currTime + // Sort keys + keys := make([]string, 0, len(params)) + for k, v := range params { + keys = append(keys, k) + for _, old := range replacements { + v = strings.ReplaceAll(v, old, "") + } + params[k] = v + } + sort.Strings(keys) + h := md5.New() + for k, v := range keys { + h.Write([]byte(v)) + h.Write([]byte{'='}) + h.Write([]byte(params[v])) + if k < len(keys)-1 { + h.Write([]byte{'&'}) + } + } + h.Write([]byte(mixinKey)) + params["w_rid"] = hex.EncodeToString(h.Sum(make([]byte, 0, md5.Size))) + return params +} + +func getWbiKeysCached() (string, string) { + if time.Since(lastUpdateTime).Minutes() > 10 { + imgKey, subKey := getWbiKeys() + cache.imgKey = imgKey + cache.subKey = subKey + lastUpdateTime = time.Now() + } + return cache.imgKey, cache.subKey +} + +func getWbiKeys() (string, string) { + data, _ := ihttp.New().WithUrl("https://api.bilibili.com/x/web-interface/nav"). + WithHeaders(iheaders).Get().ToGson() + imgURL := data.Get("data.wbi_img.img_url").Str() + subURL := data.Get("data.wbi_img.sub_url").Str() + imgKey := imgURL[strings.LastIndex(imgURL, "/")+1:] + imgKey = strings.TrimSuffix(imgKey, filepath.Ext(imgKey)) + subKey := subURL[strings.LastIndex(subURL, "/")+1:] + subKey = strings.TrimSuffix(subKey, filepath.Ext(subKey)) + return imgKey, subKey +} diff --git a/bot.go b/bot.go index 2d37f97..9f7b256 100644 --- a/bot.go +++ b/bot.go @@ -16,7 +16,7 @@ var ( func checkBotInternal(ctx *EasyBot.CQMessage) { var match [][]string //连续at两次获取帮助, 带文字则视为喊话超级用户 - match = ctx.RegexpMustCompile(fmt.Sprintf(`^\[CQ:at,qq=%d]\s*\[CQ:at,qq=%d]\s*(.*)$`, bot.GetSelfID(), bot.GetSelfID())) + match = ctx.RegexpFindAllStringSubmatch(fmt.Sprintf(`^\[CQ:at,qq=%d]\s*\[CQ:at,qq=%d]\s*(.*)$`, bot.GetSelfID(), bot.GetSelfID())) if len(match) > 0 { call := match[0][1] if len(call) > 0 { //记录喊话 @@ -97,7 +97,7 @@ func checkBotInternal(ctx *EasyBot.CQMessage) { } } //发送/清空收件箱 - match = ctx.RegexpMustCompile(`^(清空)?(喊话列表|收件箱)$`) + match = ctx.RegexpFindAllStringSubmatch(`^(清空)?(喊话列表|收件箱)$`) if len(match) > 0 && ctx.IsPrivateSU() { callSUMsgUnread = 0 //清零未读 if match[0][1] == "" { //发送 @@ -121,7 +121,7 @@ func checkBotInternal(ctx *EasyBot.CQMessage) { callSUMsg.GetCardOrNickname(), callSUMsg.GroupID) content := strings.ReplaceAll(callSUMsg.GetRawMessageOrMessage(), "CQ:at,", "CQ:at,​") //插入零宽空格阻止CQ码解析 - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( name, callSUMsg.UserID, content, int64(callSUMsg.Event.Time), int64(callSUMsg.MessageSeq), )) @@ -133,12 +133,12 @@ func checkBotInternal(ctx *EasyBot.CQMessage) { } } //注入消息 - match = ctx.Unescape().RegexpMustCompile(`run(.*)`) + match = ctx.Unescape().RegexpFindAllStringSubmatch(`run(.*)`) if len(match) > 0 && ctx.IsToMe() { ctx.SendMsg(match[0][1]) } //回复我 - match = ctx.Unescape().RegexpMustCompile(`回复我(.*)?`) + match = ctx.Unescape().RegexpFindAllStringSubmatch(`回复我(.*)?`) if len(match) > 0 && ctx.IsToMe() { if match[0][1] == "" { ctx.SendMsgReply("回复你") diff --git a/cardParse.go b/cardParse.go index e8160c8..d1de0d8 100644 --- a/cardParse.go +++ b/cardParse.go @@ -11,14 +11,20 @@ import ( func checkCardParse(ctx *EasyBot.CQMessage) { if ctx.IsJsonMsg() { log.Debug("isJsonMsg") - matches := ctx.Unescape().RegexpMustCompile(`\[CQ:json,data=(\{.*\})\]`) + matches := ctx.Unescape().RegexpFindAllStringSubmatch(`\[CQ:json,data=(\{.*\})\]`) var g gson.JSON if len(matches) > 0 { - log.Debug("matches: ", matches) log.Debug("matches[0][1]: ", matches[0][1]) g = gson.NewFrom(matches[0][1]) if url := g.Get("meta.news.jumpUrl").Str(); !g.Get("meta.news.jumpUrl").Nil() { - ctx.SendMsgReply(url) + // ctx.SendMsgReply(url) + ctx.SendForwardMsg( + EasyBot.NewForwardMsg( + EasyBot.NewMsgForwardNode(ctx.MessageID), + EasyBot.NewCustomForwardNode( + "NotingBot_CardParse", + bot.GetSelfID(), + url, 0, 0))) } } } diff --git a/context.go b/context.go new file mode 100644 index 0000000..3df9851 --- /dev/null +++ b/context.go @@ -0,0 +1,72 @@ +package main + +import ( + "NothinBot/EasyBot" + "context" + "time" + + log "github.com/sirupsen/logrus" +) + +type botContext struct { + timer *time.Timer + callbackStart func() + callbackReach func(msg *EasyBot.CQMessage) (isDone bool) + callbackEnd func() +} + +var ( + msgChans map[int64]chan *EasyBot.CQMessage +) + +func newContext(timeout time.Duration) *botContext { + timer := time.NewTimer(timeout) + return &botContext{ + timer: timer, + } +} + +// 创建上下文监听, reach 时返回 true 则结束, 或者 WithCancel 后自行调用 CancelFunc +func newMsgContext(ctx context.Context, callbackStart func(), callbackReach func(msg *EasyBot.CQMessage) (isDone bool), callbackEnd func()) { + now := time.Now().Unix() // 时间戳, 作为ID + msgChan := make(chan *EasyBot.CQMessage) // 接收消息用 + msgChans[now] = msgChan + go func() { + defer func() { + close(msgChan) + msgChans[now] = nil + }() + + log.Debug("[context] 创建了一条新的上下文: ", now) + if callbackStart != nil { + callbackStart() + } + + for { + select { + case msg := <-msgChan: + if callbackReach != nil { + if isDone := callbackReach(msg); isDone { + return + } + } + + case <-ctx.Done(): + log.Debug("[context] 上下文结束返回") + if callbackEnd != nil { + callbackEnd() + } + return + + } + } + }() + <-ctx.Done() +} + +// 将消息放入管道待取 +func checkContextPutIn(ctx *EasyBot.CQMessage) { + for _, msgChan := range msgChans { + msgChan <- ctx + } +} diff --git a/corpus.go b/corpus.go index 585c778..44a97aa 100644 --- a/corpus.go +++ b/corpus.go @@ -137,7 +137,7 @@ func initCorpus() { eachReplyMapContentSlice, isSlice := eachReplyMap["content"].([]any) if !isSlice { forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, - EasyBot.NewForwardNode( + EasyBot.NewCustomForwardNode( name, uin, fmt.Sprint(eachReplyMap["content"]), @@ -147,7 +147,7 @@ func initCorpus() { if isSlice { for _, eachEachReplyMapContentSlice := range eachReplyMapContentSlice { forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, - EasyBot.NewForwardNode( + EasyBot.NewCustomForwardNode( name, uin, fmt.Sprint(eachEachReplyMapContentSlice), @@ -160,7 +160,7 @@ func initCorpus() { //使用bot信息 forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, - EasyBot.NewForwardNode( + EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprint(eachReply), diff --git a/default_config.yml b/default_config.yml index 4cfdfaf..342e691 100644 --- a/default_config.yml +++ b/default_config.yml @@ -1,18 +1,19 @@ EasyBot: wsUrl: "ws://" #go-cqhttp + localOrRemote: "remote" #"local" / "remote", gocq与bot是否在同一台机器上 (更严格的来说: 是否可以访问到同一路径的文件, 否则都为"remote", 用于决定上传视频前通过gocq下载还是bot下载) superUsers: #int / []int - nickName: #机器人别称(用于判断是否提到了机器人) - "rurudo" - "ruru" #控制台日志等级,越大输出越多 - #PanicLevel = 0 - #FatalLevel = 1 - #ErrorLevel = 2 - #WarnLevel = 3 - #InfoLevel = 4 - #DebugLevel = 5 - #TraceLevel = 6 + #PanicLevel = iota + #FatalLevel + #ErrorLevel + #WarnLevel + #InfoLevel + #DebugLevel + #TraceLevel logLevel: 4 ban: private: @@ -25,10 +26,6 @@ setu: #热更新 enable: true pixiv: #热更新 enable: true -bertVits: #热更新 - whiteList: - private: - group: qianfan: #热更新 #https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu #留空fallback至glm @@ -41,7 +38,7 @@ qianfan: #热更新 parse: #热更新 settings: #"glm", "qianfan" - summaryBackend: "qianfan" + summaryBackend: "glm" #同一会话重复解析同一链接的间隔(秒) sameParseInterval: 60 #过长的视频/投票简介保留长度(中英字符) diff --git a/doLua.go b/doLua.go new file mode 100644 index 0000000..c648a90 --- /dev/null +++ b/doLua.go @@ -0,0 +1,108 @@ +package main + +import ( + "NothinBot/EasyBot" + "bytes" + "errors" + "time" + + log "github.com/sirupsen/logrus" + lua "github.com/yuin/gopher-lua" +) + +var ( + // Bot用的VM, 不需要重定向print + globalLuaVM = lua.NewState(lua.Options{ + MinimizeStackMemory: true, + }) +) + +var ( + doLuaEnable = false + doLuaTimeout = time.Second * 10 + doLuaVM *lua.LState + printBuffer = new(bytes.Buffer) +) + +func resetDoLuaVM() { + log.Debug("[doLua] reset doLuaVM") + printBuffer.Reset() + if doLuaVM != nil { + doLuaVM.Close() + } + doLuaVM = lua.NewState(lua.Options{ + MinimizeStackMemory: true, + }) + // alias + doLuaVM.SetGlobal("stdPrint", doLuaVM.GetGlobal("print")) + // print重定向 + doLuaVM.SetGlobal("print", doLuaVM.NewFunction(func(L *lua.LState) int { + top := L.GetTop() + for i := 1; i <= top; i++ { + if i > 1 { + printBuffer.WriteString("\t") + } + printBuffer.WriteString(L.Get(i).String()) + } + // printBuffer.WriteString("\n") + return 0 + })) + // add := func(a, b int) int { return a + b } + // doLuaVM.Register("add", add) +} + +func doLuaWithTimeout(l *lua.LState, source string, timeout time.Duration) (result string, err error) { + resetDoLuaVM() + resultChan := make(chan string) + errChan := make(chan error) + go func() { + err = doLuaVM.DoString(source) + if err == nil { + resultChan <- printBuffer.String() + } else { + errChan <- err + } + }() + + select { + case result := <-resultChan: + return result, nil + case err = <-errChan: + return "", err + case <-time.After(timeout): + log.Warn("[doLua] excute timeout (", timeout, ")") + l.Close() + return "", errors.New("excute timeout") + } +} + +func checkDoLua(ctx *EasyBot.CQMessage) { + //开关控制 + matches := ctx.RegexpFindAllStringSubmatch(`(开启|启用|关闭|禁用)do[Ll]ua`) + if len(matches) > 0 && ctx.IsPrivateSU() { + switch matches[0][1] { + case "开启", "启用": + doLuaEnable = true + ctx.SendMsg("doLua已启用") + case "关闭", "禁用": + doLuaEnable = false + ctx.SendMsg("doLua已禁用") + } + return + } + if !doLuaEnable { + return + } + + symbolMatches := ctx.RegexpFindAllStringSubmatch("do[Ll]ua\n") + if len(symbolMatches) > 0 { + luaScript := ctx.RegexpReplaceAll("do[Ll]ua\n", "") + log.Debug("execute lua:\n", luaScript) + result, err := doLuaWithTimeout(doLuaVM, luaScript, doLuaTimeout) + if err == nil { + ctx.SendMsgReply(result) + } else { + ctx.SendMsgReply("执行出现错误: ", err.Error()) + } + } +} diff --git a/go.mod b/go.mod index a4a242e..7a641cd 100644 --- a/go.mod +++ b/go.mod @@ -1,39 +1,43 @@ module NothinBot -go 1.21.1 +go 1.21.3 require ( github.com/PuerkitoBio/goquery v1.8.1 - github.com/andybalholm/brotli v1.0.5 + github.com/andybalholm/brotli v1.0.6 + github.com/fsnotify/fsnotify v1.7.0 github.com/gorilla/websocket v1.5.0 github.com/jaypipes/ghw v0.12.0 github.com/moxcomic/bcutasr v0.0.1 github.com/moxcomic/ihttp v0.0.14 - github.com/shirou/gopsutil/v3 v3.23.8 + github.com/shirou/gopsutil/v3 v3.23.9 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/viper v1.16.0 + github.com/spf13/viper v1.17.0 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/ysmood/gson v0.7.3 - golang.org/x/net v0.15.0 + github.com/yuin/gopher-lua v1.1.0 + golang.org/x/net v0.17.0 ) require ( github.com/StackExchange/wmi v1.2.1 // indirect - github.com/andybalholm/cascadia v1.3.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jaypipes/pcidb v1.0.0 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/spf13/afero v1.9.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -41,7 +45,10 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/sys v0.12.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 6177649..47510ed 100644 --- a/go.sum +++ b/go.sum @@ -44,8 +44,12 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -57,6 +61,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -67,6 +72,8 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -75,6 +82,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -114,6 +123,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -159,6 +170,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0= +github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -171,19 +184,30 @@ github.com/moxcomic/ihttp v0.0.14 h1:yyuj1CKzLs9QDUtEOKSXd8wuwbE4JKfIVRPVt+8krGw github.com/moxcomic/ihttp v0.0.14/go.mod h1:iq4aVIw1yJ2BnqV9w7V1fTLHH35FjS4HW+yHtSl6y7U= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= +github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E= +github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -191,8 +215,12 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -201,6 +229,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -228,6 +258,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= +github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -236,6 +268,10 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -254,6 +290,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -278,6 +316,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -312,9 +351,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -335,6 +378,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -377,14 +421,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -394,6 +443,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -447,6 +497,7 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -542,6 +593,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba 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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/info.go b/info.go index 0cea0d5..e334a1e 100644 --- a/info.go +++ b/info.go @@ -13,7 +13,7 @@ import ( // 运行状态 func checkInfo(ctx *EasyBot.CQMessage) { - match := ctx.RegexpMustCompile(`检查身体|运行状态`) + match := ctx.RegexpFindAllStringSubmatch(`检查身体|运行状态`) if len(match) > 0 && ctx.IsToMe() { product, _ := ghw.Product() cpuInfo, _ := cpu.Info() @@ -42,7 +42,7 @@ func checkInfo(ctx *EasyBot.CQMessage) { } return }(), - timeFormat(bot.GetRunningTime())) + formatTime(bot.GetRunningTime())) ctx.SendMsg(s) } } diff --git a/main.go b/main.go index d035f87..c99d6ba 100644 --- a/main.go +++ b/main.go @@ -7,9 +7,11 @@ import ( "fmt" "os" "os/signal" + "reflect" "strconv" "syscall" "time" + "unsafe" log "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -217,9 +219,11 @@ func initConfig() { if configUpdateCount == 0 { before() after() + _ = gocqIsLocalOrRemote() v.WatchConfig() } else { after() + _ = gocqIsLocalOrRemote() } } @@ -239,7 +243,7 @@ func initModules() { func exitJobs() { signal.Notify(mainBlock, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) <-mainBlock - runTime := timeFormat(bot.GetRunningTime()) + runTime := formatTime(bot.GetRunningTime()) err := bot.Log2SU.Info("[Exit]", "\n此次运行时长:", runTime, "\n心跳包接收计数:", bot.HeartbeatCount, @@ -276,15 +280,25 @@ func handleMessage(msg *EasyBot.CQMessage) { go checkPixiv(ctx) go checkBertVITS2(ctx) go checkInfo(ctx) + // go checkDoLua(ctx) }(msg) } +func gocqIsLocalOrRemote() string { + if lor := v.GetString("main.localOrRemote"); lor == "local" || lor == "remote" { + return lor + } else { + log.Fatal("[Init] config.main.localOrRemote: 错误的参数\"", lor, "\"") + } + return "" +} + // 测试接口调用 func checkApiCallingTesting(ctx *EasyBot.CQMessage) { if !ctx.IsPrivateSU() { return } - get_msg := ctx.RegexpMustCompile(`get_msg\s?(.*)`) + get_msg := ctx.RegexpFindAllStringSubmatch(`get_msg\s?(.*)`) if len(get_msg) > 0 { msgId, _ := strconv.Atoi(get_msg[0][1]) msg, err := bot.GetMsg(msgId) @@ -293,10 +307,33 @@ func checkApiCallingTesting(ctx *EasyBot.CQMessage) { } log.Debug(gson.New(msg).JSON("", "")) } + download_file := ctx.RegexpFindAllStringSubmatch(`download_file\s?(.*)`) + if len(download_file) > 0 { + url := download_file[0][1] + file, err := bot.DownloadFile(url, 2, iheaders) + if err != nil { + log.Error("err: ", err) + } + log.Debug(file) + } +} + +// 格式化秒级时间戳至 时分秒 x:x:x +func formatTimeSimple(timestamp int64) (format string) { + h := (timestamp / (60 * 60)) % 24 + m := (timestamp / 60) % 60 + s := timestamp % 60 + if h > 0 { + return fmt.Sprintf("%02d:%02d:%02d", h, m, s) + } + return fmt.Sprintf("%02d:%02d", m, s) } -// 格式化时间戳至 x天x小时x分钟x秒 -func timeFormat(timestamp int64) string { +// 格式化秒级时间戳至 x天x小时x分钟x秒 +func formatTime(timestamp int64) (format string) { + if timestamp == 0 { + return "0秒" + } itoa := func(i int64) string { return strconv.Itoa(int(i)) } @@ -306,14 +343,82 @@ func timeFormat(timestamp int64) string { seconds := timestamp % 60 switch { case days > 0: - return itoa(days) + "天" + itoa(hours) + "小时" + itoa(minutes) + "分钟" + itoa(seconds) + "秒" + format += itoa(days) + "天" + fallthrough + case hours > 0: + format += itoa(hours) + "小时" + fallthrough + case minutes > 0: + format += itoa(minutes) + "分钟" + fallthrough + default: + if seconds != 0 { + format += itoa(seconds) + "秒" + } + } + return format +} + +// 格式化毫秒级时间戳至 x天x小时x分钟x秒x毫秒 +func formatTimeMs(timestamp int64) (format string) { + if timestamp == 0 { + return "0毫秒" + } + itoa := func(i int64) string { + return strconv.Itoa(int(i)) + } + milliseconds := timestamp % 1000 + seconds := (timestamp / 1000) % 60 + minutes := (timestamp / (1000 * 60)) % 60 + hours := (timestamp / (1000 * 60 * 60)) % 24 + days := timestamp / (1000 * 60 * 60 * 24) + switch { + case days > 0: + format += itoa(days) + "天" + fallthrough case hours > 0: - return itoa(hours) + "小时" + itoa(minutes) + "分钟" + itoa(seconds) + "秒" + format += itoa(hours) + "小时" + fallthrough case minutes > 0: - return itoa(minutes) + "分钟" + itoa(seconds) + "秒" + format += itoa(minutes) + "分钟" + fallthrough + case seconds > 0: + format += itoa(seconds) + "秒" + fallthrough default: - return itoa(timestamp) + "秒" + if milliseconds != 0 { + format += itoa(milliseconds) + "毫秒" + } + } + return format +} + +func toCsv(items ...any) (outputWithNewLine string) { + count := len(items) + for i := 0; i < count; i++ { + outputWithNewLine += fmt.Sprint(items[i]) + if i < count-1 { + outputWithNewLine += "," + } } + return outputWithNewLine + "\n" +} + +// BytesToString 没有内存开销的转换 +// https://github.com/wdvxdr1123/ZeroBot/blob/main/utils/helper/helper.go +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// StringToBytes 没有内存开销的转换 +// https://github.com/wdvxdr1123/ZeroBot/blob/main/utils/helper/helper.go +func StringToBytes(s string) (b []byte) { + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh.Data = sh.Data + bh.Len = sh.Len + bh.Cap = sh.Len + return b } func checkDir(path string) (err error) { @@ -356,6 +461,9 @@ func handleGroupRecall(gr *EasyBot.CQNoticeGroupRecall) { // 群名片变更 func handleGroupCard(gc *EasyBot.CQNoticeGroupCard) { + if gc.CardOld == "" { + return + } avatar := bot.Utils.Format.ImageUrl(fmt.Sprintf( "http://q.qlogo.cn/headimg_dl?dst_uin=%d&spec=640&img_type=jpg", gc.UserID)) bot.SendGroupMsg(gc.GroupID, fmt.Sprint( diff --git a/pixiv.go b/pixiv.go index 26fa78a..86cc02b 100644 --- a/pixiv.go +++ b/pixiv.go @@ -28,7 +28,7 @@ func initPixiv() { func checkPixiv(ctx *EasyBot.CQMessage) { //开关控制 - matches := ctx.RegexpMustCompile(`(开启|启用|关闭|禁用)pixiv`) + matches := ctx.RegexpFindAllStringSubmatch(`(开启|启用|关闭|禁用)pixiv`) if len(matches) > 0 && ctx.IsPrivateSU() { switch matches[0][1] { case "开启", "启用": @@ -43,7 +43,7 @@ func checkPixiv(ctx *EasyBot.CQMessage) { if !pixivEnable { return } - match := ctx.RegexpMustCompile(`[看康k]{2}([Pp]|[Pp]站|[Pp][Ii][Dd]|[Pp][Ii][Xx][Ii][Vv])([0-9]+)`) + match := ctx.RegexpFindAllStringSubmatch(`[看康k]{2}([Pp]|[Pp]站|[Pp][Ii][Dd]|[Pp][Ii][Xx][Ii][Vv])([0-9]+)`) if len(match) > 0 && ctx.IsToMe() { pid, _ := strconv.Atoi(match[0][2]) p := &pixiv{ diff --git a/push.go b/push.go index 62ccbdc..330a60a 100644 --- a/push.go +++ b/push.go @@ -86,7 +86,7 @@ func checkCookieUpdate(ctx *EasyBot.CQMessage) { if !ctx.IsPrivateSU() { return } - match := ctx.RegexpMustCompile(`(设置|set)\s*(饼干|cookie)\s*(.*)`) + match := ctx.RegexpFindAllStringSubmatch(`(设置|set)\s*(饼干|cookie)\s*(.*)`) if len(match) > 0 { if c := match[0][3]; len(c) != 0 { cookie = c @@ -456,7 +456,7 @@ live.bilibili.com/%d`, title := roomJson.Get("title").Str() duration := func() string { if liveList[roomid].time != 0 { - return "本次直播持续了" + timeFormat(time.Now().Unix()-liveList[roomid].time) + return "本次直播持续了" + formatTime(time.Now().Unix()-liveList[roomid].time) } else { return "未记录本次开播时间" } diff --git a/recall.go b/recall.go index 07447d9..475525d 100644 --- a/recall.go +++ b/recall.go @@ -96,7 +96,7 @@ func (r *recall) format() (forwardMsg EasyBot.CQForwardMsg) { return "" }()) content := strings.ReplaceAll(rcMsg.Extra.MessageWithReply, "CQ:at,", "CQ:at,​") //插入零宽空格阻止CQ码解析 - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( name, rcMsg.UserID, content, 0, 0)) } return @@ -105,7 +105,7 @@ func (r *recall) format() (forwardMsg EasyBot.CQForwardMsg) { // 撤回消息记录 func checkRecall(ctx *EasyBot.CQMessage) { //开关 - match := ctx.RegexpMustCompile(`(开启|启用|关闭|禁用)撤回记录`) + match := ctx.RegexpFindAllStringSubmatch(`(开启|启用|关闭|禁用)撤回记录`) if len(match) > 0 && ctx.IsPrivateSU() { switch match[0][1] { case "开启", "启用": @@ -121,7 +121,7 @@ func checkRecall(ctx *EasyBot.CQMessage) { return } //发送 - match = ctx.RegexpMustCompile(`^让我康康(\s*\[CQ:at,qq=)?([0-9]+)?(]\s*)?撤回了什么$`) + match = ctx.RegexpFindAllStringSubmatch(`^让我康康(\s*\[CQ:at,qq=)?([0-9]+)?(]\s*)?撤回了什么$`) if len(match) > 0 { r := recall{ kind: ctx.MessageType, diff --git a/search.go b/search.go index 5436450..6df519b 100644 --- a/search.go +++ b/search.go @@ -90,7 +90,7 @@ func formatBiliSearch(KIND string, keyword string) (forwardMsg EasyBot.CQForward favor := g.Get("favorites").Int() //收藏 bvid := g.Get("bvid").Str() //bv号 - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=https:%s] av%d @@ -132,7 +132,7 @@ www.bilibili.com/video/%s`, }(g.Gets("badges", 0, "text")) url := g.Get("url").Str() //链接 - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=%s] %s @@ -172,7 +172,7 @@ CV: roomid := g.Get("roomid").Int() //房间号 uid := g.Get("uid").Int() //uid - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=https:%s][CQ:image,file=https:%s] %s的直播间%s @@ -204,7 +204,7 @@ space.bilibili.com/%d`, uid := g.Get("uid").Int() //uid if live_status == 0 { //未开播直接使用搜索返回的数据 - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=https:%s] %s @@ -225,10 +225,10 @@ space.bilibili.com/%d`, } else { //开播则调用getRoomJson和formatLive roomJson, ok := getRoomJsonUID(uid).Gets("data", uid) if ok { - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), formatLive(roomJson), 0, 0)) } else { //fallback - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=https:%s] %s @@ -261,7 +261,7 @@ space.bilibili.com/%d`, cvid := g.Get("id").Int() //cv号数字 mid := g.Get("mid").Int() //uid - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=https:%s] cv%d @@ -294,7 +294,7 @@ space.bilibili.com/%d`, } return "" }() - forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewForwardNode( + forwardMsg = EasyBot.AppendForwardMsg(forwardMsg, EasyBot.NewCustomForwardNode( "NothingBot", bot.GetSelfID(), fmt.Sprintf( `[CQ:image,file=https:%s] %s(LV%d) @@ -311,7 +311,7 @@ space.bilibili.com/%d // 哔哩哔哩快捷搜索 func checkSearch(ctx *EasyBot.CQMessage) { - match := ctx.RegexpMustCompile(biliSearchRegexp) + match := ctx.RegexpFindAllStringSubmatch(biliSearchRegexp) if len(match) > 0 { ctx.SendForwardMsg(formatBiliSearch(match[0][1], match[0][2])) } diff --git a/setu.go b/setu.go index d195ac6..7b3932d 100644 --- a/setu.go +++ b/setu.go @@ -50,7 +50,7 @@ func initSetu() { func checkSetu(ctx *EasyBot.CQMessage) { //开关控制 - matches := ctx.RegexpMustCompile(`(开启|启用|关闭|禁用)setu`) + matches := ctx.RegexpFindAllStringSubmatch(`(开启|启用|关闭|禁用)setu`) if len(matches) > 0 && ctx.IsPrivateSU() { switch matches[0][1] { case "开启", "启用": @@ -65,7 +65,7 @@ func checkSetu(ctx *EasyBot.CQMessage) { if !setuEnable { return } - matches = ctx.Unescape().RegexpMustCompile(`(来(?P点|一点|几张|几份|.*张|.*份)?(?P[Rr]18)?的?(?P.*)?的?[色瑟涩铯][图圖])|((?P[Rr]18)?的?(?P.*)?的?[色瑟涩铯][图圖]来(?P点|一点|几张|几份|.*张|.*份)?)`) + matches = ctx.Unescape().RegexpFindAllStringSubmatch(`(来(?P点|一点|几张|几份|.*张|.*份)?(?P[Rr]18)?的?(?P.*)?的?[色瑟涩铯][图圖])|((?P[Rr]18)?的?(?P.*)?的?[色瑟涩铯][图圖]来(?P点|一点|几张|几份|.*张|.*份)?)`) // 一条正则多个同名捕获组只会索引到第一个, 所以下面直接把对应的捕获组全加起来 if len(matches) > 0 && ctx.IsToMe() { var numOK bool diff --git a/watcher.go b/watcher.go new file mode 100644 index 0000000..ad5c61d --- /dev/null +++ b/watcher.go @@ -0,0 +1,165 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +func WatchFile(filePath string, callback func(fsnotify.Event)) { + initWG := sync.WaitGroup{} + initWG.Add(1) + go func() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + fmt.Printf("failed to create watcher: %s", err) + os.Exit(1) + } + defer watcher.Close() + + dir, file := filepath.Split(filePath) // 监听路径, 文件名 + + eventsWG := sync.WaitGroup{} + eventsWG.Add(1) + go func() { + for { + select { + case event, ok := <-watcher.Events: + log.Debug("case event, ok := <-watcher.Events: ", event, " - ", ok) + if !ok { // 'Events' channel is closed + eventsWG.Done() + return + } + if event.Name == file { + callback(event) + } + + case err, ok := <-watcher.Errors: + log.Debug("case err, ok := <-watcher.Errors: ", err, " - ", ok) + if ok { // 'Errors' channel is not closed + fmt.Printf("watcher error: %s", err) + } + eventsWG.Done() + return + } + } + }() + watcher.Add(dir) + initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... + eventsWG.Wait() // now, wait for event loop to end in this go-routine... + }() + initWG.Wait() // make sure that the go routine above fully ended before returning +} + +func WatchFileChanges(filePath string, callback func(in fsnotify.Event)) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + err = watcher.Add(filePath) + if err != nil { + log.Fatal(err) + } + + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + log.Error("[watcher] !ok1: ", !ok) + return + } + if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { + log.Debug("[watcher] File: ", event.Name, " Op: ", event.Op) + callback(event) + } + case err, ok := <-watcher.Errors: + if !ok || err != nil { + log.Error("[watcher] !ok2: ", !ok) + log.Error("[watcher] err: ", err) + return + } + } + } + }() +} + +// WatchConfig starts watching a config file for changes. +func WatchConfig(v *viper.Viper) { + initWG := sync.WaitGroup{} + initWG.Add(1) + go func() { + // 创建文件监视器 + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error(fmt.Sprintf("failed to create watcher: %s", err)) + os.Exit(1) + } + defer watcher.Close() // 在函数结束时关闭监视器 + + // 获取配置文件的路径 + filename, err := "filename", nil + if err != nil { + log.Error(fmt.Sprintf("get config file: %s", err)) + initWG.Done() + return + } + + configFile := filepath.Clean(filename) // 清理路径中的冗余部分 + configDir, _ := filepath.Split(configFile) // 获取文件所在的目录 + realConfigFile, _ := filepath.EvalSymlinks(filename) // 获取配置文件的实际路径 + + eventsWG := sync.WaitGroup{} + eventsWG.Add(1) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { // 'Events' 通道已关闭 + eventsWG.Done() + return + } + currentConfigFile, _ := filepath.EvalSymlinks(filename) // 获取当前配置文件的实际路径 + // 对于以下情况,我们只关心配置文件的变化: + // 1 - 如果配置文件被修改或创建 + // 2 - 如果配置文件的真实路径改变(例如:k8s ConfigMap 替换) + if (filepath.Clean(event.Name) == configFile && + (event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) || + (currentConfigFile != "" && currentConfigFile != realConfigFile) { + realConfigFile = currentConfigFile // 更新真实配置文件路径 + // err := v.ReadInConfig() // 重新加载配置文件 + if err != nil { + log.Error(fmt.Sprintf("read config file: %s", err)) + } + onConfigChange(event) // 调用配置更改回调函数 + } else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) { + eventsWG.Done() + return + } + + case err, ok := <-watcher.Errors: + if ok { // 'Errors' 通道未关闭 + log.Error(fmt.Sprintf("watcher error: %s", err)) + } + eventsWG.Done() + return + } + } + }() + watcher.Add(configDir) // 监视配置文件所在目录 + initWG.Done() // 在此 goroutine 中完成监视的初始化,以便父例程可以继续... + eventsWG.Wait() // 等待事件循环在此 goroutine 中结束... + }() + initWG.Wait() // 确保上面的 goroutine 完全结束后再返回 +} + +func onConfigChange(in fsnotify.Event) { + log.Info("文件: ", in.Name, " 发生了: ", in.Op) +}