Compare commits

...

35 Commits

Author SHA1 Message Date
手瓜一十雪
01d11d6213 refactor: 热重载 2024-11-25 15:16:12 +08:00
Mlikiowa
74a316e758 release: v4.1.19 2024-11-25 05:27:11 +00:00
手瓜一十雪
d20c5185a4 fix: 禁止once覆写package 2024-11-25 13:19:24 +08:00
手瓜一十雪
da965e7b39 fix: qrcode刷新 2024-11-25 13:01:42 +08:00
Mlikiowa
152be29739 release: v4.1.18 2024-11-24 04:53:06 +00:00
手瓜一十雪
e521740a44 fix: error 2024-11-24 12:49:37 +08:00
手瓜一十雪
ee047e8bc1 Merge pull request #561 from NapNeko/parseMult
feat: #538
2024-11-24 12:44:16 +08:00
手瓜一十雪
5eaa9ca347 Merge branch 'main' into parseMult 2024-11-24 12:43:52 +08:00
手瓜一十雪
40f79ee816 fix: error handle 2024-11-24 12:43:28 +08:00
手瓜一十雪
f0dcef7981 fix: by ai 简化逻辑 2024-11-24 12:26:44 +08:00
手瓜一十雪
3c09ff13d0 fix: check config 2024-11-24 12:23:42 +08:00
手瓜一十雪
7158f25f37 fix: error 2024-11-24 12:18:16 +08:00
手瓜一十雪
54f805b6e4 fix: #538 2024-11-24 12:17:23 +08:00
手瓜一十雪
70c4651fbf Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-23 18:53:04 +08:00
手瓜一十雪
962d3c064f update: Limited Redistribution License 2024-11-23 18:52:53 +08:00
Mlikiowa
c6a459a111 release: v4.1.16 2024-11-23 10:27:40 +00:00
手瓜一十雪
b0242ccb62 fix: typo 2024-11-22 22:08:46 +08:00
手瓜一十雪
53f5277b08 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-22 22:08:31 +08:00
手瓜一十雪
90b54435b5 fix: typo 2024-11-22 22:04:57 +08:00
手瓜一十雪
12a1681b42 fix: poke for base emoji 2024-11-22 21:39:16 +08:00
手瓜一十雪
4277cb3f3c remove: docs 2024-11-22 20:52:11 +08:00
手瓜一十雪
8353d53589 feat: baseEmoji Service 2024-11-22 20:50:51 +08:00
手瓜一十雪
9e94d98cfb rename&docs: face 2024-11-22 20:24:45 +08:00
手瓜一十雪
b6ec1aaa9b fix: 修正定义 2024-11-22 20:15:55 +08:00
手瓜一十雪
e7e8763f1c fix: GetProfileLike 2024-11-22 15:58:40 +08:00
手瓜一十雪
515c1af676 refactor: filetype 识别 2024-11-22 15:33:52 +08:00
手瓜一十雪
6fa7a973ba style: @搜寻 2024-11-22 15:08:26 +08:00
手瓜一十雪
3e63f509bc fix: 进一步标准化类型 2024-11-22 14:45:14 +08:00
Mlikiowa
b3b02e781a release: v4.1.15 2024-11-21 14:52:41 +00:00
手瓜一十雪
6d83921e20 style: enum提高可读性 2024-11-21 19:37:11 +08:00
pk5ls20
30bd372d45 feat: version 29927 2024-11-21 18:10:01 +08:00
手瓜一十雪
63254b7e55 fix: readme thanks
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 3m16s
Build Action / Build-Shell (push) Failing after 3m3s
2024-11-21 15:07:36 +08:00
Mlikiowa
f4c08d93f4 release: v4.1.14 2024-11-21 06:58:07 +00:00
手瓜一十雪
6ca1ac21e4 feat: support 29927 2024-11-21 14:55:34 +08:00
Mlikiowa
381ee1c30e release: v4.1.13 2024-11-21 06:43:58 +00:00
58 changed files with 515 additions and 486 deletions

210
LICENSE
View File

@@ -1,201 +1,19 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Limited Redistribution License for NapCat
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Copyright © 2024 Mlikiowa
1. Definitions.
1. Usage and Reproduction:
- Unauthorized use, reproduction, modification, or distribution of this code is prohibited without explicit permission from the main author of the NapCat repository.
2. Redistribution:
- Redistribution of this code is permitted, provided that the full text of this license is included, and the source and copyright information is clearly stated.
- Minor modifications and extensions are allowed for redistribution purposes, but the modified code must not be publicly released.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
3. Non-Commercial Use:
- This code is not to be used for any commercial purposes.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
4. Additional Permissions:
- Any rights not explicitly addressed in this license must be requested from and granted by the main author of the NapCat repository.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
5. Disclaimer:
- This code is provided "as is," without any express or implied warranties, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. In no event shall the author be liable for any damages or other liability arising from, out of, or in connection with the use or distribution of this code.

View File

@@ -45,12 +45,8 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
---
## 延缓Native模块与NapCat对新版QQ适配
为未来持续与高效的使用Native模块 模块代码转为完全非Git仓库的本地保存源码 并进行相关重构
同时为了保证稳定 NapCat 本体通常会在3 Week+的周期进行新版本适配
因此此时推荐使用release指定版本
## 特殊感谢
[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
## 开源附加

Binary file not shown.

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.1.12",
"version": "4.1.19",
"icon": "./logo.png",
"authors": [
{

View File

@@ -74,6 +74,26 @@ export class QQLoginManager {
}
return false;
}
public async checkQQLoginStatusWithQrcode(): Promise<{ qrcodeurl: string, isLogin: string } | undefined> {
try {
const QQLoginResponse = await fetch(`${this.apiPrefix}/QQLogin/CheckLoginStatus`, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + this.retCredential,
'Content-Type': 'application/json',
},
});
if (QQLoginResponse.status == 200) {
const QQLoginResponseJson = await QQLoginResponse.json();
if (QQLoginResponseJson.code == 0) {
return QQLoginResponseJson.data;
}
}
} catch (error) {
console.error('Error checking QQ login status:', error);
}
return undefined;
}
public async checkWebUiLogined(): Promise<boolean> {
try {

View File

@@ -2,28 +2,14 @@
<div class="login-container">
<h2 class="sotheby-font">QQ Login</h2>
<div class="login-methods">
<t-button
id="quick-login"
class="login-method"
:class="{ active: loginMethod === 'quick' }"
@click="loginMethod = 'quick'"
>Quick Login</t-button
>
<t-button
id="qrcode-login"
class="login-method"
:class="{ active: loginMethod === 'qrcode' }"
@click="loginMethod = 'qrcode'"
>QR Code</t-button
>
<t-button id="quick-login" class="login-method" :class="{ active: loginMethod === 'quick' }"
@click="loginMethod = 'quick'">Quick Login</t-button>
<t-button id="qrcode-login" class="login-method" :class="{ active: loginMethod === 'qrcode' }"
@click="loginMethod = 'qrcode'">QR Code</t-button>
</div>
<div v-show="loginMethod === 'quick'" id="quick-login-dropdown" class="login-form">
<t-select
id="quick-login-select"
v-model="selectedAccount"
placeholder="Select Account"
@change="selectAccount"
>
<t-select id="quick-login-select" v-model="selectedAccount" placeholder="Select Account"
@change="selectAccount">
<t-option v-for="account in quickLoginList" :key="account" :value="account">{{ account }}</t-option>
</t-select>
</div>
@@ -47,7 +33,7 @@ const selectedAccount = ref<string>('');
const qrcodeCanvas = ref<HTMLCanvasElement | null>(null);
const qqLoginManager = new QQLoginManager(localStorage.getItem('auth') || '');
let heartBeatTimer: number | null = null;
let qrcodeUrl: string = '';
const selectAccount = async (accountName: string): Promise<void> => {
const { result, errMsg } = await qqLoginManager.setQuickLogin(accountName);
if (result) {
@@ -73,19 +59,26 @@ const generateQrCode = (data: string, canvas: HTMLCanvasElement | null): void =>
};
const HeartBeat = async (): Promise<void> => {
const isLogined = await qqLoginManager.checkQQLoginStatus();
if (isLogined) {
const isLogined = await qqLoginManager.checkQQLoginStatusWithQrcode();
if (isLogined?.isLogin) {
if (heartBeatTimer) {
clearInterval(heartBeatTimer);
}
//判断是否已经调转
if (router.currentRoute.value.path !== '/dashboard/basic-info') {
return;
}
await router.push({ path: '/dashboard/basic-info' });
} else if (isLogined?.qrcodeurl && qrcodeUrl !== isLogined.qrcodeurl) {
qrcodeUrl = isLogined.qrcodeurl;
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
}
};
const InitPages = async (): Promise<void> => {
quickLoginList.value = await qqLoginManager.getQQQuickLoginList();
const qrcodeData = await qqLoginManager.getQQLoginQrcode();
generateQrCode(qrcodeData, qrcodeCanvas.value);
qrcodeUrl = await qqLoginManager.getQQLoginQrcode();
generateQrCode(qrcodeUrl, qrcodeCanvas.value);
heartBeatTimer = window.setInterval(HeartBeat, 3000);
};

View File

@@ -11,6 +11,9 @@
<t-form-item label="启用本地文件到URL" name="enableLocalFile2Url" class="form-item">
<t-switch v-model="otherConfig.enableLocalFile2Url" />
</t-form-item>
<t-form-item label="启用上报解析合并消息" name="parseMultMsg" class="form-item">
<t-switch v-model="otherConfig.parseMultMsg" />
</t-form-item>
</t-form>
<div class="button-container">
<t-button @click="saveConfig">保存</t-button>
@@ -28,6 +31,7 @@ import { QQLoginManager } from '@/backend/shell';
const otherConfig = ref<Partial<OneBotConfig>>({
musicSignUrl: '',
enableLocalFile2Url: false,
parseMultMsg: true
});
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
@@ -56,6 +60,7 @@ const loadConfig = async () => {
if (userConfig) {
otherConfig.value.musicSignUrl = userConfig.musicSignUrl;
otherConfig.value.enableLocalFile2Url = userConfig.enableLocalFile2Url;
otherConfig.value.parseMultMsg = userConfig.parseMultMsg;
}
} catch (error) {
console.error('Error loading config:', error);
@@ -68,6 +73,7 @@ const saveConfig = async () => {
if (userConfig) {
userConfig.musicSignUrl = otherConfig.value.musicSignUrl || '';
userConfig.enableLocalFile2Url = otherConfig.value.enableLocalFile2Url ?? false;
userConfig.parseMultMsg = otherConfig.value.parseMultMsg ?? true;
const success = await setOB11Config(userConfig);
if (success) {
MessagePlugin.success('配置保存成功');

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.1.12",
"version": "4.1.19",
"scripts": {
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
"build:shell": "npm run build:webui && vite build --mode shell || exit 1",

View File

@@ -19,14 +19,6 @@ type Uri2LocalRes = {
path: string
}
export function isGIF(path: string) {
const buffer = Buffer.alloc(4);
const fd = fs.openSync(path, 'r');
fs.readSync(fd, buffer, 0, 4, 0);
fs.closeSync(fd);
return buffer.toString() === 'GIF8';
}
// 定义一个异步函数来检查文件是否存在
export function checkFileExist(path: string, timeout: number = 3000): Promise<void> {
return new Promise((resolve, reject) => {

View File

@@ -1 +1 @@
export const napCatVersion = '4.1.12';
export const napCatVersion = '4.1.19';

View File

@@ -1,4 +1,4 @@
import { MsfChangeReasonType, MsfStatusType } from "../types/adapter";
import { MsfChangeReasonType, MsfStatusType } from "@/core/types/adapter";
export class NodeIDependsAdapter {
onMSFStatusChange(statusType: MsfStatusType, changeReasonType: MsfChangeReasonType) {

View File

@@ -20,13 +20,14 @@ import { InstanceContext, NapCatCore, SearchResultItem } from '@/core';
import * as fileType from 'file-type';
import imageSize from 'image-size';
import { ISizeCalculationResult } from 'image-size/dist/types/interface';
import { RkeyManager } from '../helper/rkey';
import { calculateFileMD5, isGIF } from '@/common/file';
import { RkeyManager } from '@/core/helper/rkey';
import { calculateFileMD5 } from '@/common/file';
import pathLib from 'node:path';
import { defaultVideoThumbB64, getVideoInfo } from '@/common/video';
import ffmpeg from 'fluent-ffmpeg';
import { encodeSilk } from '@/common/audio';
import { MessageContext } from '@/onebot/api';
import { getFileTypeForSendType } from '../helper/msg';
export class NTQQFileApi {
context: InstanceContext;
@@ -130,7 +131,7 @@ export class NTQQFileApi {
fileName: fileName,
sourcePath: path,
original: true,
picType: isGIF(picPath) ? PicType.NEWPIC_GIF : PicType.NEWPIC_JPEG,
picType: await getFileTypeForSendType(picPath),
picSubType: subType,
fileUuid: '',
fileSubId: '',

View File

@@ -18,30 +18,19 @@ export class NTQQUserApi {
async getStatusByUid(uid: string) {
return this.context.session.getProfileService().getStatus(uid);
}
async getProfileLike(uid: string, start: number, count: number) {
// 默认获取自己的 type = 2 获取别人 type = 1
async getProfileLike(uid: string, start: number, count: number, type: number = 2) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [uid],
basic: 1,
vote: 1,
favorite: 0,
userProfile: 1,
type: 2,
type: type,
start: start,
limit: count,
});
}
async fetchOtherProfileLike(uid: string) {
return this.context.session.getProfileLikeService().getBuddyProfileLike({
friendUids: [uid],
basic: 1,
vote: 1,
favorite: 0,
userProfile: 0,
type: 1,
start: 0,
limit: 20,
});
}
async setLongNick(longNick: string) {
return this.context.session.getProfileService().setLongNick(longNick);
}

View File

@@ -86,5 +86,17 @@
"6.9.59-29456": {
"appid": 537249961,
"qua": "V1_MAC_NQ_6.9.59_29456_GW_B"
},
"9.9.16-29927": {
"appid": 537255812,
"qua": "V1_WIN_NQ_9.9.16_29927_GW_B"
},
"3.2.13-29927": {
"appid": 537255847,
"qua": "V1_LNX_NQ_3.2.13_29927_GW_B"
},
"6.9.61-29927": {
"appid": 537255836,
"qua": "V1_MAC_NQ_6.9.61_29927_GW_B"
}
}

View File

@@ -82,5 +82,25 @@
"6.9.59-29456-arm64": {
"send": "4005FE8",
"recv": "4008800"
},
"9.9.16-29927-x64": {
"send": "3869C50",
"recv": "386E084"
},
"3.2.13-29927-x64": {
"send": "A1913A0",
"recv": "A194CA0"
},
"3.2.13-29927-arm64": {
"send": "6F1C7E0",
"recv": "6F20018"
},
"6.9.61-29927-x64": {
"send": "44FCC60",
"recv": "44FF4CC"
},
"6.9.61-29927-arm64": {
"send": "4038740",
"recv": "403AF58"
}
}

14
src/core/helper/msg.ts Normal file
View File

@@ -0,0 +1,14 @@
import * as fileType from 'file-type';
import { PicType } from '../types';
export async function getFileTypeForSendType(picPath: string): Promise<PicType> {
const fileTypeResult = (await fileType.fileTypeFromFile(picPath))?.ext ?? 'jpg';
const picTypeMap: { [key: string]: PicType } = {
'webp': PicType.NEWPIC_WEBP,
'gif': PicType.NEWPIC_GIF,
'png': PicType.NEWPIC_APNG,
'jpg': PicType.NEWPIC_JPEG,
'jpeg': PicType.NEWPIC_JPEG,
'bmp': PicType.NEWPIC_BMP,
};
return picTypeMap[fileTypeResult] ?? PicType.NEWPIC_JPEG;
}

View File

@@ -14,6 +14,8 @@ import {
GroupFileExtra
} from "@/core/packet/transformer/proto";
import {
BaseEmojiType,
FaceType,
NTMsgAtType,
PicType,
SendArkElement,
@@ -162,7 +164,7 @@ export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
constructor(element: SendFaceElement) {
super(element);
this.faceId = element.faceElement.faceIndex;
this.isLargeFace = element.faceElement.faceType === 3;
this.isLargeFace = element.faceElement.faceType === FaceType.AniSticke;
}
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {

View File

@@ -0,0 +1,17 @@
import { DownloadBaseEmojiByIdReq, DownloadBaseEmojiByUrlReq, GetBaseEmojiPathReq, PullSysEmojisReq } from '../types';
export interface NodeIKernelBaseEmojiService {
removeKernelBaseEmojiListener(listenerId: number): void;
addKernelBaseEmojiListener(listener: unknown): number;
isBaseEmojiPathExist(args: Array<string>): unknown;
fetchFullSysEmojis(pullSysEmojisReq: PullSysEmojisReq): unknown;
getBaseEmojiPathByIds(getBaseEmojiPathReqs: Array<GetBaseEmojiPathReq>): unknown;
downloadBaseEmojiByIdWithUrl(downloadBaseEmojiByUrlReq: DownloadBaseEmojiByUrlReq): unknown;
downloadBaseEmojiById(downloadBaseEmojiByIdReq: DownloadBaseEmojiByIdReq): unknown;
}

View File

@@ -1,6 +1,6 @@
import { GeneralCallResult } from '@/core/services/common';
import { NodeIKernelBuddyListener } from '@/core/listeners';
import { BuddyListReqType } from '../types/user';
import { BuddyListReqType } from '@/core/types/user';
export interface NodeIKernelBuddyService {
getBuddyListV2(callFrom: string, reqType: BuddyListReqType): Promise<GeneralCallResult & {

View File

@@ -1,7 +1,7 @@
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/types';
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
import { GeneralCallResult } from '@/core/services/common';
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../types/msg';
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '@/core/types/msg';
export interface NodeIKernelMsgService {

View File

@@ -1,7 +1,7 @@
import { ChatType, Peer } from '@/core/types';
import { NodeIKernelRecentContactListener } from '../listeners/NodeIKernelRecentContactListener';
import { GeneralCallResult } from './common';
import { FSABRecentContactParams } from '../types/contact';
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
import { GeneralCallResult } from '@/core/services/common';
import { FSABRecentContactParams } from '@/core/types/contact';
export interface NodeIKernelRecentContactService {
setGuildDisplayStatus(...args: unknown[]): unknown; // 2 arguments

View File

@@ -1,4 +1,4 @@
import { NodeIO3MiscListener } from "../listeners/NodeIO3MiscListener";
import { NodeIO3MiscListener } from "@/core/listeners/NodeIO3MiscListener";
export interface NodeIO3MiscService {
get(): NodeIO3MiscService;

View File

@@ -1,11 +1,11 @@
export enum MsfStatusType {
KUNKNOWN,
KDISCONNECTED,
KCONNECTED
KUNKNOWN = 0,
KDISCONNECTED = 1,
KCONNECTED = 2
}
export enum MsfChangeReasonType {
KUNKNOWN,
KUSERLOGININ,
KUSERLOGINOUT,
KAUTO
KUNKNOWN = 0,
KUSERLOGININ = 1,
KUSERLOGINOUT = 2,
KAUTO = 3
}

View File

@@ -1,4 +1,4 @@
import { ElementType, FaceType, MessageElement, NTGrayTipElementSubTypeV2, PicSubType, PicType, TipAioOpGrayTipElement, TipGroupElement, NTVideoType } from "./msg";
import { ElementType, MessageElement, NTGrayTipElementSubTypeV2, PicSubType, PicType, TipAioOpGrayTipElement, TipGroupElement, NTVideoType, FaceType } from "./msg";
type ElementFullBase = Omit<MessageElement, 'elementType' | 'elementId' | 'extBufForUI'>;
@@ -253,7 +253,7 @@ export interface FaceBubbleElement {
faceFlag: number;
content: string;
oldVersionStr: string;
faceType: number;
faceType: FaceType;
others: string;
yellowFaceInfo: {
index: number;

54
src/core/types/emoji.ts Normal file
View File

@@ -0,0 +1,54 @@
export enum PullMomentType {
REINSTALL = 0,
RESTART_FIRST_AIO = 1,
LOGIN_APP = 2,
SINGEL_PULL_NOTIFY = 3,
TRIGGER_SPECIFIC_EMOJI_RANDOM_RESULT = 4
}
export interface PullSysEmojisReq {
fetchAdvaceSource: boolean;
fetchBaseSource: boolean;
pullMoment: PullMomentType;
pullType: number;
refresh: boolean;
thresholdValue: number;
}
export enum BaseEmojiType {
NORMAL_EMOJI = 0,
SUPER_EMOJI = 1,
RANDOM_SUPER_EMOJI = 2,
CHAIN_SUPER_EMOJI = 3,
EMOJI_EMOJI = 4
}
export interface GetBaseEmojiPathReq {
emojiId: string;
type: BaseEmojiType;
}
export enum EmojiPanelCategory {
OTHER_PANEL = 0,
NORMAL_PANEL = 1,
SUPER_PANEL = 2,
RED_HEART_PANEL = 3
}
export interface DownloadBaseEmojiInfo {
baseResDownloadUrl: string;
advancedResDownloadUrl: string;
}
export interface DownloadBaseEmojiByUrlReq {
emojiId: string;
groupName: string;
panelCategory: EmojiPanelCategory;
downloadInfo: DownloadBaseEmojiInfo;
}
export interface DownloadBaseEmojiByIdReq {
emojiId: string;
groupName: string;
panelCategory: EmojiPanelCategory;
qzoneCode: string;
}

74
src/core/types/graytip.ts Normal file
View File

@@ -0,0 +1,74 @@
export enum JsonGrayBusiId {
AIO_AV_C2C_NOTICE = 2021,
AIO_AV_GROUP_NOTICE = 2022,
AIO_C2C_DONT_DISTURB = 2100,
AIO_CRM_FLAGS_TIPS = 2050,
AIO_GROUP_ESSENCE_MSG_TIP = 2401,
AIO_NUDGE_CUSTOM_GUIDE = 2041,
AIO_PUSH_GUIDE_GRAY_TIPS = 2701,
AIO_RECALL_MSGCUSTOM_WORDINGGUIDE = 2000,
AIO_ROBOT_SAFETY_TIP = 2201,
AIO_ZPLAN_EMOTICON_GUIDE = 2301,
AIO_ZPLAN_SCENE_LINKAGE = 2302,
AIO_ZPLAN_SEND_MEME = 2300,
DISBAND_DISCUSSION_GRAY_TIP_ID = 2603,
FILE_SENDING_SIZE_4GB_LIMIT = 3003,
GROUP_AIO_CONFIGURABLE_GRAY_TIPS = 2407,
GROUP_AIO_HOME_SCHOOL_WELCOME_GRAY_TIP_ID = 2404,
GROUP_AIO_MSG_FREQUENCY_GRAY_TIP_ID = 2406,
GROUP_AIO_SHUTUP_GRAY_TIP_ID = 2402,
GROUP_AIO_TEMPORARY_GRAY_TIP_ID = 2405,
GROUP_AIO_UNREAD_MSG_AI_SUMMARY = 2408,
GROUP_AIO_UPLOAD_PERMISSIONS_GRAY_TIP_ID = 2403,
LITE_ACTION = 86,
ONLINE_FILE_CANCEL_RECV_ON_RECVING = 4,
ONLINE_FILE_GO_OFFLINE = 11,
ONLINE_FILE_GO_OFFLINE_ALL = 12,
ONLINE_FILE_RECV_BY_MOBILE = 13,
ONLINE_FILE_RECV_ERROR = 10,
ONLINE_FILE_REFUSE_ALL_RECV = 7,
ONLINE_FILE_REFUSE_ALL_RECV_ON_RECVING = 8,
ONLINE_FILE_REFUSE_RECV = 3,
ONLINE_FILE_SEND_ERROR = 9,
ONLINE_FILE_STOP_ALL_SEND = 5,
ONLINE_FILE_STOP_ALL_SEND_ON_SENDING = 6,
ONLINE_FILE_STOP_SEND = 1,
ONLINE_FILE_STOP_SEND_ON_SENDING = 2,
ONLINE_GROUP_HOME_WORK = 51,
PTT_AUTO_CHANGE_GUIDE = 2060,
QCIRCLE_SHOW_FULE_TIPS = 2601,
QWALLET_GRAY_TIP_ID = 2602,
RED_BAG = 81,
RELATION_C2C_GROUP_AIO_SETUP_GROUP_AND_REMARK = 1005,
RELATION_C2C_LOVER_BONUS = 1003,
RELATION_C2C_MEMBER_ADD = 1017,
RELATION_C2C_REACTIVE_DEGRADE_MSG = 1019,
RELATION_C2C_REACTIVE_UPGRADE_MSG = 1018,
RELATION_C2C_SAY_HELLO = 1004,
RELATION_CHAIN_BLACKED = 1000,
RELATION_CHAIN_MATCH_FRIEND = 1007,
RELATION_CREATE_GROUP_GRAY_TIP_ID = 1009,
RELATION_EMOJIEGG_SHOW = 1001,
RELATION_EMOJIEGG_WILL_DEGRADE = 1002,
RELATION_FRIEND_CLONE_INFO = 1006,
RELATION_GROUP_BATCH_ADD_FRIEND = 1020,
RELATION_GROUP_MEMBER_ADD = 1022,
RELATION_GROUP_MEMBER_ADD_WITH_MODIFY_NAME = 1015,
RELATION_GROUP_MEMBER_ADD_WITH_WELCOME = 1016,
RELATION_GROUP_MEMBER_RECOMMEND = 1021,
RELATION_GROUP_SHUT_UP = 1014,
RELATION_LIMIT_TMP_CONVERSATION_SET = 1011,
RELATION_NEARBY_GOTO_VERIFY = 1008,
RELATION_ONEWAY_FRIEND_GRAY_TIP_ID = 1012,
RELATION_ONEWAY_FRIEND_NEW_GRAY_TIP_ID = 1013,
RELATION_YQT = 1010,
TROOP_ADD_FRIEND_ACTIVE = 19264,
TROOP_ADD_FRIEND_HOT_CHAT = 19265,
TROOP_ADD_FRIEND_NEW_MEMBER = 19267,
TROOP_ADD_FRIEND_REPLY_OR_AT = 19266,
TROOP_BREAK_ICE = 10405,
TROOP_FLAME_IGNITED = 19273,
UI_RESERVE_100000_110000 = 100000,
VAS_FILE_UPLOAD_OVER_1G = 3002,
VAS_FILE_UPLOAD_OVER_LIMIT = 3001,
}

View File

@@ -63,16 +63,16 @@ export interface KickMemberV2Req {
// 数据来源类型
export enum DataSource {
LOCAL,
REMOTE
LOCAL = 0,
REMOTE = 1
}
// 群列表更新类型
export enum GroupListUpdateType {
REFRESHALL,
GETALL,
MODIFIED,
REMOVE
REFRESHALL = 0,
GETALL = 1,
MODIFIED = 2,
REMOVE = 3
}
export interface GroupMemberCache {

View File

@@ -7,4 +7,6 @@ export * from './system';
export * from './webapi';
export * from './sign';
export * from './element';
export * from './constant';
export * from './constant';
export * from './graytip';
export * from './emoji';

View File

@@ -1,6 +1,10 @@
import { NTGroupMemberRole } from '@/core';
import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBubbleElement, FaceElement, FileElement, GiphyElement, GrayTipElement, MarketFaceElement, PicElement, PttElement, RecommendedMsgElement, ReplyElement, ShareLocationElement, StructLongMsgElement, TaskTopMsgElement, TextElement, TofuRecordElement, VideoElement, YoloGameResultElement } from './element';
/*
* 2024/11/22 Refactor Mlikiowa
*/
/**
* 表示对等方的信息
*/
@@ -127,7 +131,7 @@ export enum PicSubType {
KRELATED = 7
}
/**
* 消息@类型枚举
* 消息AT类型枚举
*/
export enum NTMsgAtType {
ATTYPEALL = 1,
@@ -260,16 +264,6 @@ export enum NTGrayTipElementSubTypeV2 {
GRAYTIP_ELEMENT_SUBTYPE_XMLMSG = 12,
}
/**
* 表情类型枚举
*/
export enum FaceType {
normal = 1, // 小黄脸
normal2 = 2, // 新小黄脸
dice = 3, // 骰子
poke = 5 // 拍一拍
}
/**
* Poke 类型枚举
*/
@@ -288,8 +282,8 @@ export enum PokeType {
* 表情索引枚举
*/
export enum FaceIndex {
dice = 358,
rps = 359
DICE = 358,
RPS = 359
}
/**
@@ -532,4 +526,16 @@ export interface MsgReqType {
includeSelf: boolean,
includeDeleteMsg: boolean,
extraCnt: number
}
/**
* 表情类型枚举
*/
export enum FaceType {
Unknown = 0,
OldFace = 1, // 老表情
Normal = 2, // 常规表情
AniSticke = 3, // 动画贴纸
Lottie = 4,// 新格式表情
Poke = 5 // 可变Poke
}

View File

@@ -1,20 +1,20 @@
export enum GroupNotifyMsgType {
UN_SPECIFIED,
INVITED_BY_MEMBER,
REFUSE_INVITED,
REFUSED_BY_ADMINI_STRATOR,
AGREED_TOJOIN_DIRECT,// 有人接受了邀请入群
INVITED_NEED_ADMINI_STRATOR_PASS,
AGREED_TO_JOIN_BY_ADMINI_STRATOR,
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS,
SET_ADMIN,
KICK_MEMBER_NOTIFY_ADMIN,
KICK_MEMBER_NOTIFY_KICKED,
MEMBER_LEAVE_NOTIFY_ADMIN,// 主动退出
CANCEL_ADMIN_NOTIFY_CANCELED,
CANCEL_ADMIN_NOTIFY_ADMIN,// 其他人取消管理员
TRANSFER_GROUP_NOTIFY_OLDOWNER,
TRANSFER_GROUP_NOTIFY_ADMIN
UN_SPECIFIED = 0,
INVITED_BY_MEMBER = 1,
REFUSE_INVITED = 2,
REFUSED_BY_ADMINI_STRATOR = 3,
AGREED_TOJOIN_DIRECT = 4,// 有人接受了邀请入群
INVITED_NEED_ADMINI_STRATOR_PASS = 5,
AGREED_TO_JOIN_BY_ADMINI_STRATOR = 6,
REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS = 7,
SET_ADMIN = 8,
KICK_MEMBER_NOTIFY_ADMIN = 9,
KICK_MEMBER_NOTIFY_KICKED = 10,
MEMBER_LEAVE_NOTIFY_ADMIN = 11,// 主动退出
CANCEL_ADMIN_NOTIFY_CANCELED = 12,
CANCEL_ADMIN_NOTIFY_ADMIN = 13,// 其他人取消管理员
TRANSFER_GROUP_NOTIFY_OLDOWNER = 14,
TRANSFER_GROUP_NOTIFY_ADMIN = 15
}
export interface GroupNotifies {
@@ -24,24 +24,24 @@ export interface GroupNotifies {
}
export enum GroupNotifyMsgStatus {
KINIT,//初始化
KUNHANDLE,//未处理
KAGREED,//同意
KREFUSED,//拒绝
KIGNORED//忽略
KINIT = 0,//初始化
KUNHANDLE = 1,//未处理
KAGREED = 2,//同意
KREFUSED = 3,//拒绝
KIGNORED = 4//忽略
}
export enum GroupInviteStatus {
INIT,
WAIT_TO_APPROVE,
JOINED,
REFUSED_BY_ADMINI_STRATOR
INIT = 0,
WAIT_TO_APPROVE = 1,
JOINED = 2,
REFUSED_BY_ADMINI_STRATOR = 3
}
export enum GroupInviteType {
BYBUDDY,
BYGROUPMEMBER,
BYDISCUSSMEMBER
BYBUDDY = 0,
BYGROUPMEMBER = 1,
BYDISCUSSMEMBER = 2
}
export interface ShutUpGroupHonor {
[key: string]: number;
@@ -116,20 +116,20 @@ export enum NTGroupRequestOperateTypes {
}
export enum BuddyReqType {
KMEINITIATOR,
KPEERINITIATOR,
KMEAGREED,
KMEAGREEDANDADDED,
KPEERAGREED,
KPEERAGREEDANDADDED,
KPEERREFUSED,
KMEREFUSED,
KMEIGNORED,
KMEAGREEANYONE,
KMESETQUESTION,
KMEAGREEANDADDFAILED,
KMSGINFO,
KMEINITIATORWAITPEERCONFIRM
KMEINITIATOR = 0,
KPEERINITIATOR = 1,
KMEAGREED = 2,
KMEAGREEDANDADDED = 3,
KPEERAGREED = 4,
KPEERAGREEDANDADDED = 5,
KPEERREFUSED = 6,
KMEREFUSED = 7,
KMEIGNORED = 8,
KMEAGREEANYONE = 9,
KMESETQUESTION = 10,
KMEAGREEANDADDFAILED = 11,
KMSGINFO = 12,
KMEINITIATORWAITPEERCONFIRM = 13
}
export interface FriendRequest {

View File

@@ -322,8 +322,8 @@ export type Friend = User;
// 业务键枚举
export enum BizKey {
KPRIVILEGEICON,
KPHOTOWALL
KPRIVILEGEICON = 0,
KPHOTOWALL = 1
}
// 根据UIN获取用户详细信息
@@ -347,9 +347,9 @@ export enum UserDetailSource {
// 个人资料业务类型枚举
export enum ProfileBizType {
KALL,
KBASEEXTEND,
KVAS,
KQZONE,
KOTHER
KALL = 0,
KBASEEXTEND = 1,
KVAS = 2,
KQZONE = 3,
KOTHER = 4
}

View File

@@ -1,7 +1,6 @@
import { ActionName, BaseCheckResult } from './router';
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
import { NapCatCore } from '@/core';
import { isNull } from '@/common/helper';
import { NapCatOneBot11Adapter, OB11Return } from '@/onebot';
export class OB11Response {
@@ -66,7 +65,7 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
return OB11Response.ok(resData);
} catch (e: any) {
this.core.context.logger.logError('发生错误', e);
return OB11Response.error(e?.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
return OB11Response.error((e as Error).message.toString() || e?.stack?.toString() || '未知错误,可能操作超时', 200);
}
}
@@ -80,7 +79,7 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
return OB11Response.ok(resData, echo);
} catch (e: any) {
this.core.context.logger.logError('发生错误', e);
return OB11Response.error(e.toString() || e.stack?.toString(), 1200, echo);
return OB11Response.error((e as Error).message.toString() || e.stack?.toString(), 1200, echo);
}
}

View File

@@ -1,18 +1,31 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
interface Payload {
start: number,
count: number
}
const SchemaData = {
type: 'object',
properties: {
user_id: { type: ['number', 'string'] },
start: { type: ['number', 'string'] },
count: { type: ['number', 'string'] },
type: { type: ['number', 'string'] },
},
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetProfileLike extends OneBotAction<Payload, any> {
actionName = ActionName.GetProfileLike;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const start = payload.start ? Number(payload.start) : 0;
const count = payload.count ? Number(payload.count) : 10;
const ret = await this.core.apis.UserApi.getProfileLike(this.core.selfInfo.uid, start, count);
const type = payload.count ? Number(payload.count) : 2;
const user_uid =
this.core.selfInfo.uin === payload.user_id || !payload.user_id ?
this.core.selfInfo.uid :
await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
const ret = await this.core.apis.UserApi.getProfileLike(user_uid ?? this.core.selfInfo.uid, start, count, type);
const listdata = ret.info.userLikeInfos[0].voteInfo.userInfos;
for (const item of listdata) {
item.uin = parseInt((await this.core.apis.UserApi.getUinByUidV2(item.uid)) || '');

View File

@@ -1,4 +1,4 @@
import { GetPacketStatusDepends } from '../packet/GetPacketStatus';
import { GetPacketStatusDepends } from '@/onebot/action/packet/GetPacketStatus';
import { ActionName } from '@/onebot/action/router';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';

View File

@@ -41,7 +41,7 @@ export class GoCQHTTPGetForwardMsgAction extends OneBotAction<Payload, any> {
for (const msgdata of message.message) {
if ((msgdata as OB11MessageData).type === OB11MessageDataType.forward) {
const newNode = this.createTemplateNode(message);
newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content);
newNode.data.message = await this.parseForward((msgdata as OB11MessageForward).data.content ?? []);
templateNode.data.message.push(newNode);
} else {

View File

@@ -1,5 +1,5 @@
import SendMsg, { normalize } from '../msg/SendMsg';
import { OB11PostSendMsg } from '../../types';
import SendMsg, { normalize } from '@/onebot/action/msg/SendMsg';
import { OB11PostSendMsg } from '@/onebot/types';
import { ActionName } from '@/onebot/action/router';
// 未验证

View File

@@ -5,7 +5,7 @@ import fs from 'fs';
import { uri2local } from '@/common/file';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageContext } from '@/onebot/api';
import { ContextMode, createContext } from '../msg/SendMsg';
import { ContextMode, createContext } from '@/onebot/action/msg/SendMsg';
const SchemaData = {
type: 'object',

View File

@@ -1,6 +1,6 @@
import SendMsg, { ContextMode } from '../msg/SendMsg';
import { ActionName, BaseCheckResult } from '../router';
import { OB11PostSendMsg } from '../../types';
import SendMsg, { ContextMode } from '@/onebot/action/msg/SendMsg';
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { OB11PostSendMsg } from '@/onebot/types';
// 未检测参数
class SendGroupMsg extends SendMsg {

View File

@@ -1,6 +1,6 @@
import SendMsg, { ContextMode } from './SendMsg';
import { ActionName, BaseCheckResult } from '../router';
import { OB11PostSendMsg } from '../../types';
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { OB11PostSendMsg } from '@/onebot/types';
// 未检测参数
class SendPrivateMsg extends SendMsg {

View File

@@ -1,5 +1,5 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName, BaseCheckResult } from '../router';
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
export abstract class GetPacketStatusDepends<PT, RT> extends OneBotAction<PT, RT> {

View File

@@ -1,7 +1,6 @@
import { GrayTipElement, NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11FriendPokeEvent } from '../event/notice/OB11PokeEvent';
import { OB11FriendPokeEvent } from '@/onebot/event/notice/OB11PokeEvent';
export class OneBotFriendApi {
obContext: NapCatOneBot11Adapter;

View File

@@ -1,17 +1,18 @@
import {
ChatType,
GrayTipElement,
JsonGrayBusiId,
NapCatCore,
NTGrayTipElementSubTypeV2,
RawMessage,
TipGroupElementType,
} from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11GroupBanEvent } from '../event/notice/OB11GroupBanEvent';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
import { OB11GroupBanEvent } from '@/onebot/event/notice/OB11GroupBanEvent';
import { OB11GroupIncreaseEvent } from '@/onebot/event/notice/OB11GroupIncreaseEvent';
import { OB11GroupDecreaseEvent } from '@/onebot/event/notice/OB11GroupDecreaseEvent';
import fastXmlParser from 'fast-xml-parser';
import { OB11GroupMsgEmojiLikeEvent } from '../event/notice/OB11MsgEmojiLikeEvent';
import { OB11GroupMsgEmojiLikeEvent } from '@/onebot/event/notice/OB11MsgEmojiLikeEvent';
import { MessageUnique } from '@/common/message-unique';
import { OB11GroupCardEvent } from '@/onebot/event/notice/OB11GroupCardEvent';
import { OB11GroupUploadNoticeEvent } from '@/onebot/event/notice/OB11GroupUploadNoticeEvent';
@@ -115,7 +116,7 @@ export class OneBotGroupApi {
);
}
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2401) {
if (element.grayTipElement.jsonGrayTipElement.busiId == JsonGrayBusiId.AIO_GROUP_ESSENCE_MSG_TIP) {
const searchParams = new URL(json.items[0].jp).searchParams;
const msgSeq = searchParams.get('msgSeq')!;
const Group = searchParams.get('groupCode');
@@ -138,7 +139,7 @@ export class OneBotGroupApi {
);
// 获取MsgSeq+Peer可获取具体消息
}
if (element.grayTipElement.jsonGrayTipElement.busiId == 2407) {
if (element.grayTipElement.jsonGrayTipElement.busiId == JsonGrayBusiId.GROUP_AIO_CONFIGURABLE_GRAY_TIPS) {
const type = json.items[json.items.length - 1]?.txt;
if (type === "头衔") {
const memberUin = json.items[1].param[0];

View File

@@ -7,7 +7,6 @@ import {
CustomMusicSignPostData,
ElementType,
FaceIndex,
FaceType,
IdMusicSignPostData,
MessageElement,
NapCatCore,
@@ -16,9 +15,11 @@ import {
RawMessage,
SendMessageElement,
SendTextElement,
BaseEmojiType,
FaceType,
} from '@/core';
import faceConfig from '@/core/external/face_config.json';
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageFileBase, } from '@/onebot';
import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageFileBase, OB11MessageForward, } from '@/onebot';
import { OB11Construct } from '@/onebot/helper/data';
import { EventType } from '@/onebot/event/OneBotEvent';
import { encodeCQCode } from '@/onebot/helper/cqcode';
@@ -36,21 +37,26 @@ type RawToOb11Converters = {
element: Exclude<MessageElement[Key], null | undefined>,
msg: RawMessage,
elementWrapper: MessageElement,
context: RecvMessageContext
) => PromiseLike<OB11MessageData | null>
}
type Ob11ToRawConverters = {
[Key in OB11MessageDataType]: (
sendMsg: Extract<OB11MessageData, { type: Key }>,
context: MessageContext,
context: SendMessageContext,
) => Promise<SendMessageElement | undefined>
}
export type MessageContext = {
export type SendMessageContext = {
deleteAfterSentFiles: string[],
peer: Peer
}
export type RecvMessageContext = {
parseMultMsg: boolean
}
function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters {
return key in parser;
}
@@ -144,14 +150,14 @@ export class OneBotMsgApi {
faceElement: async element => {
const faceIndex = element.faceIndex;
if (faceIndex === FaceIndex.dice) {
if (faceIndex === FaceIndex.DICE) {
return {
type: OB11MessageDataType.dice,
data: {
result: element.resultId!,
},
};
} else if (faceIndex === FaceIndex.rps) {
} else if (faceIndex === FaceIndex.RPS) {
return {
type: OB11MessageDataType.rps,
data: {
@@ -337,42 +343,26 @@ export class OneBotMsgApi {
};
},
multiForwardMsgElement: async (_, msg) => {
// const message_data: OB11MessageForward = {
// data: {} as any,
// type: OB11MessageDataType.forward,
// };
// message_data.data.id = msg.msgId;
multiForwardMsgElement: async (_, msg, wrapper, context) => {
const parentMsgPeer = msg.parentMsgPeer ?? {
chatType: msg.chatType,
guildId: '',
peerUid: msg.peerUid,
};
//判断是否在合并消息内
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
//首次列表不存在则开始创建
msg.parentMsgIdList.push(msg.msgId);
//let parentMsgId = msg.parentMsgIdList[msg.parentMsgIdList.length - 2 < 0 ? 0 : msg.parentMsgIdList.length - 2];
//加入自身MsgId
const multiMsgs = (await this.core.apis.MsgApi.getMultiMsg(parentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
//拉取下级消息
const multiMsgs = await this.getMultiMessages(msg, parentMsgPeer);
// 拉取失败则跳过
if (!multiMsgs) return null;
//拉取失败则跳过
return {
const forward: OB11MessageForward = {
type: OB11MessageDataType.forward,
data: {
id: msg.msgId,
content: (await Promise.all(multiMsgs.map(
async multiMsgItem => {
multiMsgItem.parentMsgPeer = parentMsgPeer;
multiMsgItem.parentMsgIdList = msg.parentMsgIdList;
multiMsgItem.id = MessageUnique.createUniqueMsgId(parentMsgPeer, multiMsgItem.msgId); //该ID仅用查看 无法调用
return await this.parseMessage(multiMsgItem, 'array');
},
))).filter(item => item !== undefined),
},
data: { id: msg.msgId }
};
if (!context.parseMultMsg) return forward;
forward.data.content = await this.parseMultiMessageContent(
multiMsgs,
parentMsgPeer,
msg.parentMsgIdList
);
return forward;
},
arkElement: async (element) => {
@@ -546,8 +536,8 @@ export class OneBotMsgApi {
elementType: ElementType.FACE,
elementId: '',
faceElement: {
faceIndex: FaceIndex.dice,
faceType: FaceType.dice,
faceIndex: FaceIndex.DICE,
faceType: FaceType.AniSticke,
faceText: '[骰子]',
packId: '1',
stickerId: '33',
@@ -562,9 +552,9 @@ export class OneBotMsgApi {
elementType: ElementType.FACE,
elementId: '',
faceElement: {
faceIndex: FaceIndex.rps,
faceIndex: FaceIndex.RPS,
faceText: '[包剪锤]',
faceType: 3,
faceType: FaceType.AniSticke,
packId: '1',
stickerId: '34',
sourceType: 1,
@@ -691,18 +681,48 @@ export class OneBotMsgApi {
}
}
private async getMultiMessages(msg: RawMessage, parentMsgPeer: Peer) {
//判断是否在合并消息内
msg.parentMsgIdList = msg.parentMsgIdList ?? [];
//首次列表不存在则开始创建
msg.parentMsgIdList.push(msg.msgId);
//拉取下级消息
return (await this.core.apis.MsgApi.getMultiMsg(
parentMsgPeer,
msg.parentMsgIdList[0],
msg.msgId
))?.msgList;
}
private async parseMultiMessageContent(
multiMsgs: RawMessage[],
parentMsgPeer: Peer,
parentMsgIdList: string[]
) {
const parsed = await Promise.all(multiMsgs.map(async msg => {
msg.parentMsgPeer = parentMsgPeer;
msg.parentMsgIdList = parentMsgIdList;
msg.id = MessageUnique.createUniqueMsgId(parentMsgPeer, msg.msgId);
//该ID仅用查看 无法调用
return await this.parseMessage(msg, 'array', true);
}));
return parsed.filter(item => item !== undefined);
}
async parseMessage(
msg: RawMessage,
messagePostFormat: string,
parseMultMsg: boolean = true
) {
if (messagePostFormat === 'string') {
return (await this.parseMessageV2(msg))?.stringMsg;
return (await this.parseMessageV2(msg, parseMultMsg))?.stringMsg;
}
return (await this.parseMessageV2(msg))?.arrayMsg;
return (await this.parseMessageV2(msg, parseMultMsg))?.arrayMsg;
}
async parseMessageV2(
msg: RawMessage,
parseMultMsg: boolean = true
) {
if (msg.senderUin == '0' || msg.senderUin == '') return;
if (msg.peerUin == '0' || msg.peerUin == '') return;
@@ -766,11 +786,15 @@ export class OneBotMsgApi {
element: Exclude<MessageElement[keyof RawToOb11Converters], null | undefined>,
msg: RawMessage,
elementWrapper: MessageElement,
context: RecvMessageContext
) => PromiseLike<OB11MessageData | null>;
const parsedElement = await converters?.(
element[key],
msg,
element,
{
parseMultMsg: parseMultMsg
}
);
// 对于 face 类型的消息,检查是否存在
if (key === 'faceElement' && !parsedElement) {
@@ -818,7 +842,7 @@ export class OneBotMsgApi {
}
const converter = this.ob11ToRawConverters[sendMsg.type] as (
sendMsg: Extract<OB11MessageData, { type: OB11MessageData['type'] }>,
context: MessageContext,
context: SendMessageContext,
) => Promise<SendMessageElement | undefined>;
const callResult = converter(
sendMsg,
@@ -877,7 +901,7 @@ export class OneBotMsgApi {
private async handleOb11FileLikeMessage(
{ data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext,
{ deleteAfterSentFiles }: SendMessageContext,
) {
const realUri = inputdata.url || inputdata.file || inputdata.path || '';
if (realUri.length === 0) {

View File

@@ -1,6 +1,6 @@
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '@/onebot';
import { OB11ProfileLikeEvent } from '../event/notice/OB11ProfileLikeEvent';
import { OB11ProfileLikeEvent } from '@/onebot/event/notice/OB11ProfileLikeEvent';
import { decodeProfileLikeTip } from "@/core/helper/adaptDecoder";
export class OneBotUserApi {

View File

@@ -103,6 +103,7 @@ export interface OneBotConfig {
network: NetworkConfig; // 网络配置
musicSignUrl: string; // 音乐签名地址
enableLocalFile2Url: boolean;
parseMultMsg: boolean;
}
const createDefaultConfig = <T>(config: T): T => config;
@@ -116,6 +117,7 @@ export const defaultOneBotConfigs = createDefaultConfig<OneBotConfig>({
},
musicSignUrl: '',
enableLocalFile2Url: false,
parseMultMsg: true
});
export const mergeNetworkDefaultConfig = {
@@ -149,9 +151,12 @@ export function mergeOneBotConfigs(
if (userConfig.musicSignUrl !== undefined) {
mergedConfig.musicSignUrl = userConfig.musicSignUrl;
}
if(userConfig.enableLocalFile2Url !== undefined) {
if (userConfig.enableLocalFile2Url !== undefined) {
mergedConfig.enableLocalFile2Url = userConfig.enableLocalFile2Url;
}
if (userConfig.parseMultMsg !== undefined) {
mergedConfig.parseMultMsg = userConfig.parseMultMsg;
}
return mergedConfig;
}

View File

@@ -1,4 +1,4 @@
import { EventType, OneBotEvent } from '../OneBotEvent';
import { EventType, OneBotEvent } from '@/onebot/event/OneBotEvent';
export abstract class OB11BaseMessageEvent extends OneBotEvent {
post_type = EventType.MESSAGE;

View File

@@ -1,5 +1,5 @@
import { OB11BaseNoticeEvent } from '../notice/OB11BaseNoticeEvent';
import { EventType } from '../OneBotEvent';
import { OB11BaseNoticeEvent } from '@/onebot/event/notice/OB11BaseNoticeEvent';
import { EventType } from '@/onebot/event/OneBotEvent';
import { NapCatCore } from '@/core';
export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {

View File

@@ -1,5 +1,5 @@
import { OB11GroupNoticeEvent } from '../notice/OB11GroupNoticeEvent';
import { EventType } from '../OneBotEvent';
import { OB11GroupNoticeEvent } from '@/onebot/event/notice/OB11GroupNoticeEvent';
import { EventType } from '@/onebot/event/OneBotEvent';
import { NapCatCore } from '@/core';
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent {

View File

@@ -176,67 +176,33 @@ export class NapCatOneBot11Adapter {
};
}
private async reloadNetwork(prev: OneBotConfig, now: OneBotConfig) {
private async reloadNetwork(prev: OneBotConfig, now: OneBotConfig): Promise<void> {
const prevLog = await this.creatOneBotLog(prev);
const newLog = await this.creatOneBotLog(now);
this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`);
this.context.logger.log(`[Notice] [OneBot11] 配置变更后:\n${newLog}`);
const { added: addedHttpServers, removed: removedHttpServers } = this.findDifference(prev.network.httpServers, now.network.httpServers);
const { added: addedHttpClients, removed: removedHttpClients } = this.findDifference(prev.network.httpClients, now.network.httpClients);
const { added: addedWebSocketServers, removed: removedWebSocketServers } = this.findDifference(prev.network.websocketServers, now.network.websocketServers);
const { added: addedWebSocketClients, removed: removedWebSocketClients } = this.findDifference(prev.network.websocketClients, now.network.websocketClients);
await this.handleRemovedAdapters(removedHttpServers);
await this.handleRemovedAdapters(removedHttpClients);
await this.handleRemovedAdapters(removedWebSocketServers);
await this.handleRemovedAdapters(removedWebSocketClients);
await this.handlerConfigChange(now.network.httpServers);
await this.handlerConfigChange(now.network.httpClients);
await this.handlerConfigChange(now.network.websocketServers);
await this.handlerConfigChange(now.network.websocketClients);
await this.handleAddedAdapters(addedHttpServers, OB11PassiveHttpAdapter);
await this.handleAddedAdapters(addedHttpClients, OB11ActiveHttpAdapter);
await this.handleAddedAdapters(addedWebSocketServers, OB11PassiveWebSocketAdapter);
await this.handleAddedAdapters(addedWebSocketClients, OB11ActiveWebSocketAdapter);
await this.handleConfigChange(now.network.httpServers, OB11PassiveHttpAdapter);
await this.handleConfigChange(now.network.httpClients, OB11ActiveHttpAdapter);
await this.handleConfigChange(now.network.websocketServers, OB11PassiveWebSocketAdapter);
await this.handleConfigChange(now.network.websocketClients, OB11ActiveWebSocketAdapter);
}
private async handlerConfigChange(adapters: Array<NetworkConfigAdapter>) {
private async handleConfigChange(adapters: NetworkConfigAdapter[], adapterClass: new (...args: any[]) => IOB11NetworkAdapter): Promise<void> {
for (const adapterConfig of adapters) {
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
if (existingAdapter) {
const networkChange = await existingAdapter.reload(adapterConfig);
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
this.networkManager.closeSomeAdapters([existingAdapter]);
await this.networkManager.closeSomeAdaterWhenOpen([existingAdapter]);
}
} else {
const newAdapter = new adapterClass(adapterConfig.name, adapterConfig, this.core, this.actions);
await this.networkManager.registerAdapterAndOpen(newAdapter);
}
}
}
private async handleRemovedAdapters(adapters: Array<{ name: string }>): Promise<void> {
for (const adapter of adapters) {
await this.networkManager.closeAdapterByPredicate((existingAdapter) => existingAdapter.name === adapter.name);
}
}
private async handleAddedAdapters<T extends new (...args: any[]) => IOB11NetworkAdapter>(addedAdapters: Array<NetworkConfigAdapter>, AdapterClass: T) {
for (const adapter of addedAdapters) {
if (adapter.enable) {
const newAdapter = new AdapterClass(adapter.name, adapter, this.core, this.actions);
await newAdapter.open();
this.networkManager.registerAdapter(newAdapter);
}
}
}
private findDifference<T>(prev: T[], now: T[]): { added: T[]; removed: T[] } {
const added = now.filter((item) => !prev.includes(item));
const removed = prev.filter((item) => !now.includes(item));
return { added, removed };
}
private initMsgListener() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onRecvSysMsg = (msg) => {
@@ -551,7 +517,7 @@ export class NapCatOneBot11Adapter {
}
private async handleMsg(message: RawMessage, network: Array<AdapterConfigWrap>) {
try {
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message);
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message, this.configLoader.configData.parseMultMsg);
if (ob11Msg) {
const isSelfMsg = this.isSelfMessage(ob11Msg);
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);

View File

@@ -5,8 +5,8 @@ import { QuickAction, QuickActionEvent } from '@/onebot/types';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '..';
import { RequestUtil } from '@/common/request';
import { HttpClientConfig } from '../config/config';
import { ActionMap } from '../action';
import { HttpClientConfig } from '@/onebot/config/config';
import { ActionMap } from '@/onebot/action';
export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
logger: LogWrapper;

View File

@@ -1,13 +1,13 @@
import { IOB11NetworkAdapter, OB11EmitEventContent, OB11NetworkReloadType } from '@/onebot/network/index';
import { WebSocket } from 'ws';
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent';
import { OB11HeartbeatEvent } from '@/onebot/event/meta/OB11HeartbeatEvent';
import { NapCatCore } from '@/core';
import { ActionName } from '@/onebot/action/router';
import { OB11Response } from '@/onebot/action/OneBotAction';
import { LogWrapper } from '@/common/log';
import { ActionMap } from '@/onebot/action';
import { LifeCycleSubType, OB11LifeCycleEvent } from '../event/meta/OB11LifeCycleEvent';
import { WebsocketClientConfig } from '../config/config';
import { LifeCycleSubType, OB11LifeCycleEvent } from '@/onebot/event/meta/OB11LifeCycleEvent';
import { WebsocketClientConfig } from '@/onebot/config/config';
export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
isEnable: boolean = false;

View File

@@ -1,7 +1,7 @@
import { OneBotEvent } from '@/onebot/event/OneBotEvent';
import { OB11Message } from '@/onebot';
import { ActionMap } from '@/onebot/action';
import { NetworkConfigAdapter } from '../config/config';
import { NetworkConfigAdapter } from '@/onebot/config/config';
export type OB11EmitEventContent = OneBotEvent | OB11Message;
export enum OB11NetworkReloadType {
@@ -68,6 +68,14 @@ export class OB11NetworkManager {
await adapter.close();
}
}
async closeSomeAdaterWhenOpen(adaptersToClose: IOB11NetworkAdapter[]) {
for (const adapter of adaptersToClose) {
this.adapters.delete(adapter.name);
if(adapter.isEnable){
await adapter.close();
}
}
}
findSomeAdapter(name: string) {
return this.adapters.get(name);

View File

@@ -5,7 +5,7 @@ import { NapCatCore } from '@/core';
import { OB11Response } from '@/onebot/action/OneBotAction';
import { ActionMap } from '@/onebot/action';
import cors from 'cors';
import { HttpServerConfig } from '../config/config';
import { HttpServerConfig } from '@/onebot/config/config';
export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
private app: Express | undefined;

View File

@@ -3,14 +3,14 @@ import urlParse from 'url';
import { WebSocket, WebSocketServer } from 'ws';
import { Mutex } from 'async-mutex';
import { OB11Response } from '@/onebot/action/OneBotAction';
import { ActionName } from '../action/router';
import { ActionName } from '@/onebot/action/router';
import { NapCatCore } from '@/core';
import { LogWrapper } from '@/common/log';
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent';
import { OB11HeartbeatEvent } from '@/onebot/event/meta/OB11HeartbeatEvent';
import { IncomingMessage } from 'http';
import { ActionMap } from '@/onebot/action';
import { LifeCycleSubType, OB11LifeCycleEvent } from '../event/meta/OB11LifeCycleEvent';
import { WebsocketServerConfig } from '../config/config';
import { LifeCycleSubType, OB11LifeCycleEvent } from '@/onebot/event/meta/OB11LifeCycleEvent';
import { WebsocketServerConfig } from '@/onebot/config/config';
export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
wsServer: WebSocketServer;
@@ -31,13 +31,9 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
) {
this.config = structuredClone(config);
this.logger = core.context.logger;
if (this.config.host === '0.0.0.0') {
//兼容配置同时处理0.0.0.0逻辑
this.config.host = '';
}
this.wsServer = new WebSocketServer({
port: this.config.port,
host: this.config.host,
host: this.config.host === '0.0.0.0' ? '' : this.config.host,
maxPayload: 1024 * 1024 * 1024,
});
this.wsServer.on('connection', async (wsClient, wsReq) => {
@@ -202,11 +198,13 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
this.open();
return OB11NetworkReloadType.NetWorkOpen;
} else if (!newConfig.enable && wasEnabled) {
console.log(newConfig.enable, wasEnabled);
this.close();
return OB11NetworkReloadType.NetWorkClose;
}
if (oldPort !== newConfig.port || oldHost !== newConfig.host) {
console.log(oldPort, newConfig.port, oldHost, newConfig.host);
this.close();
this.wsServer = new WebSocketServer({
port: newConfig.port,

View File

@@ -236,7 +236,7 @@ export interface OB11MessageForward {
type: OB11MessageDataType.forward;
data: {
id: string;
content: OB11Message[];
content?: OB11Message[];
};
}

View File

@@ -1,6 +1,6 @@
import { OB11BaseMetaEvent } from '../event/meta/OB11BaseMetaEvent';
import { OB11BaseNoticeEvent } from '../event/notice/OB11BaseNoticeEvent';
import { OB11Message } from './message';
import { OB11BaseMetaEvent } from '@/onebot/event/meta/OB11BaseMetaEvent';
import { OB11BaseNoticeEvent } from '@/onebot/event/notice/OB11BaseNoticeEvent';
import { OB11Message } from '@/onebot/types/message';
export type QuickActionEvent = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent;
export type PostEventType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent;

View File

@@ -1,5 +1,4 @@
import { RequestHandler } from 'express';
import { WebUiDataRuntime } from '../helper/Data';
export const LogFileListHandler: RequestHandler = async (req, res) => {
res.send({

View File

@@ -33,6 +33,7 @@ export const QQCheckLoginStatusHandler: RequestHandler = async (req, res) => {
message: 'success',
data: {
isLogin: await WebUiDataRuntime.getQQLoginStatus(),
qrcodeurl: await WebUiDataRuntime.getQQLoginQrcodeURL()
},
});
};