mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2d8f58c6d8 | ||
![]() |
65888fa816 | ||
![]() |
857e882c6e | ||
![]() |
add2931834 | ||
![]() |
cdda5f45ee | ||
![]() |
5f73d6a913 | ||
![]() |
0637882fbc | ||
![]() |
3f785bab20 | ||
![]() |
a4ca89bdd6 | ||
![]() |
1a64e796bd | ||
![]() |
a8b85a34f7 | ||
![]() |
e7bec7d6b0 | ||
![]() |
a582026037 | ||
![]() |
1a67a001c5 | ||
![]() |
406deac592 | ||
![]() |
e719ae0676 | ||
![]() |
d8b7726440 | ||
![]() |
49f642e712 | ||
![]() |
70117016ce | ||
![]() |
a4738f6281 | ||
![]() |
b1fc72d696 | ||
![]() |
457c2c2b50 | ||
![]() |
48848d7d1a | ||
![]() |
55b07ca3ab | ||
![]() |
a1d4882e18 | ||
![]() |
3843795d8f | ||
![]() |
f2bf8d42da | ||
![]() |
a3b244e114 | ||
![]() |
3093bdbc68 | ||
![]() |
9ab0799283 | ||
![]() |
236bec11ed | ||
![]() |
de48b0f940 | ||
![]() |
4885d4db86 | ||
![]() |
0c7bbda936 | ||
![]() |
fa07c2c1fb | ||
![]() |
5d17a191f6 | ||
![]() |
67fb74d3c2 | ||
![]() |
dc04cfc1b3 | ||
![]() |
d61d481965 | ||
![]() |
6b346ee1de | ||
![]() |
d0f248aaf9 | ||
![]() |
85c9227515 | ||
![]() |
73b6d3be84 | ||
![]() |
1ff6ce2343 | ||
![]() |
c145935d46 | ||
![]() |
e9ede6924e | ||
![]() |
515a21761d | ||
![]() |
8d6397028b | ||
![]() |
eb4828d81f | ||
![]() |
7e74578312 | ||
![]() |
640e3516d4 | ||
![]() |
bd295a4632 | ||
![]() |
166c30fe2c | ||
![]() |
66c1bab629 | ||
![]() |
66656304f9 | ||
![]() |
07f66e379d | ||
![]() |
7ae8fd60c4 | ||
![]() |
7275066994 | ||
![]() |
385adec186 | ||
![]() |
96b5bec5ab | ||
![]() |
6a9ec4e5f0 | ||
![]() |
d9851493df | ||
![]() |
efdb520414 | ||
![]() |
5548644aeb | ||
![]() |
e3fcd91b2d | ||
![]() |
2cae30ba88 | ||
![]() |
58cd38c4a8 | ||
![]() |
3300304feb | ||
![]() |
f0e376d06b | ||
![]() |
16f7bb48f2 | ||
![]() |
7f383dd29b | ||
![]() |
3dc529edf4 | ||
![]() |
45dedb4872 | ||
![]() |
afcdd01c0d | ||
![]() |
1164877e9a | ||
![]() |
fe92a449ba | ||
![]() |
401b0e2bd0 | ||
![]() |
cf9c71fcc1 | ||
![]() |
15a2400069 | ||
![]() |
d68a39b49e | ||
![]() |
066ca22e24 | ||
![]() |
0418b926fe | ||
![]() |
be40bbdf40 | ||
![]() |
df4f42e79e | ||
![]() |
5f80058f70 | ||
![]() |
0cbe59052d | ||
![]() |
af28a26e37 | ||
![]() |
70c596df93 | ||
![]() |
748b51428c | ||
![]() |
8ad746397c | ||
![]() |
45baed2f9a | ||
![]() |
74185f2d33 | ||
![]() |
90a91e4105 | ||
![]() |
11aa3a0315 | ||
![]() |
0c2e39214f | ||
![]() |
d89620d7a6 | ||
![]() |
edf80775b7 | ||
![]() |
46e56ac726 |
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,7 @@ if %errorLevel% == 0 (
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
|
@@ -9,7 +9,7 @@ if %errorLevel% == 0 (
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PATH=%cd%\patchNapCat.js
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
|
@@ -1 +0,0 @@
|
||||
require('./launcher.node').load('external_index', module);
|
23
launcher/qqnt.json
Normal file
23
launcher/qqnt.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "qq-chat",
|
||||
"version": "9.9.15-28060",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"description": "QQ",
|
||||
"productName": "QQ",
|
||||
"author": {
|
||||
"name": "Tencent",
|
||||
"email": "QQ-Team@tencent.com"
|
||||
},
|
||||
"homepage": "https://im.qq.com",
|
||||
"sideEffects": true,
|
||||
"bin": {
|
||||
"qd": "externals/devtools/cli/index.js"
|
||||
},
|
||||
"main": "./loadNapCat.js",
|
||||
"buildVersion": "28060",
|
||||
"isPureShell": true,
|
||||
"isByteCodeShell": true,
|
||||
"platform": "win32",
|
||||
"eleArch": "x64"
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "2.4.7",
|
||||
"version": "2.6.8",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
13
package.json
13
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "2.4.7",
|
||||
"version": "2.6.8",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
@@ -11,9 +11,6 @@
|
||||
"depend": "cd dist && npm install --omit=dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
@@ -21,9 +18,7 @@
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/fluent-ffmpeg": "^2.1.24",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^22.0.1",
|
||||
"@types/qrcode-terminal": "^0.12.2",
|
||||
"@types/ws": "^8.5.12",
|
||||
@@ -32,14 +27,8 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"i": "^0.3.7",
|
||||
"javascript-obfuscator": "^4.1.0",
|
||||
"rollup": "^4.13.2",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-cp": "^4.0.8",
|
||||
"vite-plugin-dts": "^3.8.2",
|
||||
"vite-tsconfig-paths": "^4.3.2"
|
||||
|
@@ -1,45 +0,0 @@
|
||||
# Dont Use This Script
|
||||
# 2024.7.3
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
|
||||
if (!(Test-Path $QQpath)) {
|
||||
throw "provided QQ path is invalid: $QQpath"
|
||||
}
|
||||
|
||||
$Bootfile = Join-Path $PSScriptRoot "napcat.mjs"
|
||||
$env:ELECTRON_RUN_AS_NODE = 1
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
Start-Process powershell -ArgumentList "-noexit", "-noprofile", "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging }"
|
@@ -1,90 +0,0 @@
|
||||
@echo off
|
||||
REM 检查当前会话是否具有管理员权限
|
||||
openfiles >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
REM 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
echo 请求管理员权限...
|
||||
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 设置当前工作目录
|
||||
cd /d %~dp0
|
||||
|
||||
REM 获取当前目录路径
|
||||
set currentPath=%cd%
|
||||
set currentPath=%currentPath:\=/%
|
||||
|
||||
REM 生成JavaScript代码
|
||||
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
|
||||
|
||||
REM 将JavaScript代码保存到文件中
|
||||
echo %jsCode% > loadScript.js
|
||||
echo JavaScript code has been generated and saved to loadScript.js
|
||||
|
||||
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM 获取QQ路径
|
||||
|
||||
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set RetString=%%b
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in (%RetString%) do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
REM 拿不到QQ路径则退出
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 收集dbghelp.dll路径和HASH信息
|
||||
set QQdir=%~dp0
|
||||
set oldDllPath=%QQdir%dbghelp.dll
|
||||
set newDllPath=%currentPath%\dbghelp.dll
|
||||
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
|
||||
if not defined oldDllHash set oldDllHash=%%A
|
||||
)
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
|
||||
if not defined newDllHash set newDllHash=%%A
|
||||
)
|
||||
|
||||
REM 如果文件一致则跳过
|
||||
if "%oldDllHash%" neq "%newDllHash%" (
|
||||
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
REM 文件占用则退出
|
||||
echo dbghelp.dll is in use, cannot continue.
|
||||
) else (
|
||||
REM 文件未占用则尝试覆盖
|
||||
copy /y "%newDllPath%" "%oldDllPath%"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to copy dbghelp.dll
|
||||
pause
|
||||
exit /b
|
||||
) else (
|
||||
echo dbghelp.dll has been copied to %QQdir%
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM 带参数启动QQ
|
||||
REM 判断wt是否存在,存在则通过wt启动,不存在则通过cmd启动
|
||||
REM %QQPath% --enable-logging %*
|
||||
where wt >nul 2>nul
|
||||
if %errorlevel% equ 0 (
|
||||
wt "cmd" /c "%QQPath%" --enable-logging %*
|
||||
) else (
|
||||
"%QQPath%" --enable-logging %*
|
||||
)
|
@@ -1,123 +0,0 @@
|
||||
# 检查当前会话是否具有管理员权限
|
||||
function Test-Administrator {
|
||||
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
(New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
|
||||
}
|
||||
|
||||
if (-not (Test-Administrator)) {
|
||||
# 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
$scriptPath = $myInvocation.MyCommand.Path
|
||||
if (-not $scriptPath) {
|
||||
$scriptPath = $PSCommandPath
|
||||
}
|
||||
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "powershell";
|
||||
$newProcess.Arguments = "-File `"$scriptPath`" $args"
|
||||
$newProcess.Verb = "runas";
|
||||
[System.Diagnostics.Process]::Start($newProcess);
|
||||
exit
|
||||
}
|
||||
|
||||
function Get-QQpath {
|
||||
try {
|
||||
$key = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ"
|
||||
$uninstallString = $key.UninstallString
|
||||
return [System.IO.Path]::GetDirectoryName($uninstallString) + "\QQ.exe"
|
||||
}
|
||||
catch {
|
||||
throw "get QQ path error: $_"
|
||||
}
|
||||
}
|
||||
function Select-QQPath {
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
|
||||
$dialogTitle = "Select QQ.exe"
|
||||
|
||||
$filePicker = New-Object System.Windows.Forms.OpenFileDialog
|
||||
$filePicker.Title = $dialogTitle
|
||||
$filePicker.Filter = "Executable Files (*.exe)|*.exe|All Files (*.*)|*.*"
|
||||
$filePicker.FilterIndex = 1
|
||||
$null = $filePicker.ShowDialog()
|
||||
if (-not ($filePicker.FileName)) {
|
||||
throw "User did not select an .exe file."
|
||||
}
|
||||
return $filePicker.FileName
|
||||
}
|
||||
|
||||
# 设置当前工作目录
|
||||
$scriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
Set-Location $scriptDirectory
|
||||
|
||||
# 获取当前目录路径
|
||||
$currentPath = Get-Location
|
||||
|
||||
# 替换\为/
|
||||
$currentPath = $currentPath -replace '\\', '/'
|
||||
|
||||
# 生成JavaScript代码
|
||||
$jsCode = @"
|
||||
(async () => {
|
||||
await import('file:///$currentPath/napcat.mjs');
|
||||
})();
|
||||
"@
|
||||
|
||||
# 将JavaScript代码保存到文件中
|
||||
$jsFilePath = Join-Path $currentPath "loadScript.js"
|
||||
$jsCode | Out-File -FilePath $jsFilePath -Encoding UTF8
|
||||
|
||||
Write-Output "JavaScript code has been generated and saved to $jsFilePath"
|
||||
# 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
$env:NAPCAT_PATH = $jsFilePath
|
||||
|
||||
$params = $args -join " "
|
||||
Try {
|
||||
$QQpath = Get-QQpath
|
||||
}
|
||||
Catch {
|
||||
$QQpath = Select-QQPath
|
||||
}
|
||||
# 拿不到QQ路径则退出
|
||||
if (!(Test-Path $QQpath)) {
|
||||
Write-Output "provided QQ path is invalid: $QQpath"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
|
||||
$commandInfo = Get-Command $QQpath -ErrorAction Stop
|
||||
|
||||
# 收集dbghelp.dll路径和HASH信息
|
||||
$QQpath = Split-Path $QQpath
|
||||
$oldDllPath = Join-Path $QQpath "dbghelp.dll"
|
||||
$oldDllHash = Get-FileHash $oldDllPath -Algorithm MD5
|
||||
$newDllPath = Join-Path $currentPath "dbghelp.dll"
|
||||
$newDllHash = Get-FileHash $newDllPath -Algorithm MD5
|
||||
# 如果文件一致则跳过
|
||||
if ($oldDllHash.Hash -ne $newDllHash.Hash) {
|
||||
$processes = Get-Process -Name QQ -ErrorAction SilentlyContinue
|
||||
if ($processes) {
|
||||
# 文件占用则退出
|
||||
Write-Output "dbghelp.dll is in use by the following processes:"
|
||||
$processes | ForEach-Object { Write-Output "$($_.Id) $($_.Name) $($_.Path)" }
|
||||
Write-Output "dbghelp.dll is in use, cannot continue."
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
} else {
|
||||
# 文件未占用则尝试覆盖
|
||||
try {
|
||||
Copy-Item -Path "$newDllPath" -Destination "$oldDllPath" -Force
|
||||
Write-Output "dbghelp.dll has been copied to $QQpath"
|
||||
} catch {
|
||||
Write-Output "Failed to copy dbghelp.dll: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
exit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 带参数启动QQ
|
||||
try {
|
||||
Start-Process powershell -ArgumentList '-noexit', '-noprofile', "-command &{& chcp 65001;& '$($commandInfo.Path)' --enable-logging $params}" -NoNewWindow -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Output "Failed to start process as administrator: $_"
|
||||
Read-Host "Press any key to continue..."
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
@echo off
|
||||
REM 检查当前会话是否具有管理员权限
|
||||
openfiles >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
REM 如果不是管理员,则重新启动脚本以管理员模式运行
|
||||
echo 请求管理员权限...
|
||||
where wt >nul 2>nul
|
||||
if %errorlevel% equ 0 (
|
||||
powershell -Command "Start-Process cmd -ArgumentList ' /c %~f0 %*' -Verb RunAs"
|
||||
) else (
|
||||
powershell -Command "Start-Process wt -ArgumentList 'cmd /c %~f0 %*' -Verb RunAs"
|
||||
)
|
||||
|
||||
REM wt "cmd" /c "%~f0 %*"
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 设置当前工作目录
|
||||
cd /d %~dp0
|
||||
|
||||
REM 获取当前目录路径
|
||||
set currentPath=%cd%
|
||||
set currentPath=%currentPath:\=/%
|
||||
|
||||
REM 生成JavaScript代码
|
||||
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
|
||||
|
||||
REM 将JavaScript代码保存到文件中
|
||||
echo %jsCode% > loadScript.js
|
||||
echo JavaScript code has been generated and saved to loadScript.js
|
||||
|
||||
REM 设置NAPCAT_PATH环境变量为 当前目录的loadScript.js地址
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM 获取QQ路径
|
||||
|
||||
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set RetString=%%b
|
||||
goto :napcat_boot
|
||||
)
|
||||
|
||||
:napcat_boot
|
||||
for %%a in (%RetString%) do (
|
||||
set "pathWithoutUninstall=%%~dpa"
|
||||
)
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
|
||||
REM 拿不到QQ路径则退出
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM 收集dbghelp.dll路径和HASH信息
|
||||
set QQdir=%~dp0
|
||||
set oldDllPath=%QQdir%dbghelp.dll
|
||||
set newDllPath=%currentPath%\dbghelp.dll
|
||||
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
|
||||
if not defined oldDllHash set oldDllHash=%%A
|
||||
)
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
|
||||
if not defined newDllHash set newDllHash=%%A
|
||||
)
|
||||
|
||||
REM 如果文件一致则跳过
|
||||
if "%oldDllHash%" neq "%newDllHash%" (
|
||||
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
REM 文件占用则退出
|
||||
echo dbghelp.dll is in use, cannot continue.
|
||||
) else (
|
||||
REM 文件未占用则尝试覆盖
|
||||
copy /y "%newDllPath%" "%oldDllPath%"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Failed to copy dbghelp.dll
|
||||
pause
|
||||
exit /b
|
||||
) else (
|
||||
echo dbghelp.dll has been copied to %QQdir%
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
REM 带参数启动QQ
|
||||
REM 判断wt是否存在,存在则通过wt启动,不存在则通过cmd启动
|
||||
REM %QQPath% --enable-logging %*
|
||||
chcp 65001
|
||||
"%QQPath%" --enable-logging %*
|
@@ -1,77 +0,0 @@
|
||||
@echo off
|
||||
REM Check if the script is running as administrator
|
||||
openfiles >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
REM If not, restart the script in administrator mode
|
||||
echo Requesting administrator privileges...
|
||||
powershell -Command "Start-Process cmd -ArgumentList '/c %~f0 %*' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
cd /d %~dp0
|
||||
|
||||
set currentPath=%cd%
|
||||
set currentPath=%currentPath:\=/%
|
||||
|
||||
REM Generate JavaScript code
|
||||
set "jsCode=(async () =^>await import('file:///%currentPath%/napcat.mjs'))();"
|
||||
|
||||
REM Save JavaScript code to a file
|
||||
echo %jsCode% > loadScript.js
|
||||
echo JavaScript code has been generated and saved to loadScript.js
|
||||
|
||||
REM Set NAPCAT_PATH environment variable to the address of loadScript.js in the current directory
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM Get QQ path and cache it
|
||||
:loop_read
|
||||
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||
set "RetString=%%b"
|
||||
)
|
||||
|
||||
set "pathWithoutUninstall=%RetString:Uninstall.exe=%"
|
||||
|
||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||
echo %QQPath%>qq_path_cache.txt
|
||||
echo QQ path %QQPath% has been cached to qq_path_cache.txt
|
||||
|
||||
REM Exit if QQ path is invalid
|
||||
if not exist "%QQpath%" (
|
||||
echo provided QQ path is invalid: %QQpath%
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
REM Collect dbghelp.dll path and HASH information
|
||||
set QQdir=%~dp0
|
||||
set oldDllPath=%QQdir%dbghelp.dll
|
||||
set newDllPath=%currentPath%\dbghelp.dll
|
||||
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%oldDllPath%" MD5') do (
|
||||
if not defined oldDllHash set oldDllHash=%%A
|
||||
)
|
||||
for /f "tokens=*" %%A in ('certutil -hashfile "%newDllPath%" MD5') do (
|
||||
if not defined newDllHash set newDllHash=%%A
|
||||
)
|
||||
|
||||
REM Compare the HASH of the old and new dbghelp.dll, and replace the old one if they are different
|
||||
if "%oldDllHash%" neq "%newDllHash%" (
|
||||
tasklist /fi "imagename eq QQ.exe" 2>nul | find /i "QQ.exe" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
REM If the file is in use, prompt the user to close QQ
|
||||
echo dbghelp.dll is in use, please close QQ first.
|
||||
) else (
|
||||
copy /y "%newDllPath%" "%oldDllPath%"
|
||||
if %errorlevel% neq 0 (
|
||||
echo Copy dbghelp.dll failed, please check and try again.
|
||||
pause
|
||||
exit /b
|
||||
) else (
|
||||
echo dbghelp.dll has been updated.
|
||||
echo Please run BootWay05_run.bat to start QQ.
|
||||
echo If you update QQ in the future, please run BootWay05_init.bat again.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
)
|
||||
)
|
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
set /p QQPath=<qq_path_cache.txt
|
||||
echo QQ path %QQPath% has been read from qq_path_cache.txt
|
||||
echo If failed to start QQ, please try running this script in administrator mode.
|
||||
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM Launch QQ.exe with params provided
|
||||
|
||||
"%QQPath%" --enable-logging %*
|
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
|
||||
chcp 65001
|
||||
|
||||
set /p QQPath=<qq_path_cache.txt
|
||||
echo QQ path %QQPath% has been read from qq_path_cache.txt
|
||||
echo If failed to start QQ, please try running this script in administrator mode.
|
||||
|
||||
set NAPCAT_PATH=%cd%\loadScript.js
|
||||
|
||||
REM Launch QQ.exe with params provided
|
||||
|
||||
"%QQPath%" --enable-logging %*
|
Binary file not shown.
@@ -40,12 +40,14 @@ async function convert(filePath: string, pcmPath: string, logger: LogWrapper): P
|
||||
});
|
||||
}
|
||||
|
||||
async function handleWavFile(file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper): Promise<Buffer> {
|
||||
async function handleWavFile(
|
||||
file: Buffer, filePath: string, pcmPath: string, logger: LogWrapper
|
||||
): Promise<{input: Buffer, sampleRate: number}> {
|
||||
const { fmt } = getWavFileInfo(file);
|
||||
if (!ALLOW_SAMPLE_RATE.includes(fmt.sampleRate)) {
|
||||
return await convert(filePath, pcmPath, logger);
|
||||
return { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
}
|
||||
return file;
|
||||
return { input: file, sampleRate: fmt.sampleRate };
|
||||
}
|
||||
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
@@ -55,8 +57,10 @@ export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: Log
|
||||
if (!isSilk(file)) {
|
||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||
const pcmPath = `${pttPath}.pcm`;
|
||||
const input = isWav(file) ? await handleWavFile(file, filePath, pcmPath, logger) : await convert(filePath, pcmPath, logger);
|
||||
const silk = await encode(input, 24000);
|
||||
const { input, sampleRate } = isWav(file)
|
||||
? (await handleWavFile(file, filePath, pcmPath, logger))
|
||||
: { input: await convert(filePath, pcmPath, logger), sampleRate: 24000 };
|
||||
const silk = await encode(input, sampleRate);
|
||||
await fsPromise.writeFile(pttPath, silk.data);
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
return {
|
||||
|
@@ -1,133 +0,0 @@
|
||||
import type { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
|
||||
import EventEmitter from 'node:events';
|
||||
|
||||
export type ListenerClassBase = Record<string, string>;
|
||||
|
||||
export interface ListenerIBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(listener: any): ListenerClassBase;
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class NTEventWrapperV2 extends EventEmitter {
|
||||
private wrapperApi: WrapperNodeApi;
|
||||
private wrapperSession: NodeIQQNTWrapperSession;
|
||||
private listenerRefStorage = new Map<string, ListenerIBase>();
|
||||
|
||||
constructor(WrapperApi: WrapperNodeApi, WrapperSession: NodeIQQNTWrapperSession) {
|
||||
super();
|
||||
this.on('error', () => {
|
||||
});
|
||||
this.wrapperApi = WrapperApi;
|
||||
this.wrapperSession = WrapperSession;
|
||||
}
|
||||
|
||||
dispatcherListener(ListenerEvent: string, ...args: any[]) {
|
||||
this.emit(ListenerEvent, ...args);
|
||||
}
|
||||
|
||||
createProxyDispatch(ListenerMainName: string) {
|
||||
const dispatcherListener = this.dispatcherListener.bind(this);
|
||||
return new Proxy({}, {
|
||||
get(_target: any, prop: any, _receiver: any) {
|
||||
return (...args: any[]) => {
|
||||
dispatcherListener(ListenerMainName + '/' + prop, ...args);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getOrInitListener<T>(listenerMainName: string): Promise<T> {
|
||||
const ListenerType = this.wrapperApi[listenerMainName];
|
||||
//获取NTQQ 外部 Listener包装
|
||||
if (!ListenerType) throw new Error('Init Listener not found');
|
||||
let Listener = this.listenerRefStorage.get(listenerMainName);
|
||||
//判断是否已创建 创建则跳过
|
||||
if (!Listener && ListenerType) {
|
||||
Listener = new ListenerType(this.createProxyDispatch(listenerMainName));
|
||||
if (!Listener) throw new Error('Init Listener failed');
|
||||
//实例化NTQQ Listener外包装
|
||||
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
|
||||
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener';
|
||||
const addfunc = this.createEventFunction<(listener: T) => number>(Service);
|
||||
//添加Listener到NTQQ
|
||||
addfunc!(Listener as T);
|
||||
this.listenerRefStorage.set(listenerMainName, Listener);
|
||||
//保存Listener实例
|
||||
}
|
||||
return Listener as T;
|
||||
}
|
||||
|
||||
async createEventWithListener<EventType extends (...args: any) => any, ListenerType extends (...args: any) => any>
|
||||
(
|
||||
eventName: string,
|
||||
listenerName: string,
|
||||
waitTimes = 1,
|
||||
timeout: number = 3000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
...eventArg: Parameters<EventType>
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(async (resolve, reject) => {
|
||||
const ListenerNameList = listenerName.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
//const ListenerSubName = ListenerNameList[1];
|
||||
this.getOrInitListener<ListenerType>(ListenerMainName);
|
||||
let complete = 0;
|
||||
const retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(new Error('Timeout: NTEvent EventName:' + eventName + ' ListenerName:' + listenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n'));
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
const callback = (...args: Parameters<ListenerType>) => {
|
||||
if (checker(...args)) {
|
||||
complete++;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
this.removeListener(listenerName, callback);
|
||||
databack();
|
||||
}
|
||||
}
|
||||
};
|
||||
this.on(listenerName, callback);
|
||||
const EventFunc = this.createEventFunction<EventType>(eventName);
|
||||
retEvent = await EventFunc!(...(eventArg as any[]));
|
||||
});
|
||||
}
|
||||
|
||||
private createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
|
||||
const eventNameArr = eventName.split('/');
|
||||
type eventType = {
|
||||
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
|
||||
}
|
||||
if (eventNameArr.length > 1) {
|
||||
const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', '');
|
||||
const eventName = eventNameArr[1];
|
||||
//getNodeIKernelGroupListener,GroupService
|
||||
//console.log('2', eventName);
|
||||
const services = (this.wrapperSession as unknown as eventType)[serviceName]();
|
||||
const event = services[eventName]
|
||||
//重新绑定this
|
||||
.bind(services);
|
||||
if (event) {
|
||||
return event as T;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async callEvent<EventType extends (...args: any[]) => Promise<any> | any>(
|
||||
EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
|
||||
return new Promise<Awaited<ReturnType<EventType>>>((resolve) => {
|
||||
const EventFunc = this.createEventFunction<EventType>(EventName);
|
||||
EventFunc!(...args).then((retData: Awaited<ReturnType<EventType>> | PromiseLike<Awaited<ReturnType<EventType>>>) => resolve(retData));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//NTEvent2.0
|
@@ -183,11 +183,11 @@ export class NTEventWrapper {
|
||||
timeout = 5000,
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
async (resolve, reject) => {
|
||||
(resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const retEvent: any = {};
|
||||
|
||||
function sendDataCallback() {
|
||||
if (complete == 0) {
|
||||
@@ -235,8 +235,8 @@ export class NTEventWrapper {
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const eventFunction = this.createEventFunction(serviceAndMethod);
|
||||
retEvent = await eventFunction!(...(args));
|
||||
if (!checkerEvent(retEvent)) {
|
||||
if (eventFunction) eventFunction(...(args)).then((retEvent: Awaited<ReturnType<EventType>>) => {
|
||||
if (!checkerEvent(retEvent) && timeoutRef.hasRef()) {
|
||||
clearTimeout(timeoutRef);
|
||||
reject(
|
||||
new Error(
|
||||
@@ -250,86 +250,8 @@ export class NTEventWrapper {
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
async callNormalEvent<
|
||||
Service extends keyof ServiceNamingMapping,
|
||||
ServiceMethod extends Exclude<keyof ServiceNamingMapping[Service], symbol>,
|
||||
Listener extends keyof ListenerNamingMapping,
|
||||
ListenerMethod extends Exclude<keyof ListenerNamingMapping[Listener], symbol>,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
EventType extends (...args: any) => any = ServiceNamingMapping[Service][ServiceMethod],
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
ListenerType extends (...args: any) => any = ListenerNamingMapping[Listener][ListenerMethod]
|
||||
>(
|
||||
serviceAndMethod: `${Service}/${ServiceMethod}`,
|
||||
listenerAndMethod: `${Listener}/${ListenerMethod}`,
|
||||
waitTimes = 1,
|
||||
timeout: number = 3000,
|
||||
checker: (...args: Parameters<ListenerType>) => boolean,
|
||||
...args: Parameters<EventType>
|
||||
) {
|
||||
return new Promise<[EventRet: Awaited<ReturnType<EventType>>, ...Parameters<ListenerType>]>(
|
||||
async (resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
let complete = 0;
|
||||
let retData: Parameters<ListenerType> | undefined = undefined;
|
||||
let retEvent: any = {};
|
||||
const databack = () => {
|
||||
if (complete == 0) {
|
||||
reject(
|
||||
new Error(
|
||||
'Timeout: NTEvent EventName:' +
|
||||
serviceAndMethod +
|
||||
' ListenerName:' +
|
||||
listenerAndMethod +
|
||||
' EventRet:\n' +
|
||||
JSON.stringify(retEvent, null, 4) +
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
resolve([retEvent as Awaited<ReturnType<EventType>>, ...retData!]);
|
||||
}
|
||||
};
|
||||
|
||||
const ListenerNameList = listenerAndMethod.split('/');
|
||||
const ListenerMainName = ListenerNameList[0];
|
||||
const ListenerSubName = ListenerNameList[1];
|
||||
|
||||
const Timeouter = setTimeout(databack, timeout);
|
||||
|
||||
const eventCallbak = {
|
||||
timeout: timeout,
|
||||
createtime: Date.now(),
|
||||
checker: checker,
|
||||
func: (...args: any[]) => {
|
||||
complete++;
|
||||
//console.log('func', ...args);
|
||||
retData = args as Parameters<ListenerType>;
|
||||
if (complete >= waitTimes) {
|
||||
clearTimeout(Timeouter);
|
||||
databack();
|
||||
}
|
||||
},
|
||||
};
|
||||
if (!this.EventTask.get(ListenerMainName)) {
|
||||
this.EventTask.set(ListenerMainName, new Map());
|
||||
}
|
||||
if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) {
|
||||
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map());
|
||||
}
|
||||
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak);
|
||||
this.createListenerFunction(ListenerMainName);
|
||||
const EventFunc = this.createEventFunction<EventType>(serviceAndMethod);
|
||||
retEvent = await EventFunc!(...(args as any[]));
|
||||
},
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ export interface HttpDownloadOptions {
|
||||
headers?: Record<string, string> | string;
|
||||
}
|
||||
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
async function tryDownload(options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
|
||||
// const chunks: Buffer[] = [];
|
||||
let url: string;
|
||||
let headers: Record<string, string> = {
|
||||
@@ -142,15 +142,26 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
||||
}
|
||||
}
|
||||
}
|
||||
if (useReferer && !headers['Referer']) {
|
||||
headers['Referer'] = url;
|
||||
}
|
||||
const fetchRes = await fetch(url, { headers }).catch((err) => {
|
||||
if (err.cause) {
|
||||
throw err.cause;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`);
|
||||
return fetchRes;
|
||||
}
|
||||
|
||||
const blob = await fetchRes.blob();
|
||||
export async function httpDownload(options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||
const useReferer = typeof options === 'string';
|
||||
let resp = await tryDownload(options);
|
||||
if (resp.status === 403 && useReferer) {
|
||||
resp = await tryDownload(options, true);
|
||||
}
|
||||
if (!resp.ok) throw new Error(`下载文件失败: ${resp.statusText}`);
|
||||
const blob = await resp.blob();
|
||||
const buffer = await blob.arrayBuffer();
|
||||
return Buffer.from(buffer);
|
||||
}
|
||||
|
@@ -163,34 +163,51 @@ export function isEqual(obj1: any, obj2: any) {
|
||||
export function getDefaultQQVersionConfigInfo(): QQVersionConfigType {
|
||||
if (os.platform() === 'linux') {
|
||||
return {
|
||||
baseVersion: '3.2.12-27597',
|
||||
curVersion: '3.2.12-27597',
|
||||
baseVersion: '3.2.12.28060',
|
||||
curVersion: '3.2.12.28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '27597',
|
||||
buildId: '27254',
|
||||
};
|
||||
}
|
||||
if (os.platform() === 'darwin') {
|
||||
return {
|
||||
baseVersion: '6.9.53.28060',
|
||||
curVersion: '6.9.53.28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '28060',
|
||||
};
|
||||
}
|
||||
return {
|
||||
baseVersion: '9.9.15-27597',
|
||||
curVersion: '9.9.15-27597',
|
||||
baseVersion: '9.9.15-28060',
|
||||
curVersion: '9.9.15-28060',
|
||||
prevVersion: '',
|
||||
onErrorVersions: [],
|
||||
buildId: '27597',
|
||||
buildId: '28060',
|
||||
};
|
||||
}
|
||||
|
||||
export function getQQPackageInfoPath(exePath: string = ''): string {
|
||||
export function getQQPackageInfoPath(exePath: string = '', version?: string): string {
|
||||
let packagePath;
|
||||
if (os.platform() === 'darwin') {
|
||||
return path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
|
||||
} else if (os.platform() === 'linux') {
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/package.json');
|
||||
} else {
|
||||
return path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
||||
packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json');
|
||||
}
|
||||
//下面是老版本兼容 未来去掉
|
||||
if (!fs.existsSync(packagePath)) {
|
||||
packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json');
|
||||
}
|
||||
return packagePath;
|
||||
}
|
||||
|
||||
export function getQQVersionConfigPath(exePath: string = ''): string | undefined {
|
||||
let configVersionInfoPath;
|
||||
if (os.platform() === 'win32') {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json');
|
||||
} else if (os.platform() === 'darwin') {
|
||||
const userPath = os.homedir();
|
||||
const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
|
||||
@@ -203,13 +220,18 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined
|
||||
if (typeof configVersionInfoPath !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
|
||||
}
|
||||
if (!fs.existsSync(configVersionInfoPath)) {
|
||||
return undefined;
|
||||
}
|
||||
return configVersionInfoPath;
|
||||
}
|
||||
|
||||
export function calcQQLevel(level: QQLevel) {
|
||||
export function calcQQLevel(level?: QQLevel) {
|
||||
if (!level) return 0;
|
||||
const { crownNum, sunNum, moonNum, starNum } = level;
|
||||
return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
|
||||
}
|
||||
|
@@ -19,14 +19,16 @@ export class QQBasicInfoWrapper {
|
||||
//基础目录获取
|
||||
this.context = context;
|
||||
this.QQMainPath = process.execPath;
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath);
|
||||
this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath);
|
||||
|
||||
|
||||
//基础信息获取 无快更则启用默认模板填充
|
||||
this.isQuickUpdate = !!this.QQVersionConfigPath;
|
||||
this.QQVersionConfig = this.isQuickUpdate
|
||||
? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString())
|
||||
: getDefaultQQVersionConfigInfo();
|
||||
|
||||
this.QQPackageInfoPath = getQQPackageInfoPath(this.QQMainPath, this.QQVersionConfig?.curVersion);
|
||||
this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString());
|
||||
const { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2();
|
||||
this.QQVersionAppid = IQQVersionAppid;
|
||||
@@ -65,11 +67,11 @@ export class QQBasicInfoWrapper {
|
||||
getAppidInternal() {
|
||||
switch (systemPlatform) {
|
||||
case 'linux':
|
||||
return '537243600';
|
||||
return '537246140';
|
||||
case 'darwin':
|
||||
return '537243441';
|
||||
return '537246140';
|
||||
default:
|
||||
return '537243538';
|
||||
return '537246092';
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
// 缓解Win7设备兼容性问题
|
||||
let osName: string;
|
||||
// 设备ID
|
||||
let machineId: Promise<string>;
|
||||
|
||||
try {
|
||||
osName = os.hostname();
|
||||
@@ -14,54 +10,6 @@ try {
|
||||
osName = 'NapCat'; // + crypto.randomUUID().substring(0, 4);
|
||||
}
|
||||
|
||||
const invalidMacAddresses = new Set([
|
||||
'00:00:00:00:00:00',
|
||||
'ff:ff:ff:ff:ff:ff',
|
||||
'ac:de:48:00:11:22',
|
||||
]);
|
||||
|
||||
function validateMacAddress(candidate: string): boolean {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
|
||||
return !invalidMacAddresses.has(tempCandidate);
|
||||
}
|
||||
|
||||
export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
return id ?? randomUUID(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
||||
return machineId;
|
||||
}
|
||||
|
||||
export function getMac(): string {
|
||||
const ifaces = networkInterfaces();
|
||||
for (const name in ifaces) {
|
||||
const networkInterface = ifaces[name];
|
||||
if (networkInterface) {
|
||||
for (const { mac } of networkInterface) {
|
||||
if (validateMacAddress(mac)) {
|
||||
return mac;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unable to retrieve mac address (unexpected format)');
|
||||
}
|
||||
|
||||
async function getMacMachineId(): Promise<string | undefined> {
|
||||
try {
|
||||
const crypto = await import('crypto');
|
||||
const macAddress = getMac();
|
||||
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const homeDir = os.homedir();
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '2.4.7';
|
||||
export const napCatVersion = '2.6.8';
|
||||
|
@@ -147,7 +147,15 @@ export class NTQQFileApi {
|
||||
} catch (e) {
|
||||
logger.logError('获取视频信息失败,将使用默认值', e);
|
||||
}
|
||||
const newFilePath = filePath + '.mp4';
|
||||
|
||||
let fileExt = 'mp4';
|
||||
try {
|
||||
const tempExt = (await fileType.fileTypeFromFile(filePath))?.ext;
|
||||
if (tempExt) fileExt = tempExt;
|
||||
} catch (e) {
|
||||
this.context.logger.logError('获取文件类型失败', e);
|
||||
}
|
||||
const newFilePath = filePath + '.' + fileExt;
|
||||
fs.copyFileSync(filePath, newFilePath);
|
||||
context.deleteAfterSentFiles.push(newFilePath);
|
||||
filePath = newFilePath;
|
||||
@@ -189,11 +197,12 @@ export class NTQQFileApi {
|
||||
thumbPath.set(0, _thumbPath);
|
||||
const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : '';
|
||||
context.deleteAfterSentFiles.push(path);
|
||||
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith('.' + fileExt.toLocaleLowerCase()) ? (fileName || _fileName) : (fileName || _fileName) + '.' + fileExt;
|
||||
return {
|
||||
elementType: ElementType.VIDEO,
|
||||
elementId: '',
|
||||
videoElement: {
|
||||
fileName: fileName || _fileName,
|
||||
fileName: uploadName,
|
||||
filePath: path,
|
||||
videoMd5: md5,
|
||||
thumbMd5,
|
||||
|
@@ -34,7 +34,13 @@ export class NTQQGroupApi {
|
||||
}
|
||||
this.context.logger.logDebug(`加载${this.groups.length}个群组缓存完成`);
|
||||
}
|
||||
|
||||
async getCoreAndBaseInfo(uids: string[]) {
|
||||
return await this.core.eventWrapper.callNoListenerEvent(
|
||||
'NodeIKernelProfileService/getCoreAndBaseInfo',
|
||||
'nodeStore',
|
||||
uids,
|
||||
);
|
||||
}
|
||||
async fetchGroupEssenceList(groupCode: string) {
|
||||
const pskey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||
return this.context.session.getGroupService().fetchGroupEssenceList({
|
||||
@@ -262,6 +268,23 @@ export class NTQQGroupApi {
|
||||
}
|
||||
return member;
|
||||
}
|
||||
async searchGroup(groupCode: string) {
|
||||
const [, ret] = await this.core.eventWrapper.callNormalEventV2(
|
||||
'NodeIKernelSearchService/searchGroup',
|
||||
'NodeIKernelSearchListener/onSearchGroupResult',
|
||||
[{
|
||||
keyWords: groupCode,
|
||||
groupNum: 25,
|
||||
exactSearch: false,
|
||||
penetrate: ''
|
||||
}],
|
||||
(ret) => ret.result === 0,
|
||||
(params) => !!params.groupInfos.find(g => g.groupCode === groupCode),
|
||||
1,
|
||||
5000
|
||||
);
|
||||
return ret.groupInfos.find(g => g.groupCode === groupCode);
|
||||
}
|
||||
async getGroupMemberEx(GroupCode: string, uid: string, forced = false, retry = 2) {
|
||||
const data = await solveAsyncProblem((eventWrapper: NTEventWrapper, GroupCode: string, uid: string, forced = false) => {
|
||||
return eventWrapper.callNormalEventV2(
|
||||
|
@@ -94,6 +94,7 @@ export class NTQQMsgApi {
|
||||
pageLimit: 1,
|
||||
});
|
||||
}
|
||||
//@deprecated
|
||||
async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) {
|
||||
return await this.context.session.getMsgService().getMsgsBySeqAndCount(peer, seq, count, desc, z);
|
||||
}
|
||||
|
@@ -11,7 +11,13 @@ export class NTQQUserApi {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
//self_tind格式
|
||||
async createUidFromTinyId(tinyId: string) {
|
||||
return this.context.session.getMsgService().createUidFromTinyId(this.core.selfInfo.uin, tinyId);
|
||||
}
|
||||
async getStatusByUid(uid: string) {
|
||||
return this.context.session.getProfileService().getStatus(uid);
|
||||
}
|
||||
async getProfileLike(uid: string) {
|
||||
return this.context.session.getProfileLikeService().getBuddyProfileLike({
|
||||
friendUids: [uid],
|
||||
@@ -24,7 +30,18 @@ export class NTQQUserApi {
|
||||
limit: 20,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@@ -959,3 +959,18 @@ export interface TmpChatInfo {
|
||||
sessionType: number;
|
||||
sig: string;
|
||||
}
|
||||
export interface MsgReqType {
|
||||
peer: Peer,
|
||||
byType: number,
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
clientSeq: string,
|
||||
cnt: number,
|
||||
queryOrder: boolean,
|
||||
includeSelf: boolean,
|
||||
includeDeleteMsg: boolean,
|
||||
extraCnt: number
|
||||
}
|
||||
//getMsgsIncludeSelf Peer必须 byType 1
|
||||
//getMsgsWithMsgTimeAndClientSeqForC2C Peer必须 byType 3
|
@@ -216,7 +216,7 @@ export interface BuddyProfileLikeReq {
|
||||
userProfile: number;
|
||||
type: number;
|
||||
start: number;
|
||||
limit: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface QQLevel {
|
||||
|
16
src/core/external/appid.json
vendored
16
src/core/external/appid.json
vendored
@@ -1,14 +1,10 @@
|
||||
{
|
||||
"3.2.12-27597": {
|
||||
"appid": 537243600,
|
||||
"qua": "V1_LNX_NQ_3.2.12_27597_GW_B"
|
||||
"9.9.15-28060":{
|
||||
"appid": 537246092,
|
||||
"qua": "V1_WIN_NQ_9.9.15_28060_GW_B"
|
||||
},
|
||||
"9.9.15-27597": {
|
||||
"appid": 537243441,
|
||||
"qua": "V1_WIN_NQ_9.9.15_27597_GW_B"
|
||||
},
|
||||
"6.9.53-27597": {
|
||||
"appid": 537243538,
|
||||
"qua": "V1_MAC_NQ_6.9.53_27597_GW_B"
|
||||
"3.2.12-28060":{
|
||||
"appid": 537246140,
|
||||
"qua": "V1_LNX_NQ_3.2.12_28060_GW_B"
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
|
||||
import { NapCatPathWrapper } from '@/common/path';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { getMachineId, hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { hostname, systemName, systemVersion } from '@/common/system';
|
||||
import { NTEventWrapper } from '@/common/event';
|
||||
import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities';
|
||||
import { NapCatConfigLoader } from '@/core/helper/config';
|
||||
@@ -45,12 +45,18 @@ export function loadQQWrapper(QQVersion: string): WrapperNodeApi {
|
||||
let appPath;
|
||||
if (os.platform() === 'darwin') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), '../Resources/app');
|
||||
} else {
|
||||
} else if (os.platform() === 'linux') {
|
||||
appPath = path.resolve(path.dirname(process.execPath), './resources/app');
|
||||
} else {
|
||||
appPath = path.resolve(path.dirname(process.execPath), `./versions/${QQVersion}/`);
|
||||
}
|
||||
let wrapperNodePath = path.resolve(appPath, 'wrapper.node');
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(appPath, `versions/${QQVersion}/wrapper.node`);
|
||||
wrapperNodePath = path.join(appPath, `./resources/app/wrapper.node`);
|
||||
}
|
||||
//老版本兼容 未来去掉
|
||||
if (!fs.existsSync(wrapperNodePath)) {
|
||||
wrapperNodePath = path.join(path.dirname(process.execPath), `./resources/app/versions/${QQVersion}/wrapper.node`);
|
||||
}
|
||||
const nativemodule: any = { exports: {} };
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
@@ -241,22 +247,41 @@ export class NapCatCore {
|
||||
}
|
||||
}
|
||||
|
||||
export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise<WrapperSessionInitConfig> {
|
||||
export async function genSessionConfig(
|
||||
guid:string,
|
||||
QQVersionAppid: string,
|
||||
QQVersion: string,
|
||||
selfUin: string,
|
||||
selfUid: string,
|
||||
account_path: string
|
||||
): Promise<WrapperSessionInitConfig> {
|
||||
const downloadPath = path.join(account_path, 'NapCat', 'temp');
|
||||
fs.mkdirSync(downloadPath, { recursive: true });
|
||||
const guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a
|
||||
//os.platform()
|
||||
let systemPlatform = PlatformType.KWINDOWS;
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
systemPlatform = PlatformType.KWINDOWS;
|
||||
break;
|
||||
case 'darwin':
|
||||
systemPlatform = PlatformType.KMAC;
|
||||
break;
|
||||
case 'linux':
|
||||
systemPlatform = PlatformType.KLINUX;
|
||||
break;
|
||||
}
|
||||
return {
|
||||
selfUin,
|
||||
selfUid,
|
||||
desktopPathConfig: {
|
||||
account_path, // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取
|
||||
},
|
||||
clientVer: QQVersion, // 9.9.8-22355
|
||||
clientVer: QQVersion,
|
||||
a2: '',
|
||||
d2: '',
|
||||
d2Key: '',
|
||||
machineId: '',
|
||||
platform: PlatformType.KWINDOWS, // 3是Windows?
|
||||
platform: systemPlatform, // 3是Windows?
|
||||
platVer: systemVersion, // 系统版本号, 应该可以固定
|
||||
appid: QQVersionAppid,
|
||||
rdeliveryConfig: {
|
||||
@@ -264,7 +289,7 @@ export async function genSessionConfig(QQVersionAppid: string, QQVersion: string
|
||||
systemId: 0,
|
||||
appId: '',
|
||||
logicEnvironment: '',
|
||||
platform: PlatformType.KWINDOWS,
|
||||
platform: systemPlatform,
|
||||
language: '',
|
||||
sdkVersion: '',
|
||||
userId: '',
|
||||
|
97
src/core/listeners/NodeIKernelSearchListener.ts
Normal file
97
src/core/listeners/NodeIKernelSearchListener.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { ChatType } from '@/core';
|
||||
export interface SearchGroupInfo {
|
||||
groupCode: string;
|
||||
ownerUid: string;
|
||||
groupFlag: number;
|
||||
groupFlagExt: number;
|
||||
maxMemberNum: number;
|
||||
memberNum: number;
|
||||
groupOption: number;
|
||||
classExt: number;
|
||||
groupName: string;
|
||||
fingerMemo: string;
|
||||
groupQuestion: string;
|
||||
certType: number;
|
||||
shutUpAllTimestamp: number;
|
||||
shutUpMeTimestamp: number;
|
||||
groupTypeFlag: number;
|
||||
privilegeFlag: number;
|
||||
groupSecLevel: number;
|
||||
groupFlagExt3: number;
|
||||
isConfGroup: number;
|
||||
isModifyConfGroupFace: number;
|
||||
isModifyConfGroupName: number;
|
||||
noFigerOpenFlag: number;
|
||||
noCodeFingerOpenFlag: number;
|
||||
groupFlagExt4: number;
|
||||
groupMemo: string;
|
||||
cmdUinMsgSeq: number;
|
||||
cmdUinJoinTime: number;
|
||||
cmdUinUinFlag: number;
|
||||
cmdUinMsgMask: number;
|
||||
groupSecLevelInfo: number;
|
||||
cmdUinPrivilege: number;
|
||||
cmdUinFlagEx2: number;
|
||||
appealDeadline: number;
|
||||
remarkName: string;
|
||||
isTop: boolean;
|
||||
richFingerMemo: string;
|
||||
groupAnswer: string;
|
||||
joinGroupAuth: string;
|
||||
isAllowModifyConfGroupName: number;
|
||||
}
|
||||
|
||||
export interface GroupInfo {
|
||||
groupCode: string;
|
||||
searchGroupInfo: SearchGroupInfo;
|
||||
privilege: number;
|
||||
}
|
||||
|
||||
export interface GroupSearchResult {
|
||||
keyWord: string;
|
||||
errorCode: number;
|
||||
groupInfos: GroupInfo[];
|
||||
penetrate: string;
|
||||
isEnd: boolean;
|
||||
nextPos: number;
|
||||
}
|
||||
export interface NodeIKernelSearchListener {
|
||||
|
||||
onSearchGroupResult(params: GroupSearchResult): void;
|
||||
|
||||
onSearchFileKeywordsResult(params: {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo: {
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[],
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits: {
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
}): void;
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
import { ChatType } from '@/core';
|
||||
|
||||
export interface NodeIKernelSearchListener_Polyfill {
|
||||
onSearchFileKeywordsResult(params: {
|
||||
searchId: string,
|
||||
hasMore: boolean,
|
||||
resultItems: {
|
||||
chatType: ChatType,
|
||||
buddyChatInfo: any[],
|
||||
discussChatInfo: any[],
|
||||
groupChatInfo: {
|
||||
groupCode: string,
|
||||
isConf: boolean,
|
||||
hasModifyConfGroupFace: boolean,
|
||||
hasModifyConfGroupName: boolean,
|
||||
groupName: string,
|
||||
remark: string
|
||||
}[],
|
||||
dataLineChatInfo: any[],
|
||||
tmpChatInfo: any[],
|
||||
msgId: string,
|
||||
msgSeq: string,
|
||||
msgTime: string,
|
||||
senderUid: string,
|
||||
senderNick: string,
|
||||
senderRemark: string,
|
||||
senderCard: string,
|
||||
elemId: string,
|
||||
elemType: number,
|
||||
fileSize: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
hits: {
|
||||
start: number,
|
||||
end: number
|
||||
}[]
|
||||
}[]
|
||||
}): void;
|
||||
}
|
5
src/core/listeners/NodeIO3MiscListener.ts
Normal file
5
src/core/listeners/NodeIO3MiscListener.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class NodeIO3MiscListener {
|
||||
getOnAmgomDataPiece(...arg: unknown[]) {
|
||||
|
||||
}
|
||||
}
|
@@ -9,7 +9,7 @@ export * from './NodeIKernelProfileListener';
|
||||
export * from './NodeIKernelTicketListener';
|
||||
export * from './NodeIKernelStorageCleanListener';
|
||||
export * from './NodeIKernelFileAssistantListener';
|
||||
export * from './NodeIKernelSearchListener_Polyfill';
|
||||
export * from './NodeIKernelSearchListener';
|
||||
|
||||
import type {
|
||||
NodeIKernelBuddyListener,
|
||||
@@ -19,11 +19,11 @@ import type {
|
||||
NodeIKernelMsgListener,
|
||||
NodeIKernelProfileListener,
|
||||
NodeIKernelRobotListener,
|
||||
NodeIKernelSearchListener_Polyfill,
|
||||
NodeIKernelSessionListener,
|
||||
NodeIKernelStorageCleanListener,
|
||||
NodeIKernelTicketListener,
|
||||
} from '.';
|
||||
import { NodeIKernelSearchListener } from './NodeIKernelSearchListener';
|
||||
|
||||
export type ListenerNamingMapping = {
|
||||
NodeIKernelSessionListener: NodeIKernelSessionListener;
|
||||
@@ -36,5 +36,5 @@ export type ListenerNamingMapping = {
|
||||
NodeIKernelTicketListener: NodeIKernelTicketListener;
|
||||
NodeIKernelStorageCleanListener: NodeIKernelStorageCleanListener;
|
||||
NodeIKernelFileAssistantListener: NodeIKernelFileAssistantListener;
|
||||
NodeIKernelSearchListener: NodeIKernelSearchListener_Polyfill;
|
||||
NodeIKernelSearchListener: NodeIKernelSearchListener;
|
||||
};
|
||||
|
@@ -14,9 +14,15 @@ export interface LikeMsgType {
|
||||
detail: LikeDetailType;
|
||||
}
|
||||
|
||||
export interface ProfileLikeTipType {
|
||||
export interface profileLikeSubTipType {
|
||||
msg: LikeMsgType;
|
||||
}
|
||||
|
||||
export interface ProfileLikeTipType {
|
||||
msgType: number;
|
||||
subType: number;
|
||||
content: profileLikeSubTipType;
|
||||
}
|
||||
export interface SysMessageHeaderType {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
@@ -78,6 +84,12 @@ export const likeMsg = new pb.Type("likeMsg")
|
||||
.add(new pb.Field("time", 2, "int32"))
|
||||
.add(new pb.Field("detail", 3, "likeDetail"));
|
||||
|
||||
export const profileLikeTip = new pb.Type("profileLikeTip")
|
||||
export const profileLikeSubTip = new pb.Type("profileLikeSubTip")
|
||||
.add(likeMsg)
|
||||
.add(new pb.Field("msg", 14, "likeMsg"));
|
||||
.add(new pb.Field("msg", 14, "likeMsg"))
|
||||
|
||||
export const profileLikeTip = new pb.Type("profileLikeTip")
|
||||
.add(profileLikeSubTip)
|
||||
.add(new pb.Field("msgType", 1, "int32"))
|
||||
.add(new pb.Field("subType", 2, "int32"))
|
||||
.add(new pb.Field("content", 203, "profileLikeSubTip"));
|
||||
|
@@ -114,7 +114,7 @@ export interface NodeIKernelBuddyService {
|
||||
|
||||
reportDoubtBuddyReqUnread(): void;
|
||||
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<unknown>;
|
||||
getBuddyRecommendContactArkJson(uid: string, phoneNumber: string): Promise<GeneralCallResult & { arkMsg: string }>;
|
||||
|
||||
isNull(): boolean;
|
||||
}
|
||||
|
@@ -59,9 +59,11 @@ export interface QuickLoginResult {
|
||||
}
|
||||
|
||||
export interface NodeIKernelLoginService {
|
||||
getMachineGuid(): string;
|
||||
|
||||
get(): NodeIKernelLoginService;
|
||||
|
||||
connect(): boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(): NodeIKernelLoginService;
|
||||
|
||||
addKernelLoginListener(listener: NodeIKernelLoginListener): number;
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/core/entities';
|
||||
import { NodeIKernelMsgListener } from '@/core/listeners/NodeIKernelMsgListener';
|
||||
import { GeneralCallResult } from '@/core/services/common';
|
||||
import { QueryMsgsParams, TmpChatInfoApi } from '../entities/msg';
|
||||
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '../entities/msg';
|
||||
|
||||
export interface NodeIKernelMsgService {
|
||||
|
||||
@@ -147,12 +147,15 @@ export interface NodeIKernelMsgService {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
//@deprecated
|
||||
getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise<unknown>;
|
||||
|
||||
//@deprecated
|
||||
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
//@deprecated
|
||||
getMsgsWithMsgTimeAndClientSeqForC2C(...args: unknown[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsWithStatus(params: {
|
||||
@@ -168,7 +171,7 @@ export interface NodeIKernelMsgService {
|
||||
getMsgsBySeqRange(peer: Peer, startSeq: string, endSeq: string): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
|
||||
//@deprecated
|
||||
getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, unknownArg: boolean): Promise<GeneralCallResult & {
|
||||
msgList: RawMessage[]
|
||||
}>;
|
||||
@@ -179,6 +182,8 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getMsgsBySeqList(peer: Peer, seqList: string[]): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getMsgsExt(msgReq: MsgReqType): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSingleMsg(Peer: Peer, msgSeq: string): Promise<GeneralCallResult & { msgList: RawMessage[] }>;
|
||||
|
||||
getSourceOfReplyMsg(peer: Peer, MsgId: string, SourceSeq: string): unknown;
|
||||
@@ -318,7 +323,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getFileThumbSavePath(...args: unknown[]): unknown;
|
||||
|
||||
translatePtt2Text(msgId: string, peer: Peer, msgElement: unknown): unknown;
|
||||
translatePtt2Text(msgId: string, peer: Peer, msgElement: MessageElement): unknown;
|
||||
|
||||
setPttPlayedState(...args: unknown[]): unknown;
|
||||
|
||||
@@ -391,7 +396,12 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
getEmojiResourcePath(...args: unknown[]): unknown;
|
||||
|
||||
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: any/*joinDragonGroupEmojiReq*/): unknown;
|
||||
JoinDragonGroupEmoji(JoinDragonGroupEmojiReq: {
|
||||
latestMsgSeq: string,
|
||||
manageEmojiId: number,
|
||||
manageMsgSeq: string,
|
||||
peerContact: Peer
|
||||
}): Promise<unknown>;
|
||||
|
||||
getMsgAbstracts(...args: unknown[]): unknown;
|
||||
|
||||
@@ -518,7 +528,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
canImportOldDbMsg(...args: unknown[]): unknown;
|
||||
|
||||
setPowerStatus(z: boolean): unknown;
|
||||
setPowerStatus(isPowerOn: boolean): unknown;
|
||||
|
||||
canProcessDataMigration(...args: unknown[]): unknown;
|
||||
|
||||
@@ -607,7 +617,7 @@ export interface NodeIKernelMsgService {
|
||||
setIKernelPublicAccountAdapter(...args: unknown[]): unknown;
|
||||
|
||||
//tempChatGameSession有关
|
||||
createUidFromTinyId(fromTinyId: string, toTinyId: string): unknown;
|
||||
createUidFromTinyId(fromTinyId: string, toTinyId: string): string;
|
||||
|
||||
dataMigrationGetDataAvaiableContactList(...args: unknown[]): unknown;
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { ChatType } from '../entities';
|
||||
import { GeneralCallResult } from './common';
|
||||
|
||||
export interface NodeIKernelSearchService {
|
||||
|
||||
@@ -8,7 +9,12 @@ export interface NodeIKernelSearchService {
|
||||
|
||||
searchStranger(unknown: string, searchStranger: unknown, searchParams: unknown): Promise<unknown>;
|
||||
|
||||
searchGroup(...args: any[]): unknown;// needs 1 arguments
|
||||
searchGroup(param: {
|
||||
keyWords: string,
|
||||
groupNum: number,
|
||||
exactSearch: boolean,
|
||||
penetrate: string
|
||||
}): Promise<GeneralCallResult>;// needs 1 arguments
|
||||
|
||||
searchLocalInfo(keywords: string, unknown: number/*4*/): unknown;
|
||||
|
||||
|
11
src/core/services/NodeIO3MiscService.ts
Normal file
11
src/core/services/NodeIO3MiscService.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { NodeIO3MiscListener } from "../listeners/NodeIO3MiscListener";
|
||||
|
||||
export interface NodeIO3MiscService {
|
||||
get(): NodeIO3MiscService;
|
||||
|
||||
addO3MiscListener(listeners: NodeIO3MiscListener): number;
|
||||
|
||||
setAmgomDataPiece(appid: string, dataPiece: Uint8Array): void;
|
||||
|
||||
reportAmgomWeather(type: string, uk2: string, arg: Array<string>): void;
|
||||
}
|
@@ -26,6 +26,7 @@ import { NodeIKernelRecentContactService } from './services/NodeIKernelRecentCon
|
||||
import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
|
||||
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
|
||||
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
||||
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
|
||||
|
||||
export interface NodeQQNTWrapperUtil {
|
||||
get(): unknown;
|
||||
@@ -143,8 +144,7 @@ export interface NodeQQNTWrapperUtil {
|
||||
}
|
||||
|
||||
export interface NodeIQQNTWrapperSession {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(): NodeIQQNTWrapperSession;
|
||||
create(): NodeIQQNTWrapperSession;
|
||||
|
||||
init(
|
||||
wrapperSessionInitConfig: WrapperSessionInitConfig,
|
||||
@@ -250,11 +250,11 @@ export interface NodeIQQNTWrapperSession {
|
||||
|
||||
export interface EnginInitDesktopConfig {
|
||||
base_path_prefix: string;
|
||||
platform_type: 3;
|
||||
platform_type: PlatformType;
|
||||
app_type: 4;
|
||||
app_version: string;
|
||||
os_version: string;
|
||||
use_xlog: true;
|
||||
use_xlog: boolean;
|
||||
qua: string;
|
||||
global_path_config: {
|
||||
desktopGlobalPath: string;
|
||||
@@ -263,28 +263,26 @@ export interface EnginInitDesktopConfig {
|
||||
}
|
||||
|
||||
export interface NodeIQQNTWrapperEngine {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-new
|
||||
new(): NodeIQQNTWrapperEngine;
|
||||
get(): NodeIQQNTWrapperEngine;
|
||||
|
||||
initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void;
|
||||
}
|
||||
|
||||
export interface WrapperNodeApi {
|
||||
[key: string]: any;
|
||||
|
||||
NodeIO3MiscService: NodeIO3MiscService;
|
||||
NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;
|
||||
NodeIQQNTWrapperSession: NodeIQQNTWrapperSession;
|
||||
NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine;
|
||||
NodeIKernelLoginService: NodeIKernelLoginService;
|
||||
NodeIKernelProfileService: NodeIKernelProfileService;
|
||||
}
|
||||
|
||||
}
|
||||
export enum PlatformType {
|
||||
KUNKNOWN,
|
||||
KANDROID,
|
||||
KIOS,
|
||||
KWINDOWS,
|
||||
KMAC,
|
||||
KLINUX
|
||||
}
|
||||
|
||||
export enum DeviceType {
|
||||
|
11
src/onebot/action/extends/FetchUserProfileLike.ts
Normal file
11
src/onebot/action/extends/FetchUserProfileLike.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
|
||||
export class FetchUserProfileLike extends BaseAction<{ qq: number }, any> {
|
||||
actionName = ActionName.FetchUserProfileLike;
|
||||
|
||||
async _handle(payload: { qq: number }) {
|
||||
if (!payload.qq) throw new Error('qq is required');
|
||||
return await this.core.apis.UserApi.getUidByUinV2(payload.qq.toString());
|
||||
}
|
||||
}
|
@@ -7,8 +7,9 @@ const SchemaData = {
|
||||
properties: {
|
||||
group_id: { type: ['string', 'number'] },
|
||||
folder_id: { type: 'string' },
|
||||
folder: { type: 'string' }
|
||||
},
|
||||
required: ['group_id', 'folder_id'],
|
||||
required: ['group_id'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
@@ -18,6 +19,6 @@ export class DeleteGroupFileFolder extends BaseAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
return (await this.core.apis.GroupApi.DelGroupFileFolder(
|
||||
payload.group_id.toString(), payload.folder_id)).groupFileCommonResult;
|
||||
payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult;
|
||||
}
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
|
||||
headers[headerItem] = '';
|
||||
} else {
|
||||
const key = headerItem.substring(0, spilt);
|
||||
headers[key] = headerItem.substring(0, spilt + 1);
|
||||
headers[key] = headerItem.substring(spilt + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,9 +8,10 @@ const SchemaData = {
|
||||
properties: {
|
||||
group_id: { type: ['string', 'number'] },
|
||||
folder_id: { type: 'string' },
|
||||
folder: { type: 'string' },
|
||||
file_count: { type: ['string', 'number'] },
|
||||
},
|
||||
required: ['group_id', 'folder_id'],
|
||||
required: ['group_id'],
|
||||
} as const satisfies JSONSchema;
|
||||
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
@@ -26,7 +27,7 @@ export class GetGroupFilesByFolder extends BaseAction<any, any> {
|
||||
startIndex: 0,
|
||||
sortOrder: 2,
|
||||
showOnlinedocFolder: 0,
|
||||
folderId: payload.folder_id,
|
||||
folderId: payload.folder ?? payload.folder_id ?? '',
|
||||
}).catch(() => []);
|
||||
return {
|
||||
files: ret.filter(item => item.fileInfo)
|
||||
|
@@ -21,24 +21,25 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
|
||||
async _handle(payload: Payload): Promise<OB11User> {
|
||||
const user_id = payload.user_id.toString();
|
||||
const extendData = await this.core.apis.UserApi.getUserDetailInfoByUin(user_id);
|
||||
const uid = (await this.core.apis.UserApi.getUidByUinV2(user_id))!;
|
||||
if (!uid || uid.indexOf('*') != -1) {
|
||||
let uid = (await this.core.apis.UserApi.getUidByUinV2(user_id));
|
||||
if (!uid) uid = extendData.detail.uid;
|
||||
const info = (await this.core.apis.UserApi.getUserDetailInfo(uid));
|
||||
return {
|
||||
...extendData.detail.simpleInfo.coreInfo,
|
||||
...extendData.detail.commonExt,
|
||||
...extendData.detail.simpleInfo.baseInfo,
|
||||
...extendData.detail.simpleInfo.relationFlags,
|
||||
...extendData.detail.simpleInfo.status,
|
||||
user_id: parseInt(extendData.detail.uin) || 0,
|
||||
user_id: parseInt(extendData.detail.uin) ?? 0,
|
||||
uid: info.uid ?? uid,
|
||||
nickname: extendData.detail.simpleInfo.coreInfo.nick,
|
||||
sex: OB11UserSex.unknown,
|
||||
age: extendData.detail.simpleInfo.baseInfo.age || 0,
|
||||
age: extendData.detail.simpleInfo.baseInfo.age ?? info.age,
|
||||
qid: extendData.detail.simpleInfo.baseInfo.qid,
|
||||
level: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? 0) || 0,
|
||||
login_days: 0,
|
||||
qqLevel: calcQQLevel(extendData.detail.commonExt?.qqLevel ?? info.qqLevel),
|
||||
sex: OB11Entities.sex(extendData.detail.simpleInfo.baseInfo.sex) ?? OB11UserSex.unknown,
|
||||
long_nick: extendData.detail.simpleInfo.baseInfo.longNick ?? info.longNick,
|
||||
reg_time: extendData.detail.commonExt.regTime ?? info.regTime,
|
||||
is_vip: extendData.detail.simpleInfo.vasInfo?.svipFlag,
|
||||
is_years_vip: extendData.detail.simpleInfo.vasInfo?.yearVipFlag,
|
||||
vip_level: extendData.detail.simpleInfo.vasInfo?.vipLevel,
|
||||
remark: extendData.detail.simpleInfo.coreInfo.remark ?? info.remark,
|
||||
status: extendData.detail.simpleInfo.status?.status ?? info.status,
|
||||
login_days: 0,//失效
|
||||
};
|
||||
}
|
||||
const data = { ...extendData, ...(await this.core.apis.UserApi.getUserDetailInfo(uid)) };
|
||||
return OB11Entities.stranger(data);
|
||||
}
|
||||
}
|
||||
|
@@ -41,7 +41,7 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
|
||||
peer: peer,
|
||||
deleteAfterSentFiles: []
|
||||
};
|
||||
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder_id);
|
||||
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id);
|
||||
await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], [], true);
|
||||
return null;
|
||||
}
|
||||
|
@@ -20,7 +20,17 @@ class GetGroupInfo extends BaseAction<Payload, OB11Group> {
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const group = (await this.core.apis.GroupApi.getGroups()).find(e => e.groupCode == payload.group_id.toString());
|
||||
if (!group) throw `群${payload.group_id}不存在`;
|
||||
if (!group) {
|
||||
const data = await this.core.apis.GroupApi.searchGroup(payload.group_id.toString());
|
||||
if (!data) throw new Error('Group not found');
|
||||
return {
|
||||
...data.searchGroupInfo,
|
||||
group_id: +payload.group_id,
|
||||
group_name: data.searchGroupInfo.groupName,
|
||||
member_count: data.searchGroupInfo.memberNum,
|
||||
max_member_count: data.searchGroupInfo.maxMemberNum,
|
||||
};
|
||||
}
|
||||
return OB11Entities.group(group);
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { OB11Entities } from '@/onebot/entities';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { calcQQLevel } from '@/common/helper';
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -22,7 +23,8 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
||||
async _handle(payload: Payload) {
|
||||
const groupMembers = await this.core.apis.GroupApi.getGroupMembersV2(payload.group_id.toString());
|
||||
const groupMembersArr = Array.from(groupMembers.values());
|
||||
|
||||
const uids = groupMembersArr.map(item => item.uid);
|
||||
//let CoreAndBase = await this.core.apis.GroupApi.getCoreAndBaseInfo(uids)
|
||||
let _groupMembers = groupMembersArr.map(item => {
|
||||
return OB11Entities.groupMember(payload.group_id.toString(), item);
|
||||
});
|
||||
@@ -32,20 +34,19 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
||||
|
||||
for (let i = 0, len = _groupMembers.length; i < len; i++) {
|
||||
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
|
||||
_groupMembers[i].join_time = date;
|
||||
_groupMembers[i].last_sent_time = date;
|
||||
const Member = await this.core.apis.GroupApi.getGroupMember(payload.group_id.toString(), _groupMembers[i].user_id);
|
||||
_groupMembers[i].join_time = +(Member?.joinTime ?? date);
|
||||
_groupMembers[i].last_sent_time = +(Member?.lastSpeakTime ?? date);
|
||||
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]);
|
||||
}
|
||||
|
||||
|
||||
const selfRole = groupMembers.get(this.core.selfInfo.uid)?.role;
|
||||
const isPrivilege = selfRole === 3 || selfRole === 4;
|
||||
|
||||
_groupMembers.forEach(item => {
|
||||
item.last_sent_time = date;
|
||||
item.join_time = date;
|
||||
});
|
||||
|
||||
if (isPrivilege) {
|
||||
try {
|
||||
const webGroupMembers = await this.core.apis.WebApi.getGroupMembers(payload.group_id.toString());
|
||||
for (let i = 0, len = webGroupMembers.length; i < len; i++) {
|
||||
if (!webGroupMembers[i]?.uin) {
|
||||
@@ -60,6 +61,11 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
|
||||
MemberMap.set(webGroupMembers[i]?.uin, MemberData);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const logger = this.core.context.logger;
|
||||
logger.logError.bind(logger)('GetGroupMemberList', e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_groupMembers = Array.from(MemberMap.values());
|
||||
|
@@ -68,8 +68,8 @@ import SetGroupPortrait from './go-cqhttp/SetGroupPortrait';
|
||||
import { FetchCustomFace } from './extends/FetchCustomFace';
|
||||
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivateFile';
|
||||
import { FetchEmojiLike } from './extends/FetchEmojiLike';
|
||||
import { FetchUserProfileLike } from './extends/FetchUserProfileLike';
|
||||
import { NapCatCore } from '@/core';
|
||||
|
||||
import { NapCatOneBot11Adapter } from '@/onebot';
|
||||
import GetGuildProfile from './guild/GetGuildProfile';
|
||||
import SetModelShow from './go-cqhttp/SetModelShow';
|
||||
@@ -85,6 +85,7 @@ import { GetGroupRootFiles } from '@/onebot/action/go-cqhttp/GetGroupRootFiles';
|
||||
import { GetGroupFilesByFolder } from '@/onebot/action/go-cqhttp/GetGroupFilesByFolder';
|
||||
import { GetGroupSystemMsg } from './system/GetSystemMsg';
|
||||
|
||||
|
||||
export type ActionMap = Map<string, BaseAction<any, any>>;
|
||||
|
||||
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore): ActionMap {
|
||||
@@ -178,6 +179,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
|
||||
new GetGroupFileSystemInfo(obContext, core),
|
||||
new GetGroupFilesByFolder(obContext, core),
|
||||
new GetGroupSystemMsg(obContext, core),
|
||||
new FetchUserProfileLike(obContext, core),
|
||||
];
|
||||
const actionMap = new Map();
|
||||
for (const action of actionHandlers) {
|
||||
|
@@ -16,8 +16,8 @@ export interface InvalidCheckResult {
|
||||
export enum ActionName {
|
||||
// 以下为扩展napcat扩展
|
||||
Unknown = 'unknown',
|
||||
SharePeer = 'ArkShareGroup',
|
||||
ShareGroupEx = 'ArkSharePeer',
|
||||
SharePeer = 'ArkSharePeer',
|
||||
ShareGroupEx = 'ArkShareGroup',
|
||||
RebootNormal = 'reboot_normal',//无快速登录重新启动
|
||||
GetRobotUinRange = 'get_robot_uin_range',
|
||||
SetOnlineStatus = 'set_online_status',
|
||||
@@ -118,4 +118,5 @@ export enum ActionName {
|
||||
DelGroupNotice = '_del_group_notice',
|
||||
GetGroupInfoEx = "get_group_info_ex",
|
||||
GetGroupSystemMsg = 'get_group_system_msg',
|
||||
FetchUserProfileLike = "fetch_user_profile_like",
|
||||
}
|
||||
|
@@ -466,7 +466,6 @@ export class OneBotMsgApi {
|
||||
sendMsg.data.summary,
|
||||
sendMsg.data.sub_type,
|
||||
);
|
||||
context.deleteAfterSentFiles.push(sendPicElement.picElement.sourcePath);
|
||||
return sendPicElement;
|
||||
},
|
||||
|
||||
@@ -575,9 +574,10 @@ export class OneBotMsgApi {
|
||||
} else {
|
||||
postData = data;
|
||||
}
|
||||
const signUrl = this.obContext.configLoader.configData.musicSignUrl;
|
||||
let signUrl = this.obContext.configLoader.configData.musicSignUrl;
|
||||
if (!signUrl) {
|
||||
throw Error('音乐消息签名地址未配置');
|
||||
signUrl = 'https://ss.xingzhige.com/music_card/card';//感谢思思!
|
||||
//throw Error('音乐消息签名地址未配置');
|
||||
}
|
||||
try {
|
||||
const musicJson = await RequestUtil.HttpGetJson<any>(signUrl, 'POST', postData);
|
||||
@@ -608,6 +608,14 @@ export class OneBotMsgApi {
|
||||
}),
|
||||
|
||||
[OB11MessageDataType.miniapp]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.contact]: async ({ data }, context) => {
|
||||
const arkJson = await this.core.apis.UserApi.getBuddyRecommendContactArkJson(data.id.toString(), '');
|
||||
return this.ob11ToRawConverters.json({
|
||||
data: { data: arkJson.arkMsg },
|
||||
type: OB11MessageDataType.json
|
||||
}, context);
|
||||
}
|
||||
};
|
||||
|
||||
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
@@ -630,7 +638,7 @@ export class OneBotMsgApi {
|
||||
if (element.grayTipElement.subElementType == NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
|
||||
//好友添加成功事件
|
||||
if (element.grayTipElement.xmlElement.templId === '10229' && msg.peerUin !== '') {
|
||||
return new OB11FriendAddNoticeEvent(this.core, parseInt(msg.peerUin));
|
||||
return new OB11FriendAddNoticeEvent(this.core, parseInt(msg.peerUin) || Number(await this.core.apis.UserApi.getUinByUidV2(msg.peerUid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@ export class OneBotQuickActionApi {
|
||||
const peerContextMode = msg.message_type == 'private' ? ContextMode.Private : ContextMode.Group;
|
||||
|
||||
const peer: Peer = await createContext(this.core, {
|
||||
message: "",
|
||||
message_type: undefined,
|
||||
group_id: msg.group_id?.toString(),
|
||||
user_id: msg.user_id?.toString(),
|
||||
}, peerContextMode);
|
||||
|
@@ -13,9 +13,10 @@ export class OneBotUserApi {
|
||||
this.core = core;
|
||||
}
|
||||
async parseLikeEvent(wrappedBody: Uint8Array): Promise<OB11ProfileLikeEvent | undefined> {
|
||||
const likeTip = profileLikeTip.decode(Uint8Array.from(wrappedBody.slice(12))) as unknown as ProfileLikeTipType;
|
||||
const likeTip = profileLikeTip.decode(Uint8Array.from(wrappedBody)) as unknown as ProfileLikeTipType;
|
||||
if (likeTip?.msgType !== 0 || likeTip?.subType !== 203) return;
|
||||
this.core.context.logger.logDebug("收到点赞通知消息");
|
||||
const likeMsg = likeTip.msg;
|
||||
const likeMsg = likeTip.content.msg;
|
||||
if (!likeMsg) return;
|
||||
const detail = likeMsg.detail;
|
||||
if (!detail) return;
|
||||
|
@@ -48,7 +48,8 @@ export class OB11Entities {
|
||||
}[role];
|
||||
}
|
||||
|
||||
static sex(sex: Sex): OB11UserSex {
|
||||
static sex(sex?: Sex): OB11UserSex {
|
||||
if (!sex) return OB11UserSex.unknown;
|
||||
return {
|
||||
[Sex.male]: OB11UserSex.male,
|
||||
[Sex.female]: OB11UserSex.female,
|
||||
@@ -126,6 +127,7 @@ export class OB11Entities {
|
||||
return {
|
||||
group_id: parseInt(peerId),
|
||||
folder_id: folder.folderId,
|
||||
folder: folder.folderId,
|
||||
folder_name: folder.folderName,
|
||||
create_time: folder.createTime,
|
||||
creator: parseInt(folder.createUin),
|
||||
|
@@ -48,8 +48,8 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
||||
//鉴权
|
||||
this.authorize(token, wsClient, wsReq);
|
||||
const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url;
|
||||
const isEventConnect = paramUrl === '/event' || paramUrl === '' || paramUrl === '/';
|
||||
if (isEventConnect) {
|
||||
const isApiConnect = paramUrl === '/api' || paramUrl === '/api/';
|
||||
if (!isApiConnect) {
|
||||
this.connectEvent(core, wsClient);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
|
||||
});
|
||||
});
|
||||
await this.wsClientsMutex.runExclusive(async () => {
|
||||
if(isEventConnect){
|
||||
if (!isApiConnect) {
|
||||
this.wsClientWithEvent.push(wsClient);
|
||||
}
|
||||
this.wsClients.push(wsClient);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export interface OB11User {
|
||||
[key: string]: any;
|
||||
user_id: number;
|
||||
nickname: string;
|
||||
remark?: string;
|
||||
@@ -81,6 +82,7 @@ export interface OB11GroupFile {
|
||||
export interface OB11GroupFileFolder {
|
||||
group_id: number,
|
||||
folder_id: string,
|
||||
folder: string,
|
||||
folder_name: string,
|
||||
create_time: number,
|
||||
creator: number,
|
||||
|
@@ -62,6 +62,7 @@ export enum OB11MessageDataType {
|
||||
dice = 'dice',
|
||||
RPS = 'rps',
|
||||
miniapp = 'miniapp',//json类
|
||||
contact = 'contact',
|
||||
Location = 'location'
|
||||
}
|
||||
|
||||
@@ -81,10 +82,15 @@ export interface OB11MessageText {
|
||||
text: string, // 纯文本
|
||||
}
|
||||
}
|
||||
|
||||
export interface OB11MessageContext {
|
||||
type: OB11MessageDataType.contact,
|
||||
data: {
|
||||
id: string,
|
||||
}
|
||||
}
|
||||
export interface OB11MessageFileBase {
|
||||
data: {
|
||||
file_unique?:string,
|
||||
file_unique?: string,
|
||||
path?: string;
|
||||
thumb?: string;
|
||||
name?: string;
|
||||
@@ -198,7 +204,7 @@ export type OB11MessageData =
|
||||
OB11MessageAt | OB11MessageReply |
|
||||
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
|
||||
OB11MessageNode | OB11MessageIdMusic | OB11MessageCustomMusic | OB11MessageJson |
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward | OB11MessageContext
|
||||
|
||||
export interface OB11PostSendMsg {
|
||||
message_type?: 'private' | 'group'
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
NapCatCore,
|
||||
NapCatCoreWorkingEnv,
|
||||
NodeIQQNTWrapperSession,
|
||||
PlatformType,
|
||||
WrapperNodeApi,
|
||||
} from '@/core';
|
||||
import { QQBasicInfoWrapper } from '@/common/qq-basic-info';
|
||||
@@ -27,6 +28,7 @@ import { NapCatOneBot11Adapter } from '@/onebot';
|
||||
import { InitWebUi } from '@/webui';
|
||||
import { WebUiDataRuntime } from '@/webui/src/helper/Data';
|
||||
import { napCatVersion } from '@/common/version';
|
||||
import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener';
|
||||
|
||||
program.option('-q, --qq [number]', 'QQ号').parse(process.argv);
|
||||
const cmdOptions = program.opts();
|
||||
@@ -39,14 +41,19 @@ export async function NCoreInitShell() {
|
||||
const logger = new LogWrapper(pathWrapper.logsPath);
|
||||
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
||||
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
|
||||
|
||||
const o3Service = wrapper.NodeIO3MiscService.get();
|
||||
o3Service.addO3MiscListener(new NodeIO3MiscListener());
|
||||
|
||||
logger.log(`[NapCat] [Core] NapCat.Core Version: ` + napCatVersion);
|
||||
InitWebUi(logger, pathWrapper).then().catch(logger.logError);
|
||||
|
||||
// from constructor
|
||||
const engine = new wrapper.NodeIQQNTWrapperEngine();
|
||||
const engine = wrapper.NodeIQQNTWrapperEngine.get();
|
||||
//const util = wrapper.NodeQQNTWrapperUtil.get();
|
||||
const loginService = new wrapper.NodeIKernelLoginService();
|
||||
const session = new wrapper.NodeIQQNTWrapperSession();
|
||||
const loginService = wrapper.NodeIKernelLoginService.get();
|
||||
|
||||
const session = wrapper.NodeIQQNTWrapperSession.create();
|
||||
|
||||
// from get dataPath
|
||||
const [dataPath, dataPathGlobal] = (() => {
|
||||
@@ -63,17 +70,29 @@ export async function NCoreInitShell() {
|
||||
const dataPathGlobal = path.resolve(dataPath, './nt_qq/global');
|
||||
return [dataPath, dataPathGlobal];
|
||||
})();
|
||||
|
||||
let systemPlatform = PlatformType.KWINDOWS;
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
systemPlatform = PlatformType.KWINDOWS;
|
||||
break;
|
||||
case 'darwin':
|
||||
systemPlatform = PlatformType.KMAC;
|
||||
break;
|
||||
case 'linux':
|
||||
systemPlatform = PlatformType.KLINUX;
|
||||
break;
|
||||
}
|
||||
if (!basicInfoWrapper.QQVersionAppid || !basicInfoWrapper.QQVersionQua) throw new Error('QQVersionAppid or QQVersionQua is not defined');
|
||||
// from initConfig
|
||||
engine.initWithDeskTopConfig(
|
||||
{
|
||||
base_path_prefix: '',
|
||||
platform_type: 3,
|
||||
platform_type: systemPlatform,
|
||||
app_type: 4,
|
||||
app_version: basicInfoWrapper.getFullQQVesion(),
|
||||
os_version: 'Windows 10 Pro',
|
||||
use_xlog: true,
|
||||
qua: basicInfoWrapper.QQVersionQua!,
|
||||
os_version: systemVersion,
|
||||
use_xlog: false,
|
||||
qua: basicInfoWrapper.QQVersionQua,
|
||||
global_path_config: {
|
||||
desktopGlobalPath: dataPathGlobal,
|
||||
},
|
||||
@@ -83,7 +102,7 @@ export async function NCoreInitShell() {
|
||||
);
|
||||
loginService.initConfig({
|
||||
machineId: '',
|
||||
appid: basicInfoWrapper.QQVersionAppid!,
|
||||
appid: basicInfoWrapper.QQVersionAppid,
|
||||
platVer: systemVersion,
|
||||
commonPath: dataPathGlobal,
|
||||
clientVer: basicInfoWrapper.getFullQQVesion(),
|
||||
@@ -100,21 +119,25 @@ export async function NCoreInitShell() {
|
||||
quickLoginUin = '';
|
||||
}
|
||||
}
|
||||
|
||||
let dataTimestape = new Date().getTime().toString();
|
||||
o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']);
|
||||
const selfInfo = await new Promise<SelfInfo>((resolve) => {
|
||||
const loginListener = new NodeIKernelLoginListener();
|
||||
|
||||
let isLogined = false;
|
||||
// from constructor
|
||||
loginListener.onUserLoggedIn = (userid: string) => {
|
||||
logger.logError(`当前账号(${userid})已登录,无法重复登录`);
|
||||
};
|
||||
|
||||
loginListener.onQRCodeLoginSucceed = async (loginResult) => resolve({
|
||||
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
|
||||
isLogined = true;
|
||||
resolve({
|
||||
uid: loginResult.uid,
|
||||
uin: loginResult.uin,
|
||||
nick: '', // 获取不到
|
||||
online: true,
|
||||
});
|
||||
};
|
||||
|
||||
loginListener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => {
|
||||
//设置WebuiQrcode
|
||||
@@ -138,11 +161,13 @@ export async function NCoreInitShell() {
|
||||
};
|
||||
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number, errMsg: string) => {
|
||||
//logger.logError('登录失败(onQRCodeSessionFailed)', errCode, errMsg);
|
||||
if (!isLogined) {
|
||||
logger.logError('[Core] [Login] Login Error,ErrCode: ', errCode, ' ErrMsg:', errMsg);
|
||||
if (errType == 1 && errCode == 3) {
|
||||
// 二维码过期刷新
|
||||
}
|
||||
loginService.getQRCodePicture();
|
||||
}
|
||||
};
|
||||
loginListener.onLoginFailed = (args) => {
|
||||
//logger.logError('登录失败(onLoginFailed)', args);
|
||||
@@ -189,14 +214,14 @@ export async function NCoreInitShell() {
|
||||
.then(result => {
|
||||
if (result.loginErrorInfo.errMsg) {
|
||||
logger.logError('快速登录错误:', result.loginErrorInfo.errMsg);
|
||||
loginService.getQRCodePicture();
|
||||
if (!isLogined) loginService.getQRCodePicture();
|
||||
}
|
||||
})
|
||||
.catch();
|
||||
}, 1000);
|
||||
} else {
|
||||
logger.logError('快速登录失败,未找到该 QQ 历史登录记录,将使用二维码登录方式');
|
||||
loginService.getQRCodePicture();
|
||||
if (!isLogined) loginService.getQRCodePicture();
|
||||
}
|
||||
} else {
|
||||
logger.log('没有 -q 指令指定快速登录,将使用二维码登录方式');
|
||||
@@ -210,12 +235,19 @@ export async function NCoreInitShell() {
|
||||
}
|
||||
});
|
||||
// BEFORE LOGGING IN
|
||||
|
||||
let amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
|
||||
o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex')));
|
||||
// AFTER LOGGING IN
|
||||
//99b15bdb4c984fc69d5aa1feb9aa16xx --> 99b15bdb-4c98-4fc6-9d5a-a1feb9aa16xx
|
||||
//把guid从左向右转换为guid格式 loginService.getMachineGuid()
|
||||
|
||||
let guid = loginService.getMachineGuid();
|
||||
guid = guid.slice(0, 8) + '-' + guid.slice(8, 12) + '-' + guid.slice(12, 16) + '-' + guid.slice(16, 20) + '-' + guid.slice(20);
|
||||
//console.log('guid:', guid);
|
||||
// from initSession
|
||||
await new Promise<void>(async (resolve, reject) => {
|
||||
const sessionConfig = await genSessionConfig(
|
||||
guid,
|
||||
basicInfoWrapper.QQVersionAppid!,
|
||||
basicInfoWrapper.getFullQQVesion(),
|
||||
selfInfo.uin,
|
||||
|
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
undefined,
|
||||
SettingButton('V2.4.7', 'napcat-update-button', 'secondary'),
|
||||
SettingButton('V2.6.8', 'napcat-update-button', 'secondary'),
|
||||
),
|
||||
]),
|
||||
SettingList([
|
||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
void 0,
|
||||
SettingButton("V2.4.7", "napcat-update-button", "secondary")
|
||||
SettingButton("V2.6.8", "napcat-update-button", "secondary")
|
||||
)
|
||||
]),
|
||||
SettingList([
|
||||
|
@@ -1,10 +1,8 @@
|
||||
// import PreprocessorDirectives from 'unplugin-preprocessor-directives/vite';
|
||||
import cp from 'vite-plugin-cp';
|
||||
import { defineConfig, PluginOption, UserConfig } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||
import { builtinModules } from 'module';
|
||||
import babel from 'vite-plugin-babel';
|
||||
//依赖排除
|
||||
const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'log4js', 'qrcode-terminal'];
|
||||
const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat();
|
||||
@@ -15,31 +13,15 @@ let startScripts: string[] | undefined = undefined;
|
||||
if (process.env.NAPCAT_BUILDSYS == 'linux') {
|
||||
if (process.env.NAPCAT_BUILDARCH == 'x64') {
|
||||
}
|
||||
startScripts = ['./script/napcat.sh'];
|
||||
startScripts = [];
|
||||
} else if (process.env.NAPCAT_BUILDSYS == 'win32') {
|
||||
if (process.env.NAPCAT_BUILDARCH == 'x64') {
|
||||
}
|
||||
startScripts = ['./script/BootWay05.ps1', './script/dbghelp.dll',
|
||||
'./script/BootWay05_init.bat', './script/BootWay05_run.bat', './script/BootWay05_run.utf8.bat', './script/KillQQ.bat'];
|
||||
startScripts = ['./script/KillQQ.bat'];
|
||||
} else {
|
||||
startScripts = ['./script/BootWay05.ps1', './script/dbghelp.dll',
|
||||
'./script/BootWay05_init.bat', './script/BootWay05_run.bat', './script/BootWay05_run.utf8.bat', './script/KillQQ.bat'];
|
||||
startScripts = ['./script/KillQQ.bat'];
|
||||
}
|
||||
const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
// PreprocessorDirectives(),
|
||||
babel({
|
||||
filter: /.*\.(ts|js)$/,
|
||||
babelConfig: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
presets: ['@babel/preset-typescript'],
|
||||
plugins: [
|
||||
//'2018-09', decoratorsBeforeExport: true
|
||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
],
|
||||
},
|
||||
}),
|
||||
cp({
|
||||
targets: [
|
||||
{ src: './manifest.json', dest: 'dist' },
|
||||
@@ -52,38 +34,18 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
{ src: './src/framework/renderer.js', dest: 'dist' },
|
||||
{ src: './package.json', dest: 'dist' },
|
||||
{ src: './logo.png', dest: 'dist' },
|
||||
//...external.map(genCpModule)
|
||||
],
|
||||
}),
|
||||
nodeResolve(),
|
||||
];
|
||||
const ShellBaseConfigPlugin: PluginOption[] = [
|
||||
// PreprocessorDirectives(),
|
||||
babel({
|
||||
filter: /.*\.(ts|js)$/,
|
||||
babelConfig: {
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
presets: ['@babel/preset-typescript'],
|
||||
plugins: [
|
||||
//'2018-09', decoratorsBeforeExport: true
|
||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
],
|
||||
},
|
||||
}),
|
||||
cp({
|
||||
targets: [
|
||||
// ...external.map(genCpModule),
|
||||
// { src: './src/napcat.json', dest: 'dist/config/' },
|
||||
{ src: './static/', dest: 'dist/static/', flatten: false },
|
||||
// { src: './src/onebot11/onebot11.json', dest: 'dist/config/' },
|
||||
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||
{ src: './package.json', dest: 'dist' },
|
||||
{ src: './launcher/', dest: 'dist', flatten: true },
|
||||
// { src: './README.md', dest: 'dist' },
|
||||
// { src: './logo.png', dest: 'dist/logs' },
|
||||
...(startScripts.map((startScript) => {
|
||||
return { src: startScript, dest: 'dist' };
|
||||
})),
|
||||
|
Reference in New Issue
Block a user