mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-11 10:00:08 +08:00
1096 lines
41 KiB
Vue
1096 lines
41 KiB
Vue
<script setup lang="ts">
|
|
import api from '@/api'
|
|
import { NotificationConf } from '@/api/types'
|
|
import { getLogoUrl } from '@/utils/imageUtils'
|
|
import { useToast } from 'vue-toastification'
|
|
import { cloneDeep } from 'lodash-es'
|
|
import QRCode from 'qrcode'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useDisplay } from 'vuetify'
|
|
|
|
// 显示器宽度
|
|
const display = useDisplay()
|
|
|
|
const { t } = useI18n()
|
|
|
|
// 定义输入
|
|
const props = defineProps({
|
|
// 单个通知
|
|
notification: {
|
|
type: Object as PropType<NotificationConf>,
|
|
required: true,
|
|
},
|
|
// 所有通知
|
|
notifications: {
|
|
type: Array as PropType<NotificationConf[]>,
|
|
required: true,
|
|
},
|
|
})
|
|
|
|
// 定义触发的自定义事件
|
|
const emit = defineEmits(['close', 'change', 'done'])
|
|
|
|
// 提示框
|
|
const $toast = useToast()
|
|
|
|
// 通知详情弹窗
|
|
const notificationInfoDialog = ref(false)
|
|
|
|
// 通知详情
|
|
const notificationInfo = ref<NotificationConf>({
|
|
name: '',
|
|
type: '',
|
|
enabled: false,
|
|
config: {},
|
|
})
|
|
|
|
// 各通知类型的名称字典
|
|
const notificationTypeNames: { [key: string]: string } = {
|
|
wechat: t('notification.wechat.name'),
|
|
wechatclawbot: t('notification.wechatclawbot.name'),
|
|
telegram: t('notification.telegram.name'),
|
|
qqbot: t('notification.qqbot.name'),
|
|
vocechat: t('notification.vocechat.name'),
|
|
synologychat: t('notification.synologychat.name'),
|
|
slack: t('notification.slack.name'),
|
|
discord: t('notification.discord.name'),
|
|
webpush: t('notification.webpush.name'),
|
|
custom: t('setting.notification.custom'),
|
|
}
|
|
|
|
// 消息类型下拉字典
|
|
const notificationTypes = [
|
|
{ value: '资源下载', title: t('notificationSwitch.resourceDownload') },
|
|
{ value: '整理入库', title: t('notificationSwitch.organize') },
|
|
{ value: '订阅', title: t('notificationSwitch.subscribe') },
|
|
{ value: '站点', title: t('notificationSwitch.site') },
|
|
{ value: '媒体服务器', title: t('notificationSwitch.mediaServer') },
|
|
{ value: '手动处理', title: t('notificationSwitch.manual') },
|
|
{ value: '插件', title: t('notificationSwitch.plugin') },
|
|
{ value: '智能体', title: t('notificationSwitch.agent') },
|
|
{ value: '其它', title: t('notificationSwitch.other') },
|
|
]
|
|
|
|
interface WechatClawBotStatus {
|
|
connected?: boolean
|
|
account_id?: string | null
|
|
qrcode?: string | null
|
|
qrcode_url?: string | null
|
|
qrcode_status?: string | null
|
|
qrcode_updated_at?: number | null
|
|
known_targets?: Array<{ userid: string; username: string; last_active?: number | null }>
|
|
default_target?: string | null
|
|
base_url?: string | null
|
|
}
|
|
|
|
interface WechatClawBotStatusFetchOptions {
|
|
autoGenerateQrcode?: boolean
|
|
silent?: boolean
|
|
autoRefreshExpired?: boolean
|
|
showErrorToast?: boolean
|
|
}
|
|
|
|
interface WechatClawBotRefreshOptions {
|
|
silent?: boolean
|
|
showToast?: boolean
|
|
}
|
|
|
|
function ensureWechatConfigDefaults(notification: NotificationConf) {
|
|
if (notification.type !== 'wechat') {
|
|
return
|
|
}
|
|
if (!notification.config) {
|
|
notification.config = {}
|
|
}
|
|
if (!notification.config.WECHAT_MODE) {
|
|
notification.config.WECHAT_MODE = 'app'
|
|
}
|
|
if (!notification.config.WECHAT_BOT_WS_URL) {
|
|
notification.config.WECHAT_BOT_WS_URL = 'wss://openws.work.weixin.qq.com'
|
|
}
|
|
}
|
|
|
|
function ensureWechatClawBotConfigDefaults(notification: NotificationConf) {
|
|
if (notification.type !== 'wechatclawbot') {
|
|
return
|
|
}
|
|
if (!notification.config) {
|
|
notification.config = {}
|
|
}
|
|
if (!notification.config.WECHATCLAWBOT_BASE_URL) {
|
|
notification.config.WECHATCLAWBOT_BASE_URL = 'https://ilinkai.weixin.qq.com'
|
|
}
|
|
if (!notification.config.WECHATCLAWBOT_POLL_TIMEOUT) {
|
|
notification.config.WECHATCLAWBOT_POLL_TIMEOUT = 25
|
|
}
|
|
}
|
|
|
|
const wechatClawBotLoading = ref(false)
|
|
const wechatClawBotActionLoading = ref(false)
|
|
const wechatClawBotStatus = ref<WechatClawBotStatus | null>(null)
|
|
const wechatClawBotQrImage = ref('')
|
|
const wechatClawBotExpiredRefreshAttempted = ref(false)
|
|
let wechatClawBotTimer: number | null = null
|
|
|
|
function isImageSource(value?: string | null) {
|
|
if (!value) {
|
|
return false
|
|
}
|
|
const raw = value.trim()
|
|
if (!raw) {
|
|
return false
|
|
}
|
|
if (raw.toLowerCase().startsWith('data:image/')) {
|
|
return true
|
|
}
|
|
return /\.(png|jpe?g|gif|webp|svg)(\?|$)/i.test(raw)
|
|
}
|
|
|
|
function getWechatClawBotQrText(status?: WechatClawBotStatus | null) {
|
|
const directUrl = status?.qrcode_url?.trim()
|
|
if (directUrl) {
|
|
return directUrl
|
|
}
|
|
const qrcode = status?.qrcode?.trim()
|
|
if (!qrcode) {
|
|
return ''
|
|
}
|
|
return `https://liteapp.weixin.qq.com/q/7GiQu1?qrcode=${encodeURIComponent(qrcode)}&bot_type=3`
|
|
}
|
|
|
|
async function updateWechatClawBotQrImage(status?: WechatClawBotStatus | null) {
|
|
const directUrl = status?.qrcode_url?.trim()
|
|
if (isImageSource(directUrl)) {
|
|
wechatClawBotQrImage.value = directUrl || ''
|
|
return
|
|
}
|
|
const qrText = getWechatClawBotQrText(status)
|
|
if (!qrText) {
|
|
wechatClawBotQrImage.value = ''
|
|
return
|
|
}
|
|
try {
|
|
wechatClawBotQrImage.value = await QRCode.toDataURL(qrText, {
|
|
width: 220,
|
|
margin: 1,
|
|
})
|
|
} catch (error) {
|
|
console.error(error)
|
|
wechatClawBotQrImage.value = ''
|
|
}
|
|
}
|
|
|
|
function getWechatClawBotRequestParams(extraParams: Record<string, any> = {}) {
|
|
const config = notificationInfo.value.config || {}
|
|
return {
|
|
source: notificationInfo.value.name,
|
|
fallback_source: props.notification.name,
|
|
WECHATCLAWBOT_BASE_URL: config.WECHATCLAWBOT_BASE_URL,
|
|
WECHATCLAWBOT_DEFAULT_TARGET: config.WECHATCLAWBOT_DEFAULT_TARGET,
|
|
WECHATCLAWBOT_ADMINS: config.WECHATCLAWBOT_ADMINS,
|
|
WECHATCLAWBOT_POLL_TIMEOUT: config.WECHATCLAWBOT_POLL_TIMEOUT,
|
|
...extraParams,
|
|
}
|
|
}
|
|
|
|
const isWechatBotMode = computed({
|
|
get: () => notificationInfo.value.config?.WECHAT_MODE === 'bot',
|
|
set: value => {
|
|
if (!notificationInfo.value.config) {
|
|
notificationInfo.value.config = {}
|
|
}
|
|
notificationInfo.value.config.WECHAT_MODE = value ? 'bot' : 'app'
|
|
if (value && !notificationInfo.value.config.WECHAT_BOT_WS_URL) {
|
|
notificationInfo.value.config.WECHAT_BOT_WS_URL = 'wss://openws.work.weixin.qq.com'
|
|
}
|
|
},
|
|
})
|
|
|
|
// 打开详情弹窗
|
|
function openNotificationInfoDialog() {
|
|
// 替换成深复制,避免修改时影响原数据
|
|
notificationInfo.value = cloneDeep(props.notification)
|
|
ensureWechatConfigDefaults(notificationInfo.value)
|
|
ensureWechatClawBotConfigDefaults(notificationInfo.value)
|
|
notificationInfoDialog.value = true
|
|
if (notificationInfo.value.type === 'wechatclawbot') {
|
|
fetchWechatClawBotStatus({
|
|
autoGenerateQrcode: true,
|
|
autoRefreshExpired: true,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 保存详情数据
|
|
function saveNotificationInfo() {
|
|
// 为空不保存,跳出警告框
|
|
if (!notificationInfo.value.name) {
|
|
$toast.error(t('notification.name') + t('common.required'))
|
|
return
|
|
}
|
|
// 重名判断
|
|
if (props.notifications.some(item => item.name === notificationInfo.value.name && item !== props.notification)) {
|
|
$toast.error(t('notification.channel') + `【${notificationInfo.value.name}】` + t('common.exists'))
|
|
return
|
|
}
|
|
ensureWechatConfigDefaults(notificationInfo.value)
|
|
ensureWechatClawBotConfigDefaults(notificationInfo.value)
|
|
notificationInfoDialog.value = false
|
|
emit('change', notificationInfo.value, props.notification.name)
|
|
emit('done')
|
|
}
|
|
|
|
function clearWechatClawBotTimer() {
|
|
if (wechatClawBotTimer) {
|
|
window.clearTimeout(wechatClawBotTimer)
|
|
wechatClawBotTimer = null
|
|
}
|
|
}
|
|
|
|
function scheduleWechatClawBotRefresh() {
|
|
clearWechatClawBotTimer()
|
|
if (!notificationInfoDialog.value || notificationInfo.value.type !== 'wechatclawbot') {
|
|
return
|
|
}
|
|
const connected = wechatClawBotStatus.value?.connected
|
|
const pendingStatus = ['waiting', 'scanned'].includes((wechatClawBotStatus.value?.qrcode_status || '').toLowerCase())
|
|
if (connected || pendingStatus) {
|
|
wechatClawBotTimer = window.setTimeout(() => {
|
|
fetchWechatClawBotStatus({
|
|
silent: true,
|
|
autoRefreshExpired: true,
|
|
})
|
|
}, connected ? 10000 : 3000)
|
|
}
|
|
}
|
|
|
|
async function fetchWechatClawBotStatus(options: WechatClawBotStatusFetchOptions = {}) {
|
|
const {
|
|
autoGenerateQrcode = false,
|
|
silent = false,
|
|
autoRefreshExpired = false,
|
|
showErrorToast = true,
|
|
} = options
|
|
if (notificationInfo.value.type !== 'wechatclawbot' || !notificationInfo.value.name) {
|
|
return
|
|
}
|
|
if (!silent) {
|
|
wechatClawBotLoading.value = true
|
|
}
|
|
try {
|
|
const result: { [key: string]: any } = await api.get('notification/wechatclawbot/status', {
|
|
params: getWechatClawBotRequestParams({ auto_generate_qrcode: autoGenerateQrcode }),
|
|
})
|
|
if (result.success) {
|
|
wechatClawBotStatus.value = result.data
|
|
await updateWechatClawBotQrImage(result.data)
|
|
const status = (result.data?.qrcode_status || '').toLowerCase()
|
|
if (status !== 'expired') {
|
|
wechatClawBotExpiredRefreshAttempted.value = false
|
|
}
|
|
if (
|
|
autoRefreshExpired &&
|
|
!result.data?.connected &&
|
|
status === 'expired' &&
|
|
!wechatClawBotExpiredRefreshAttempted.value
|
|
) {
|
|
wechatClawBotExpiredRefreshAttempted.value = true
|
|
await refreshWechatClawBotQrcode({
|
|
silent: true,
|
|
showToast: false,
|
|
})
|
|
return
|
|
}
|
|
scheduleWechatClawBotRefresh()
|
|
} else {
|
|
wechatClawBotStatus.value = null
|
|
wechatClawBotQrImage.value = ''
|
|
clearWechatClawBotTimer()
|
|
if (showErrorToast) {
|
|
$toast.error(result.message || t('notification.wechatclawbot.statusLoadFailed'))
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
clearWechatClawBotTimer()
|
|
if (showErrorToast) {
|
|
$toast.error(t('notification.wechatclawbot.statusLoadFailed'))
|
|
}
|
|
} finally {
|
|
if (!silent) {
|
|
wechatClawBotLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
async function refreshWechatClawBotQrcode(options: WechatClawBotRefreshOptions = {}) {
|
|
const { silent = false, showToast = true } = options
|
|
if (!notificationInfo.value.name) {
|
|
return
|
|
}
|
|
if (!silent) {
|
|
wechatClawBotActionLoading.value = true
|
|
}
|
|
try {
|
|
const result: { [key: string]: any } = await api.post('notification/wechatclawbot/refresh', null, {
|
|
params: getWechatClawBotRequestParams(),
|
|
})
|
|
if (result.success) {
|
|
wechatClawBotStatus.value = result.data
|
|
await updateWechatClawBotQrImage(result.data)
|
|
wechatClawBotExpiredRefreshAttempted.value = false
|
|
scheduleWechatClawBotRefresh()
|
|
if (showToast) {
|
|
$toast.success(t('notification.wechatclawbot.qrcodeRefreshSuccess'))
|
|
}
|
|
} else {
|
|
if (showToast) {
|
|
$toast.error(result.message || t('notification.wechatclawbot.qrcodeRefreshFailed'))
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
if (showToast) {
|
|
$toast.error(t('notification.wechatclawbot.qrcodeRefreshFailed'))
|
|
}
|
|
} finally {
|
|
if (!silent) {
|
|
wechatClawBotActionLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
async function logoutWechatClawBot() {
|
|
if (!notificationInfo.value.name) {
|
|
return
|
|
}
|
|
wechatClawBotActionLoading.value = true
|
|
try {
|
|
const result: { [key: string]: any } = await api.post('notification/wechatclawbot/logout', null, {
|
|
params: getWechatClawBotRequestParams(),
|
|
})
|
|
if (result.success) {
|
|
$toast.success(result.message || t('notification.wechatclawbot.logoutSuccess'))
|
|
await fetchWechatClawBotStatus({
|
|
autoGenerateQrcode: true,
|
|
autoRefreshExpired: true,
|
|
})
|
|
} else {
|
|
$toast.error(result.message || t('notification.wechatclawbot.logoutFailed'))
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
$toast.error(t('notification.wechatclawbot.logoutFailed'))
|
|
} finally {
|
|
wechatClawBotActionLoading.value = false
|
|
}
|
|
}
|
|
|
|
function formatWechatClawBotTime(timestamp?: number | null) {
|
|
if (!timestamp) {
|
|
return ''
|
|
}
|
|
return new Date(timestamp * 1000).toLocaleString()
|
|
}
|
|
|
|
const wechatClawBotStatusText = computed(() => {
|
|
const status = (wechatClawBotStatus.value?.qrcode_status || '').toLowerCase()
|
|
if (wechatClawBotStatus.value?.connected) {
|
|
return t('notification.wechatclawbot.connected')
|
|
}
|
|
if (status === 'scanned') {
|
|
return t('notification.wechatclawbot.scanned')
|
|
}
|
|
if (status === 'expired') {
|
|
return t('notification.wechatclawbot.expired')
|
|
}
|
|
if (status === 'confirmed') {
|
|
return t('notification.wechatclawbot.confirmed')
|
|
}
|
|
return t('notification.wechatclawbot.waiting')
|
|
})
|
|
|
|
// 根据存储类型选择图标
|
|
const getIcon = computed(() => {
|
|
switch (props.notification.type) {
|
|
case 'wechat':
|
|
return getLogoUrl('wechat')
|
|
case 'wechatclawbot':
|
|
return getLogoUrl('wechatclawbot')
|
|
case 'telegram':
|
|
return getLogoUrl('telegram')
|
|
case 'qqbot':
|
|
return getLogoUrl('qq')
|
|
case 'vocechat':
|
|
return getLogoUrl('vocechat')
|
|
case 'synologychat':
|
|
return getLogoUrl('synologychat')
|
|
case 'slack':
|
|
return getLogoUrl('slack')
|
|
case 'discord':
|
|
return getLogoUrl('discord')
|
|
case 'webpush':
|
|
return getLogoUrl('chrome')
|
|
default:
|
|
return getLogoUrl('notification')
|
|
}
|
|
})
|
|
|
|
// 按钮点击
|
|
function onClose() {
|
|
clearWechatClawBotTimer()
|
|
emit('close')
|
|
}
|
|
|
|
watch(notificationInfoDialog, value => {
|
|
if (!value) {
|
|
clearWechatClawBotTimer()
|
|
wechatClawBotQrImage.value = ''
|
|
wechatClawBotExpiredRefreshAttempted.value = false
|
|
}
|
|
})
|
|
</script>
|
|
<template>
|
|
<div>
|
|
<VCard variant="tonal" class="app-card-shell" @click="openNotificationInfoDialog">
|
|
<span class="app-card-top-action absolute top-3 right-12">
|
|
<IconBtn @click.stop>
|
|
<VIcon class="cursor-move" icon="mdi-drag" />
|
|
</IconBtn>
|
|
</span>
|
|
<VDialogCloseBtn @click="onClose" />
|
|
<VCardText class="app-card-summary app-card-summary--double-action app-card-summary--title-subtitle">
|
|
<div class="app-card-summary__content">
|
|
<div class="app-card-summary__title-row">
|
|
<VBadge v-if="props.notification.enabled" dot inline color="success" class="me-1" />
|
|
<span class="app-card-summary__title text-h6">{{ props.notification.name }}</span>
|
|
</div>
|
|
<div class="app-card-summary__subtitle text-body-1">{{ notificationTypeNames[notification.type] }}</div>
|
|
</div>
|
|
<div class="app-card-summary__media" aria-hidden="true">
|
|
<VImg :src="getIcon" contain class="app-card-summary__image" />
|
|
</div>
|
|
</VCardText>
|
|
</VCard>
|
|
|
|
<VDialog
|
|
v-if="notificationInfoDialog"
|
|
v-model="notificationInfoDialog"
|
|
scrollable
|
|
max-width="40rem"
|
|
:fullscreen="!display.mdAndUp.value"
|
|
>
|
|
<VCard>
|
|
<VCardItem class="py-2">
|
|
<template #prepend>
|
|
<VIcon icon="mdi-cog" class="me-2" />
|
|
</template>
|
|
<VCardTitle>{{ t('common.config') }}</VCardTitle>
|
|
<VCardSubtitle>{{ props.notification.name }}</VCardSubtitle>
|
|
</VCardItem>
|
|
<VDialogCloseBtn @click="notificationInfoDialog = false" />
|
|
<VDivider />
|
|
<VCardText>
|
|
<VForm>
|
|
<VRow>
|
|
<VCol cols="12" md="6">
|
|
<VSwitch v-model="notificationInfo.enabled" :label="t('notification.enabled')" />
|
|
</VCol>
|
|
<VCol cols="12">
|
|
<VAutocomplete
|
|
v-model="notificationInfo.switchs"
|
|
:items="notificationTypes"
|
|
:label="t('notification.type')"
|
|
:hint="t('notification.typeHint')"
|
|
multiple
|
|
clearable
|
|
chips
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-bell-outline"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-if="notificationInfo.type == 'wechat'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VSwitch
|
|
v-model="isWechatBotMode"
|
|
:label="t('notification.wechat.useBotMode')"
|
|
:hint="t('notification.wechat.useBotModeHint')"
|
|
persistent-hint
|
|
color="primary"
|
|
/>
|
|
</VCol>
|
|
<template v-if="isWechatBotMode">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_BOT_ID"
|
|
:label="t('notification.wechat.botId')"
|
|
:hint="t('notification.wechat.botIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-robot"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_BOT_SECRET"
|
|
:label="t('notification.wechat.botSecret')"
|
|
:hint="t('notification.wechat.botSecretHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_BOT_CHAT_ID"
|
|
:label="t('notification.wechat.botChatId')"
|
|
:placeholder="t('notification.wechat.botChatIdPlaceholder')"
|
|
:hint="t('notification.wechat.botChatIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-chat-processing"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_BOT_WS_URL"
|
|
:label="t('notification.wechat.botWsUrl')"
|
|
:hint="t('notification.wechat.botWsUrlHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-lan-connect"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_ADMINS"
|
|
:label="t('notification.wechat.admins')"
|
|
:placeholder="t('notification.wechat.adminsPlaceholder')"
|
|
:hint="t('notification.wechat.adminsHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-supervisor"
|
|
/>
|
|
</VCol>
|
|
</template>
|
|
<template v-else>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_CORPID"
|
|
:label="t('notification.wechat.corpId')"
|
|
:hint="t('notification.wechat.corpIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-domain"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_APP_ID"
|
|
:label="t('notification.wechat.appId')"
|
|
:hint="t('notification.wechat.appIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-application"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_APP_SECRET"
|
|
:label="t('notification.wechat.appSecret')"
|
|
:hint="t('notification.wechat.appSecretHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_PROXY"
|
|
:label="t('notification.wechat.proxy')"
|
|
:hint="t('notification.wechat.proxyHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-server-network"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_TOKEN"
|
|
:label="t('notification.wechat.token')"
|
|
:hint="t('notification.wechat.tokenHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key-variant"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_ENCODING_AESKEY"
|
|
:label="t('notification.wechat.encodingAesKey')"
|
|
:hint="t('notification.wechat.encodingAesKeyHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-lock"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHAT_ADMINS"
|
|
:label="t('notification.wechat.admins')"
|
|
:placeholder="t('notification.wechat.adminsPlaceholder')"
|
|
:hint="t('notification.wechat.adminsHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-supervisor"
|
|
/>
|
|
</VCol>
|
|
</template>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'wechatclawbot'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHATCLAWBOT_BASE_URL"
|
|
:label="t('notification.wechatclawbot.baseUrl')"
|
|
:hint="t('notification.wechatclawbot.baseUrlHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-web"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHATCLAWBOT_DEFAULT_TARGET"
|
|
:label="t('notification.wechatclawbot.defaultTarget')"
|
|
:placeholder="t('notification.wechatclawbot.defaultTargetPlaceholder')"
|
|
:hint="t('notification.wechatclawbot.defaultTargetHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-arrow-right"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHATCLAWBOT_ADMINS"
|
|
:label="t('notification.wechatclawbot.admins')"
|
|
:placeholder="t('notification.wechatclawbot.adminsPlaceholder')"
|
|
:hint="t('notification.wechatclawbot.adminsHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-supervisor"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WECHATCLAWBOT_POLL_TIMEOUT"
|
|
:label="t('notification.wechatclawbot.pollTimeout')"
|
|
:hint="t('notification.wechatclawbot.pollTimeoutHint')"
|
|
persistent-hint
|
|
type="number"
|
|
prepend-inner-icon="mdi-timer-outline"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12">
|
|
<VCard variant="tonal" class="pa-4">
|
|
<div class="d-flex flex-wrap align-center justify-space-between gap-3 mb-3">
|
|
<div>
|
|
<div class="text-subtitle-1 font-weight-medium">{{ t('notification.wechatclawbot.loginStatus') }}</div>
|
|
<div class="text-body-2 text-medium-emphasis">{{ wechatClawBotStatusText }}</div>
|
|
</div>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<VBtn
|
|
size="small"
|
|
variant="tonal"
|
|
:loading="wechatClawBotLoading"
|
|
@click.stop="fetchWechatClawBotStatus({ autoGenerateQrcode: true, autoRefreshExpired: true })"
|
|
>
|
|
{{ t('common.refresh') }}
|
|
</VBtn>
|
|
<VBtn
|
|
size="small"
|
|
color="primary"
|
|
variant="tonal"
|
|
:loading="wechatClawBotActionLoading"
|
|
@click.stop="refreshWechatClawBotQrcode"
|
|
>
|
|
{{ t('notification.wechatclawbot.refreshQrcode') }}
|
|
</VBtn>
|
|
<VBtn
|
|
size="small"
|
|
color="error"
|
|
variant="tonal"
|
|
:loading="wechatClawBotActionLoading"
|
|
:disabled="!wechatClawBotStatus?.connected"
|
|
@click.stop="logoutWechatClawBot"
|
|
>
|
|
{{ t('notification.wechatclawbot.logout') }}
|
|
</VBtn>
|
|
</div>
|
|
</div>
|
|
<VRow>
|
|
<VCol cols="12" md="5">
|
|
<div class="rounded text-center p-3 border h-100 d-flex align-center justify-center min-h-[16rem]">
|
|
<VImg
|
|
v-if="wechatClawBotQrImage"
|
|
:src="wechatClawBotQrImage"
|
|
width="220"
|
|
height="220"
|
|
class="mx-auto"
|
|
/>
|
|
<VProgressCircular v-else-if="wechatClawBotLoading" indeterminate color="primary" />
|
|
<div v-else class="text-body-2 text-medium-emphasis">
|
|
{{ t('notification.wechatclawbot.noQrcode') }}
|
|
</div>
|
|
</div>
|
|
</VCol>
|
|
<VCol cols="12" md="7">
|
|
<VAlert variant="tonal" :type="wechatClawBotStatus?.connected ? 'success' : 'info'" class="mb-3">
|
|
<div class="text-body-2">{{ t('notification.wechatclawbot.scanHint') }}</div>
|
|
<div v-if="wechatClawBotStatus?.account_id" class="mt-2">
|
|
{{ t('notification.wechatclawbot.accountId') }}: {{ wechatClawBotStatus.account_id }}
|
|
</div>
|
|
<div v-if="wechatClawBotStatus?.qrcode_updated_at" class="mt-2">
|
|
{{ t('notification.wechatclawbot.qrcodeUpdatedAt') }}:
|
|
{{ formatWechatClawBotTime(wechatClawBotStatus.qrcode_updated_at) }}
|
|
</div>
|
|
</VAlert>
|
|
<div class="text-subtitle-2 mb-2">{{ t('notification.wechatclawbot.knownTargets') }}</div>
|
|
<VList v-if="wechatClawBotStatus?.known_targets?.length" density="compact" class="border rounded">
|
|
<VListItem
|
|
v-for="item in wechatClawBotStatus.known_targets"
|
|
:key="item.userid"
|
|
:title="item.username || item.userid"
|
|
:subtitle="`${item.userid}${item.last_active ? ` · ${formatWechatClawBotTime(item.last_active)}` : ''}`"
|
|
/>
|
|
</VList>
|
|
<div v-else class="text-body-2 text-medium-emphasis">
|
|
{{ t('notification.wechatclawbot.noKnownTargets') }}
|
|
</div>
|
|
</VCol>
|
|
</VRow>
|
|
</VCard>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'telegram'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.TELEGRAM_TOKEN"
|
|
:label="t('notification.telegram.token')"
|
|
:hint="t('notification.telegram.tokenHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.TELEGRAM_CHAT_ID"
|
|
:label="t('notification.telegram.chatId')"
|
|
:hint="t('notification.telegram.chatIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-chat"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.TELEGRAM_USERS"
|
|
:label="t('notification.telegram.users')"
|
|
:placeholder="t('notification.telegram.usersPlaceholder')"
|
|
:hint="t('notification.telegram.usersHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-group"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.TELEGRAM_ADMINS"
|
|
:label="t('notification.telegram.admins')"
|
|
:placeholder="t('notification.telegram.adminsPlaceholder')"
|
|
:hint="t('notification.telegram.adminsHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-supervisor"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.API_URL"
|
|
:label="t('notification.telegram.apiUrl')"
|
|
:placeholder="t('notification.telegram.apiUrlPlaceholder')"
|
|
:hint="t('notification.telegram.apiUrlHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-web"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'slack'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.SLACK_OAUTH_TOKEN"
|
|
:label="t('notification.slack.oauthToken')"
|
|
:placeholder="t('notification.slack.oauthTokenPlaceholder')"
|
|
:hint="t('notification.slack.oauthTokenHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.SLACK_APP_TOKEN"
|
|
:label="t('notification.slack.appToken')"
|
|
:placeholder="t('notification.slack.appTokenPlaceholder')"
|
|
:hint="t('notification.slack.appTokenHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-application"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.SLACK_CHANNEL"
|
|
:label="t('notification.slack.channel')"
|
|
:placeholder="t('notification.slack.channelPlaceholder')"
|
|
:hint="t('notification.slack.channelHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-pound"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'discord'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.DISCORD_BOT_TOKEN"
|
|
:label="t('notification.discord.botToken')"
|
|
:hint="t('notification.discord.botTokenHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key-variant"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.DISCORD_GUILD_ID"
|
|
:label="t('notification.discord.guildId')"
|
|
:placeholder="t('notification.discord.guildIdPlaceholder')"
|
|
:hint="t('notification.discord.guildIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-pound"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.DISCORD_CHANNEL_ID"
|
|
:label="t('notification.discord.channelId')"
|
|
:placeholder="t('notification.discord.channelIdPlaceholder')"
|
|
:hint="t('notification.discord.channelIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-pound-box"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'synologychat'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.SYNOLOGYCHAT_WEBHOOK"
|
|
:label="t('notification.synologychat.webhook')"
|
|
:hint="t('notification.synologychat.webhookHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-webhook"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.SYNOLOGYCHAT_TOKEN"
|
|
:label="t('notification.synologychat.token')"
|
|
:hint="t('notification.synologychat.tokenHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'vocechat'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.VOCECHAT_HOST"
|
|
:label="t('notification.vocechat.host')"
|
|
:hint="t('notification.vocechat.hostHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-server"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.VOCECHAT_API_KEY"
|
|
:label="t('notification.vocechat.apiKey')"
|
|
:hint="t('notification.vocechat.apiKeyHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.VOCECHAT_CHANNEL_ID"
|
|
:label="t('notification.vocechat.channelId')"
|
|
:placeholder="t('notification.vocechat.channelIdPlaceholder')"
|
|
:hint="t('notification.vocechat.channelIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-pound"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'qqbot'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.QQ_APP_ID"
|
|
:label="t('notification.qqbot.appId')"
|
|
:hint="t('notification.qqbot.appIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-application"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.QQ_APP_SECRET"
|
|
:label="t('notification.qqbot.appSecret')"
|
|
:hint="t('notification.qqbot.appSecretHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-key"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.QQ_OPENID"
|
|
:label="t('notification.qqbot.openId')"
|
|
:placeholder="t('notification.qqbot.openIdPlaceholder')"
|
|
:hint="t('notification.qqbot.openIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.QQ_GROUP_OPENID"
|
|
:label="t('notification.qqbot.groupOpenId')"
|
|
:placeholder="t('notification.qqbot.groupOpenIdPlaceholder')"
|
|
:hint="t('notification.qqbot.groupOpenIdHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account-group"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else-if="notificationInfo.type == 'webpush'">
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:placeholder="t('notification.name')"
|
|
:hint="t('notification.nameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.config.WEBPUSH_USERNAME"
|
|
:label="t('notification.webpush.username')"
|
|
:hint="t('notification.webpush.usernameHint')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-account"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
<VRow v-else>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.type"
|
|
:label="t('notification.type')"
|
|
:hint="t('notification.customTypeHint')"
|
|
persistent-hint
|
|
active
|
|
prepend-inner-icon="mdi-cog"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField
|
|
v-model="notificationInfo.name"
|
|
:label="t('notification.name')"
|
|
:hint="t('notification.nameRequired')"
|
|
persistent-hint
|
|
prepend-inner-icon="mdi-label"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
</VForm>
|
|
</VCardText>
|
|
<VCardActions class="pt-3">
|
|
<VBtn @click="saveNotificationInfo" prepend-icon="mdi-content-save" class="px-5">
|
|
{{ t('common.confirm') }}
|
|
</VBtn>
|
|
</VCardActions>
|
|
</VCard>
|
|
</VDialog>
|
|
</div>
|
|
</template>
|