feat: 增加windows发送能力

抽离功能到设置页面
抽离文字转语音功能
This commit is contained in:
电摇小子
2026-08-31 02:44:24 +08:00
parent 698251796f
commit 5acfa14475
33 changed files with 2226 additions and 905 deletions
@@ -31,12 +31,25 @@ const status = (
describe('PersonalWechatCapabilityService', () => {
it.each([
['unsupported', status({ platform: 'win32' })],
['unsupported', status({ platform: 'linux' })],
['unconfigured', status({ runtimeReady: false, boundWechatPid: undefined })],
['needs_binding', status({ runtimeReady: true, boundWechatPid: undefined })],
['needs_verification', status({ boundWechatPid: 123 })],
['ready', status({ state: 'online', boundWechatPid: 123, canSendImage: true, canSend: true })],
['error', status({ state: 'error', boundWechatPid: 123, error: 'hook failed' })]
['error', status({ state: 'error', boundWechatPid: 123, error: 'hook failed' })],
['needs_verification', status({ platform: 'win32', endpoint: '127.0.0.1:4567' })],
[
'ready',
status({
platform: 'win32',
state: 'online',
endpoint: '127.0.0.1:4567',
canSend: true,
canSendText: true,
canSendImage: true,
canSendVoice: true
})
]
])('maps %s', (expected, senderStatus) => {
const service = new PersonalWechatCapabilityService({ getStatus: async () => senderStatus })
return expect(service.getPersonalWechatSendCapability()).resolves.toMatchObject({
@@ -13,9 +13,13 @@ vi.mock('electron', () => ({
import {
buildPersonalWechatVoiceDiagnostic,
buildWindowsWechatRequest,
buildPersonalWechatOneBotRequest,
findWechatImagePath,
findPersonalWechatRuntime,
normalizeWindowsWechatPort,
parseWindowsLoginStatus,
parseWindowsHookResponse,
parsePersonalWechatHookLog
} from '../../src/main/services/personal-wechat-send-service'
@@ -130,6 +134,91 @@ describe('personal WeChat OneBot request', () => {
})
})
describe('Windows request', () => {
it('accepts only a valid configured local port', () => {
expect(normalizeWindowsWechatPort('')).toBeNull()
expect(normalizeWindowsWechatPort(' 4567 ')).toBe('4567')
expect(normalizeWindowsWechatPort('0')).toBeNull()
expect(normalizeWindowsWechatPort('65536')).toBeNull()
expect(normalizeWindowsWechatPort('port')).toBeNull()
})
it('requires ret=0 for message responses but accepts the status shape', () => {
expect(parseWindowsHookResponse('{"ret":0,"retmsg":"success"}', true)).toEqual({
ret: 0,
retmsg: 'success'
})
expect(parseWindowsHookResponse('{"IsLogin":0,"hWeixin":123}', false)).toEqual({
IsLogin: 0,
hWeixin: 123
})
expect(() => parseWindowsHookResponse('{"ret":1,"retmsg":"fail"}', true)).toThrow('fail')
expect(() => parseWindowsHookResponse('{"retmsg":"success"}', true)).toThrow('success')
expect(() => parseWindowsHookResponse('{}', true)).toThrow('ret=undefined')
expect(() => parseWindowsHookResponse('', true)).toThrow('空响应')
})
it('allows Windows sending only for a strict logged-in status', () => {
expect(parseWindowsLoginStatus({ status: true })).toBe(true)
expect(parseWindowsLoginStatus({ status: false })).toBe(false)
expect(parseWindowsLoginStatus({ status: 1 })).toBe(false)
expect(parseWindowsLoginStatus({ status: 'true' })).toBe(false)
expect(parseWindowsLoginStatus({ IsLogin: 1, hWeixin: 123 })).toBe(false)
})
it('builds the unified text request', () => {
expect(
buildWindowsWechatRequest({
to: ' wxid_fixture ',
type: 'text',
text: ' 测试发送 ',
isGroup: false
})
).toEqual({
endpoint: '/SendMsg',
body: { toWxid: 'wxid_fixture', type: 'text', msg: '测试发送' }
})
})
it('builds the unified image request with a local path', () => {
expect(
buildWindowsWechatRequest({
to: 'room@chatroom',
type: 'image',
filePath: 'C:\\fixture\\image.png',
isGroup: true
})
).toEqual({
endpoint: '/SendMsg',
body: { toWxid: 'room@chatroom', type: 'image', msg: 'C:\\fixture\\image.png' }
})
})
it('builds the unified voice request with sender wxid and duration', () => {
expect(
buildWindowsWechatRequest(
{
to: 'wxid_fixture',
type: 'voice',
filePath: '/source/input.wav',
fromId: ' wxid_self ',
isGroup: false
},
{ filePath: 'C:\\Temp\\prepared.silk', durationMs: 1234 }
)
).toEqual({
endpoint: '/SendMsg',
body: {
toWxid: 'wxid_fixture',
type: 'voice',
msg: 'C:\\Temp\\prepared.silk',
fromWxid: 'wxid_self',
duration: 1234
}
})
})
})
describe('personal WeChat runtime discovery', () => {
it('uses the newest current-month WeChat image directory', () => {
const root = temporaryDirectory()
+1
View File
@@ -8,6 +8,7 @@ describe('settings navigation', () => {
it('places text-to-speech under intelligent capabilities', () => {
const intelligent = SETTINGS_NAVIGATION.find((group) => group.label === '智能能力')
expect(intelligent?.items.map((item) => item.id)).toEqual([
'wechat-send',
'voice-recognition',
'text-to-speech',
'ai-model'