mirror of
https://github.com/idootop/mi-gpt.git
synced 2024-09-20 06:45:38 +00:00
feat: export MiGPT
This commit is contained in:
parent
2dc65da0fe
commit
9e1e9a3973
50
src/index.ts
50
src/index.ts
|
@ -1,9 +1,49 @@
|
|||
import { AISpeaker, AISpeakerConfig } from "./services/speaker/ai";
|
||||
import { MyBot, MyBotConfig } from "./services/bot";
|
||||
import { runWithDB } from "./services/db";
|
||||
import { println } from "./utils/base";
|
||||
import { kBannerASCII } from "./utils/string";
|
||||
|
||||
async function main() {
|
||||
println(kBannerASCII);
|
||||
}
|
||||
export type MiGPTConfig = Omit<MyBotConfig, "speaker"> & {
|
||||
speaker: AISpeakerConfig;
|
||||
};
|
||||
|
||||
runWithDB(main);
|
||||
export class MiGPT {
|
||||
static instance: MiGPT | null;
|
||||
static reset() {
|
||||
MiGPT.instance = null;
|
||||
}
|
||||
static create(config: MiGPTConfig) {
|
||||
if (MiGPT.instance) {
|
||||
console.log("🚨 注意:MiGPT 是单例,暂不支持多设备、多账号!");
|
||||
console.log("如果需要切换设备或账号,请先使用 MiGPT.reset() 重置实例。");
|
||||
} else {
|
||||
MiGPT.instance = new MiGPT({ ...config, fromCreate: true });
|
||||
}
|
||||
return MiGPT.instance;
|
||||
}
|
||||
|
||||
ai: MyBot;
|
||||
speaker: AISpeaker;
|
||||
constructor(config: MiGPTConfig & { fromCreate?: boolean }) {
|
||||
console.assert(config.fromCreate, "请使用 MiGPT.create() 获取客户端实例!");
|
||||
const { speaker, ...myBotConfig } = config;
|
||||
this.speaker = new AISpeaker(speaker);
|
||||
this.ai = new MyBot({
|
||||
...myBotConfig,
|
||||
speaker: this.speaker,
|
||||
});
|
||||
}
|
||||
|
||||
async start() {
|
||||
// todo init DB
|
||||
const main = () => {
|
||||
console.log(kBannerASCII);
|
||||
return this.ai.run();
|
||||
};
|
||||
return runWithDB(main);
|
||||
}
|
||||
|
||||
async stop() {
|
||||
return this.ai.stop();
|
||||
}
|
||||
}
|
||||
|
|
9
src/runner.ts
Normal file
9
src/runner.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { runWithDB } from "./services/db";
|
||||
import { println } from "./utils/base";
|
||||
import { kBannerASCII } from "./utils/string";
|
||||
|
||||
async function main() {
|
||||
println(kBannerASCII);
|
||||
}
|
||||
|
||||
runWithDB(main);
|
|
@ -67,10 +67,11 @@ const userTemplate = `
|
|||
{{message}}
|
||||
`.trim();
|
||||
|
||||
export type MyBotConfig = DeepPartial<IBotConfig> & { speaker: AISpeaker };
|
||||
export class MyBot {
|
||||
speaker: AISpeaker;
|
||||
manager: ConversationManager;
|
||||
constructor(config: DeepPartial<IBotConfig> & { speaker: AISpeaker }) {
|
||||
constructor(config: MyBotConfig) {
|
||||
this.speaker = config.speaker;
|
||||
this.manager = new ConversationManager(config);
|
||||
}
|
||||
|
@ -154,6 +155,7 @@ export class MyBot {
|
|||
.chatStream({
|
||||
...options,
|
||||
requestId,
|
||||
trace: true,
|
||||
onStream: (text) => {
|
||||
if (stream.status === "canceled") {
|
||||
return openai.abort(requestId);
|
||||
|
|
|
@ -3,9 +3,9 @@ import { HttpsProxyAgent } from "https-proxy-agent";
|
|||
import { isNotEmpty } from "../utils/is";
|
||||
import { Logger } from "../utils/log";
|
||||
|
||||
export const kProxyAgent = new HttpsProxyAgent(
|
||||
process.env.HTTP_PROXY ?? "http://127.0.0.1:7890"
|
||||
);
|
||||
export const kProxyAgent = process.env.HTTP_PROXY
|
||||
? new HttpsProxyAgent(process.env.HTTP_PROXY)
|
||||
: null;
|
||||
|
||||
const _baseConfig: CreateAxiosDefaults = {
|
||||
timeout: 10 * 1000,
|
||||
|
|
|
@ -17,10 +17,14 @@ export interface ChatOptions {
|
|||
tools?: Array<ChatCompletionTool>;
|
||||
jsonMode?: boolean;
|
||||
requestId?: string;
|
||||
trace?: boolean;
|
||||
}
|
||||
|
||||
class OpenAIClient {
|
||||
private _logger = Logger.create({ tag: "OpenAI" });
|
||||
traceInput = false;
|
||||
traceOutput = true;
|
||||
private _logger = Logger.create({ tag: "Open AI" });
|
||||
|
||||
private _client = new OpenAI({
|
||||
httpAgent: kProxyAgent,
|
||||
apiKey: kEnvs.OPENAI_API_KEY!,
|
||||
|
@ -44,14 +48,14 @@ class OpenAIClient {
|
|||
tools,
|
||||
jsonMode,
|
||||
requestId,
|
||||
trace = false,
|
||||
model = kEnvs.OPENAI_MODEL ?? "gpt-3.5-turbo-0125",
|
||||
} = options;
|
||||
this._logger.log(
|
||||
`🔥 onAskAI
|
||||
🤖️ System: ${system ?? "None"}
|
||||
😊 User: ${user}
|
||||
`.trim()
|
||||
);
|
||||
if (trace && this.traceInput) {
|
||||
this._logger.log(
|
||||
`🔥 onAskAI\n🤖️ System: ${system ?? "None"}\n😊 User: ${user}`.trim()
|
||||
);
|
||||
}
|
||||
const systemMsg: ChatCompletionMessageParam[] = system
|
||||
? [{ role: "system", content: system }]
|
||||
: [];
|
||||
|
@ -76,7 +80,9 @@ class OpenAIClient {
|
|||
return null;
|
||||
});
|
||||
const message = chatCompletion?.choices?.[0]?.message;
|
||||
this._logger.success(`🤖️ Answer: ${message?.content ?? "None"}`.trim());
|
||||
if (trace && this.traceOutput) {
|
||||
this._logger.log(`✅ Answer: ${message?.content ?? "None"}`.trim());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
@ -92,14 +98,14 @@ class OpenAIClient {
|
|||
jsonMode,
|
||||
requestId,
|
||||
onStream,
|
||||
trace = false,
|
||||
model = kEnvs.OPENAI_MODEL ?? "gpt-3.5-turbo-0125",
|
||||
} = options;
|
||||
this._logger.log(
|
||||
`🔥 onAskAI
|
||||
🤖️ System: ${system ?? "None"}
|
||||
😊 User: ${user}
|
||||
`.trim()
|
||||
);
|
||||
if (trace && this.traceInput) {
|
||||
this._logger.log(
|
||||
`🔥 onAskAI\n🤖️ System: ${system ?? "None"}\n😊 User: ${user}`.trim()
|
||||
);
|
||||
}
|
||||
const systemMsg: ChatCompletionMessageParam[] = system
|
||||
? [{ role: "system", content: system }]
|
||||
: [];
|
||||
|
@ -135,7 +141,9 @@ class OpenAIClient {
|
|||
content += text;
|
||||
}
|
||||
}
|
||||
this._logger.success(`🤖️ Answer: ${content ?? "None"}`.trim());
|
||||
if (trace && this.traceOutput) {
|
||||
this._logger.log(`✅ Answer: ${content ?? "None"}`.trim());
|
||||
}
|
||||
return withDefault(content, undefined);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { pickOne } from "../../utils/base";
|
||||
import { pickOne, toSet } from "../../utils/base";
|
||||
import {
|
||||
Speaker,
|
||||
SpeakerCommand,
|
||||
|
@ -9,12 +9,6 @@ import {
|
|||
|
||||
export type AISpeakerConfig = SpeakerConfig & {
|
||||
askAI?: (msg: QueryMessage) => Promise<SpeakerAnswer>;
|
||||
/**
|
||||
* 切换音色前缀
|
||||
*
|
||||
* 比如:音色切换到(文静毛毛)
|
||||
*/
|
||||
switchSpeakerPrefix?: string;
|
||||
/**
|
||||
* AI 开始回答时的提示语
|
||||
*
|
||||
|
@ -41,6 +35,12 @@ export type AISpeakerConfig = SpeakerConfig & {
|
|||
* 比如:打开/进入/召唤豆包
|
||||
*/
|
||||
callAIPrefix?: string[];
|
||||
/**
|
||||
* 切换音色前缀
|
||||
*
|
||||
* 比如:音色切换到(文静毛毛)
|
||||
*/
|
||||
switchSpeakerPrefix?: string[];
|
||||
/**
|
||||
* 唤醒关键词
|
||||
*
|
||||
|
@ -67,6 +67,14 @@ export type AISpeakerConfig = SpeakerConfig & {
|
|||
* 比如:豆包已退出
|
||||
*/
|
||||
onExitAI?: string[];
|
||||
/**
|
||||
* AI 回答开始提示音
|
||||
*/
|
||||
audio_active?: string;
|
||||
/**
|
||||
* AI 回答异常提示音
|
||||
*/
|
||||
audio_error?: string;
|
||||
};
|
||||
|
||||
type AnswerStep = (
|
||||
|
@ -77,7 +85,7 @@ type AnswerStep = (
|
|||
export class AISpeaker extends Speaker {
|
||||
askAI: AISpeakerConfig["askAI"];
|
||||
name: string;
|
||||
switchSpeakerPrefix: string;
|
||||
switchSpeakerPrefix: string[];
|
||||
onEnterAI: string[];
|
||||
onExitAI: string[];
|
||||
callAIPrefix: string[];
|
||||
|
@ -85,23 +93,30 @@ export class AISpeaker extends Speaker {
|
|||
exitKeywords: string[];
|
||||
onAIAsking: string[];
|
||||
onAIError: string[];
|
||||
audio_active?: string;
|
||||
audio_error?: string;
|
||||
|
||||
constructor(config: AISpeakerConfig) {
|
||||
super(config);
|
||||
const {
|
||||
askAI,
|
||||
name = "豆包",
|
||||
switchSpeakerPrefix = "音色切换到",
|
||||
switchSpeakerPrefix,
|
||||
wakeUpKeyWords = ["打开", "进入", "召唤"],
|
||||
exitKeywords = ["关闭", "退出", "再见"],
|
||||
onAIAsking = ["让我先想想", "请稍等"],
|
||||
onAIError = ["啊哦,出错了,请稍后再试吧!"],
|
||||
audio_active = process.env.AUDIO_ACTIVE,
|
||||
audio_error = process.env.AUDIO_ERROR,
|
||||
} = config;
|
||||
this.askAI = askAI;
|
||||
this.switchSpeakerPrefix = switchSpeakerPrefix;
|
||||
this.name = name;
|
||||
this.onAIError = onAIError;
|
||||
this.onAIAsking = onAIAsking;
|
||||
this.audio_active = audio_active;
|
||||
this.audio_error = audio_error;
|
||||
this.switchSpeakerPrefix =
|
||||
switchSpeakerPrefix ?? getDefaultSwitchSpeakerPrefix();
|
||||
this.wakeUpKeyWords = wakeUpKeyWords.map((e) => e + this.name);
|
||||
this.exitKeywords = exitKeywords.map((e) => e + this.name);
|
||||
this.onEnterAI = config.onEnterAI ?? [
|
||||
|
@ -150,12 +165,16 @@ export class AISpeaker extends Speaker {
|
|||
},
|
||||
},
|
||||
{
|
||||
match: (msg) => msg.text.startsWith(this.switchSpeakerPrefix),
|
||||
match: (msg) =>
|
||||
this.switchSpeakerPrefix.some((e) => msg.text.startsWith(e)),
|
||||
run: async (msg) => {
|
||||
await this.response({
|
||||
text: "正在切换音色,请稍等...",
|
||||
});
|
||||
const speaker = msg.text.replace(this.switchSpeakerPrefix, "");
|
||||
const prefix = this.switchSpeakerPrefix.find((e) =>
|
||||
msg.text.startsWith(e)
|
||||
)!;
|
||||
const speaker = msg.text.replace(prefix, "");
|
||||
const success = await this.switchDefaultSpeaker(speaker);
|
||||
await this.response({
|
||||
text: success ? "音色已切换!" : "音色切换失败!",
|
||||
|
@ -177,7 +196,7 @@ export class AISpeaker extends Speaker {
|
|||
async (msg, data) => {
|
||||
// 思考中
|
||||
await this.response({
|
||||
audio: process.env.AUDIO_ACTIVE,
|
||||
audio: this.audio_active,
|
||||
text: pickOne(this.onAIAsking)!,
|
||||
});
|
||||
},
|
||||
|
@ -190,7 +209,7 @@ export class AISpeaker extends Speaker {
|
|||
if (!data.answer) {
|
||||
// 回答异常
|
||||
await this.response({
|
||||
audio: process.env.AUDIO_ERROR,
|
||||
audio: this.audio_error,
|
||||
text: pickOne(this.onAIError)!,
|
||||
keepAlive: this.keepAlive,
|
||||
});
|
||||
|
@ -217,3 +236,20 @@ export class AISpeaker extends Speaker {
|
|||
return data.answer;
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultSwitchSpeakerPrefix = () => {
|
||||
let prefixes = ["音色切换到", "切换音色到", "把音色调到"];
|
||||
const replaces = [
|
||||
["音色", "声音"],
|
||||
["切换", "调"],
|
||||
["到", "为"],
|
||||
["到", "成"],
|
||||
];
|
||||
for (const r of replaces) {
|
||||
prefixes = toSet([
|
||||
...prefixes,
|
||||
...prefixes.map((e) => e.replace(r[0], r[1])),
|
||||
]);
|
||||
}
|
||||
return prefixes;
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { sleep } from "../../utils/base";
|
|||
import { Logger } from "../../utils/log";
|
||||
import { Http } from "../http";
|
||||
import { StreamResponse } from "./stream";
|
||||
import { kAreYouOK } from "../../utils/string";
|
||||
|
||||
export type TTSProvider = "xiaoai" | "doubao";
|
||||
|
||||
|
@ -23,6 +24,10 @@ export type BaseSpeakerConfig = MiServiceConfig & {
|
|||
tts?: TTSProvider;
|
||||
// 检测间隔(单位毫秒,默认 100 毫秒)
|
||||
interval?: number;
|
||||
/**
|
||||
* TTS 开始/结束提示音
|
||||
*/
|
||||
audio_beep?: string;
|
||||
};
|
||||
|
||||
export class BaseSpeaker {
|
||||
|
@ -35,7 +40,12 @@ export class BaseSpeaker {
|
|||
config: MiServiceConfig;
|
||||
constructor(config: BaseSpeakerConfig) {
|
||||
this.config = config;
|
||||
const { interval = 100, tts = "doubao" } = config;
|
||||
const {
|
||||
interval = 100,
|
||||
tts = "doubao",
|
||||
audio_beep = process.env.AUDIO_BEEP,
|
||||
} = config;
|
||||
this.audio_beep = audio_beep;
|
||||
this.interval = interval;
|
||||
this.tts = tts;
|
||||
}
|
||||
|
@ -52,9 +62,11 @@ export class BaseSpeaker {
|
|||
|
||||
async unWakeUp() {
|
||||
// 通过 TTS 不发音文本,使小爱退出唤醒状态
|
||||
await this.MiIOT!.doAction(5, 1, "¿ʞо ∩оʎ ǝɹɐ"); // are you ok?
|
||||
await this.MiNA!.pause()
|
||||
await this.MiIOT!.doAction(5, 1, kAreYouOK);
|
||||
}
|
||||
|
||||
audio_beep?: string;
|
||||
responding = false;
|
||||
async response(options: {
|
||||
tts?: TTSProvider;
|
||||
|
@ -93,7 +105,7 @@ export class BaseSpeaker {
|
|||
if (_response.length < 1) {
|
||||
// 播放开始提示音
|
||||
if (playSFX) {
|
||||
await this.MiNA!.play({ url: process.env.AUDIO_BEEP });
|
||||
await this.MiNA!.play({ url: this.audio_beep });
|
||||
}
|
||||
// 在播放 TTS 语音之前,先取消小爱音箱的唤醒状态,防止将 TTS 语音识别成用户指令
|
||||
if (ttsNotXiaoai) {
|
||||
|
@ -117,7 +129,7 @@ export class BaseSpeaker {
|
|||
if (_response.length > 0) {
|
||||
// 播放结束提示音
|
||||
if (playSFX) {
|
||||
await this.MiNA!.play({ url: process.env.AUDIO_BEEP });
|
||||
await this.MiNA!.play({ url: this.audio_beep });
|
||||
}
|
||||
}
|
||||
// 保持唤醒状态
|
||||
|
@ -163,14 +175,14 @@ export class BaseSpeaker {
|
|||
const play = async (args?: { tts?: string; url?: string }) => {
|
||||
// 播放开始提示音
|
||||
if (playSFX) {
|
||||
await this.MiNA!.play({ url: process.env.AUDIO_BEEP });
|
||||
await this.MiNA!.play({ url: this.audio_beep });
|
||||
}
|
||||
// 在播放 TTS 语音之前,先取消小爱音箱的唤醒状态,防止将 TTS 语音识别成用户指令
|
||||
if (ttsNotXiaoai) {
|
||||
await this.unWakeUp();
|
||||
}
|
||||
await this.MiNA!.play(args);
|
||||
this.logger.success(ttsText ?? audio);
|
||||
this.logger.log("🔊 " + (ttsText ?? audio));
|
||||
// 等待回答播放完毕
|
||||
while (true) {
|
||||
const res = await this.MiNA!.getStatus();
|
||||
|
@ -188,7 +200,7 @@ export class BaseSpeaker {
|
|||
}
|
||||
// 播放结束提示音
|
||||
if (playSFX) {
|
||||
await this.MiNA!.play({ url: process.env.AUDIO_BEEP });
|
||||
await this.MiNA!.play({ url: this.audio_beep });
|
||||
}
|
||||
// 保持唤醒状态
|
||||
if (keepAlive) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { firstOf, lastOf, sleep } from "../../utils/base";
|
||||
import { kAreYouOK } from "../../utils/string";
|
||||
import { BaseSpeaker, BaseSpeakerConfig } from "./base";
|
||||
import { StreamResponse } from "./stream";
|
||||
|
||||
|
@ -38,6 +39,10 @@ export type SpeakerConfig = BaseSpeakerConfig & {
|
|||
* 无响应一段时间后,多久自动退出唤醒模式(单位秒,默认30秒)
|
||||
*/
|
||||
exitKeepAliveAfter?: number;
|
||||
/**
|
||||
* 静音音频链接
|
||||
*/
|
||||
audio_silent?: string;
|
||||
};
|
||||
|
||||
export class Speaker extends BaseSpeaker {
|
||||
|
@ -47,7 +52,12 @@ export class Speaker extends BaseSpeaker {
|
|||
|
||||
constructor(config: SpeakerConfig) {
|
||||
super(config);
|
||||
const { heartbeat = 1000, exitKeepAliveAfter = 30 } = config;
|
||||
const {
|
||||
heartbeat = 1000,
|
||||
exitKeepAliveAfter = 30,
|
||||
audio_silent = process.env.AUDIO_SILENT,
|
||||
} = config;
|
||||
this.audio_silent = audio_silent;
|
||||
this._commands = config.commands ?? [];
|
||||
this.heartbeat = heartbeat;
|
||||
this.exitKeepAliveAfter = exitKeepAliveAfter;
|
||||
|
@ -78,13 +88,16 @@ export class Speaker extends BaseSpeaker {
|
|||
}
|
||||
}
|
||||
|
||||
audio_silent?: string;
|
||||
async activeKeepAliveMode() {
|
||||
while (this.status === "running") {
|
||||
if (this.keepAlive) {
|
||||
// 唤醒中
|
||||
if (!this.responding) {
|
||||
// 没有回复时,一直播放静音音频使小爱闭嘴
|
||||
await this.MiNA?.play({ url: process.env.AUDIO_SILENT });
|
||||
await this.MiNA?.play(
|
||||
this.audio_silent ? { url: this.audio_silent } : { tts: kAreYouOK }
|
||||
);
|
||||
}
|
||||
}
|
||||
await sleep(this.interval);
|
||||
|
|
|
@ -2,6 +2,8 @@ import { readJSONSync } from "./io";
|
|||
|
||||
export const kVersion = readJSONSync("package.json").version;
|
||||
|
||||
export const kAreYouOK = "¿ʞо ∩оʎ ǝɹɐ"; // are you ok?
|
||||
|
||||
export const kBannerASCII = `
|
||||
|
||||
/$$ /$$ /$$ /$$$$$$ /$$$$$$$ /$$$$$$$$
|
||||
|
|
|
@ -7,16 +7,18 @@ import { testSpeaker } from "./speaker";
|
|||
import { testOpenAI } from "./openai";
|
||||
import { testMyBot } from "./bot";
|
||||
import { testLog } from "./log";
|
||||
import { testMiGPT } from "./migpt";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function main() {
|
||||
println(kBannerASCII);
|
||||
// println(kBannerASCII);
|
||||
// testDB();
|
||||
// testSpeaker();
|
||||
// testOpenAI();
|
||||
// testMyBot();
|
||||
testLog();
|
||||
// testLog();
|
||||
testMiGPT();
|
||||
}
|
||||
|
||||
runWithDB(main);
|
||||
|
|
60
tests/migpt.ts
Normal file
60
tests/migpt.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { MiGPT } from "../src";
|
||||
|
||||
const botProfile = `
|
||||
性别:女
|
||||
年龄:20岁
|
||||
学校:位于一个风景如画的小城市,一所综合性大学的文学院学生。
|
||||
性格特点:
|
||||
- 温婉可亲,对待人和事总是保持着乐观和善良的态度。
|
||||
- 内向而思维敏捷,喜欢独处时阅读和思考。
|
||||
- 对待朋友非常真诚,虽然不善于表达,但总是用行动去关心和帮助别人。
|
||||
外貌特征:
|
||||
- 清秀脱俗,长发及腰,喜欢简单的束发。
|
||||
- 眼睛大而有神,总是带着温和的微笑。
|
||||
- 穿着简单大方,偏爱文艺范的衣服,如棉麻连衣裙,不追求名牌,却总能穿出自己的风格。
|
||||
爱好:
|
||||
- 阅读,尤其是古典文学和现代诗歌,她的书房里收藏了大量的书籍。
|
||||
- 写作,喜欢在闲暇时写写诗或是短篇小说,有时也会在学校的文学社团里分享自己的作品。
|
||||
- 摄影,喜欢用镜头记录生活中的美好瞬间,尤其是自然风光和人文景观。
|
||||
特长:
|
||||
- 写作能力突出,曾多次获得学校文学比赛的奖项。
|
||||
- 擅长钢琴,从小学习,能够演奏多首经典曲目。
|
||||
- 有一定的绘画基础,喜欢在空闲时画一些风景或是静物。
|
||||
梦想:
|
||||
- 希望能成为一名作家,将自己对生活的感悟和对美的追求通过文字传达给更多的人。
|
||||
- 想要环游世界,用镜头和笔记录下世界各地的美丽和人文。
|
||||
`;
|
||||
|
||||
const masterProfile = `
|
||||
性别:男
|
||||
年龄:18
|
||||
爱好:跑步,骑行,读书,追剧,旅游,听歌
|
||||
职业:程序员
|
||||
其他:
|
||||
- 喜欢的电视剧有《请回答1988》、《漫长的季节》、《爱的迫降》等
|
||||
- 喜欢吃土豆丝、茄子、山药、米线
|
||||
- 喜欢黑红配色,浅蓝色和粉色
|
||||
- 有空喜欢去公园静观人来人往
|
||||
`;
|
||||
|
||||
export async function testMiGPT() {
|
||||
const name = "豆包";
|
||||
const client = MiGPT.create({
|
||||
speaker: {
|
||||
name,
|
||||
tts: "doubao",
|
||||
userId: process.env.MI_USER!,
|
||||
password: process.env.MI_PASS!,
|
||||
did: process.env.MI_DID,
|
||||
},
|
||||
bot: {
|
||||
name,
|
||||
profile: botProfile,
|
||||
},
|
||||
master: {
|
||||
name: "王黎",
|
||||
profile: masterProfile,
|
||||
},
|
||||
});
|
||||
await client.start();
|
||||
}
|
Loading…
Reference in New Issue
Block a user