mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-06 05:27:48 +08:00
feat: 优化 macOS 微信发送消息能力
参考 wechat_chatter PR #36: https://github.com/yincongcyincong/wechat_chatter/pull/36 补充 WeChat 4.1.11.53 的 GetService / CdnManager 地址, 并适配绑定后的发送能力检测逻辑。
This commit is contained in:
@@ -19,10 +19,7 @@ import {
|
||||
Switch
|
||||
} from '../ui'
|
||||
import { isMac, isWindows } from '../../utils/runtime-environment'
|
||||
import {
|
||||
PersonalWechatChatComposer,
|
||||
type ChatMessage
|
||||
} from './PersonalWechatChatComposer'
|
||||
import { PersonalWechatChatComposer, type ChatMessage } from './PersonalWechatChatComposer'
|
||||
import { PersonalWechatSetupGuide } from './PersonalWechatSetupGuide'
|
||||
import { PersonalWechatVoiceDiagnosticDialog } from './PersonalWechatVoiceDiagnosticDialog'
|
||||
import { PersonalWechatWindowsSendDialog } from './PersonalWechatWindowsSendDialog'
|
||||
@@ -86,7 +83,6 @@ function PersonalWechatMacSendDialog({
|
||||
const [binding, setBinding] = useState(false)
|
||||
const [runtimeBusy, setRuntimeBusy] = useState(false)
|
||||
const [sendBusy, setSendBusy] = useState(false)
|
||||
const [detectionAttempted, setDetectionAttempted] = useState(false)
|
||||
// 状态可能来自之前的 OneBot 进程或日志;发送入口只信任当前语音能力状态。
|
||||
const [sessionBound, setSessionBound] = useState(false)
|
||||
const [messages, setMessages] = useState<ChatMessage[]>([])
|
||||
@@ -144,6 +140,21 @@ function PersonalWechatMacSendDialog({
|
||||
return unsubscribe
|
||||
}, [refreshStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (!senderStatus || senderStatus.canSend || senderStatus.state === 'error') return undefined
|
||||
const bound =
|
||||
senderStatus.state === 'online' ||
|
||||
Boolean(
|
||||
senderStatus.wechatPid &&
|
||||
senderStatus.boundWechatPid === senderStatus.wechatPid &&
|
||||
senderStatus.attachReady &&
|
||||
senderStatus.baseAddressReady
|
||||
)
|
||||
if (!bound) return undefined
|
||||
const timer = window.setInterval(() => void refreshStatus(), 1_000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [refreshStatus, senderStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMac) return undefined
|
||||
let active = true
|
||||
@@ -193,7 +204,6 @@ function PersonalWechatMacSendDialog({
|
||||
const handleBind = async (): Promise<void> => {
|
||||
if (binding) return
|
||||
setBinding(true)
|
||||
setDetectionAttempted(false)
|
||||
setSendError(null)
|
||||
try {
|
||||
const nextStatus = await window.api.rebindPersonalWechatSender()
|
||||
@@ -207,7 +217,7 @@ function PersonalWechatMacSendDialog({
|
||||
nextStatus.baseAddressReady
|
||||
)
|
||||
)
|
||||
if (nextStatus.state !== 'online' && nextStatus.message) setSendError(nextStatus.message)
|
||||
if (nextStatus.state === 'error' && nextStatus.message) setSendError(nextStatus.message)
|
||||
} catch (error) {
|
||||
setSendError(error instanceof Error ? error.message : String(error))
|
||||
} finally {
|
||||
@@ -215,11 +225,6 @@ function PersonalWechatMacSendDialog({
|
||||
}
|
||||
}
|
||||
|
||||
const handleDetect = async (): Promise<void> => {
|
||||
setDetectionAttempted(true)
|
||||
await refreshStatus()
|
||||
}
|
||||
|
||||
const handleClose = (): void => {
|
||||
if (isBusy || closingRef.current) return
|
||||
closingRef.current = true
|
||||
@@ -235,9 +240,7 @@ function PersonalWechatMacSendDialog({
|
||||
onOpenTextToSpeechSettings()
|
||||
}
|
||||
|
||||
const handleSend = async (
|
||||
filePath: string
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
const handleSend = async (filePath: string): Promise<{ success: boolean; error?: string }> => {
|
||||
setSendBusy(true)
|
||||
setSendError(null)
|
||||
try {
|
||||
@@ -364,8 +367,6 @@ function PersonalWechatMacSendDialog({
|
||||
sessionBound={sessionBound}
|
||||
onDownloadRuntime={() => void handleDownloadRuntime()}
|
||||
onBind={() => void handleBind()}
|
||||
detectionAttempted={detectionAttempted}
|
||||
onDetect={() => void handleDetect()}
|
||||
onStartSending={() => undefined}
|
||||
onOpenTextToSpeechSettings={handleOpenSettings}
|
||||
/>
|
||||
|
||||
@@ -15,18 +15,16 @@ interface PersonalWechatSetupGuideProps {
|
||||
runtimeBusy: boolean
|
||||
binding: boolean
|
||||
detecting: boolean
|
||||
detectionAttempted: boolean
|
||||
sessionBound: boolean
|
||||
onDownloadRuntime: () => void
|
||||
onBind: () => void
|
||||
onDetect: () => void
|
||||
onStartSending: () => void
|
||||
onOpenTextToSpeechSettings?: () => void
|
||||
}
|
||||
|
||||
function capabilityLabel(ready: boolean, detectionAttempted: boolean): string {
|
||||
function capabilityLabel(ready: boolean, initializing: boolean): string {
|
||||
if (ready) return '已就绪'
|
||||
return detectionAttempted ? '未检测' : '待检测'
|
||||
return initializing ? '初始化中' : '未就绪'
|
||||
}
|
||||
|
||||
function diagnosticValue(value: unknown): string {
|
||||
@@ -47,9 +45,7 @@ function bindingHint(status: PersonalWechatSenderStatus): string {
|
||||
}
|
||||
|
||||
function isWechatBound(status: PersonalWechatSenderStatus): boolean {
|
||||
if (status.state === 'online') return true
|
||||
return Boolean(
|
||||
status.state === 'hook_not_ready' &&
|
||||
status.wechatPid &&
|
||||
status.boundWechatPid === status.wechatPid &&
|
||||
status.attachReady &&
|
||||
@@ -64,11 +60,9 @@ export function PersonalWechatSetupGuide({
|
||||
runtimeBusy,
|
||||
binding,
|
||||
detecting,
|
||||
detectionAttempted,
|
||||
sessionBound,
|
||||
onDownloadRuntime,
|
||||
onBind,
|
||||
onDetect,
|
||||
onStartSending,
|
||||
onOpenTextToSpeechSettings
|
||||
}: PersonalWechatSetupGuideProps): React.ReactElement {
|
||||
@@ -78,9 +72,8 @@ export function PersonalWechatSetupGuide({
|
||||
const runtimeDownloading = runtimeBusy || runtimeStatus?.state === 'downloading'
|
||||
const connected = sessionBound && (senderStatus ? isWechatBound(senderStatus) : false)
|
||||
const canSendVoice = Boolean(senderStatus?.canSendVoice)
|
||||
const detectedVoiceReady = detectionAttempted && canSendVoice
|
||||
const verificationComplete = connected && detectedVoiceReady
|
||||
const allReady = verificationComplete
|
||||
const initializing = connected && !canSendVoice && senderStatus?.state !== 'error'
|
||||
const allReady = connected && Boolean(senderStatus?.canSend)
|
||||
const progress = runtimeProgress || runtimeStatus
|
||||
const progressPercent = Math.max(0, Math.min(100, Math.round((progress?.progress || 0) * 100)))
|
||||
|
||||
@@ -181,44 +174,38 @@ export function PersonalWechatSetupGuide({
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li
|
||||
className={verificationComplete ? 'is-complete' : connected ? 'is-current' : 'is-pending'}
|
||||
>
|
||||
<span className="personal-wechat-step-number">{verificationComplete ? '✓' : '3'}</span>
|
||||
<li className={allReady ? 'is-complete' : connected ? 'is-current' : 'is-pending'}>
|
||||
<span className="personal-wechat-step-number">{allReady ? '✓' : '3'}</span>
|
||||
<div className="personal-wechat-step-content">
|
||||
<strong>验证消息能力</strong>
|
||||
<p>请打开微信,可以给任意好友发送一张图片,完成一次能力初始化。</p>
|
||||
<p className="personal-wechat-step-hint">完成后点击“重新检测”。</p>
|
||||
{connected && !verificationComplete && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onDetect}
|
||||
disabled={detecting || binding}
|
||||
>
|
||||
{detecting ? '正在检测…' : '重新检测'}
|
||||
</Button>
|
||||
)}
|
||||
{detectionAttempted && !detecting && !verificationComplete && (
|
||||
<strong>初始化发送能力</strong>
|
||||
<p>绑定微信后,TraceMemo 会自动完成文字、图片和语音发送所需的初始化。</p>
|
||||
{initializing && (
|
||||
<p className="personal-wechat-step-hint" role="status">
|
||||
暂未检测到语音发送能力,请确认微信已完成初始化后再次检测。
|
||||
{detecting ? '正在初始化发送能力…' : '正在等待发送运行时完成初始化…'}
|
||||
</p>
|
||||
)}
|
||||
{senderStatus?.state === 'error' && (
|
||||
<p className="personal-wechat-step-hint" role="status">
|
||||
{senderStatus.message || '发送能力初始化失败,请重新绑定。'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li
|
||||
className={allReady ? 'is-complete' : verificationComplete ? 'is-current' : 'is-pending'}
|
||||
>
|
||||
<li className={allReady ? 'is-complete' : initializing ? 'is-current' : 'is-pending'}>
|
||||
<span className="personal-wechat-step-number">{allReady ? '✓' : '4'}</span>
|
||||
<div className="personal-wechat-step-content">
|
||||
<strong>能力检测</strong>
|
||||
<div className="personal-wechat-capabilities" aria-label="微信消息能力">
|
||||
{[['语音消息', detectedVoiceReady]].map(([label, ready]) => (
|
||||
{[
|
||||
['文字消息', Boolean(senderStatus?.canSendText)],
|
||||
['图片消息', Boolean(senderStatus?.canSendImage)],
|
||||
['语音消息', canSendVoice]
|
||||
].map(([label, ready]) => (
|
||||
<span key={String(label)} className={ready ? 'is-ready' : ''}>
|
||||
<b aria-hidden>{ready ? '✓' : '−'}</b>
|
||||
{label}
|
||||
<small>{capabilityLabel(Boolean(ready), detectionAttempted)}</small>
|
||||
<small>{capabilityLabel(Boolean(ready), initializing)}</small>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -229,15 +216,10 @@ export function PersonalWechatSetupGuide({
|
||||
开始发送
|
||||
</Button>
|
||||
</>
|
||||
) : verificationComplete ? (
|
||||
<>
|
||||
<p className="personal-wechat-step-hint">
|
||||
语音能力还未就绪,请在微信中完成一次媒体消息初始化后重新检测。
|
||||
</p>
|
||||
<Button size="sm" variant="outline" onClick={onDetect} disabled={detecting}>
|
||||
{detecting ? '正在检测…' : '重新检测能力'}
|
||||
</Button>
|
||||
</>
|
||||
) : initializing ? (
|
||||
<p className="personal-wechat-step-hint" role="status">
|
||||
发送能力准备完成后会自动进入已就绪状态。
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -113,8 +113,8 @@ const capabilityCopy = (capability: PersonalWechatSendCapability | null): string
|
||||
case 'needs_binding':
|
||||
case 'unconfigured':
|
||||
return '请先绑定个人微信'
|
||||
case 'needs_verification':
|
||||
return '请先完成微信消息能力检测'
|
||||
case 'initializing':
|
||||
return '微信发送能力正在初始化'
|
||||
default:
|
||||
return '微信发送能力异常'
|
||||
}
|
||||
@@ -598,8 +598,9 @@ export function ScheduledReportsWorkspace({
|
||||
const [editingTask, setEditingTask] = React.useState<ScheduledReportTask | null>(null)
|
||||
const [saving, setSaving] = React.useState(false)
|
||||
const [deletingTask, setDeletingTask] = React.useState<ScheduledReportTask | null>(null)
|
||||
const [retryingExecution, setRetryingExecution] =
|
||||
React.useState<ScheduledReportExecution | null>(null)
|
||||
const [retryingExecution, setRetryingExecution] = React.useState<ScheduledReportExecution | null>(
|
||||
null
|
||||
)
|
||||
const [busyTaskId, setBusyTaskId] = React.useState<string | null>(null)
|
||||
|
||||
const load = React.useCallback(async (): Promise<void> => {
|
||||
@@ -917,11 +918,11 @@ export function ScheduledReportsWorkspace({
|
||||
</span>
|
||||
{(capability?.status === 'needs_binding' ||
|
||||
capability?.status === 'unconfigured' ||
|
||||
capability?.status === 'needs_verification' ||
|
||||
capability?.status === 'initializing' ||
|
||||
capability?.status === 'error' ||
|
||||
capabilityError) && (
|
||||
<Button variant="link" size="sm" className="ml-auto" onClick={onOpenWechatSettings}>
|
||||
{capability?.status === 'needs_verification' ? '去检测' : '去配置'}
|
||||
去配置
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@ const capabilityLabel: Record<PersonalWechatSendCapability['status'], string> =
|
||||
unsupported: '暂不支持',
|
||||
unconfigured: '尚未配置',
|
||||
needs_binding: '需要绑定',
|
||||
needs_verification: '需要检测',
|
||||
initializing: '初始化中',
|
||||
ready: '已就绪',
|
||||
error: '异常'
|
||||
}
|
||||
@@ -81,7 +81,6 @@ export function PersonalWechatSendPage({
|
||||
const [runtimeBusy, setRuntimeBusy] = useState(false)
|
||||
const [binding, setBinding] = useState(false)
|
||||
const [detecting, setDetecting] = useState(false)
|
||||
const [detectionAttempted, setDetectionAttempted] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [windowsSenderStatus, setWindowsSenderStatus] = useState<PersonalWechatSenderStatus | null>(
|
||||
null
|
||||
@@ -129,6 +128,15 @@ export function PersonalWechatSendPage({
|
||||
return () => unsubscribe?.()
|
||||
}, [refresh])
|
||||
|
||||
useEffect(() => {
|
||||
if (isWindows || !senderStatus || senderStatus.canSend || senderStatus.state === 'error') {
|
||||
return undefined
|
||||
}
|
||||
if (!boundToCurrentWechat(senderStatus)) return undefined
|
||||
const timer = window.setInterval(() => void refresh(), 1_000)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [refresh, senderStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWindows) return
|
||||
let active = true
|
||||
@@ -215,13 +223,12 @@ export function PersonalWechatSendPage({
|
||||
if (binding) return
|
||||
setBinding(true)
|
||||
setError('')
|
||||
setDetectionAttempted(false)
|
||||
try {
|
||||
const nextStatus = await window.api.rebindPersonalWechatSender()
|
||||
setSenderStatus(nextStatus)
|
||||
const nextCapability = await window.api.getPersonalWechatSendCapability()
|
||||
setCapability(nextCapability)
|
||||
if (nextStatus.state !== 'online' && nextStatus.message) setError(nextStatus.message)
|
||||
if (nextStatus.state === 'error' && nextStatus.message) setError(nextStatus.message)
|
||||
} catch (reason) {
|
||||
setError(reason instanceof Error ? reason.message : '个人微信绑定失败')
|
||||
} finally {
|
||||
@@ -236,7 +243,6 @@ export function PersonalWechatSendPage({
|
||||
}
|
||||
if (detecting) return
|
||||
setDetecting(true)
|
||||
setDetectionAttempted(true)
|
||||
try {
|
||||
await refresh()
|
||||
} finally {
|
||||
@@ -324,7 +330,7 @@ export function PersonalWechatSendPage({
|
||||
: windowsSenderStatus?.state === 'error' && windowsSenderStatus.endpointReady
|
||||
? 'error'
|
||||
: windowsPortInput.trim()
|
||||
? 'needs_verification'
|
||||
? 'initializing'
|
||||
: 'unconfigured'
|
||||
: status
|
||||
const pageReady = isWindows ? Boolean(windowsSenderStatus?.canSend) : ready
|
||||
@@ -374,7 +380,7 @@ export function PersonalWechatSendPage({
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={() => void detectCapability()}>
|
||||
{detecting ? '检测中…' : '重新检测'}
|
||||
{detecting ? '刷新中…' : isWindows ? '重新检测' : '刷新状态'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-5 grid grid-cols-3 gap-2" aria-label="微信发送能力明细">
|
||||
@@ -574,7 +580,7 @@ export function PersonalWechatSendPage({
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<h2 className="settings-section-heading">配置与检测</h2>
|
||||
<h2 className="settings-section-heading">配置状态</h2>
|
||||
<PersonalWechatSetupGuide
|
||||
runtimeStatus={runtimeStatus}
|
||||
senderStatus={senderStatus}
|
||||
@@ -582,11 +588,9 @@ export function PersonalWechatSendPage({
|
||||
runtimeBusy={runtimeBusy}
|
||||
binding={binding}
|
||||
detecting={detecting}
|
||||
detectionAttempted={detectionAttempted}
|
||||
sessionBound={boundToCurrentWechat(senderStatus)}
|
||||
onDownloadRuntime={() => void downloadRuntime()}
|
||||
onBind={() => void bindWechat()}
|
||||
onDetect={() => void detectCapability()}
|
||||
onStartSending={() =>
|
||||
onNotice('微信消息发送能力已就绪,请在档案中选择会话开始发送。')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user