Compare commits

..

33 Commits

Author SHA1 Message Date
手瓜一十雪
9a64b8bdb6 release:1.8.2 2024-08-06 20:10:01 +08:00
手瓜一十雪
c01e493bd2 feat: 多层合并完成 2024-08-06 18:55:36 +08:00
手瓜一十雪
890236af23 chore: 去除风控延迟 2024-08-06 18:01:36 +08:00
手瓜一十雪
ea678d805d chore: 多层合并转发初步可用 2024-08-06 17:58:48 +08:00
手瓜一十雪
29699418ff fix: 对漏掉的部分加入 2024-08-06 17:02:26 +08:00
手瓜一十雪
87bb36c39f refactor: SendMessageContext 2024-08-06 17:00:27 +08:00
手瓜一十雪
7cb85ed73c fix: 再次优化nodeid 2024-08-06 16:43:06 +08:00
手瓜一十雪
c647771a6d fix: 合并转发Node节点异常问题 2024-08-06 16:27:06 +08:00
手瓜一十雪
6c3d737219 fix: 合并reply 2024-08-06 16:00:25 +08:00
手瓜一十雪
a1f38fed7a release: v1.8.1 2024-08-06 15:55:59 +08:00
手瓜一十雪
c36bb77286 feat: 合并消息初步解析完成 2024-08-06 14:48:20 +08:00
手瓜一十雪
1a1acdc3c9 feat: 转发着色 2024-08-06 11:40:36 +08:00
手瓜一十雪
ef8e60c405 build: 180test 2024-08-06 11:31:48 +08:00
手瓜一十雪
31c1cc47bf build: 180 2024-08-06 11:30:35 +08:00
手瓜一十雪
351fed7359 fix #102 2024-08-06 11:30:12 +08:00
手瓜一十雪
f49e7cbe57 chore: log 2024-08-06 11:15:44 +08:00
手瓜一十雪
7da8ea5e99 style:lint 2024-08-06 11:12:25 +08:00
手瓜一十雪
8fa6a12a7c build: 181test 2024-08-06 11:07:37 +08:00
手瓜一十雪
1070278eaf release: 1.8.0 2024-08-06 09:13:03 +08:00
手瓜一十雪
16b1a6b153 release: 1.7.9 2024-08-05 21:04:49 +08:00
手瓜一十雪
096a7534e0 build: 1.7.9-test 2024-08-05 20:36:08 +08:00
手瓜一十雪
b0897187d2 feat: fetch_emoji_like 2024-08-05 20:33:24 +08:00
手瓜一十雪
885d94882d remove: 😢意外加入的文件 2024-08-05 18:40:39 +08:00
手瓜一十雪
11a3341e13 feat: GetRecentContact 私有标准 2024-08-05 18:39:02 +08:00
手瓜一十雪
231890f78a refactor: msgRandom控制 2024-08-05 17:42:07 +08:00
手瓜一十雪
a272feda6a release: 1.7.8 2024-08-05 13:52:34 +08:00
手瓜一十雪
bc936a0ca7 build: 178发言时间与加入时间完全兜底 2024-08-05 13:45:07 +08:00
手瓜一十雪
28030c2d13 chore: test 2024-08-05 13:01:26 +08:00
手瓜一十雪
7fe9176286 feat: richmsg failed 2024-08-05 12:45:06 +08:00
手瓜一十雪
1105f9b8d6 docs: 补充 2024-08-05 01:11:11 +08:00
手瓜一十雪
e4653defa8 LICENSE: break 2024-08-05 00:48:41 +08:00
Alen
1c62a1e839 Merge pull request #208 from cnxysoft/main
BUG修复
2024-08-05 00:20:17 +08:00
Alen
3a3bbfe201 BUG修复
修复(群聊/私聊)转发合并消息错误的问题
2024-08-05 00:18:42 +08:00
44 changed files with 860 additions and 301 deletions

386
LICENSE
View File

@@ -1,21 +1,373 @@
MIT License Mozilla Public License Version 2.0
==================================
Copyright (c) 2024 NapCatQQ 1. Definitions
--------------
Permission is hereby granted, free of charge, to any person obtaining a copy 1.1. "Contributor"
of this software and associated documentation files (the "Software"), to deal means each individual or legal entity that creates, contributes to
in the Software without restriction, including without limitation the rights the creation of, or owns Covered Software.
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all 1.2. "Contributor Version"
copies or substantial portions of the Software. means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1.3. "Contribution"
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, means Covered Software of a particular Contributor.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1.4. "Covered Software"
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, means Source Code Form to which the initial Contributor has attached
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE the notice in Exhibit A, the Executable Form of such Source Code
SOFTWARE. Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -1,4 +1,4 @@
# v1.7.7 # v1.8.2
QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702 QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702
@@ -6,6 +6,8 @@ QQ Version: Windows 9.9.15-26702 / Linux 3.2.12-26702
Way03/Way05 Way03/Way05
## 新增与调整 ## 新增与调整
1. 支持 9.9.15/3.2.12 版本QQ 1. 多层转发消息接收/发送
2. 消息列表排序修正
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api) 新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)

View File

@@ -2,7 +2,7 @@
"name": "napcat", "name": "napcat",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "1.7.7", "version": "1.8.2",
"scripts": { "scripts": {
"watch:dev": "vite --mode development", "watch:dev": "vite --mode development",
"watch:prod": "vite --mode production", "watch:prod": "vite --mode production",

View File

@@ -99,8 +99,10 @@ class MessageUniqueWrapper {
} }
createMsg(peer: Peer, msgId: string): number | undefined { createMsg(peer: Peer, msgId: string): number | undefined {
const key = `${msgId}|${peer.chatType}|${peer.peerUid}`; const key = `${msgId}|${peer.chatType}|${peer.peerUid}`;
const hash = crypto.createHash('md5').update(key); const hash = crypto.createHash('md5').update(key).digest();
const shortId = hash.digest().readInt32BE(0); //设置第一个bit为0 保证shortId为正数
hash[0] &= 0x7f;
const shortId = hash.readInt32BE(0);
//减少性能损耗 //减少性能损耗
// const isExist = this.msgIdMap.getKey(shortId); // const isExist = this.msgIdMap.getKey(shortId);
// if (isExist && isExist === msgId) { // if (isExist && isExist === msgId) {

View File

@@ -6,15 +6,15 @@ import AppidTable from '@/core/external/appid.json';
import { log } from './log'; import { log } from './log';
//基础目录获取 //基础目录获取
export let QQMainPath = process.execPath; export const QQMainPath = process.execPath;
export let QQPackageInfoPath: string = path.join(path.dirname(QQMainPath), 'resources', 'app', 'package.json'); export const QQPackageInfoPath: string = path.join(path.dirname(QQMainPath), 'resources', 'app', 'package.json');
export let QQVersionConfigPath: string | undefined = getQQVersionConfigPath(QQMainPath); export const QQVersionConfigPath: string | undefined = getQQVersionConfigPath(QQMainPath);
//基础信息获取 无快更则启用默认模板填充 //基础信息获取 无快更则启用默认模板填充
export let isQuickUpdate: boolean = !!QQVersionConfigPath; export const isQuickUpdate: boolean = !!QQVersionConfigPath;
export let QQVersionConfig: QQVersionConfigType = isQuickUpdate ? JSON.parse(fs.readFileSync(QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo(); export const QQVersionConfig: QQVersionConfigType = isQuickUpdate ? JSON.parse(fs.readFileSync(QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo();
export let QQPackageInfo: QQPackageInfoType = JSON.parse(fs.readFileSync(QQPackageInfoPath).toString()); export const QQPackageInfo: QQPackageInfoType = JSON.parse(fs.readFileSync(QQPackageInfoPath).toString());
export let { appid: QQVersionAppid, qua: QQVersionQua } = getAppidV2(); export const { appid: QQVersionAppid, qua: QQVersionQua } = getAppidV2();
//基础函数 //基础函数
export function getQQBuildStr() { export function getQQBuildStr() {
@@ -31,17 +31,18 @@ export function getQUAInternal() {
return systemPlatform === 'linux' ? `V1_LNX_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B`; return systemPlatform === 'linux' ? `V1_LNX_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${getFullQQVesion()}_${getQQBuildStr()}_GW_B`;
} }
export function getAppidV2(): { appid: string, qua: string } { export function getAppidV2(): { appid: string, qua: string } {
let appidTbale = AppidTable as unknown as QQAppidTableType; const appidTbale = AppidTable as unknown as QQAppidTableType;
try { try {
let data = appidTbale[getFullQQVesion()]; const data = appidTbale[getFullQQVesion()];
if (data) { if (data) {
return data; return data;
} }
} }
catch (e) { catch (e) {
log('[QQ版本兼容性检测] 版本兼容性不佳,可能会导致一些功能无法正常使用', e); log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`);
} }
// 以下是兜底措施 // 以下是兜底措施
log(`[QQ版本兼容性检测] ${getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`);
return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: getQUAInternal() }; return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: getQUAInternal() };
} }
// platform_type: 3, // platform_type: 3,

View File

@@ -181,7 +181,7 @@ type Uri2LocalRes = {
isLocal: boolean isLocal: boolean
} }
export async function uri2local(uri: string, fileName: string | null = null): Promise<Uri2LocalRes> { export async function uri2local(UriOrPath: string, fileName: string | null = null): Promise<Uri2LocalRes> {
const res = { const res = {
success: false, success: false,
errMsg: '', errMsg: '',
@@ -190,26 +190,29 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
path: '', path: '',
isLocal: false isLocal: false
}; };
if (!fileName) { if (!fileName) fileName = randomUUID();
fileName = randomUUID(); let filePath = path.join(getTempDir(), fileName);//临时目录
}
let filePath = path.join(getTempDir(), fileName);
let url = null; let url = null;
//区分path和uri
try { try {
url = new URL(uri); if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath);
} catch (e: any) { } catch (error: any) { }
res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在`; try {
url = new URL(UriOrPath);
} catch (error: any) { }
//验证url
if (!url) {
res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`;
return res; return res;
} }
// log("uri protocol", url.protocol, uri);
if (url.protocol == 'base64:') { if (url.protocol == 'base64:') {
// base64转成文件 // base64转成文件
const base64Data = uri.split('base64://')[1]; const base64Data = UriOrPath.split('base64://')[1];
try { try {
const buffer = Buffer.from(base64Data, 'base64'); const buffer = Buffer.from(base64Data, 'base64');
fs.writeFileSync(filePath, buffer); fs.writeFileSync(filePath, buffer);
} catch (e: any) { } catch (e: any) {
res.errMsg = 'base64文件下载失败,' + e.toString(); res.errMsg = 'base64文件下载失败,' + e.toString();
return res; return res;
@@ -218,7 +221,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
// 下载文件 // 下载文件
let buffer: Buffer | null = null; let buffer: Buffer | null = null;
try { try {
buffer = await httpDownload(uri); buffer = await httpDownload(UriOrPath);
} catch (e: any) { } catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString(); res.errMsg = `${url}下载失败,` + e.toString();
return res; return res;
@@ -252,6 +255,7 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
} }
} }
else { else {
// 26702执行forword file文件操作 不应该在这里乱来
// const cache = await dbUtil.getFileCacheByName(uri); // const cache = await dbUtil.getFileCacheByName(uri);
// if (cache) { // if (cache) {
// filePath = cache.path; // filePath = cache.path;
@@ -259,7 +263,6 @@ export async function uri2local(uri: string, fileName: string | null = null): Pr
// filePath = uri; // filePath = uri;
// } // }
} }
res.isLocal = true; res.isLocal = true;
} }
// else{ // else{

View File

@@ -360,12 +360,12 @@ export async function promisePipeline(promises: Promise<any>[], callback: (resul
callbackCalled = callback(result); callbackCalled = callback(result);
} }
} catch (error) { } catch (error) {
console.error("Error in promise pipeline:", error); console.error('Error in promise pipeline:', error);
} }
} }
} }
export function getQQVersionConfigPath(exePath: string = ""): string | undefined { export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
let configVersionInfoPath; let configVersionInfoPath;
if (os.platform() !== 'linux') { if (os.platform() !== 'linux') {
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json'); configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');

View File

@@ -17,7 +17,7 @@ async function LoadMessageIdList(Peer: Peer, msgId: string) {
} }
async function loadMessageUnique() { async function loadMessageUnique() {
if (groups.size > 100) { if (groups.size > 100) {
logWarn('群数量大于100可能会导致性能问题'); logWarn('[性能检测] 群数量大于100可能会导致性能问题');
} }
let predict = (groups.size + friends.size / 2) / 5; let predict = (groups.size + friends.size / 2) / 5;
predict = predict < 20 ? 20 : predict; predict = predict < 20 ? 20 : predict;
@@ -67,6 +67,13 @@ setTimeout(() => {
// }, 25000) // }, 25000)
export class NTQQMsgApi { export class NTQQMsgApi {
static async FetchLongMsg(peer: Peer, msgId: string) {
return napCatCore.session.getMsgService().fetchLongMsg(peer, msgId);
}
static async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
//console.log(peer, msgSeq, emojiId, emojiType, count);
return napCatCore.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, "", false, 20)
}
// static napCatCore: NapCatCore | null = null; // static napCatCore: NapCatCore | null = null;
// enum BaseEmojiType { // enum BaseEmojiType {
// NORMAL_EMOJI, // NORMAL_EMOJI,
@@ -87,20 +94,25 @@ export class NTQQMsgApi {
} | undefined> { } | undefined> {
return napCatCore.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId); return napCatCore.session.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId);
} }
static async getLastestMsgByUids(peer: Peer, count: number = 20) { static async ForwardMsg(peer: Peer, msgIds: string[]) {
return napCatCore.session.getMsgService().forwardMsg(msgIds, peer, [peer], new Map());
}
static async getLastestMsgByUids(peer: Peer, count: number = 20, isReverseOrder: boolean = false) {
let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', { let ret = await napCatCore.session.getMsgService().queryMsgsWithFilterEx('0', '0', '0', {
chatInfo: peer, chatInfo: peer,
filterMsgType: [], filterMsgType: [],
filterSendersUid: [], filterSendersUid: [],
filterMsgToTime: '0', filterMsgToTime: '0',
filterMsgFromTime: '0', filterMsgFromTime: '0',
isReverseOrder: false, isReverseOrder: isReverseOrder,
isIncludeCurrent: true, isIncludeCurrent: true,
pageLimit: 1, pageLimit: count,
}); });
return ret; return ret;
} }
static async getMsgsByMsgId(peer: Peer, msgIds: string[]) { static async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) {
if (!peer) throw new Error('peer is not allowed');
if (!msgIds) throw new Error('msgIds is not allowed');
return await napCatCore.session.getMsgService().getMsgsByMsgId(peer, msgIds); return await napCatCore.session.getMsgService().getMsgsByMsgId(peer, msgIds);
} }
static async getSingleMsg(peer: Peer, seq: string) { static async getSingleMsg(peer: Peer, seq: string) {
@@ -145,9 +157,9 @@ export class NTQQMsgApi {
); );
return data[1].item; return data[1].item;
} }
static async getMsgHistory(peer: Peer, msgId: string, count: number) { static async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) {
// 消息时间从旧到新 // 消息时间从旧到新
return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, true); return napCatCore.session.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder);
} }
static async recallMsg(peer: Peer, msgIds: string[]) { static async recallMsg(peer: Peer, msgIds: string[]) {
await napCatCore.session.getMsgService().recallMsg({ await napCatCore.session.getMsgService().recallMsg({
@@ -165,7 +177,9 @@ export class NTQQMsgApi {
// const msgId = BigInt("0x" + buffer.toString('hex')).toString(); // const msgId = BigInt("0x" + buffer.toString('hex')).toString();
// return msgId; // return msgId;
// } // }
// 此处有采用Hack方法 利用数据返回正确得到对应消息
// 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同
// 谨慎采用 目前测试暂无问题 Developer.Mlikiowa
let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime()); let msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime());
let data = await NTEventDispatch.CallNormalEvent< let data = await NTEventDispatch.CallNormalEvent<
(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>, (msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<any, any>) => Promise<unknown>,

View File

@@ -19,6 +19,43 @@ export interface CustomMusicSignPostData {
image?: string, image?: string,
singer?: string singer?: string
} }
// let t = await napCatCore.session.getGroupService().shareDigest({
// appId: "100497308",
// appType: 1,
// msgStyle: 0,
// recvUin: "726067488",
// sendType: 1,
// clientInfo: {
// platform: 1
// },
// richMsg: {
// usingArk: true,
// title: "Bot测试title",
// summary: "Bot测试summary",
// url: "https://www.bilibili.com",
// pictureUrl: "https://y.qq.com/music/photo_new/T002R300x300M0000035DC6W4ZpSqf_1.jpg?max_age=2592000",
// brief: "Bot测试brief",
// }
// });
// {
// errCode: 0,
// errMsg: '',
// rsp: {
// sendType: 1,
// recvUin: '726067488',
// recvOpenId: '',
// errCode: 901501,
// errMsg: 'imagent service_error:150_OIDB_NO_PRIV',
// extInfo: {
// wording: '消息下发失败(错误码901501)',
// jumpResult: 0,
// jumpUrl: '',
// level: 0,
// subLevel: 0,
// developMsg: 'imagent error'
// }
// }
// }
// export class MusicSign { // export class MusicSign {
// private readonly url: string; // private readonly url: string;

View File

@@ -322,6 +322,7 @@ export class NapCatCore {
if (groupMembers.has(groupCode)) { if (groupMembers.has(groupCode)) {
const existMembers = groupMembers.get(groupCode)!; const existMembers = groupMembers.get(groupCode)!;
arg.infos.forEach((member, uid) => { arg.infos.forEach((member, uid) => {
//console.log('onMemberListChange', member);
const existMember = existMembers.get(uid); const existMember = existMembers.get(uid);
if (existMember) { if (existMember) {
Object.assign(existMember, member); Object.assign(existMember, member);

View File

@@ -7,7 +7,7 @@ export enum GroupListUpdateType {
} }
export interface Group { export interface Group {
groupCode: string, groupCode: string,
createTime?:string,//高版本才有 createTime?: string,//高版本才有
maxMember: number, maxMember: number,
memberCount: number, memberCount: number,
groupName: string, groupName: string,
@@ -59,4 +59,6 @@ export interface GroupMember {
sex?: Sex sex?: Sex
qqLevel?: QQLevel qqLevel?: QQLevel
isChangeRole: boolean; isChangeRole: boolean;
joinTime: string;
lastSpeakTime: string;
} }

View File

@@ -731,10 +731,12 @@ export interface MultiForwardMsgElement {
} }
export interface RawMessage { export interface RawMessage {
parentMsgPeer: Peer;
parentMsgIdList:string[];
id?: number;//扩展字段 用于处理OB11 ID
guildId: string; guildId: string;
msgRandom: string; msgRandom: string;
// int32, 自己维护的消息id
id?: number;
msgId: string; msgId: string;

View File

@@ -11,6 +11,10 @@
"appid": 537237950, "appid": 537237950,
"qua": "V1_LNX_NQ_3.2.12_26702_GW_B" "qua": "V1_LNX_NQ_3.2.12_26702_GW_B"
}, },
"3.2.12-26740": {
"appid": 537237950,
"qua": "V1_WIN_NQ_9.9.15_26740_GW_B"
},
"9.9.11-24815": { "9.9.11-24815": {
"appid": 537226656, "appid": 537226656,
"qua": "V1_WIN_NQ_9.9.11_24815_GW_B" "qua": "V1_WIN_NQ_9.9.11_24815_GW_B"
@@ -38,5 +42,9 @@
"9.9.15-26702": { "9.9.15-26702": {
"appid": 537237765, "appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B" "qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
},
"9.9.15-26740": {
"appid": 537237765,
"qua": "V1_WIN_NQ_9.9.15_26702_GW_B"
} }
} }

View File

@@ -477,7 +477,19 @@ export interface NodeIKernelMsgService {
setMsgEmojiLikes(...args: unknown[]): unknown; setMsgEmojiLikes(...args: unknown[]): unknown;
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<unknown>; getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{
result: number,
errMsg: string,
emojiLikesList:
Array<{
tinyId: string,
nickName: string,
headUrl: string
}>,
cookie: string,
isLastPage: boolean,
isFirstPage: boolean
}>;
setMsgEmojiLikesForRole(...args: unknown[]): unknown; setMsgEmojiLikesForRole(...args: unknown[]): unknown;
@@ -592,7 +604,7 @@ export interface NodeIKernelMsgService {
// this.selfPhone = str5; // this.selfPhone = str5;
// this.gameSession = tempChatGameSession; // this.gameSession = tempChatGameSession;
prepareTempChat(args: unknown): unknown;//主动临时消息 不做 prepareTempChat(args: unknown): unknown;//主动临时消息 不做
sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>; sendSsoCmdReqByContend(cmd: string, param: string): Promise<unknown>;
//chattype,uid->Promise<any> //chattype,uid->Promise<any>

View File

@@ -1,4 +1,4 @@
import { Peer } from "../entities"; import { ChatType, Peer } from "../entities";
import { NodeIKernelRecentContactListener } from "../listeners/NodeIKernelRecentContactListener"; import { NodeIKernelRecentContactListener } from "../listeners/NodeIKernelRecentContactListener";
import { GeneralCallResult } from "./common"; import { GeneralCallResult } from "./common";
export interface FSABRecentContactParams { export interface FSABRecentContactParams {
@@ -39,7 +39,13 @@ export interface NodeIKernelRecentContactService {
errCode: number, errCode: number,
errMsg: string, errMsg: string,
sortedContactList: Array<number>, sortedContactList: Array<number>,
changedList: Array<any> changedList: Array<{
remark: any;
peerName: any;
sendMemberName: any;
sendNickName: any;
peerUid: string; peerUin: string, msgTime: string, chatType: ChatType, msgId: string
}>
} }
}>; // 1 arguments }>; // 1 arguments

View File

@@ -0,0 +1,32 @@
//getMsgEmojiLikesList
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { NTQQMsgApi } from '@/core/apis';
import { MessageUnique } from '@/common/utils/MessageUnique';
const SchemaData = {
type: 'object',
properties: {
user_id: { type: 'string' },
group_id: { type: 'string' },
emojiId: { type: 'string' },
emojiType: { type: 'string' },
message_id: { type: ['string', 'number'] },
count: { type: 'number' }
},
required: ['emojiId', 'emojiType', 'message_id']
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class FetchEmojioLike extends BaseAction<Payload, any> {
actionName = ActionName.FetchEmojioLike;
PayloadSchema = SchemaData;
protected async _handle(payload: Payload) {
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(parseInt(payload.message_id.toString()));
if(!msgIdPeer) throw new Error('消息不存在');
const msg = (await NTQQMsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
const ret = await NTQQMsgApi.getMsgEmojiLikesList(msgIdPeer.Peer,msg.msgSeq,payload.emojiId,payload.emojiType,payload.count);
return ret;
}
}

View File

@@ -20,8 +20,8 @@ export class OCRImage extends BaseAction<Payload, any> {
actionName = ActionName.OCRImage; actionName = ActionName.OCRImage;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const { path, isLocal, errMsg } = (await uri2local(payload.image)); const { path, isLocal, errMsg,success } = (await uri2local(payload.image));
if (errMsg) { if (!success) {
throw `OCR ${payload.image}失败,image字段可能格式不正确`; throw `OCR ${payload.image}失败,image字段可能格式不正确`;
} }
if (path) { if (path) {

View File

@@ -26,8 +26,8 @@ export default class SetGroupHeader extends BaseAction<Payload, any> {
}; };
} }
protected async _handle(payload: Payload): Promise<any> { protected async _handle(payload: Payload): Promise<any> {
const { path, isLocal, errMsg } = (await uri2local(payload.file)); const { path, isLocal, errMsg,success } = (await uri2local(payload.file));
if (errMsg) { if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`; throw `头像${payload.file}设置失败,file字段可能格式不正确`;
} }
if (path) { if (path) {

View File

@@ -24,8 +24,8 @@ export default class SetAvatar extends BaseAction<Payload, null> {
}; };
} }
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const { path, isLocal, errMsg } = (await uri2local(payload.file)); const { path, isLocal, errMsg,success } = (await uri2local(payload.file));
if (errMsg) { if (!success) {
throw `头像${payload.file}设置失败,file字段可能格式不正确`; throw `头像${payload.file}设置失败,file字段可能格式不正确`;
} }
if (path) { if (path) {

View File

@@ -1,7 +1,7 @@
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { OB11Message, OB11User } from '../../types'; import { OB11Message, OB11User } from '../../types';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { ChatType } from '@/core/entities'; import { ChatType, RawMessage } from '@/core/entities';
import { NTQQMsgApi } from '@/core/apis/msg'; import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -17,9 +17,10 @@ const SchemaData = {
properties: { properties: {
user_id: { type: ['number', 'string'] }, user_id: { type: ['number', 'string'] },
message_seq: { type: 'number' }, message_seq: { type: 'number' },
count: { type: 'number' } count: { type: 'number' },
reverseOrder: { type: 'boolean' }
}, },
required: ['user_id', 'message_seq', 'count'] required: ['user_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>; type Payload = FromSchema<typeof SchemaData>;
@@ -28,21 +29,28 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
actionName = ActionName.GetFriendMsgHistory; actionName = ActionName.GetFriendMsgHistory;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<Response> { protected async _handle(payload: Payload): Promise<Response> {
//处理参数
const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString()); const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
if (!uid) { const MsgCount = payload.count || 20;
throw `记录${payload.user_id}不存在`; const isReverseOrder = payload.reverseOrder || true;
} if (!uid) throw `记录${payload.user_id}不存在`;
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq))?.MsgId || '0';
const friend = await NTQQFriendApi.isBuddy(uid); const friend = await NTQQFriendApi.isBuddy(uid);
const historyResult = (await NTQQMsgApi.getMsgHistory({ const peer = { chatType: friend ? ChatType.friend : ChatType.temp, peerUid: uid };
chatType: friend ? ChatType.friend : ChatType.temp,
peerUid: uid //拉取消息
}, startMsgId, parseInt(payload.count?.toString()) || 20)); let msgList: RawMessage[];
//logDebug(historyResult); if (!payload.message_seq || payload.message_seq == 0) {
const msgList = historyResult.msgList; msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, MsgCount)).msgList;
} else {
const startMsgId = MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq)?.MsgId;
if (!startMsgId) throw `消息${payload.message_seq}不存在`;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
}
if(isReverseOrder) msgList.reverse();
await Promise.all(msgList.map(async msg => { await Promise.all(msgList.map(async msg => {
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId); msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
})); }));
//转换消息
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg))); const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
return { 'messages': ob11MsgList }; return { 'messages': ob11MsgList };
} }

View File

@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction';
import { OB11Message, OB11User } from '../../types'; import { OB11Message, OB11User } from '../../types';
import { getGroup, groups } from '@/core/data'; import { getGroup, groups } from '@/core/data';
import { ActionName } from '../types'; import { ActionName } from '../types';
import { ChatType, RawMessage } from '@/core/entities'; import { ChatType, Peer, RawMessage } from '@/core/entities';
import { NTQQMsgApi } from '@/core/apis/msg'; import { NTQQMsgApi } from '@/core/apis/msg';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
@@ -16,7 +16,8 @@ const SchemaData = {
properties: { properties: {
group_id: { type: ['number', 'string'] }, group_id: { type: ['number', 'string'] },
message_seq: { type: 'number' }, message_seq: { type: 'number' },
count: { type: 'number' } count: { type: 'number' },
reverseOrder: { type: 'boolean' }
}, },
required: ['group_id'] required: ['group_id']
} as const satisfies JSONSchema; } as const satisfies JSONSchema;
@@ -27,25 +28,28 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory; actionName = ActionName.GoCQHTTP_GetGroupMsgHistory;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload): Promise<Response> { protected async _handle(payload: Payload): Promise<Response> {
//处理参数
const group = await getGroup(payload.group_id.toString()); const group = await getGroup(payload.group_id.toString());
if (!group) { const isReverseOrder = payload.reverseOrder || true;
throw `${payload.group_id}不存在`; const MsgCount = payload.count || 20;
} const peer: Peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() };
let targetMsgShortId, count = parseInt(payload.count?.toString() ?? '20'); if (!group) throw `${payload.group_id}不存在`;
const peer = {
chatType: ChatType.group, //拉取消息
peerUid: group.groupCode
};
let msgList: RawMessage[]; let msgList: RawMessage[];
if (!payload.message_seq) { if (!payload.message_seq || payload.message_seq == 0) {
msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, count)).msgList; msgList = (await NTQQMsgApi.getLastestMsgByUids(peer, MsgCount)).msgList;
} else { } else {
const startMsgId = (await MessageUnique.getMsgIdAndPeerByShortId(targetMsgShortId ?? (payload.message_seq ?? 0)))?.MsgId || '0'; const startMsgId = MessageUnique.getMsgIdAndPeerByShortId(payload.message_seq)?.MsgId;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, count)).msgList; if (!startMsgId) throw `消息${payload.message_seq}不存在`;
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
} }
if(isReverseOrder) msgList.reverse();
await Promise.all(msgList.map(async msg => { await Promise.all(msgList.map(async msg => {
msg.id = await MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId); msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
})); }));
//转换消息
const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg))); const ob11MsgList = await Promise.all(msgList.map(msg => OB11Constructor.message(msg)));
return { 'messages': ob11MsgList }; return { 'messages': ob11MsgList };
} }

View File

@@ -7,7 +7,7 @@ import { FromSchema, JSONSchema } from 'json-schema-to-ts';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
group_id: { type: [ 'number' , 'string' ] }, group_id: { type: ['number', 'string'] },
content: { type: 'string' }, content: { type: 'string' },
image: { type: 'string' }, image: { type: 'string' },
pinned: { type: 'number' }, pinned: { type: 'number' },
@@ -24,8 +24,8 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
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) {
//公告图逻辑 //公告图逻辑
const { errMsg, path, isLocal } = (await uri2local(payload.image)); const { errMsg, path, isLocal, success } = (await uri2local(payload.image));
if (errMsg) { if (!success) {
throw `群公告${payload.image}设置失败,image字段可能格式不正确`; throw `群公告${payload.image}设置失败,image字段可能格式不正确`;
} }
if (!path) { if (!path) {

View File

@@ -34,7 +34,7 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
file = `file://${file}`; file = `file://${file}`;
} }
const downloadResult = await uri2local(file); const downloadResult = await uri2local(file);
if (downloadResult.errMsg) { if (!downloadResult.success) {
throw new Error(downloadResult.errMsg); throw new Error(downloadResult.errMsg);
} }
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id); const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id);

View File

@@ -41,7 +41,7 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
file = `file://${file}`; file = `file://${file}`;
} }
const downloadResult = await uri2local(file); const downloadResult = await uri2local(file);
if (downloadResult.errMsg) { if (!downloadResult.success) {
throw new Error(downloadResult.errMsg); throw new Error(downloadResult.errMsg);
} }
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name); const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name);

View File

@@ -19,7 +19,7 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
actionName = ActionName.GetGroupList; actionName = ActionName.GetGroupList;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
let groupList: Group[] = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload.no_cache === 'true'); const groupList: Group[] = await NTQQGroupApi.getGroups(payload?.no_cache === true || payload.no_cache === 'true');
return OB11Constructor.groups(groupList); return OB11Constructor.groups(groupList);
} }
} }

View File

@@ -7,7 +7,8 @@ import { logDebug } from '@/common/utils/log';
import { WebApi } from '@/core/apis/webapi'; import { WebApi } from '@/core/apis/webapi';
import { NTQQGroupApi } from '@/core'; import { NTQQGroupApi } from '@/core';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { selfInfo } from '@/core/data'; import { getGroupMember, selfInfo } from '@/core/data';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
@@ -25,11 +26,11 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const isNocache = payload.no_cache == true || payload.no_cache === 'true'; const isNocache = payload.no_cache == true || payload.no_cache === 'true';
let uid = await NTQQUserApi.getUidByUin(payload.user_id.toString()); const uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
if (!uid) { if (!uid) {
throw (`Uin2Uid Error ${payload.user_id}不存在`); throw (`Uin2Uid Error ${payload.user_id}不存在`);
} }
let member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache); const member = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), uid, isNocache);
if (!member) { if (!member) {
throw (`群(${payload.group_id})成员${payload.user_id}不存在`); throw (`群(${payload.group_id})成员${payload.user_id}不存在`);
} }
@@ -40,31 +41,37 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
} catch (e) { } catch (e) {
logDebug('获取群成员详细信息失败, 只能返回基础信息', e); logDebug('获取群成员详细信息失败, 只能返回基础信息', e);
} }
const date = Math.round(Date.now() / 1000);
const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member); const retMember = OB11Constructor.groupMember(payload.group_id.toString(), member);
let SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), selfInfo.uid, isNocache); if (!requireMinNTQQBuild('26702')) {
let isPrivilege = false; const SelfInfoInGroup = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), selfInfo.uid, isNocache);
if (SelfInfoInGroup) { let isPrivilege = false;
isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4; if (SelfInfoInGroup) {
} isPrivilege = SelfInfoInGroup.role === 3 || SelfInfoInGroup.role === 4;
if (isPrivilege) { }
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()); if (isPrivilege) {
for (let i = 0, len = webGroupMembers.length; i < len; i++) { const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) { for (let i = 0, len = webGroupMembers.length; i < len; i++) {
retMember.join_time = webGroupMembers[i]?.join_time; if (webGroupMembers[i]?.uin && webGroupMembers[i].uin === retMember.user_id) {
retMember.last_sent_time = webGroupMembers[i]?.last_speak_time; retMember.join_time = webGroupMembers[i]?.join_time;
retMember.qage = webGroupMembers[i]?.qage; retMember.last_sent_time = webGroupMembers[i]?.last_speak_time;
retMember.level = webGroupMembers[i]?.lv.level.toString(); retMember.qage = webGroupMembers[i]?.qage;
retMember.level = webGroupMembers[i]?.lv.level.toString();
}
}
} else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]);
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) {
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
} }
} }
} else { } else {
const LastestMsgList = await NTQQGroupApi.getLastestMsg(payload.group_id.toString(), [payload.user_id.toString()]); retMember.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
if (LastestMsgList?.msgList?.length && LastestMsgList?.msgList?.length > 0) { retMember.join_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
const last_send_time = LastestMsgList.msgList[0].msgTime;
if (last_send_time && last_send_time != '0' && last_send_time != '') {
retMember.last_sent_time = parseInt(last_send_time);
retMember.join_time = Math.round(Date.now() / 1000);//兜底数据 防止群管乱杀
}
}
} }
return retMember; return retMember;
} }

View File

@@ -28,7 +28,7 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (requireMinNTQQBuild('26702')) { if (requireMinNTQQBuild('26702')) {
let V2Data = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), payload.user_id.toString(), payload.no_cache == true || payload.no_cache === 'true'); const V2Data = await NTQQGroupApi.getGroupMemberV2(payload.group_id.toString(), payload.user_id.toString(), payload.no_cache == true || payload.no_cache === 'true');
if (V2Data) { if (V2Data) {
return OB11Constructor.groupMember(payload.group_id.toString(), V2Data); return OB11Constructor.groupMember(payload.group_id.toString(), V2Data);
} else { } else {

View File

@@ -1,4 +1,4 @@
import { selfInfo } from '@/core/data'; import { getGroup, getGroupMember, selfInfo } from '@/core/data';
import { OB11GroupMember } from '../../types'; import { OB11GroupMember } from '../../types';
import { OB11Constructor } from '../../constructor'; import { OB11Constructor } from '../../constructor';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
@@ -6,6 +6,7 @@ import { ActionName } from '../types';
import { NTQQGroupApi } from '@/core'; import { NTQQGroupApi } from '@/core';
import { WebApi } from '@/core/apis/webapi'; import { WebApi } from '@/core/apis/webapi';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { requireMinNTQQBuild } from '@/common/utils/QQBasicInfo';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
@@ -29,7 +30,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
if (!group) { if (!group) {
throw (`${payload.group_id}不存在`); throw (`${payload.group_id}不存在`);
} }
let groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString()); const groupMembers = await NTQQGroupApi.getGroupMembers(payload.group_id.toString());
let _groupMembers = Array.from(groupMembers.values()) let _groupMembers = Array.from(groupMembers.values())
.map(item => { return OB11Constructor.groupMember(group.groupCode, item); }); .map(item => { return OB11Constructor.groupMember(group.groupCode, item); });
@@ -43,40 +44,47 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]); MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
} }
const selfRole = groupMembers.get(selfInfo.uid)?.role; if (!requireMinNTQQBuild('26702')) {
const isPrivilege = selfRole === 3 || selfRole === 4; const selfRole = groupMembers.get(selfInfo.uid)?.role;
const isPrivilege = selfRole === 3 || selfRole === 4;
if (isPrivilege) { if (isPrivilege) {
const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString()); const webGroupMembers = await WebApi.getGroupMembers(payload.group_id.toString());
for (let i = 0, len = webGroupMembers.length; i < len; i++) { for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) { if (!webGroupMembers[i]?.uin) {
continue; continue;
} }
const MemberData = MemberMap.get(webGroupMembers[i]?.uin); const MemberData = MemberMap.get(webGroupMembers[i]?.uin);
if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time;
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
}
}
} else {
if (isNocache) {
const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
for (const DateUin of DateMap.keys()) {
const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) { if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!); MemberData.join_time = webGroupMembers[i]?.join_time;
//join_time 有基础数据兜底 MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time;
MemberData.qage = webGroupMembers[i]?.qage;
MemberData.level = webGroupMembers[i]?.lv.level.toString();
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
} }
} }
} else { } else {
_groupMembers.forEach(item => { if (isNocache) {
item.last_sent_time = date; const DateMap = await NTQQGroupApi.getGroupMemberLastestSendTimeCache(payload.group_id.toString());//开始从本地拉取
item.join_time = date; for (const DateUin of DateMap.keys()) {
}); const MemberData = MemberMap.get(parseInt(DateUin));
if (MemberData) {
MemberData.last_sent_time = parseInt(DateMap.get(DateUin)!);
//join_time 有基础数据兜底
}
}
} else {
_groupMembers.forEach(item => {
item.last_sent_time = date;
item.join_time = date;
});
}
} }
} else {
_groupMembers.forEach(async item => {
item.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.lastSpeakTime || date.toString());
item.join_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.joinTime || date.toString());
});
} }
// 还原索引到Array 一同返回 // 还原索引到Array 一同返回

View File

@@ -78,8 +78,10 @@ import SetGroupHeader from './extends/SetGroupHeader';
import { FetchCustomFace } from './extends/FetchCustomFace'; import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile'; import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivareFile';
import TestApi01 from './extends/TestApi01'; import TestApi01 from './extends/TestApi01';
import { FetchEmojioLike } from './extends/FetchEmojioLike';
export const actionHandlers = [ export const actionHandlers = [
new FetchEmojioLike(),
new RebootNormal(), new RebootNormal(),
new GetFile(), new GetFile(),
new Debug(), new Debug(),

View File

@@ -3,12 +3,15 @@ import { ActionName } from '../types';
import BaseAction from '../BaseAction'; import BaseAction from '../BaseAction';
import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/utils/MessageUnique'; import { MessageUnique } from '@/common/utils/MessageUnique';
import { sleep } from '@/common/utils/helper';
import { NTEventDispatch } from '@/common/utils/EventTask';
import { NodeIKernelMsgListener } from '@/core';
const SchemaData = { const SchemaData = {
type: 'object', type: 'object',
properties: { properties: {
message_id: { message_id: {
oneOf:[ oneOf: [
{ type: 'number' }, { type: 'number' },
{ type: 'string' } { type: 'string' }
] ]
@@ -23,9 +26,27 @@ class DeleteMsg extends BaseAction<Payload, void> {
actionName = ActionName.DeleteMsg; actionName = ActionName.DeleteMsg;
PayloadSchema = SchemaData; PayloadSchema = SchemaData;
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const msg = await MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id)); const msg = MessageUnique.getMsgIdAndPeerByShortId(Number(payload.message_id));
if (msg) { if (msg) {
let ret = NTEventDispatch.RegisterListen<NodeIKernelMsgListener['onMsgInfoListUpdate']>
(
'NodeIKernelMsgListener/onMsgInfoListUpdate',
1,
5000,
(msgs) => {
if (msgs.find(m => m.msgId === msg.MsgId && m.recallTime !== '0')) {
return true;
}
return false;
}
).catch(e => new Promise<undefined>((resolve, reject) => { resolve(undefined) }));
await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]); await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]);
let data = await ret;
if (!data) {
throw new Error('Recall failed');
}
//await sleep(100);
//await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId]);
} }
} }
} }

View File

@@ -6,6 +6,7 @@ import {
IdMusicSignPostData, IdMusicSignPostData,
NTQQFileApi, NTQQFileApi,
NTQQMsgApi, NTQQMsgApi,
Peer,
SendArkElement, SendArkElement,
SendMessageElement, SendMessageElement,
SendMsgElementConstructor, SendMsgElementConstructor,
@@ -17,19 +18,19 @@ import { uri2local } from '@/common/utils/file';
import { ob11Config } from '@/onebot11/config'; import { ob11Config } from '@/onebot11/config';
import { RequestUtil } from '@/common/utils/request'; import { RequestUtil } from '@/common/utils/request';
import { MessageUnique } from '@/common/utils/MessageUnique'; import { MessageUnique } from '@/common/utils/MessageUnique';
console.log(process.pid)
export type MessageContext = { export type MessageContext = {
group?: Group,
deleteAfterSentFiles: string[], deleteAfterSentFiles: string[],
peer:Peer
} }
async function handleOb11FileLikeMessage( async function handleOb11FileLikeMessage(
{ data: inputdata }: OB11MessageFileBase, { data: inputdata }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext { deleteAfterSentFiles }: MessageContext
) { ) {
//有的奇怪的框架将url作为参数 而不是file 此时优先url //有的奇怪的框架将url作为参数 而不是file 此时优先url
const { path, isLocal, fileName, errMsg } = (await uri2local(inputdata?.url || inputdata.file)); const { path, isLocal, fileName, errMsg,success } = (await uri2local(inputdata?.url || inputdata.file));
if (errMsg) { if (!success) {
logError('文件下载失败', errMsg); logError('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg); throw Error('文件下载失败' + errMsg);
} }
@@ -52,12 +53,12 @@ const _handlers: {
[OB11MessageDataType.text]: async ({ data: { text } }) => SendMsgElementConstructor.text(text), [OB11MessageDataType.text]: async ({ data: { text } }) => SendMsgElementConstructor.text(text),
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => { [OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
if (!context.group) return undefined; if (!context.peer) return undefined;
if (atQQ === 'all') return SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员'); if (atQQ === 'all') return SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member // then the qq is a group member
const atMember = await getGroupMember(context.group.groupCode, atQQ); const atMember = await getGroupMember(context.peer.peerUid, atQQ);
return atMember ? return atMember ?
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) : SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
undefined; undefined;
@@ -206,7 +207,7 @@ const handlers = <{
export default async function createSendElements( export default async function createSendElements(
messageData: OB11MessageData[], messageData: OB11MessageData[],
group?: Group, peer: Peer,
ignoreTypes: OB11MessageDataType[] = [] ignoreTypes: OB11MessageDataType[] = []
) { ) {
const deleteAfterSentFiles: string[] = []; const deleteAfterSentFiles: string[] = [];
@@ -217,7 +218,7 @@ export default async function createSendElements(
} }
const callResult = handlers[sendMsg.type]( const callResult = handlers[sendMsg.type](
sendMsg, sendMsg,
{ group, deleteAfterSentFiles } { peer, deleteAfterSentFiles }
)?.catch(undefined); )?.catch(undefined);
callResultList.push(callResult); callResultList.push(callResult);
} }
@@ -228,7 +229,7 @@ export default async function createSendElements(
export async function createSendElementsParallel( export async function createSendElementsParallel(
messageData: OB11MessageData[], messageData: OB11MessageData[],
group?: Group, peer: Peer,
ignoreTypes: OB11MessageDataType[] = [] ignoreTypes: OB11MessageDataType[] = []
) { ) {
const deleteAfterSentFiles: string[] = []; const deleteAfterSentFiles: string[] = [];
@@ -236,7 +237,7 @@ export async function createSendElementsParallel(
await Promise.all( await Promise.all(
messageData.map(async sendMsg => ignoreTypes.includes(sendMsg.type) ? messageData.map(async sendMsg => ignoreTypes.includes(sendMsg.type) ?
undefined : undefined :
handlers[sendMsg.type](sendMsg, { group, deleteAfterSentFiles })) handlers[sendMsg.type](sendMsg, { peer, deleteAfterSentFiles }))
).then( ).then(
results => results.filter( results => results.filter(
element => element !== undefined element => element !== undefined

View File

@@ -1,5 +1,5 @@
import { ChatType, ElementType, Group, NTQQMsgApi, Peer, RawMessage, SendMessageElement } from '@/core'; import { ChatType, ElementType, Group, NTQQMsgApi, Peer, RawMessage, SendMessageElement } from '@/core';
import { OB11MessageNode } from '@/onebot11/types'; import { OB11MessageDataType, OB11MessageNode } from '@/onebot11/types';
import { selfInfo } from '@/core/data'; import { selfInfo } from '@/core/data';
import createSendElements from '@/onebot11/action/msg/SendMsg/create-send-elements'; import createSendElements from '@/onebot11/action/msg/SendMsg/create-send-elements';
import { logDebug, logError } from '@/common/utils/log'; import { logDebug, logError } from '@/common/utils/log';
@@ -24,135 +24,92 @@ async function cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
if (sendElements.length === 0) { if (sendElements.length === 0) {
logDebug('需要clone的消息无法解析将会忽略掉', msg); logDebug('需要clone的消息无法解析将会忽略掉', msg);
} }
//logDebug('克隆消息', sendElements);
try { try {
const nodeMsg = await NTQQMsgApi.sendMsg(selfPeer, sendElements, true); const nodeMsg = await NTQQMsgApi.sendMsg(selfPeer, sendElements, true);
await sleep(500); // 防止风控
return nodeMsg; return nodeMsg;
} catch (e) { } catch (e) {
logError(e, '克隆转发消息失败,将忽略本条消息', msg); logError(e, '克隆转发消息失败,将忽略本条消息', msg);
} }
} }
export async function handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined): Promise<RawMessage | null> { export async function handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise<RawMessage | null> {
const selfPeer = { const selfPeer = {
chatType: ChatType.friend, chatType: ChatType.friend,
peerUid: selfInfo.uid peerUid: selfInfo.uid
}; };
let nodeMsgIds: string[] = []; let nodeMsgIds: string[] = [];
// 先判断一遍是不是id和自定义混用
const needClone =
messageNodes.filter(node => node.data.id).length &&
messageNodes.filter(node => !node.data.id).length;
for (const messageNode of messageNodes) { for (const messageNode of messageNodes) {
// 一个node表示一个人的消息
const nodeId = messageNode.data.id; const nodeId = messageNode.data.id;
// 有nodeId表示一个子转发消息卡片
if (nodeId) { if (nodeId) {
const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)); //对Mgsid和OB11ID混用情况兜底
if (!needClone) { const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId);
nodeMsgIds.push(nodeMsg!.MsgId); if (!nodeMsg) {
} else { logError('转发消息失败,未找到消息', nodeId);
if (nodeMsg!.Peer.peerUid !== selfInfo.uid) { continue;
// need cloning
const rawClone = await NTQQMsgApi.getMsgsByMsgId(nodeMsg?.Peer!, [nodeMsg?.MsgId!]);
const clonedMsg = await cloneMsg(rawClone.msgList[0]);
if (clonedMsg) {
nodeMsgIds.push(clonedMsg.msgId);
}
}
} }
nodeMsgIds.push(nodeMsg.MsgId);
} else { } else {
// 自定义的消息 // 自定义的消息
// 提取消息段发给自己生成消息id
try { try {
const { sendElements } = await createSendElements(normalize(messageNode.data.content), group); let OB11Data = normalize(messageNode.data.content);
//logDebug('开始生成转发节点', sendElements); //筛选node消息
const sendElementsSplit: SendMessageElement[][] = []; let isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息
let splitIndex = 0; if (isNodeMsg !== 0) {
for (const sendElement of sendElements) { if (isNodeMsg !== OB11Data.length) { logError('子消息中包含非node消息 跳过不合法部分'); continue; }
if (!sendElementsSplit[splitIndex]) { const nodeMsg = await handleForwardNode(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node));
sendElementsSplit[splitIndex] = []; if (nodeMsg) { nodeMsgIds.push(nodeMsg.msgId); MessageUnique.createMsg(selfPeer, nodeMsg.msgId) };
} //完成子卡片生成跳过后续
continue;
if (sendElement.elementType === ElementType.FILE || sendElement.elementType === ElementType.VIDEO) {
if (sendElementsSplit[splitIndex].length > 0) {
splitIndex++;
}
sendElementsSplit[splitIndex] = [sendElement];
splitIndex++;
} else {
sendElementsSplit[splitIndex].push(sendElement);
}
//logDebug(sendElementsSplit);
} }
// log("分割后的转发节点", sendElementsSplit) const { sendElements } = await createSendElements(OB11Data, destPeer);
//拆分消息
let MixElement = sendElements.filter(element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO);
let SingleElement = sendElements.filter(element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO).map(e => [e]);
let AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0);
const MsgNodeList: Promise<RawMessage | undefined>[] = []; const MsgNodeList: Promise<RawMessage | undefined>[] = [];
for (const sendElementsSplitElement of sendElementsSplit) { for (const sendElementsSplitElement of AllElement) {
MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true)); MsgNodeList.push(sendMsg(selfPeer, sendElementsSplitElement, [], true).catch(e => new Promise((resolve, reject) => { resolve(undefined) })));
await sleep(Math.trunc(sendElementsSplit.length / 10) * 100);
//await sleep(10);
} }
for (const msgNode of MsgNodeList) { (await Promise.allSettled(MsgNodeList)).map((result) => {
const result = await msgNode; if (result.status === 'fulfilled' && result.value) {
if (result) { nodeMsgIds.push(result.value.msgId);
nodeMsgIds.push(result.msgId); MessageUnique.createMsg(selfPeer, result.value.msgId);
} }
//logDebug('转发节点生成成功', result.msgId); });
}
} catch (e) { } catch (e) {
logDebug('生成转发消息节点失败', e); logDebug('生成转发消息节点失败', e);
} }
} }
} }
// 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发
const nodeMsgArray: Array<RawMessage> = []; const nodeMsgArray: Array<RawMessage> = [];
let srcPeer: Peer | undefined = undefined; let srcPeer: Peer | undefined = undefined;
let needSendSelf = false; let needSendSelf = false;
for (const msgId of nodeMsgIds) { //检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送
const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId); for (let msgId of nodeMsgIds) {
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer?.Peer!, [msgId])).msgList[0]; const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId);
if (nodeMsg) { if (!nodeMsgPeer) {
nodeMsgArray.push(nodeMsg); logError('转发消息失败,未找到消息', msgId);
if (!srcPeer) { continue;
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
} else if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
srcPeer = selfPeer;
}
} }
const nodeMsg = (await NTQQMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0];
srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
}
nodeMsgArray.push(nodeMsg);
} }
// logDebug('nodeMsgArray', nodeMsgArray);
nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); nodeMsgIds = nodeMsgArray.map(msg => msg.msgId);
let retMsgIds: string[] = [];
if (needSendSelf) { if (needSendSelf) {
//logDebug('需要克隆转发消息');
for (const [index, msg] of nodeMsgArray.entries()) { for (const [index, msg] of nodeMsgArray.entries()) {
if (msg.peerUid !== selfInfo.uid) { if (msg.peerUid === selfInfo.uid) continue;
const clonedMsg = await cloneMsg(msg); const ClonedMsg = await cloneMsg(msg);
if (clonedMsg) { if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId);
nodeMsgIds[index] = clonedMsg.msgId;
}
}
} }
} else {
retMsgIds = nodeMsgIds;
} }
// elements之间用换行符分隔 if (nodeMsgIds.length === 0) throw Error('转发消息失败,生成节点为空');
// let _sendForwardElements: SendMessageElement[] = []
// for(let i = 0; i < sendForwardElements.length; i++){
// _sendForwardElements.push(sendForwardElements[i])
// _sendForwardElements.push(SendMsgElementConstructor.text("\n\n"))
// }
// const nodeMsg = await NTQQApi.sendMsg(selfPeer, _sendForwardElements, true);
// nodeIds.push(nodeMsg.msgId)
// await sleep(500);
// 开发转发
if (nodeMsgIds.length === 0) {
throw Error('转发消息失败,生成节点为空');
}
try { try {
logDebug('开发转发', srcPeer, destPeer, nodeMsgIds); logDebug('开发转发', srcPeer, destPeer, nodeMsgIds);
return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds); return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds);

View File

@@ -76,9 +76,7 @@ export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], de
return returnMsg; return returnMsg;
} }
async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<{ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode): Promise<Peer> {
peer: Peer, group?: Group
}> {
// This function determines the type of message by the existence of user_id / group_id, // This function determines the type of message by the existence of user_id / group_id,
// not message_type. // not message_type.
// This redundant design of Ob11 here should be blamed. // This redundant design of Ob11 here should be blamed.
@@ -86,11 +84,8 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode)
if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) { if ((contextMode === ContextMode.Group || contextMode === ContextMode.Normal) && payload.group_id) {
const group = (await getGroup(payload.group_id))!; // checked before const group = (await getGroup(payload.group_id))!; // checked before
return { return {
peer: { chatType: ChatType.group,
chatType: ChatType.group, peerUid: group.groupCode
peerUid: group.groupCode
},
group: group,
}; };
} }
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) { if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
@@ -98,10 +93,8 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode)
const isBuddy = await NTQQFriendApi.isBuddy(Uid!); const isBuddy = await NTQQFriendApi.isBuddy(Uid!);
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy); //console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy);
return { return {
peer: { chatType: isBuddy ? ChatType.friend : ChatType.temp,
chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid: Uid!
peerUid: Uid!
},
}; };
} }
throw '请指定 group_id 或 user_id'; throw '请指定 group_id 或 user_id';
@@ -139,7 +132,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
protected async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> { protected async _handle(payload: OB11PostSendMsg): Promise<{ message_id: number }> {
const { peer, group } = await createContext(payload, this.contextMode); const peer = await createContext(payload, this.contextMode);
const messages = normalize( const messages = normalize(
payload.message, payload.message,
@@ -147,9 +140,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
); );
if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { if (getSpecialMsgNum(payload, OB11MessageDataType.node)) {
const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[], group); const returnMsg = await handleForwardNode(peer, messages as OB11MessageNode[]);
if (returnMsg) { if (returnMsg) {
const msgShortId = await MessageUnique.createMsg({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType }, returnMsg!.msgId); const msgShortId = MessageUnique.createMsg({ guildId: '', peerUid: peer.peerUid, chatType: peer.chatType }, returnMsg!.msgId);
return { message_id: msgShortId! }; return { message_id: msgShortId! };
} else { } else {
throw Error('发送转发消息失败'); throw Error('发送转发消息失败');
@@ -163,7 +156,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
// log("send msg:", peer, sendElements) // log("send msg:", peer, sendElements)
const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, group); const { sendElements, deleteAfterSentFiles } = await createSendElements(messages, peer);
//console.log(peer, JSON.stringify(sendElements,null,2)); //console.log(peer, JSON.stringify(sendElements,null,2));
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles); const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles);
return { message_id: returnMsg!.id! }; return { message_id: returnMsg!.id! };

View File

@@ -102,5 +102,6 @@ export enum ActionName {
SetGroupHeader = 'set_group_head', SetGroupHeader = 'set_group_head',
FetchCustomFace = 'fetch_custom_face', FetchCustomFace = 'fetch_custom_face',
GOCQHTTP_UploadPrivateFile = 'upload_private_file', GOCQHTTP_UploadPrivateFile = 'upload_private_file',
TestApi01 = 'test_api_01' TestApi01 = 'test_api_01',
FetchEmojioLike = 'fetch_emoji_like'
} }

View File

@@ -1,12 +1,52 @@
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 { NTQQUserApi } from '@/core'; import { NTQQMsgApi, NTQQUserApi } from '@/core';
import { OB11Constructor } from '@/onebot11/constructor';
export default class GetRecentContact extends BaseAction<void, any> { const SchemaData = {
type: 'object',
properties: {
count: { type: ['number', 'string'] }
}
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export default class GetRecentContact extends BaseAction<Payload, any> {
actionName = ActionName.GetRecentContact; actionName = ActionName.GetRecentContact;
protected async _handle(payload: void) { PayloadSchema = SchemaData;
//没有效果 protected async _handle(payload: Payload) {
return await NTQQUserApi.getRecentContactListSnapShot(10); const ret = await NTQQUserApi.getRecentContactListSnapShot(parseInt((payload.count || 10).toString()));
const data = await Promise.all(ret.info.changedList.map(async (t) => {
const FastMsg = await NTQQMsgApi.getMsgsByMsgId({ chatType: t.chatType, peerUid: t.peerUid }, [t.msgId]);
if (FastMsg.msgList.length > 0) {
//扩展ret.info.changedList
const lastestMsg = await OB11Constructor.message(FastMsg.msgList[0]);
return {
lastestMsg: lastestMsg,
peerUin: t.peerUin,
remark: t.remark,
msgTime: t.msgTime,
chatType: t.chatType,
msgId: t.msgId,
sendNickName: t.sendNickName,
sendMemberName: t.sendMemberName,
peerName: t.peerName
};
}
return {
peerUin: t.peerUin,
remark: t.remark,
msgTime: t.msgTime,
chatType: t.chatType,
msgId: t.msgId,
sendNickName: t.sendNickName,
sendMemberName: t.sendMemberName,
peerName: t.peerName
};
}));
return data;
} }
} }

View File

@@ -65,7 +65,7 @@ export class OB11Constructor {
real_id: msg.id!, real_id: msg.id!,
message_type: msg.chatType == ChatType.group ? 'group' : 'private', message_type: msg.chatType == ChatType.group ? 'group' : 'private',
sender: { sender: {
user_id: parseInt(msg.senderUin!), user_id: parseInt(msg.senderUin || '0'),
nickname: msg.sendNickName, nickname: msg.sendNickName,
card: msg.sendMemberName || '', card: msg.sendMemberName || '',
}, },
@@ -153,21 +153,22 @@ export class OB11Constructor {
message_data['type'] = OB11MessageDataType.reply; message_data['type'] = OB11MessageDataType.reply;
//log("收到回复消息", element.replyElement); //log("收到回复消息", element.replyElement);
try { try {
//做这么多都是因为NC速度太快 可能nt还没有写入数据库 const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
//const records = msg.records.find(msgRecord => msgRecord.msgId === element.replyElement.sourceMsgIdInRecords);
const peer = { const peer = {
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
guildId: '', guildId: '',
}; };
let replyMsg: RawMessage | undefined; let replyMsg: RawMessage | undefined;
if (!records) throw new Error('找不到回复消息');
replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true)).msgList[0]; replyMsg = (await NTQQMsgApi.getMsgsBySeqAndCount({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, element.replyElement.replayMsgSeq, 1, true, true)).msgList[0];
if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) { if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0]; replyMsg = (await NTQQMsgApi.getSingleMsg(peer, element.replyElement.replayMsgSeq)).msgList[0];
} }
if (msg.peerUin == '284840486') {
if (!replyMsg || element.replyElement.replayMsgSeq !== replyMsg.msgSeq) { //合并消息内侧 消息具体定位不到
}
if ((!replyMsg || records.msgRandom !== replyMsg.msgRandom) && msg.peerUin !== '284840486') {
throw new Error('回复消息消息验证失败'); throw new Error('回复消息消息验证失败');
} }
message_data['data']['id'] = MessageUnique.createMsg({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, replyMsg.msgId)?.toString(); message_data['data']['id'] = MessageUnique.createMsg({ peerUid: msg.peerUid, guildId: '', chatType: msg.chatType }, replyMsg.msgId)?.toString();
@@ -222,7 +223,11 @@ export class OB11Constructor {
const videoElement: VideoElement = element.videoElement; const videoElement: VideoElement = element.videoElement;
//读取视频链接并兜底 //读取视频链接并兜底
let videoUrl;//Array let videoUrl;//Array
if (msg.peerUin = '284840486') {
//合并消息内部 应该进行特殊处理 可能需要重写peer 待测试与研究
}
try { try {
videoUrl = await NTQQFileApi.getVideoUrl({ videoUrl = await NTQQFileApi.getVideoUrl({
chatType: msg.chatType, chatType: msg.chatType,
peerUid: msg.peerUid, peerUid: msg.peerUid,
@@ -328,6 +333,27 @@ export class OB11Constructor {
else if (element.multiForwardMsgElement) { else if (element.multiForwardMsgElement) {
message_data['type'] = OB11MessageDataType.forward; message_data['type'] = OB11MessageDataType.forward;
message_data['data']['id'] = msg.msgId; message_data['data']['id'] = msg.msgId;
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
let MultiMsgs = (await NTQQMsgApi.getMultiMsg(ParentMsgPeer, msg.parentMsgIdList[0], msg.msgId))?.msgList;
//拉取下级消息
if (!MultiMsgs) continue;
//拉取失败则跳过
message_data['data']['content'] = [];
for (let MultiMsg of MultiMsgs) {
//对每条拉取的消息传递ParentMsgPeer修正Peer
MultiMsg.parentMsgPeer = ParentMsgPeer;
MultiMsg.parentMsgIdList = msg.parentMsgIdList;
MultiMsg.id = MessageUnique.createMsg(ParentMsgPeer, MultiMsg.msgId);//该ID仅用查看 无法调用
let msgList = await OB11Constructor.message(MultiMsg);
message_data['data']['content'].push(msgList);
//console.log("合并消息", msgList);
}
} }
if ((message_data.type as string) !== 'unknown' && message_data.data) { if ((message_data.type as string) !== 'unknown' && message_data.data) {
const cqCode = encodeCQCode(message_data); const cqCode = encodeCQCode(message_data);
@@ -459,6 +485,7 @@ export class OB11Constructor {
}); });
} }
if (grayTipElement) { if (grayTipElement) {
//console.log('收到群提示消息', grayTipElement);
if (grayTipElement.xmlElement?.templId === '10382') { if (grayTipElement.xmlElement?.templId === '10382') {
const emojiLikeData = new fastXmlParser.XMLParser({ const emojiLikeData = new fastXmlParser.XMLParser({
ignoreAttributes: false, ignoreAttributes: false,

View File

@@ -2,7 +2,7 @@ import { OB11Message } from '@/onebot11/types';
import { log } from '@/common/utils/log'; import { log } from '@/common/utils/log';
import { getGroup, getGroupMember, selfInfo } from '@/core/data'; import { getGroup, getGroupMember, selfInfo } from '@/core/data';
import exp from 'constants'; import exp from 'constants';
import { Group } from '@/core'; import { Group, NTQQMsgApi } from '@/core';
import chalk from 'chalk'; import chalk from 'chalk';
const spSegColor = chalk.blue;// for special segment const spSegColor = chalk.blue;// for special segment
@@ -24,8 +24,13 @@ export async function logMessage(ob11Message: OB11Message) {
} }
} }
if (ob11Message.message_type === 'group') { if (ob11Message.message_type === 'group') {
group = await getGroup(ob11Message.group_id!); if (ob11Message.group_id == 284840486) {
prefix += `群[${group?.groupName}(${ob11Message.group_id})] `; group = await getGroup(ob11Message.group_id!);
prefix += `转发消息[外部来源] `;
} else {
group = await getGroup(ob11Message.group_id!);
prefix += `群[${group?.groupName}(${ob11Message.group_id})] `;
}
} }
let msgChain = ''; let msgChain = '';
if (Array.isArray(ob11Message.message)) { if (Array.isArray(ob11Message.message)) {
@@ -66,6 +71,13 @@ export async function logMessage(ob11Message: OB11Message) {
else if (segment.type === 'video') { else if (segment.type === 'video') {
msgParts.push(spSegColor(`[视频|${segment.data.url}]`)); msgParts.push(spSegColor(`[视频|${segment.data.url}]`));
} }
else if (segment.type === 'forward') {
msgParts.push(spSegColor(`[转发|${segment.data.id}|消息开始]`));
segment.data.content.forEach((msg) => {
logMessage(msg);
});
msgParts.push(spSegColor(`[转发|${segment.data.id}|消息结束]`));
}
else { else {
msgParts.push(spSegColor(`[未实现|${JSON.stringify(segment)}]`)); msgParts.push(spSegColor(`[未实现|${JSON.stringify(segment)}]`));
} }

View File

@@ -98,6 +98,8 @@ export class NapCatOnebot11 {
// .map(x => x.toString(16).padStart(2, '0')) // .map(x => x.toString(16).padStart(2, '0'))
// .join(''); // .join('');
// } // }
// const hex = buf2hex(Buffer.from(protobufData));
// console.log(hex);
// // let Data: Data = { // // let Data: Data = {
// // header: { // // header: {
// // GroupNumber: 0, // // GroupNumber: 0,
@@ -192,6 +194,7 @@ export class NapCatOnebot11 {
// // 732 17 GroupRecall // // 732 17 GroupRecall
// // 732 20 GroupCommonTips // // 732 20 GroupCommonTips
// // 732 21 EssenceMessage // // 732 21 EssenceMessage
// // 732 16 16 GrayTips ->Busi->GroupEmjioLike
// } catch (e) { // } catch (e) {
// log('解析SysMsg异常', e); // log('解析SysMsg异常', e);
// // console.log(e); // // console.log(e);
@@ -209,7 +212,7 @@ export class NapCatOnebot11 {
// 临时会话更新 tempGroupCodeMap uid -> source/GroupCode // 临时会话更新 tempGroupCodeMap uid -> source/GroupCode
}; };
msgListener.onRecvMsg = async (msg) => { msgListener.onRecvMsg = async (msg) => {
//console.log('ob11 onRecvMsg', JSON.stringify(msg, null, 2)); //console.log('ob11 onRecvMsg', JSON.stringify(msg, null, 2));
// logDebug('收到消息', msg); // logDebug('收到消息', msg);
for (const m of msg) { for (const m of msg) {
@@ -291,7 +294,7 @@ export class NapCatOnebot11 {
} }
}; };
groupListener.onMemberInfoChange = async (groupCode: string, changeType: number, members: Map<string, GroupMember>) => { groupListener.onMemberInfoChange = async (groupCode: string, changeType: number, members: Map<string, GroupMember>) => {
// console.log("ob11 onMemberInfoChange", groupCode, changeType, members) // console.log("ob11 onMemberInfoChange", groupCode, changeType, members)
if (changeType === 1) { if (changeType === 1) {
let member; let member;
for (const [key, value] of members) { for (const [key, value] of members) {

View File

@@ -11,7 +11,7 @@ class OB11HTTPServer extends HttpServerBase {
name = 'OneBot V11 server'; name = 'OneBot V11 server';
handleFailed(res: Response, payload: any, e: Error) { handleFailed(res: Response, payload: any, e: Error) {
res.send(OB11Response.error(e?.stack?.toString() || e.message || "Error Handle", 200)); res.send(OB11Response.error(e?.stack?.toString() || e.message || 'Error Handle', 200));
} }
protected listen(port: number, host: string) { protected listen(port: number, host: string) {

View File

@@ -159,7 +159,7 @@ async function handleMsg(msg: OB11Message, quickAction: QuickAction) {
} }
} }
replyMessage = replyMessage.concat(normalize(reply, quickAction.auto_escape)); replyMessage = replyMessage.concat(normalize(reply, quickAction.auto_escape));
const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, group); const { sendElements, deleteAfterSentFiles } = await createSendElements(replyMessage, peer);
sendMsg(peer, sendElements, deleteAfterSentFiles, false).then().catch(logError); sendMsg(peer, sendElements, deleteAfterSentFiles, false).then().catch(logError);
} }
} }

View File

@@ -181,7 +181,8 @@ export interface OB11MessageMarkdown {
export interface OB11MessageForward { export interface OB11MessageForward {
type: OB11MessageDataType.forward type: OB11MessageDataType.forward
data: { data: {
id: string id: string,
content: OB11Message[]
} }
} }

View File

@@ -1 +1 @@
export const version = '1.7.7'; export const version = '1.8.2';

View File

@@ -29,7 +29,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('V1.7.7', 'napcat-update-button', 'secondary') SettingButton('V1.8.2', 'napcat-update-button', 'secondary')
), ),
]), ]),
SettingList([ SettingList([

View File

@@ -163,7 +163,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("V1.7.7", "napcat-update-button", "secondary") SettingButton("V1.8.2", "napcat-update-button", "secondary")
) )
]), ]),
SettingList([ SettingList([