mirror of
https://github.com/crystelf/crystelf-admin.git
synced 2025-12-05 13:41:57 +00:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # lib/config/configControl.js
This commit is contained in:
commit
0992d0d809
@ -18,7 +18,7 @@ export default class CoreRestart extends plugin {
|
||||
}
|
||||
|
||||
async restart(e) {
|
||||
if (!configControl.get('core')) {
|
||||
if (!configControl.get()?.core) {
|
||||
return e.reply(`晶灵核心未启用..`, true);
|
||||
}
|
||||
const returnData = await systemControl.systemRestart();
|
||||
|
||||
326
apps/login.js
Normal file
326
apps/login.js
Normal file
@ -0,0 +1,326 @@
|
||||
import plugin from '../../../lib/plugins/plugin.js';
|
||||
import ConfigControl from '../lib/config/configControl.js';
|
||||
import configControl from '../lib/config/configControl.js';
|
||||
import Meme from '../lib/core/meme.js';
|
||||
import NapcatService from '../lib/login/napcat.js';
|
||||
import LgrService from '../lib/login/lgr.js';
|
||||
const loginSessions = new Map(); //正在进行的登录会话
|
||||
const bindSessions = new Map(); //正在进行的绑定会话
|
||||
const activeLogins = new Map(); //在线登录实例
|
||||
|
||||
export default class LoginService extends plugin {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'Onebot登录相关服务',
|
||||
dsc: '方便操作',
|
||||
event: 'message',
|
||||
priority: 50,
|
||||
rule: [
|
||||
{
|
||||
reg: '^#登录(\\d+)?$',
|
||||
fnc: 'loginHandler',
|
||||
},
|
||||
{
|
||||
reg: '^#绑定账号(\\s+\\d+)?$',
|
||||
fnc: 'bindAccount',
|
||||
},
|
||||
{
|
||||
reg: '^#解绑账号\\s+\\d+$',
|
||||
fnc: 'unbindAccount',
|
||||
},
|
||||
{
|
||||
reg: '^#退出登录\\s+\\d+$',
|
||||
fnc: 'logoutHandler',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录命令入口
|
||||
* @param e
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async loginHandler(e) {
|
||||
let config = await configControl.get();
|
||||
if (!config?.login?.allowGroups.includes(e.group_id)) {
|
||||
const img = await Meme.getMeme('zhenxun', 'default');
|
||||
return e.reply(segment.image(img)); //都不在群里玩什么;[
|
||||
}
|
||||
const isAdmin = e.isMaster;
|
||||
const userId = e.user_id;
|
||||
const match = e.msg.match(/^#登录(\d+)?$/);
|
||||
let targetQq = match[1];
|
||||
|
||||
if (!targetQq) {
|
||||
const binds = config?.login?.userBinds[userId] || [];
|
||||
if (binds.length === 0) {
|
||||
return e.reply('管理员似乎没有给你分配可用账户,请联系管理员添加..', true);
|
||||
} else if (binds.length === 1) {
|
||||
targetQq = binds[0].qq;
|
||||
} else {
|
||||
loginSessions.set(userId, { step: 'chooseQq', options: binds });
|
||||
return e.reply(
|
||||
`你小子账号还挺多,选一个qq登录吧:\n${binds.map((b) => b.qq).join('\n')}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
await this.startAdminLogin(e, targetQq);
|
||||
} else {
|
||||
const binds = config?.login?.userBinds[userId] || [];
|
||||
const bind = binds.find((b) => b.qq === targetQq);
|
||||
if (!bind) {
|
||||
return e.reply('你没有权限登录该账号,请联系管理员分配..', true);
|
||||
}
|
||||
await this.startUserLogin(e, bind);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
* @param e
|
||||
* @param qq
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async startAdminLogin(e, qq) {
|
||||
loginSessions.set(e.user_id, {
|
||||
step: 'askNickname',
|
||||
qq,
|
||||
admin: true,
|
||||
});
|
||||
return e.reply(`QQ[${qq}]的英文名叫什么?`, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通用户
|
||||
* @param e
|
||||
* @param bind
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async startUserLogin(e, bind) {
|
||||
loginSessions.set(e.user_id, {
|
||||
step: 'autoLogin',
|
||||
qq: bind.qq,
|
||||
nickname: bind.nickname,
|
||||
method: bind.method,
|
||||
admin: false,
|
||||
});
|
||||
return this.doLogin(e, {
|
||||
qq: bind.qq,
|
||||
nickname: bind.nickname,
|
||||
method: bind.method,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定账号
|
||||
* @param e
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async bindAccount(e) {
|
||||
if (!e.isMaster) {
|
||||
const img = await Meme.getMeme('zhenxun', 'default');
|
||||
return e.reply(segment.image(img));
|
||||
}
|
||||
const at = e.at || e.user_id;
|
||||
bindSessions.set(e.user_id, { step: 'askQq', targetUser: at });
|
||||
return e.reply('要绑定的QQ号是哪个?', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑账号
|
||||
* @param e
|
||||
* @returns {Promise<*|boolean>}
|
||||
*/
|
||||
async unbindAccount(e) {
|
||||
if (!e.isMaster) {
|
||||
const img = await Meme.getMeme('zhenxun', 'default');
|
||||
return e.reply(segment.image(img));
|
||||
}
|
||||
let config = await configControl.get()?.login;
|
||||
const match = e.msg.match(/^#解绑账号\s+(\d+)$/);
|
||||
if (!match) return;
|
||||
const qq = match[1];
|
||||
const at = e.at || e.user_id;
|
||||
if (!config?.userBinds[at]) {
|
||||
return e.reply('该用户没有绑定账号..', true);
|
||||
}
|
||||
config.userBinds[at] = config.userBinds[at].filter((q) => q.qq !== qq);
|
||||
await ConfigControl.set('login', config);
|
||||
return e.reply(`已为 ${at} 解绑账号 ${qq}`, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
* @param e
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async logoutHandler(e) {
|
||||
const match = e.msg.match(/^#退出登录\s+(\d+)$/);
|
||||
if (!match) return;
|
||||
const qq = match[1];
|
||||
const instance = activeLogins.get(qq);
|
||||
if (!instance) {
|
||||
return e.reply(`QQ[${qq}] 没有活跃的登录会话..`, true);
|
||||
}
|
||||
let config = await configControl.get();
|
||||
const isAdmin = e.isMaster;
|
||||
const userId = e.user_id;
|
||||
const binds = config?.login?.userBinds[userId] || [];
|
||||
if (!isAdmin && !binds.includes(qq)) {
|
||||
return e.reply(`你没有权限退出 QQ[${qq}] 的会话..`, true);
|
||||
}
|
||||
await instance.disconnect();
|
||||
activeLogins.delete(qq);
|
||||
return e.reply(`QQ[${qq}] 已退出登录..`, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录流程
|
||||
* @param e
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async accept(e) {
|
||||
const session = loginSessions.get(e.user_id);
|
||||
const bindSession = bindSessions.get(e.user_id);
|
||||
if (!session && !bindSession) return;
|
||||
|
||||
if (bindSession) {
|
||||
if (bindSession.step === 'askQq') {
|
||||
bindSession.qq = e.msg.trim();
|
||||
bindSession.step = 'askNickname';
|
||||
return e.reply(`QQ[${bindSession.qq}]的英文名叫什么?`, true);
|
||||
}
|
||||
if (bindSession.step === 'askNickname') {
|
||||
bindSession.nickname = e.msg.trim();
|
||||
bindSession.step = 'askMethod';
|
||||
return e.reply(`请选择登录方式\n[nc]或[lgr]\n来绑定 QQ[${bindSession.qq}]`, true);
|
||||
}
|
||||
if (bindSession.step === 'askMethod') {
|
||||
const method = e.msg.trim().toLowerCase();
|
||||
if (!['nc', 'lgr'].includes(method)) {
|
||||
return e.reply('登录方式无效', true);
|
||||
}
|
||||
bindSession.method = method;
|
||||
let config = await configControl.get()?.login;
|
||||
if (!config.userBinds[bindSession.targetUser])
|
||||
config.userBinds[bindSession.targetUser] = [];
|
||||
config.userBinds[bindSession.targetUser].push({
|
||||
qq: bindSession.qq,
|
||||
nickname: bindSession.nickname,
|
||||
method: bindSession.method,
|
||||
});
|
||||
await ConfigControl.set('login', config);
|
||||
bindSessions.delete(e.user_id);
|
||||
return e.reply(
|
||||
`已为 ${bindSession.targetUser} 绑定账号 ${bindSession.qq} (${bindSession.nickname}, ${bindSession.method})`,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (session) {
|
||||
let config = await configControl.get();
|
||||
if (!e.group_id || !config?.login?.allowGroups.includes(e.group_id)) return;
|
||||
|
||||
if (session.step === 'chooseQq') {
|
||||
const chosen = e.msg.trim();
|
||||
const bind = session.options.find((b) => b.qq === chosen);
|
||||
if (!bind) {
|
||||
return e.reply('请选择列表中的 QQ', true);
|
||||
}
|
||||
loginSessions.delete(e.user_id);
|
||||
return this.doLogin(e, {
|
||||
qq: bind.qq,
|
||||
nickname: bind.nickname,
|
||||
method: bind.method,
|
||||
});
|
||||
}
|
||||
|
||||
if (session.step === 'askNickname') {
|
||||
session.nickname = e.msg.trim();
|
||||
session.step = 'askMethod';
|
||||
return e.reply(`请选择登录方式\n[nc]或[lgr]\n来登录 QQ[${session.qq}]`, true);
|
||||
}
|
||||
|
||||
if (session.step === 'askMethod') {
|
||||
const method = e.msg.trim().toLowerCase();
|
||||
if (!['nc', 'lgr'].includes(method)) {
|
||||
return e.reply('登录方式无效', true);
|
||||
}
|
||||
session.method = method;
|
||||
loginSessions.delete(e.user_id);
|
||||
await this.doLogin(e, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行登录
|
||||
* @param e
|
||||
* @param session
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async doLogin(e, session) {
|
||||
try {
|
||||
const { qq, method, nickname } = session;
|
||||
e.reply(`开始尝试使用 ${method} 登录 QQ[${qq}]`, true);
|
||||
let loginInstance;
|
||||
if (method === 'nc') {
|
||||
loginInstance = new NapcatService();
|
||||
} else {
|
||||
loginInstance = new LgrService();
|
||||
}
|
||||
activeLogins.set(qq, loginInstance);
|
||||
const qrPath = await loginInstance.login(qq, nickname);
|
||||
const loginTimers = new Map();
|
||||
if (qrPath && qrPath !== 'none') {
|
||||
await e.reply(
|
||||
[
|
||||
segment.image(`file:///${qrPath}`),
|
||||
'\n请使用手机qq摄像头扫码登录并勾选保存登录状态\n二维码有效期2分钟..',
|
||||
],
|
||||
true,
|
||||
{ recallMsg: 120 }
|
||||
);
|
||||
const timerKey = `login:timer:${qq}`;
|
||||
if (loginTimers.has(timerKey)) {
|
||||
clearTimeout(loginTimers.get(timerKey).timeout);
|
||||
clearInterval(loginTimers.get(timerKey).check);
|
||||
loginTimers.delete(timerKey);
|
||||
}
|
||||
const check = setInterval(async () => {
|
||||
const status = await loginInstance.checkStatus(qq);
|
||||
if (status) {
|
||||
clearInterval(check);
|
||||
clearTimeout(timerObj.timeout);
|
||||
loginTimers.delete(timerKey);
|
||||
return e.reply(`QQ[${qq}] 登录成功!`, true);
|
||||
}
|
||||
}, 5000);
|
||||
const timeout = setTimeout(async () => {
|
||||
clearInterval(check);
|
||||
loginTimers.delete(timerKey);
|
||||
await loginInstance.disconnect(nickname);
|
||||
activeLogins.delete(qq);
|
||||
return e.reply(`QQ[${qq}] 登录超时,已断开,请重新发起登录..`, true);
|
||||
}, 120 * 1000);
|
||||
const timerObj = { check, timeout };
|
||||
loginTimers.set(timerKey, timerObj);
|
||||
} else {
|
||||
const status = await loginInstance.checkStatus(qq);
|
||||
if (status) {
|
||||
return e.reply(`QQ[${qq}] 使用上次登录缓存登录成功!`, true);
|
||||
} else {
|
||||
return e.reply(`QQ[${qq}] 登录出现未知错误,请联系管理员操作..`, true);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('[crystelf-admin]登录流程出现错误', err);
|
||||
return e.reply(`出了点小问题,过会儿再来试试吧..`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,13 +26,13 @@ export default class ReportBots extends plugin {
|
||||
|
||||
async autoReport() {
|
||||
logger.mark(`[crystelf-admin] 正在自动同步bot数据到晶灵核心..`);
|
||||
if (configControl.get('core')) {
|
||||
if (configControl.get()?.core) {
|
||||
await botControl.reportBots();
|
||||
}
|
||||
}
|
||||
|
||||
async manualReport(e) {
|
||||
if (!configControl.get('core')) {
|
||||
if (!configControl.get()?.core) {
|
||||
return e.reply(`[crystelf-admin] 晶灵核心未启用..`, true);
|
||||
}
|
||||
let success = await botControl.reportBots();
|
||||
@ -51,10 +51,10 @@ export default class ReportBots extends plugin {
|
||||
await e.reply(`开始广播消息到所有群..`);
|
||||
try {
|
||||
const sendData = {
|
||||
token: configControl.get('coreConfig')?.token,
|
||||
token: configControl.get()?.coreConfig?.token,
|
||||
message: msg.toString(),
|
||||
};
|
||||
const url = configControl.get('coreConfig')?.coreUrl;
|
||||
const url = configControl.get()?.coreConfig?.coreUrl;
|
||||
const returnData = await axios.post(`${url}/api/bot/broadcast`, sendData);
|
||||
if (returnData?.data?.success) {
|
||||
return await e.reply(`操作成功:${returnData?.data.data?.toString()}`);
|
||||
|
||||
@ -7,5 +7,12 @@
|
||||
"wsSecret": "",
|
||||
"wsReConnectInterval": "5000",
|
||||
"token": ""
|
||||
},
|
||||
"napcat": {
|
||||
"basePath": "/opt/QQ/resources/app/app_launcher/napcat",
|
||||
"userPath": ""
|
||||
},
|
||||
"lgr": {
|
||||
"basePath": ""
|
||||
}
|
||||
}
|
||||
|
||||
4
config/login.json
Normal file
4
config/login.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"allowGroups": [],
|
||||
"userBinds": {}
|
||||
}
|
||||
26
config/napcat.json
Normal file
26
config/napcat.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"network": {
|
||||
"httpServers": [],
|
||||
"httpSseServers": [],
|
||||
"httpClients": [],
|
||||
"websocketServers": [
|
||||
],
|
||||
"websocketClients": [
|
||||
{
|
||||
"enable": true,
|
||||
"name": "trss",
|
||||
"url": "ws://localhost:2536/OneBotv11",
|
||||
"reportSelfMessage": false,
|
||||
"messagePostFormat": "array",
|
||||
"token": "",
|
||||
"debug": false,
|
||||
"heartInterval": 30000,
|
||||
"reconnectInterval": 10000
|
||||
}
|
||||
],
|
||||
"plugins": []
|
||||
},
|
||||
"musicSignUrl": "",
|
||||
"enableLocalFile2Url": false,
|
||||
"parseMultMsg": false
|
||||
}
|
||||
14
lib/core/meme.js
Normal file
14
lib/core/meme.js
Normal file
@ -0,0 +1,14 @@
|
||||
import ConfigControl from '../config/configControl.js';
|
||||
import axios from 'axios';
|
||||
|
||||
const Meme = {
|
||||
async getMeme(character, status) {
|
||||
const coreConfig = await ConfigControl.get()?.coreConfig;
|
||||
const coreUrl = coreConfig?.coreUrl;
|
||||
const token = coreConfig?.token;
|
||||
//logger.info(`${coreUrl}/api/meme`);
|
||||
return `${coreUrl}/api/meme?token=${token}?character=${character}&status=${status}`;
|
||||
},
|
||||
};
|
||||
|
||||
export default Meme;
|
||||
115
lib/login/lgr.js
Normal file
115
lib/login/lgr.js
Normal file
@ -0,0 +1,115 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import util from 'util';
|
||||
import configControl from '../config/configControl.js';
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
export default class LgrService {
|
||||
constructor() {
|
||||
const config = configControl.get('config')?.lgr || {};
|
||||
this.basePath = config.basePath;
|
||||
if (!this.basePath) {
|
||||
logger.error('[crystelf-admin] 未检测到lgr配置..');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lgr登录方法
|
||||
* @param qq qq号
|
||||
* @param nickname 昵称
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async login(qq, nickname) {
|
||||
if (!this.basePath) {
|
||||
logger.error('[crystelf-admin] 未配置 lgr.basePath');
|
||||
}
|
||||
const parentDir = path.dirname(this.basePath);
|
||||
const targetDir = path.join(parentDir, String(qq));
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
try {
|
||||
await execAsync(`cp -r "${this.basePath}" "${targetDir}"`);
|
||||
logger.info(`[crystelf-admin] 已复制 ${this.basePath} 到 ${targetDir}..`);
|
||||
} catch (err) {
|
||||
logger.error(`[crystelf-admin] 复制文件夹失败: ${err.message}..`);
|
||||
}
|
||||
}
|
||||
const exeFile = path.join(targetDir, 'lgr');
|
||||
try {
|
||||
await execAsync(`chmod +777 "${exeFile}"`);
|
||||
} catch (err) {
|
||||
logger.error(`[crystelf-admin] chmod 失败: ${err.message}..`);
|
||||
}
|
||||
try {
|
||||
await execAsync(`tmux has-session -t ${nickname}`);
|
||||
await execAsync(`tmux kill-session -t ${nickname}`);
|
||||
await execAsync(`tmux new -s ${nickname} -d "cd '${targetDir}' && ./lgr"`);
|
||||
} catch {
|
||||
await execAsync(`tmux new -s ${nickname} -d "cd '${targetDir}' && ./lgr"`);
|
||||
}
|
||||
|
||||
return await this.waitForQrUpdate(targetDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待qr图片更新
|
||||
* @param targetDir 目标文件夹
|
||||
* @param timeout 最大等待时间 (默认 30s)
|
||||
* @returns {Promise<string|undefined>}
|
||||
*/
|
||||
async waitForQrUpdate(targetDir, timeout = 30000) {
|
||||
const qrPath = path.join(targetDir, 'qr-0.png');
|
||||
if (!fs.existsSync(qrPath)) {
|
||||
return 'none';
|
||||
}
|
||||
let lastMtime = fs.statSync(qrPath).mtimeMs;
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
watcher.close();
|
||||
resolve(undefined);
|
||||
}
|
||||
}, timeout);
|
||||
const watcher = fs.watch(qrPath, (eventType) => {
|
||||
if (eventType === 'change') {
|
||||
const stat = fs.statSync(qrPath);
|
||||
if (stat.mtimeMs !== lastMtime) {
|
||||
lastMtime = stat.mtimeMs;
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timer);
|
||||
watcher.close();
|
||||
resolve(qrPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开lgr连接
|
||||
* @param {string} nickname
|
||||
*/
|
||||
async disconnect(nickname) {
|
||||
try {
|
||||
await execAsync(`tmux kill-session -t ${nickname}`);
|
||||
return `已关闭会话: ${nickname}`;
|
||||
} catch (err) {
|
||||
return `关闭会话失败: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* qq是否登录成功
|
||||
* @param qq
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async checkStatus(qq) {
|
||||
return Bot.uin.includes(qq);
|
||||
}
|
||||
}
|
||||
121
lib/login/napcat.js
Normal file
121
lib/login/napcat.js
Normal file
@ -0,0 +1,121 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { exec } from 'child_process';
|
||||
import util from 'util';
|
||||
import configControl from '../config/configControl.js';
|
||||
import Path from '../../constants/path.js';
|
||||
|
||||
const execAsync = util.promisify(exec);
|
||||
|
||||
export default class NapcatService {
|
||||
constructor() {
|
||||
const config = configControl.get('config')?.napcat || {};
|
||||
this.basePath = config.basePath;
|
||||
this.userPath = config.userPath;
|
||||
if (!this.basePath || !this.userPath) {
|
||||
logger.error('[crystelf-admin] 未检测到napcat配置..');
|
||||
}
|
||||
this.qrPath = path.join(this.basePath, 'cache', 'qrcode.png');
|
||||
this.configPath = path.join(this.basePath, 'config');
|
||||
}
|
||||
|
||||
/**
|
||||
* nc登录方法
|
||||
* @param qq qq号
|
||||
* @param nickname 昵称
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async login(qq, nickname) {
|
||||
const shFile = path.join(this.userPath, `${qq}.sh`);
|
||||
if (!fs.existsSync(this.userPath)) {
|
||||
fs.mkdirSync(this.userPath, { recursive: true });
|
||||
}
|
||||
const userConfigFile = path.join(this.configPath, `onebot11_${qq}.json`);
|
||||
if (!fs.existsSync(userConfigFile)) {
|
||||
try {
|
||||
const defaultConfigFile = path.join(Path.config || '', 'napcat.json');
|
||||
if (!fs.existsSync(defaultConfigFile)) {
|
||||
logger.error(`[crystelf-admin] 默认配置文件不存在: ${defaultConfigFile}`);
|
||||
}
|
||||
fs.copyFileSync(defaultConfigFile, userConfigFile);
|
||||
logger.info(`[crystelf-admin] 已复制默认配置到 ${userConfigFile}`);
|
||||
} catch (err) {
|
||||
logger.error(`[crystelf-admin] 复制默认配置失败: ${err.message}`);
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(shFile)) {
|
||||
const scriptContent = `#!/bin/bash\nxvfb-run -a qq --no-sandbox -q ${qq}\n`;
|
||||
fs.writeFileSync(shFile, scriptContent, { mode: 0o755 });
|
||||
}
|
||||
try {
|
||||
await execAsync(`tmux has-session -t ${nickname}`);
|
||||
// 存在就先干掉
|
||||
await execAsync(`tmux kill-session -t ${nickname}`);
|
||||
await execAsync(`tmux new -s ${nickname} -d "bash '${shFile}'"`);
|
||||
} catch {
|
||||
// 不存在再新建
|
||||
await execAsync(`tmux new -s ${nickname} -d "bash '${shFile}'"`);
|
||||
}
|
||||
|
||||
return await this.waitForQrUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待qrcode图像更新
|
||||
* @param timeout
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
async waitForQrUpdate(timeout = 30000) {
|
||||
if (!fs.existsSync(this.qrPath)) {
|
||||
return 'none';
|
||||
}
|
||||
let lastMtime = fs.statSync(this.qrPath).mtimeMs;
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
const timer = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
watcher.close();
|
||||
resolve(undefined);
|
||||
}
|
||||
}, timeout);
|
||||
const watcher = fs.watch(this.qrPath, (eventType) => {
|
||||
if (eventType === 'change') {
|
||||
const stat = fs.statSync(this.qrPath);
|
||||
if (stat.mtimeMs !== lastMtime) {
|
||||
lastMtime = stat.mtimeMs;
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timer);
|
||||
watcher.close();
|
||||
resolve(this.qrPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开nc连接
|
||||
* @param nickname 昵称
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async disconnect(nickname) {
|
||||
try {
|
||||
await execAsync(`tmux kill-session -t ${nickname}`);
|
||||
return `已关闭会话: ${nickname}`;
|
||||
} catch (err) {
|
||||
return `关闭会话失败: ${err.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* qq是否登录成功
|
||||
* @param qq
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async checkStatus(qq) {
|
||||
return Bot.uin.includes(qq);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
import configControl from '../config/configControl.js';
|
||||
import wsClient from '../../modules/ws/wsClient.js';
|
||||
|
||||
export const crystelfInit = {
|
||||
async CSH() {
|
||||
await configControl.init();
|
||||
await wsClient.initialize();
|
||||
},
|
||||
};
|
||||
|
||||
@ -19,12 +19,12 @@ class WsClient {
|
||||
return;
|
||||
}
|
||||
|
||||
this.wsURL = configControl.get('coreConfig')?.wsUrl;
|
||||
this.secret = configControl.get('coreConfig')?.wsSecret;
|
||||
this.clientId = configControl.get('coreConfig')?.wsClientId;
|
||||
this.reconnectInterval = configControl.get('coreConfig')?.wsReConnectInterval;
|
||||
this.wsURL = configControl.get('config')?.coreConfig?.coreUrl;
|
||||
this.secret = configControl.get('config')?.coreConfig?.wsSecret;
|
||||
this.clientId = configControl.get('config')?.coreConfig?.wsClientId;
|
||||
this.reconnectInterval = configControl.get('config')?.coreConfig?.wsReConnectInterval;
|
||||
|
||||
//logger.info(this.wsURL);
|
||||
//logger.info(configControl.get('config'));
|
||||
this.ws = new WebSocket(this.wsURL);
|
||||
|
||||
this.ws.on('open', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user