mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ac2b0118a6 | ||
![]() |
3eb8fd4abe | ||
![]() |
48b389ebe3 | ||
![]() |
065adeb2cd | ||
![]() |
269d0a06fe | ||
![]() |
8eca26b1a5 | ||
![]() |
3019ef7de4 | ||
![]() |
522311b547 | ||
![]() |
21061561ec | ||
![]() |
b83c41ad56 | ||
![]() |
e80a1cc64a | ||
![]() |
a01e4ca89f | ||
![]() |
c20362e9b6 | ||
![]() |
c90cfb99bd | ||
![]() |
7bcea14799 | ||
![]() |
b415c1a6d1 | ||
![]() |
452c72d280 | ||
![]() |
48350be625 | ||
![]() |
ab824fb219 | ||
![]() |
043d8a1861 | ||
![]() |
074ac15d0f | ||
![]() |
d36a28fa81 | ||
![]() |
ba12bc6c91 | ||
![]() |
87332778e5 | ||
![]() |
453feb8473 | ||
![]() |
8ff469974c | ||
![]() |
994ec5ac0f | ||
![]() |
43f7f9a363 | ||
![]() |
4a11ebc9b9 | ||
![]() |
d76a1305e7 | ||
![]() |
6a0d592491 | ||
![]() |
9898c2196d | ||
![]() |
41a8dc840f | ||
![]() |
c3eaae9d88 | ||
![]() |
3ca959b7a6 |
@@ -17,5 +17,8 @@ charset = utf-8
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.bat]
|
||||||
|
charset = latin1
|
||||||
|
|
||||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
||||||
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
||||||
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -10,6 +10,7 @@ body:
|
|||||||
在提交新的 Bug 反馈前,请确保您:
|
在提交新的 Bug 反馈前,请确保您:
|
||||||
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
* 已经搜索了现有的 issues,并且没有找到可以解决您问题的方法
|
||||||
* 不与现有的某一 issue 重复
|
* 不与现有的某一 issue 重复
|
||||||
|
* 不涉及[已经停止维护的特性](https://github.com/NapNeko/NapCatQQ?tab=readme-ov-file#挥别昨日),例如 CQ 码
|
||||||
- type: input
|
- type: input
|
||||||
id: system-version
|
id: system-version
|
||||||
attributes:
|
attributes:
|
||||||
@@ -78,4 +79,4 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: OneBot 客户端运行日志
|
label: OneBot 客户端运行日志
|
||||||
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
description: 粘贴 OneBot 客户端的相关日志内容到此处
|
||||||
render: shell
|
render: shell
|
||||||
|
@@ -9,6 +9,8 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
## 猫猫技能
|
## 猫猫技能
|
||||||
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
- [x] **高性能**:1K+ 群聊数目、20 线程并行发送消息毫无压力
|
||||||
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
- [x] **多种启动方式**:支持以无头、LiteLoader 插件、仅 QQ GUI 三种方式启动
|
||||||
|
- [x] **多平台支持**: 覆盖 Windows / Linux (可选 Docker) / Android Termux / MacOS
|
||||||
|
- [x] **安装简单**: 支持一键脚本/程序自动部署/镜像部署等多种覆盖范围
|
||||||
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
|
||||||
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
- [x] **超多接口**:实现大部分 OneBot 和 go-cqhttp 接口,超多扩展 API
|
||||||
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
|
||||||
@@ -36,4 +38,4 @@ NapCatQQ (aka 猫猫框架) 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|||||||
> [!CAUTION]\
|
> [!CAUTION]\
|
||||||
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
> **请不要在 QQ 官方群聊和任何影响力较大的简中互联网平台(包括但不限于: 哔哩哔哩,微博,知乎,抖音等)发布和讨论*任何*与本项目存在相关性的信息**
|
||||||
|
|
||||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经授权二次分发或基于 NapCat 代码开发。**
|
BIN
launcher/NapCatWinBootHook.dll
Normal file
BIN
launcher/NapCatWinBootHook.dll
Normal file
Binary file not shown.
BIN
launcher/NapCatWinBootMain.exe
Normal file
BIN
launcher/NapCatWinBootMain.exe
Normal file
Binary file not shown.
40
launcher/launcher-win10.bat
Normal file
40
launcher/launcher-win10.bat
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorLevel% == 0 (
|
||||||
|
echo Administrator mode detected.
|
||||||
|
) else (
|
||||||
|
echo Please run this script in administrator mode.
|
||||||
|
powershell -Command "Start-Process 'cmd.exe' -ArgumentList '/c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
|
||||||
|
exit
|
||||||
|
)
|
||||||
|
|
||||||
|
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
||||||
|
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||||
|
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||||
|
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||||
|
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||||
|
:loop_read
|
||||||
|
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||||
|
set RetString=%%b
|
||||||
|
goto :napcat_boot
|
||||||
|
)
|
||||||
|
|
||||||
|
:napcat_boot
|
||||||
|
for %%a in ("%RetString%") do (
|
||||||
|
set "pathWithoutUninstall=%%~dpa"
|
||||||
|
)
|
||||||
|
|
||||||
|
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||||
|
|
||||||
|
if not exist "%QQpath%" (
|
||||||
|
echo provided QQ path is invalid: %QQpath%
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||||
|
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
|
||||||
|
|
||||||
|
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
|
||||||
|
|
||||||
|
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
|
41
launcher/launcher.bat
Normal file
41
launcher/launcher.bat
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorLevel% == 0 (
|
||||||
|
echo Administrator mode detected.
|
||||||
|
) else (
|
||||||
|
echo Please run this script in administrator mode.
|
||||||
|
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /c cd /d \"%cd%\" && \"%~f0\"' -Verb runAs"
|
||||||
|
exit
|
||||||
|
)
|
||||||
|
|
||||||
|
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
||||||
|
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||||
|
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||||
|
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||||
|
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||||
|
:loop_read
|
||||||
|
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||||
|
set RetString=%%b
|
||||||
|
goto :napcat_boot
|
||||||
|
)
|
||||||
|
|
||||||
|
:napcat_boot
|
||||||
|
for %%a in ("%RetString%") do (
|
||||||
|
set "pathWithoutUninstall=%%~dpa"
|
||||||
|
)
|
||||||
|
|
||||||
|
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||||
|
|
||||||
|
if not exist "%QQpath%" (
|
||||||
|
echo provided QQ path is invalid: %QQpath%
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||||
|
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > %NAPCAT_LOAD_PATH%
|
||||||
|
|
||||||
|
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%"
|
||||||
|
|
||||||
|
REM "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" 123456
|
5
launcher/loadNapCat.js
Normal file
5
launcher/loadNapCat.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const CurrentPath = path.dirname(__filename);
|
||||||
|
(async () => {
|
||||||
|
await import("file://" + path.join(CurrentPath, './napcat/napcat.mjs'));
|
||||||
|
})();
|
1
launcher/patchNapCat.js
Normal file
1
launcher/patchNapCat.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require('./launcher.node').load('external_index', module);
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "2.2.44",
|
"version": "2.3.5",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.2.44",
|
"version": "2.3.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:framework": "vite build --mode framework",
|
"build:framework": "vite build --mode framework",
|
||||||
"build:shell": "vite build --mode shell",
|
"build:shell": "vite build --mode shell",
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '2.2.44';
|
export const napCatVersion = '2.3.5';
|
||||||
|
@@ -298,65 +298,6 @@ export class NTQQFileApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) {
|
|
||||||
let GroupData;
|
|
||||||
let BuddyData;
|
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
|
||||||
GroupData =
|
|
||||||
[{
|
|
||||||
groupCode: peer.peerUid,
|
|
||||||
isConf: false,
|
|
||||||
hasModifyConfGroupFace: true,
|
|
||||||
hasModifyConfGroupName: true,
|
|
||||||
groupName: 'NapCat.Cached',
|
|
||||||
remark: 'NapCat.Cached',
|
|
||||||
}];
|
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
|
||||||
BuddyData = [{
|
|
||||||
category_name: 'NapCat.Cached',
|
|
||||||
peerUid: peer.peerUid,
|
|
||||||
peerUin: peer.peerUid,
|
|
||||||
remark: 'NapCat.Cached',
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.context.session.getSearchService().addSearchHistory({
|
|
||||||
type: 4,
|
|
||||||
contactList: [],
|
|
||||||
id: -1,
|
|
||||||
groupInfos: [],
|
|
||||||
msgs: [],
|
|
||||||
fileInfos: [
|
|
||||||
{
|
|
||||||
chatType: peer.chatType,
|
|
||||||
buddyChatInfo: BuddyData || [],
|
|
||||||
discussChatInfo: [],
|
|
||||||
groupChatInfo: GroupData || [],
|
|
||||||
dataLineChatInfo: [],
|
|
||||||
tmpChatInfo: [],
|
|
||||||
msgId: msgId,
|
|
||||||
msgSeq: msgSeq,
|
|
||||||
msgTime: Math.floor(Date.now() / 1000).toString(),
|
|
||||||
senderUid: senderUid,
|
|
||||||
senderNick: 'NapCat.Cached',
|
|
||||||
senderRemark: 'NapCat.Cached',
|
|
||||||
senderCard: 'NapCat.Cached',
|
|
||||||
elemId: elemId,
|
|
||||||
elemType: elemType,
|
|
||||||
fileSize: fileSize,
|
|
||||||
filePath: '',
|
|
||||||
fileName: fileName,
|
|
||||||
hits: [{
|
|
||||||
start: 12,
|
|
||||||
end: 14,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
|
async searchForFile(keys: string[]): Promise<SearchResultItem | undefined> {
|
||||||
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
|
const randomResultId = 100000 + Math.floor(Math.random() * 10000);
|
||||||
let searchId = 0;
|
let searchId = 0;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ChatType,
|
|
||||||
GeneralCallResult,
|
GeneralCallResult,
|
||||||
Group,
|
Group,
|
||||||
GroupMember,
|
GroupMember,
|
||||||
@@ -10,7 +9,7 @@ import {
|
|||||||
MemberExtSourceType,
|
MemberExtSourceType,
|
||||||
NapCatCore,
|
NapCatCore,
|
||||||
} from '@/core';
|
} from '@/core';
|
||||||
import { isNumeric, runAllWithTimeout } from '@/common/helper';
|
import { isNumeric } from '@/common/helper';
|
||||||
import { LimitedHashTable } from '@/common/message-unique';
|
import { LimitedHashTable } from '@/common/message-unique';
|
||||||
|
|
||||||
export class NTQQGroupApi {
|
export class NTQQGroupApi {
|
||||||
@@ -248,7 +247,7 @@ export class NTQQGroupApi {
|
|||||||
'NodeIKernelGroupListener/onMemberInfoChange',
|
'NodeIKernelGroupListener/onMemberInfoChange',
|
||||||
1,
|
1,
|
||||||
forced ? 5000 : 250,
|
forced ? 5000 : 250,
|
||||||
(params) => params === GroupCode,
|
(params, _, members) => params === GroupCode && members.size > 0,
|
||||||
);
|
);
|
||||||
const retData = await (
|
const retData = await (
|
||||||
this.core.eventWrapper
|
this.core.eventWrapper
|
||||||
|
@@ -75,7 +75,6 @@ export class NTQQUserApi {
|
|||||||
(profile) => profile.uid === uid,
|
(profile) => profile.uid === uid,
|
||||||
);
|
);
|
||||||
const RetUser: User = {
|
const RetUser: User = {
|
||||||
...profile.simpleInfo.coreInfo,
|
|
||||||
...profile.simpleInfo.status,
|
...profile.simpleInfo.status,
|
||||||
...profile.simpleInfo.vasInfo,
|
...profile.simpleInfo.vasInfo,
|
||||||
...profile.commonExt,
|
...profile.commonExt,
|
||||||
@@ -83,17 +82,22 @@ export class NTQQUserApi {
|
|||||||
qqLevel: profile.commonExt?.qqLevel,
|
qqLevel: profile.commonExt?.qqLevel,
|
||||||
age: profile.simpleInfo.baseInfo.age,
|
age: profile.simpleInfo.baseInfo.age,
|
||||||
pendantId: '',
|
pendantId: '',
|
||||||
|
...profile.simpleInfo.coreInfo
|
||||||
};
|
};
|
||||||
return RetUser;
|
return RetUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserDetailInfo(uid: string): Promise<User> {
|
async getUserDetailInfo(uid: string): Promise<User> {
|
||||||
const retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
|
let retUser = await solveAsyncProblem(async (uid) => this.fetchUserDetailInfo(uid, UserDetailSource.KDB), uid);
|
||||||
if (retUser && retUser.uin !== '0') {
|
if (retUser && retUser.uin !== '0') {
|
||||||
return retUser;
|
return retUser;
|
||||||
}
|
}
|
||||||
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
|
this.context.logger.logDebug('[NapCat] [Mark] getUserDetailInfo Mode1 Failed.');
|
||||||
return this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
|
retUser = await this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER);
|
||||||
|
if (retUser && retUser.uin === '0') {
|
||||||
|
retUser.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0';
|
||||||
|
}
|
||||||
|
return retUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async modifySelfProfile(param: ModifyProfileParams) {
|
async modifySelfProfile(param: ModifyProfileParams) {
|
||||||
|
@@ -131,20 +131,53 @@ export class NTQQWebApi {
|
|||||||
// return await res.json();
|
// return await res.json();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async setGroupNotice(GroupCode: string, Content: string) {
|
async setGroupNotice(
|
||||||
|
GroupCode: string,
|
||||||
|
Content: string,
|
||||||
|
pinned: number = 0,
|
||||||
|
type: number = 1,
|
||||||
|
is_show_edit_card: number = 1,
|
||||||
|
tip_window_type: number = 1,
|
||||||
|
confirm_required: number = 1,
|
||||||
|
picId: string = '',
|
||||||
|
imgWidth: number = 540,
|
||||||
|
imgHeight: number = 300,
|
||||||
|
) {
|
||||||
|
interface SetNoticeRetSuccess {
|
||||||
|
ec: number;
|
||||||
|
em: string;
|
||||||
|
id: number;
|
||||||
|
ltsm: number;
|
||||||
|
new_fid: string;
|
||||||
|
read_only: number;
|
||||||
|
role: number;
|
||||||
|
srv_code: number;
|
||||||
|
}
|
||||||
|
|
||||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||||
let ret: any = undefined;
|
|
||||||
try {
|
try {
|
||||||
ret = await RequestUtil.HttpGetJson<any>(
|
let settings = JSON.stringify({
|
||||||
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice${new URLSearchParams({
|
is_show_edit_card: is_show_edit_card,
|
||||||
|
tip_window_type: tip_window_type,
|
||||||
|
confirm_required: confirm_required
|
||||||
|
});
|
||||||
|
const externalParam = {
|
||||||
|
pic: picId,
|
||||||
|
imgWidth: imgWidth.toString(),
|
||||||
|
imgHeight: imgHeight.toString(),
|
||||||
|
};
|
||||||
|
let ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||||
|
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||||
bkn: this.getBknFromCookie(cookieObject),
|
bkn: this.getBknFromCookie(cookieObject),
|
||||||
qid: GroupCode,
|
qid: GroupCode,
|
||||||
text: Content,
|
text: Content,
|
||||||
pinned: '0',
|
pinned: pinned.toString(),
|
||||||
type: '1',
|
type: type.toString(),
|
||||||
settings: '{"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}',
|
settings: settings,
|
||||||
|
...(picId === '' ? {} : externalParam)
|
||||||
}).toString()}`,
|
}).toString()}`,
|
||||||
'GET',
|
'POST',
|
||||||
'',
|
'',
|
||||||
{ 'Cookie': this.cookieToString(cookieObject) }
|
{ 'Cookie': this.cookieToString(cookieObject) }
|
||||||
);
|
);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { OB11Return } from '../types';
|
import { OB11Return } from '../types';
|
||||||
|
|
||||||
import { isNull } from '../../common/helper';
|
import { isNull } from '@/common/helper';
|
||||||
|
|
||||||
export class OB11Response {
|
export class OB11Response {
|
||||||
static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {
|
static res<T>(data: T, status: string, retcode: number, message: string = ''): OB11Return<T> {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
@@ -2,6 +2,7 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
@@ -2,6 +2,7 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
|||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
import { OB11Entities } from '@/onebot/entities';
|
import { OB11Entities } from '@/onebot/entities';
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
@@ -11,7 +11,10 @@ const SchemaData = {
|
|||||||
content: { type: 'string' },
|
content: { type: 'string' },
|
||||||
image: { type: 'string' },
|
image: { type: 'string' },
|
||||||
pinned: { type: ['number', 'string'] },
|
pinned: { type: ['number', 'string'] },
|
||||||
|
type: { type: ['number', 'string'] },
|
||||||
confirm_required: { type: ['number', 'string'] },
|
confirm_required: { type: ['number', 'string'] },
|
||||||
|
is_show_edit_card: { type: ['number', 'string'] },
|
||||||
|
tip_window_type: { type: ['number', 'string'] },
|
||||||
},
|
},
|
||||||
required: ['group_id', 'content'],
|
required: ['group_id', 'content'],
|
||||||
} as const satisfies JSONSchema;
|
} as const satisfies JSONSchema;
|
||||||
@@ -22,6 +25,7 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
|
|||||||
actionName = ActionName.GoCQHTTP_SendGroupNotice;
|
actionName = ActionName.GoCQHTTP_SendGroupNotice;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
|
|
||||||
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
|
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
|
||||||
if (payload.image) {
|
if (payload.image) {
|
||||||
//公告图逻辑
|
//公告图逻辑
|
||||||
@@ -47,12 +51,28 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
|
|||||||
}
|
}
|
||||||
UploadImage = ImageUploadResult.picInfo;
|
UploadImage = ImageUploadResult.picInfo;
|
||||||
}
|
}
|
||||||
const noticePinned = +(payload.pinned ?? 0);
|
|
||||||
const noticeConfirmRequired = +(payload.confirm_required ?? 0);
|
|
||||||
const publishGroupBulletinResult = await this.core.apis.GroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
|
|
||||||
|
|
||||||
if (publishGroupBulletinResult.result != 0) {
|
const noticeType = +(payload.type ?? 1);
|
||||||
throw `设置群公告失败,错误信息:${publishGroupBulletinResult.errMsg}`;
|
const noticePinned = +(payload.pinned ?? 0);
|
||||||
|
|
||||||
|
const noticeShowEditCard = +(payload.is_show_edit_card ?? 0);
|
||||||
|
const noticeTipWindowType = +(payload.tip_window_type ?? 0);
|
||||||
|
const noticeConfirmRequired = +(payload.confirm_required ?? 1);
|
||||||
|
//const publishGroupBulletinResult = await this.core.apis.GroupApi.publishGroupBulletin(payload.group_id.toString(), payload.content, UploadImage, noticePinned, noticeConfirmRequired);
|
||||||
|
const publishGroupBulletinResult = await this.core.apis.WebApi.setGroupNotice(
|
||||||
|
payload.group_id.toString(),
|
||||||
|
payload.content,
|
||||||
|
noticePinned,
|
||||||
|
noticeType,
|
||||||
|
noticeShowEditCard,
|
||||||
|
noticeTipWindowType,
|
||||||
|
noticeConfirmRequired,
|
||||||
|
UploadImage?.id,
|
||||||
|
UploadImage?.width,
|
||||||
|
UploadImage?.height
|
||||||
|
);
|
||||||
|
if (!publishGroupBulletinResult || publishGroupBulletinResult.ec != 0) {
|
||||||
|
throw `设置群公告失败,错误信息:${publishGroupBulletinResult?.em}`;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -115,6 +115,7 @@ export class OneBotMsgApi {
|
|||||||
file_id: encodedFileId,
|
file_id: encodedFileId,
|
||||||
url: await this.core.apis.FileApi.getImageUrl(element),
|
url: await this.core.apis.FileApi.getImageUrl(element),
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
|
file_unique: element.fileName
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -129,20 +130,6 @@ export class OneBotMsgApi {
|
|||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
await this.core.apis.FileApi.addFileCache(
|
|
||||||
{
|
|
||||||
peerUid: msg.peerUid,
|
|
||||||
chatType: msg.chatType,
|
|
||||||
guildId: '',
|
|
||||||
},
|
|
||||||
msg.msgId,
|
|
||||||
msg.msgSeq,
|
|
||||||
msg.senderUid,
|
|
||||||
elementWrapper.elementId,
|
|
||||||
elementWrapper.elementType.toString(),
|
|
||||||
element.fileSize,
|
|
||||||
element.fileName,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.file,
|
type: OB11MessageDataType.file,
|
||||||
data: {
|
data: {
|
||||||
@@ -151,6 +138,7 @@ export class OneBotMsgApi {
|
|||||||
url: element.filePath,
|
url: element.filePath,
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
|
file_unique: element.fileName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -182,20 +170,6 @@ export class OneBotMsgApi {
|
|||||||
},
|
},
|
||||||
|
|
||||||
marketFaceElement: async (_, msg, elementWrapper) => {
|
marketFaceElement: async (_, msg, elementWrapper) => {
|
||||||
await this.core.apis.FileApi.addFileCache(
|
|
||||||
{
|
|
||||||
peerUid: msg.peerUid,
|
|
||||||
chatType: msg.chatType,
|
|
||||||
guildId: '',
|
|
||||||
},
|
|
||||||
msg.msgId,
|
|
||||||
msg.msgSeq,
|
|
||||||
msg.senderUid,
|
|
||||||
elementWrapper.elementId,
|
|
||||||
elementWrapper.elementType.toString(),
|
|
||||||
'0',
|
|
||||||
'marketface',
|
|
||||||
);
|
|
||||||
const peer = {
|
const peer = {
|
||||||
chatType: msg.chatType,
|
chatType: msg.chatType,
|
||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
@@ -208,6 +182,7 @@ export class OneBotMsgApi {
|
|||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
||||||
path: elementWrapper.elementId,
|
path: elementWrapper.elementId,
|
||||||
url: elementWrapper.elementId,
|
url: elementWrapper.elementId,
|
||||||
|
file_unique: _.key
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -283,21 +258,6 @@ export class OneBotMsgApi {
|
|||||||
videoDownUrl = element.filePath;
|
videoDownUrl = element.filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.apis.FileApi.addFileCache(
|
|
||||||
{
|
|
||||||
peerUid: msg.peerUid,
|
|
||||||
chatType: msg.chatType,
|
|
||||||
guildId: '',
|
|
||||||
},
|
|
||||||
msg.msgId,
|
|
||||||
msg.msgSeq,
|
|
||||||
msg.senderUid,
|
|
||||||
elementWrapper.elementId,
|
|
||||||
elementWrapper.elementType.toString(),
|
|
||||||
element.fileSize ?? '0',
|
|
||||||
element.fileName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.video,
|
type: OB11MessageDataType.video,
|
||||||
data: {
|
data: {
|
||||||
@@ -306,6 +266,7 @@ export class OneBotMsgApi {
|
|||||||
url: videoDownUrl,
|
url: videoDownUrl,
|
||||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId),
|
||||||
file_size: element.fileSize,
|
file_size: element.fileSize,
|
||||||
|
file_unique: element.fileName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -316,20 +277,6 @@ export class OneBotMsgApi {
|
|||||||
peerUid: msg.peerUid,
|
peerUid: msg.peerUid,
|
||||||
guildId: '',
|
guildId: '',
|
||||||
};
|
};
|
||||||
await this.core.apis.FileApi.addFileCache(
|
|
||||||
{
|
|
||||||
peerUid: msg.peerUid,
|
|
||||||
chatType: msg.chatType,
|
|
||||||
guildId: '',
|
|
||||||
},
|
|
||||||
msg.msgId,
|
|
||||||
msg.msgSeq,
|
|
||||||
msg.senderUid,
|
|
||||||
elementWrapper.elementId,
|
|
||||||
elementWrapper.elementType.toString(),
|
|
||||||
element.fileSize || '0',
|
|
||||||
element.fileUuid || '',
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
type: OB11MessageDataType.voice,
|
type: OB11MessageDataType.voice,
|
||||||
data: {
|
data: {
|
||||||
@@ -730,8 +677,10 @@ export class OneBotMsgApi {
|
|||||||
if (ret.result === 0) {
|
if (ret.result === 0) {
|
||||||
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
|
resMsg.group_id = parseInt(ret.tmpChatInfo!.groupCode);
|
||||||
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
|
resMsg.sender.nickname = ret.tmpChatInfo!.fromNick;
|
||||||
|
resMsg.temp_source = resMsg.group_id;
|
||||||
} else {
|
} else {
|
||||||
resMsg.group_id = 284840486; //兜底数据
|
resMsg.group_id = 284840486; //兜底数据
|
||||||
|
resMsg.temp_source = resMsg.group_id;
|
||||||
resMsg.sender.nickname = '临时会话';
|
resMsg.sender.nickname = '临时会话';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,10 +66,7 @@ export function encodeCQCode(data: OB11MessageData) {
|
|||||||
|
|
||||||
let result = '[CQ:' + data.type;
|
let result = '[CQ:' + data.type;
|
||||||
for (const name in data.data) {
|
for (const name in data.data) {
|
||||||
const value =
|
const value = (data.data as any)[name];
|
||||||
// eslint-disable-next-line
|
|
||||||
// @ts-ignore
|
|
||||||
data.data[name];
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@@ -43,9 +43,6 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec
|
|||||||
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
|
||||||
import { LRUCache } from '@/common/lru-cache';
|
import { LRUCache } from '@/common/lru-cache';
|
||||||
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
|
||||||
import { SysMessage } from '@/core/proto/SysMessage';
|
|
||||||
import { GreyTipWrapper } from '@/core/proto/GreyTipWrapper';
|
|
||||||
import { EmojiLikeToOthersWrapper1 } from '@/core/proto/EmojiLikeToOthers';
|
|
||||||
|
|
||||||
//OneBot实现类
|
//OneBot实现类
|
||||||
export class NapCatOneBot11Adapter {
|
export class NapCatOneBot11Adapter {
|
||||||
@@ -101,7 +98,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
if (ob11Config.http.enablePost) {
|
if (ob11Config.http.enablePost) {
|
||||||
ob11Config.http.postUrls.forEach(url => {
|
ob11Config.http.postUrls.forEach(url => {
|
||||||
this.networkManager.registerAdapter(new OB11ActiveHttpAdapter(
|
this.networkManager.registerAdapter(new OB11ActiveHttpAdapter(
|
||||||
url, ob11Config.token, this.core, this,
|
url, ob11Config.http.secret, this.core, this,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -171,7 +168,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
if (now.http.enablePost) {
|
if (now.http.enablePost) {
|
||||||
now.http.postUrls.forEach(url => {
|
now.http.postUrls.forEach(url => {
|
||||||
this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
|
this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
|
||||||
url, now.token, this.core, this,
|
url, now.http.secret, this.core, this,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -185,7 +182,7 @@ export class NapCatOneBot11Adapter {
|
|||||||
);
|
);
|
||||||
for (const url of added) {
|
for (const url of added) {
|
||||||
await this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
|
await this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
|
||||||
url, now.token, this.core, this,
|
url, now.http.secret, this.core, this,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,33 +238,35 @@ export class NapCatOneBot11Adapter {
|
|||||||
private initMsgListener() {
|
private initMsgListener() {
|
||||||
const msgListener = new NodeIKernelMsgListener();
|
const msgListener = new NodeIKernelMsgListener();
|
||||||
|
|
||||||
msgListener.onRecvSysMsg = async msg => {
|
/*
|
||||||
// const sysMsg = SysMessage.fromBinary(Uint8Array.from(msg));
|
msgListener.onRecvSysMsg = async () => {
|
||||||
// if (sysMsg.msgSpec.length === 0) {
|
const sysMsg = SysMessage.fromBinary(Uint8Array.from(msg));
|
||||||
// return;
|
if (sysMsg.msgSpec.length === 0) {
|
||||||
// }
|
return;
|
||||||
// const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
|
}
|
||||||
// if (msgType === 732 && subType === 16 && subSubType === 16) {
|
const { msgType, subType, subSubType } = sysMsg.msgSpec[0];
|
||||||
// const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
|
if (msgType === 732 && subType === 16 && subSubType === 16) {
|
||||||
// if (greyTip.subTypeId === 36) {
|
const greyTip = GreyTipWrapper.fromBinary(Uint8Array.from(sysMsg.bodyWrapper!.wrappedBody.slice(7)));
|
||||||
// const emojiLikeToOthers = EmojiLikeToOthersWrapper1
|
if (greyTip.subTypeId === 36) {
|
||||||
// .fromBinary(greyTip.rest)
|
const emojiLikeToOthers = EmojiLikeToOthersWrapper1
|
||||||
// .wrapper!
|
.fromBinary(greyTip.rest)
|
||||||
// .body!;
|
.wrapper!
|
||||||
// if (emojiLikeToOthers.attributes?.operation !== 1) { // Un-like
|
.body!;
|
||||||
// return;
|
if (emojiLikeToOthers.attributes?.operation !== 1) { // Un-like
|
||||||
// }
|
return;
|
||||||
// const eventOrEmpty = await this.apis.GroupApi.createGroupEmojiLikeEvent(
|
}
|
||||||
// greyTip.groupCode.toString(),
|
const eventOrEmpty = await this.apis.GroupApi.createGroupEmojiLikeEvent(
|
||||||
// await this.core.apis.UserApi.getUinByUidV2(emojiLikeToOthers.attributes!.senderUid),
|
greyTip.groupCode.toString(),
|
||||||
// emojiLikeToOthers.msgSpec!.msgSeq.toString(),
|
await this.core.apis.UserApi.getUinByUidV2(emojiLikeToOthers.attributes!.senderUid),
|
||||||
// emojiLikeToOthers.attributes!.emojiId,
|
emojiLikeToOthers.msgSpec!.msgSeq.toString(),
|
||||||
// );
|
emojiLikeToOthers.attributes!.emojiId,
|
||||||
// // eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
);
|
||||||
// eventOrEmpty && await this.networkManager.emitEvent(eventOrEmpty);
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
// }
|
eventOrEmpty && await this.networkManager.emitEvent(eventOrEmpty);
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
msgListener.onInputStatusPush = async data => {
|
msgListener.onInputStatusPush = async data => {
|
||||||
const uin = await this.core.apis.UserApi.getUinByUidV2(data.fromUin);
|
const uin = await this.core.apis.UserApi.getUinByUidV2(data.fromUin);
|
||||||
|
@@ -8,6 +8,7 @@ export enum OB11MessageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface OB11Message {
|
export interface OB11Message {
|
||||||
|
temp_source?: number;
|
||||||
message_sent_type?: string;
|
message_sent_type?: string;
|
||||||
target_id?: number; // 自己发送的消息才有此字段
|
target_id?: number; // 自己发送的消息才有此字段
|
||||||
self_id?: number,
|
self_id?: number,
|
||||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
undefined,
|
undefined,
|
||||||
SettingButton('V2.2.44', 'napcat-update-button', 'secondary'),
|
SettingButton('V2.3.5', 'napcat-update-button', 'secondary'),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
void 0,
|
void 0,
|
||||||
SettingButton("V2.2.44", "napcat-update-button", "secondary")
|
SettingButton("V2.3.5", "napcat-update-button", "secondary")
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
@@ -25,12 +25,6 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@*": [
|
"@*": [
|
||||||
"./src*"
|
"./src*"
|
||||||
],
|
|
||||||
"@/core": [
|
|
||||||
"./src/core/index",
|
|
||||||
],
|
|
||||||
"@/core/*": [
|
|
||||||
"./src/core/*"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -11,7 +11,6 @@ const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].fl
|
|||||||
function genCpModule(module: string) {
|
function genCpModule(module: string) {
|
||||||
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };
|
return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
let startScripts: string[] | undefined = undefined;
|
let startScripts: string[] | undefined = undefined;
|
||||||
if (process.env.NAPCAT_BUILDSYS == 'linux') {
|
if (process.env.NAPCAT_BUILDSYS == 'linux') {
|
||||||
if (process.env.NAPCAT_BUILDARCH == 'x64') {
|
if (process.env.NAPCAT_BUILDARCH == 'x64') {
|
||||||
@@ -82,6 +81,7 @@ const ShellBaseConfigPlugin: PluginOption[] = [
|
|||||||
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||||
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||||
{ src: './package.json', dest: 'dist' },
|
{ src: './package.json', dest: 'dist' },
|
||||||
|
{ src: './launcher/', dest: 'dist', flatten: true },
|
||||||
// { src: './README.md', dest: 'dist' },
|
// { src: './README.md', dest: 'dist' },
|
||||||
// { src: './logo.png', dest: 'dist/logs' },
|
// { src: './logo.png', dest: 'dist/logs' },
|
||||||
...(startScripts.map((startScript) => {
|
...(startScripts.map((startScript) => {
|
||||||
|
Reference in New Issue
Block a user