feat: add itchat-uos WeChat adapter with QR code login

- Add itchat-uos adapter supporting personal WeChat via QR code login
- Implement message/event converters for text, image, voice, sharing types
- Bridge sync itchat callbacks to async LangBot pipeline via asyncio
- Add QR login API endpoints with session management
- Add frontend QR code login dialog integration
- Fix plugin connector handler attribute check
- Use fresh Core instance per login to avoid singleton state pollution
This commit is contained in:
fdc310
2026-07-01 18:03:26 +08:00
parent 7de7c7f714
commit 78fb40a28a
9 changed files with 1026 additions and 2 deletions
@@ -16,7 +16,12 @@ import {
} from 'lucide-react';
import QRCode from 'qrcode';
export type QrLoginPlatform = 'feishu' | 'weixin' | 'dingtalk' | 'wecombot';
export type QrLoginPlatform =
| 'feishu'
| 'weixin'
| 'dingtalk'
| 'wecombot'
| 'itchat';
interface PlatformConfig {
titleKey: string;
@@ -92,6 +97,20 @@ const PLATFORM_CONFIGS: Record<QrLoginPlatform, PlatformConfig> = {
}),
successNoteKey: 'wecombot.robotNameNote',
},
itchat: {
titleKey: 'itchat.scanLogin',
connectingKey: 'itchat.connecting',
scanQRCodeKey: 'itchat.scanQRCode',
waitingKey: 'itchat.waitingForScan',
successKey: 'itchat.loginSuccess',
failedKey: 'itchat.loginFailed',
retryKey: 'itchat.retry',
apiBase: '/api/v1/platform/adapters/itchat/login',
extractSuccess: (data) => ({
account_id: data.wxid || '',
nickname: data.nickname || '',
}),
},
};
interface QrCodeLoginDialogProps {
@@ -116,6 +135,7 @@ export default function QrCodeLoginDialog({
const [state, setState] = useState<DialogState>('connecting');
const [qrDataUrl, setQrDataUrl] = useState('');
const qrDataUrlRef = useRef('');
const [expireIn, setExpireIn] = useState(0);
const [errorMessage, setErrorMessage] = useState('');
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
@@ -176,6 +196,7 @@ export default function QrCodeLoginDialog({
cleanedRef.current = false;
setState('connecting');
setQrDataUrl('');
qrDataUrlRef.current = '';
setExpireIn(0);
setErrorMessage('');
@@ -204,12 +225,14 @@ export default function QrCodeLoginDialog({
if (qr_data_url) {
setQrDataUrl(qr_data_url);
qrDataUrlRef.current = qr_data_url;
} else if (qr_url) {
const dataUrl = await QRCode.toDataURL(qr_url, {
width: 224,
margin: 2,
});
setQrDataUrl(dataUrl);
qrDataUrlRef.current = dataUrl;
}
setState('waiting');
@@ -289,6 +312,19 @@ export default function QrCodeLoginDialog({
cleanup();
setExpireIn(0);
setState('expired');
} else if (status === 'waiting') {
// Update QR data URL if regenerated (e.g. itchat QR expiry)
if (rest.qr_data_url && rest.qr_data_url !== qrDataUrlRef.current) {
setQrDataUrl(rest.qr_data_url);
qrDataUrlRef.current = rest.qr_data_url;
}
if (rest.expire_at) {
const remaining = Math.max(
0,
Math.floor(rest.expire_at - Date.now() / 1000),
);
setExpireIn(remaining);
}
}
} catch {
// ignore poll errors
+10
View File
@@ -1769,6 +1769,16 @@ const enUS = {
loginSuccess: 'Login successful! Token has been filled in',
loginFailed: 'Login failed',
},
itchat: {
scanLogin: 'Scan QR Login',
scanQRCode: 'Scan the QR code below with WeChat to login',
loginSuccess:
'Login successful! Session cached. Save config to start using.',
loginFailed: 'Login failed',
connecting: 'Starting WeChat login...',
waitingForScan: 'Waiting for scan',
retry: 'Retry',
},
dingtalk: {
createApp: 'One-Click Create DingTalk App',
scanQRCode:
+9
View File
@@ -1691,6 +1691,15 @@ const zhHans = {
loginSuccess: '登录成功!令牌已自动填入',
loginFailed: '登录失败',
},
itchat: {
scanLogin: '扫码登录微信',
scanQRCode: '请使用微信扫描以下二维码登录',
loginSuccess: '登录成功!会话已缓存,保存配置后即可使用',
loginFailed: '登录失败',
connecting: '正在启动微信登录...',
waitingForScan: '等待扫码中',
retry: '重试',
},
dingtalk: {
createApp: '一键创建钉钉应用',
scanQRCode: '请使用钉钉扫描以下二维码,授权后将自动创建应用并填写凭据',