support webdav backup

This commit is contained in:
pompurin404 2024-08-16 20:43:34 +08:00
parent dbb50421a9
commit 9266d6b76e
No known key found for this signature in database
8 changed files with 639 additions and 217 deletions

View File

@ -22,7 +22,9 @@
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@mihomo-party/sysproxy": "^2.0.0",
"adm-zip": "^0.5.15",
"axios": "^1.7.3",
"webdav": "^5.7.1",
"ws": "^8.18.0",
"yaml": "^2.5.0"
},
@ -40,7 +42,6 @@
"@types/react-dom": "^18.3.0",
"@types/ws": "^8.5.12",
"@vitejs/plugin-react": "^4.3.1",
"adm-zip": "^0.5.15",
"autoprefixer": "^10.4.20",
"dayjs": "^1.11.12",
"electron": "^31.3.1",

View File

@ -17,9 +17,15 @@ importers:
'@mihomo-party/sysproxy':
specifier: ^2.0.0
version: 2.0.0
adm-zip:
specifier: ^0.5.15
version: 0.5.15
axios:
specifier: ^1.7.3
version: 1.7.3
webdav:
specifier: ^5.7.1
version: 5.7.1
ws:
specifier: ^8.18.0
version: 8.18.0
@ -66,9 +72,6 @@ importers:
'@vitejs/plugin-react':
specifier: ^4.3.1
version: 4.3.1(vite@5.3.5(@types/node@22.1.0))
adm-zip:
specifier: ^0.5.15
version: 0.5.15
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.4.41)
@ -273,6 +276,9 @@ packages:
resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==}
engines: {node: '>=6.9.0'}
'@buttercup/fetch@0.2.1':
resolution: {integrity: sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==}
'@develar/schema-utils@2.6.5':
resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
engines: {node: '>= 8.9.0'}
@ -2126,6 +2132,9 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base-64@1.0.0:
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@ -2179,6 +2188,9 @@ packages:
builder-util@25.0.3:
resolution: {integrity: sha512-eH5c1ukdY2xjtFQWQ6jlzEuXuqcuAVc3UQ6V6fdYu9Kg3CkDbCR82Mox42uaJDmee9WXSbP/88cOworFdOHPhw==}
byte-length@1.0.2:
resolution: {integrity: sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==}
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@ -2218,6 +2230,9 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@ -2359,6 +2374,9 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@ -2367,6 +2385,10 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
data-view-buffer@1.0.1:
resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
engines: {node: '>= 0.4'}
@ -2529,6 +2551,10 @@ packages:
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
entities@5.0.0:
resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==}
engines: {node: '>=0.12'}
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@ -2674,12 +2700,20 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-xml-parser@4.4.1:
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
hasBin: true
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@ -2726,6 +2760,10 @@ packages:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
@ -2907,6 +2945,9 @@ packages:
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
engines: {node: '>=10'}
hot-patcher@2.0.1:
resolution: {integrity: sha512-ECg1JFG0YzehicQaogenlcs2qg6WsXQsxtnbr1i696u5tLUjtJdQAh0u2g0Q5YV45f263Ta1GnUJsc8WIfJf4Q==}
http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
@ -2999,6 +3040,9 @@ packages:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
is-callable@1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'}
@ -3184,6 +3228,9 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
layerr@3.0.0:
resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==}
lazy-val@1.0.5:
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
@ -3286,6 +3333,9 @@ packages:
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
engines: {node: '>=10'}
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@ -3442,6 +3492,9 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
nested-property@4.0.0:
resolution: {integrity: sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==}
next-themes@0.3.0:
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
peerDependencies:
@ -3458,6 +3511,14 @@ packages:
node-api-version@0.2.0:
resolution: {integrity: sha512-fthTTsi8CxaBXMaBAD7ST2uylwvsnYxh2PfaScwpMhos6KlSFajXQPcM4ogNE1q2s3Lbz9GCGqeIHC+C6OZnKg==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-gyp@9.4.1:
resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==}
engines: {node: ^12.13 || ^14.13 || >=16}
@ -3576,6 +3637,9 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-posix@1.0.0:
resolution: {integrity: sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
@ -3708,6 +3772,9 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@ -3844,6 +3911,9 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
resedit@1.7.0:
resolution: {integrity: sha512-dbsZ0gk5opWPFlKMqvxCrLCuMZUVmsW3yTPT0tT4mYwo5fjQM8c4HMN9ZJt6dRDqDV/78m9SU4rv24PN4NiYaA==}
engines: {node: '>=12', npm: '>=6'}
@ -4070,6 +4140,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
@ -4244,6 +4317,13 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
url-join@5.0.0:
resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
use-callback-ref@1.3.2:
resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==}
engines: {node: '>=10'}
@ -4354,6 +4434,14 @@ packages:
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
webdav@5.7.1:
resolution: {integrity: sha512-JVPn3nLxXJfHSRvennHsOrDYjFLkilZ1Qlw8Ff6hpqp6AvkgF7a//aOh5wA4rMp+sLZ1Km0V+iv0LyO1FIwtXg==}
engines: {node: '>=16'}
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
@ -4590,6 +4678,10 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.7
to-fast-properties: 2.0.0
'@buttercup/fetch@0.2.1':
optionalDependencies:
node-fetch: 3.3.2
'@develar/schema-utils@2.6.5':
dependencies:
ajv: 6.12.6
@ -7415,6 +7507,8 @@ snapshots:
balanced-match@1.0.2: {}
base-64@1.0.0: {}
base64-js@1.5.1: {}
binary-extensions@2.3.0: {}
@ -7512,6 +7606,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
byte-length@1.0.2: {}
cac@6.7.14: {}
cacache@16.1.3:
@ -7574,6 +7670,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
charenc@0.0.2: {}
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@ -7705,10 +7803,14 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crypt@0.0.2: {}
cssesc@3.0.0: {}
csstype@3.1.3: {}
data-uri-to-buffer@4.0.1: {}
data-view-buffer@1.0.1:
dependencies:
call-bind: 1.0.7
@ -7923,6 +8025,8 @@ snapshots:
dependencies:
once: 1.4.0
entities@5.0.0: {}
env-paths@2.2.1: {}
err-code@2.0.3: {}
@ -8188,6 +8292,10 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-xml-parser@4.4.1:
dependencies:
strnum: 1.0.5
fastq@1.17.1:
dependencies:
reusify: 1.0.4
@ -8196,6 +8304,11 @@ snapshots:
dependencies:
pend: 1.2.0
fetch-blob@3.2.0:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.2.0
@ -8240,6 +8353,10 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.35
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
fraction.js@4.3.7: {}
framer-motion@11.3.21(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
@ -8449,6 +8566,8 @@ snapshots:
dependencies:
lru-cache: 6.0.0
hot-patcher@2.0.1: {}
http-cache-semantics@4.1.1: {}
http-proxy-agent@5.0.0:
@ -8553,6 +8672,8 @@ snapshots:
call-bind: 1.0.7
has-tostringtag: 1.0.2
is-buffer@1.1.6: {}
is-callable@1.2.7: {}
is-ci@3.0.1:
@ -8716,6 +8837,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
layerr@3.0.0: {}
lazy-val@1.0.5: {}
lazystream@1.0.1:
@ -8817,6 +8940,12 @@ snapshots:
escape-string-regexp: 4.0.0
optional: true
md5@2.3.0:
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: 1.1.6
merge2@1.4.1: {}
meta-json-schema@1.18.6: {}
@ -8959,6 +9088,8 @@ snapshots:
negotiator@0.6.3: {}
nested-property@4.0.0: {}
next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
@ -8975,6 +9106,14 @@ snapshots:
dependencies:
semver: 7.6.3
node-domexception@1.0.0: {}
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-gyp@9.4.1:
dependencies:
env-paths: 2.2.1
@ -9104,6 +9243,8 @@ snapshots:
path-parse@1.0.7: {}
path-posix@1.0.0: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
@ -9206,6 +9347,8 @@ snapshots:
punycode@2.3.1: {}
querystringify@2.2.0: {}
queue-microtask@1.2.3: {}
quick-lru@5.1.1: {}
@ -9358,6 +9501,8 @@ snapshots:
require-directory@2.1.1: {}
requires-port@1.0.0: {}
resedit@1.7.0:
dependencies:
pe-library: 0.4.0
@ -9635,6 +9780,8 @@ snapshots:
strip-json-comments@3.1.1: {}
strnum@1.0.5: {}
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.5
@ -9856,6 +10003,13 @@ snapshots:
dependencies:
punycode: 2.3.1
url-join@5.0.0: {}
url-parse@1.5.10:
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
use-callback-ref@1.3.2(@types/react@18.3.3)(react@18.3.1):
dependencies:
react: 18.3.1
@ -9933,6 +10087,25 @@ snapshots:
dependencies:
defaults: 1.0.4
web-streams-polyfill@3.3.3: {}
webdav@5.7.1:
dependencies:
'@buttercup/fetch': 0.2.1
base-64: 1.0.0
byte-length: 1.0.2
entities: 5.0.0
fast-xml-parser: 4.4.1
hot-patcher: 2.0.1
layerr: 3.0.0
md5: 2.3.0
minimatch: 9.0.5
nested-property: 4.0.0
node-fetch: 3.3.2
path-posix: 1.0.0
url-join: 5.0.0
url-parse: 1.5.10
which-boxed-primitive@1.0.2:
dependencies:
is-bigint: 1.0.4

View File

@ -0,0 +1,72 @@
import { getAppConfig } from '../config'
import AdmZip from 'adm-zip'
import {
appConfigPath,
controledMihomoConfigPath,
dataDir,
overrideConfigPath,
overrideDir,
profileConfigPath,
profilesDir
} from '../utils/dirs'
import { app } from 'electron'
export async function webdavBackup(): Promise<boolean> {
const webdav = await import('webdav')
const createClient = webdav.createClient
const { webdavUrl = '', webdavUsername = '', webdavPassword = '' } = await getAppConfig()
const zip = new AdmZip()
zip.addLocalFile(appConfigPath())
zip.addLocalFile(controledMihomoConfigPath())
zip.addLocalFile(profileConfigPath())
zip.addLocalFile(overrideConfigPath())
zip.addLocalFolder(profilesDir(), 'profiles')
zip.addLocalFolder(overrideDir(), 'override')
const zipFileName = `backup-${new Date().toISOString().replace(/:/g, '-')}.zip`
const client = createClient(webdavUrl, {
username: webdavUsername,
password: webdavPassword
})
try {
await client.createDirectory('mihomo-party')
} catch {
// ignore
}
return await client.putFileContents(`mihomo-party/${zipFileName}`, zip.toBuffer())
}
export async function webdavRestore(filename: string): Promise<void> {
const webdav = await import('webdav')
const createClient = webdav.createClient
const { webdavUrl = '', webdavUsername = '', webdavPassword = '' } = await getAppConfig()
const client = createClient(webdavUrl, {
username: webdavUsername,
password: webdavPassword
})
const zipData = await client.getFileContents(`/mihomo-party/${filename}`)
const zip = new AdmZip(zipData)
zip.extractAllTo(dataDir(), true)
app.relaunch()
app.quit()
}
export async function listWebdavBackups(): Promise<string[]> {
const webdav = await import('webdav')
const createClient = webdav.createClient
const { webdavUrl = '', webdavUsername = '', webdavPassword = '' } = await getAppConfig()
const client = createClient(webdavUrl, {
username: webdavUsername,
password: webdavPassword
})
const files = await client.getDirectoryContents('mihomo-party', { glob: '*.zip' })
if (Array.isArray(files)) {
return files.map((file) => file.basename)
} else {
return files.data.map((file) => file.basename)
}
}

View File

@ -50,6 +50,7 @@ import { checkUpdate } from '../resolve/autoUpdater'
import { getFilePath, openUWPTool, readTextFile, setupFirewall } from '../sys/misc'
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
import { isPortable, setPortable } from './dirs'
import { listWebdavBackups, webdavBackup, webdavRestore } from '../resolve/backup'
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -137,5 +138,8 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('setupFirewall', ipcErrorWrapper(setupFirewall))
ipcMain.handle('setPortable', (_e, portable) => ipcErrorWrapper(setPortable)(portable))
ipcMain.handle('isPortable', isPortable)
ipcMain.handle('webdavBackup', ipcErrorWrapper(webdavBackup))
ipcMain.handle('webdavRestore', (_e, filename) => ipcErrorWrapper(webdavRestore)(filename))
ipcMain.handle('listWebdavBackups', ipcErrorWrapper(listWebdavBackups))
ipcMain.handle('quitApp', () => app.quit())
}

View File

@ -0,0 +1,59 @@
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@nextui-org/react'
import { webdavRestore } from '@renderer/utils/ipc'
import React, { useState } from 'react'
interface Props {
filenames: string[]
onClose: () => void
}
const WebdavRestoreModal: React.FC<Props> = (props) => {
const { filenames, onClose } = props
const [restoring, setRestoring] = useState(false)
return (
<Modal
backdrop="blur"
hideCloseButton
isOpen={true}
onOpenChange={onClose}
scrollBehavior="inside"
>
<ModalContent>
<ModalHeader className="flex"></ModalHeader>
<ModalBody>
{filenames.length === 0 ? (
<div className="flex justify-center"></div>
) : (
filenames.map((filename) => (
<Button
size="sm"
fullWidth
key={filename}
isLoading={restoring}
variant="flat"
onPress={async () => {
setRestoring(true)
try {
await webdavRestore(filename)
} catch (e) {
alert(`恢复失败: ${e}`)
} finally {
setRestoring(false)
}
}}
>
{filename}
</Button>
))
)}
</ModalBody>
<ModalFooter>
<Button variant="light" onPress={onClose}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}
export default WebdavRestoreModal

View File

@ -12,7 +12,9 @@ import {
patchControledMihomoConfig,
isPortable,
setPortable,
restartCore
restartCore,
webdavBackup,
listWebdavBackups
} from '@renderer/utils/ipc'
import { IoLogoGithub } from 'react-icons/io5'
import { platform, version } from '@renderer/utils/init'
@ -20,6 +22,7 @@ import useSWR from 'swr'
import { Key, useState } from 'react'
import debounce from '@renderer/utils/debounce'
import { useTheme } from 'next-themes'
import WebdavRestoreModal from '@renderer/components/settings/webdav-restore-modal'
const Settings: React.FC = () => {
const { setTheme } = useTheme()
@ -37,8 +40,15 @@ const Settings: React.FC = () => {
autoCheckUpdate,
userAgent,
autoCloseConnection = true,
appTheme = 'system'
appTheme = 'system',
webdavUrl,
webdavUsername,
webdavPassword
} = appConfig || {}
const [backuping, setBackuping] = useState(false)
const [restoring, setRestoring] = useState(false)
const [filenames, setFilenames] = useState<string[]>([])
const [restoreOpen, setRestoreOpen] = useState(false)
const [url, setUrl] = useState(delayTestUrl)
const setUrlDebounce = debounce((v: string) => {
patchAppConfig({ delayTestUrl: v })
@ -47,7 +57,10 @@ const Settings: React.FC = () => {
const setUaDebounce = debounce((v: string) => {
patchAppConfig({ userAgent: v })
}, 500)
const [webdav, setWebdav] = useState({ webdavUrl, webdavUsername, webdavPassword })
const setWebdavDebounce = debounce(({ webdavUrl, webdavUsername, webdavPassword }) => {
patchAppConfig({ webdavUrl, webdavUsername, webdavPassword })
}, 500)
const onThemeChange = (key: Key, type: 'theme' | 'color'): void => {
const [theme, color] = appTheme.split('-')
@ -72,238 +85,323 @@ const Settings: React.FC = () => {
}
}
const handleBackup = async (): Promise<void> => {
setBackuping(true)
try {
await webdavBackup()
new window.Notification('备份成功', { body: '备份文件已上传至WebDav' })
} catch (e) {
alert(e)
} finally {
setBackuping(false)
}
}
const handleRestore = async (): Promise<void> => {
try {
setRestoring(true)
const filenames = await listWebdavBackups()
setRestoring(false)
setFilenames(filenames)
setRestoreOpen(true)
} catch (e) {
alert(`获取备份列表失败: ${e}`)
}
}
return (
<BasePage
title="应用设置"
header={
<Button
isIconOnly
size="sm"
onPress={() => {
window.open('https://github.com/pompurin404/mihomo-party')
}}
>
<IoLogoGithub className="text-lg" />
</Button>
}
>
<SettingCard>
<SettingItem title="开机自启" divider>
<Switch
<>
{restoreOpen && (
<WebdavRestoreModal filenames={filenames} onClose={() => setRestoreOpen(false)} />
)}
<BasePage
title="应用设置"
header={
<Button
isIconOnly
size="sm"
isSelected={enable}
onValueChange={async (v) => {
try {
if (v) {
await enableAutoRun()
} else {
await disableAutoRun()
}
} catch (e) {
alert(e)
} finally {
mutateEnable()
}
onPress={() => {
window.open('https://github.com/pompurin404/mihomo-party')
}}
/>
</SettingItem>
<SettingItem title="自动检查更新" divider>
<Switch
size="sm"
isSelected={autoCheckUpdate}
onValueChange={(v) => {
patchAppConfig({ autoCheckUpdate: v })
}}
/>
</SettingItem>
<SettingItem title="静默启动" divider>
<Switch
size="sm"
isSelected={silentStart}
onValueChange={(v) => {
patchAppConfig({ silentStart: v })
}}
/>
</SettingItem>
{platform === 'darwin' && (
<>
<SettingItem title="显示Dock图标" divider>
<Switch
size="sm"
isSelected={useDockIcon}
onValueChange={async (v) => {
await patchAppConfig({ useDockIcon: v })
}}
/>
</SettingItem>
<SettingItem title="显示网速信息" divider>
<Switch
size="sm"
isSelected={showTraffic}
onValueChange={async (v) => {
await patchAppConfig({ showTraffic: v })
await restartCore()
}}
/>
</SettingItem>
</>
)}
{platform === 'win32' && (
<SettingItem title="数据存储路径" divider>
<Select
className="w-[150px]"
>
<IoLogoGithub className="text-lg" />
</Button>
}
>
<SettingCard>
<SettingItem title="开机自启" divider>
<Switch
size="sm"
selectedKeys={new Set([portable ? 'portable' : 'data'])}
onSelectionChange={async (v) => {
isSelected={enable}
onValueChange={async (v) => {
try {
await setPortable(v.currentKey === 'portable')
if (v) {
await enableAutoRun()
} else {
await disableAutoRun()
}
} catch (e) {
alert(e)
} finally {
mutatePortable()
mutateEnable()
}
}}
>
<SelectItem key="data">AppData</SelectItem>
<SelectItem key="portable"></SelectItem>
</Select>
/>
</SettingItem>
)}
<SettingItem title="背景色" divider={appTheme !== 'system'}>
<Tabs
size="sm"
color="primary"
selectedKey={appTheme.split('-')[0]}
onSelectionChange={(key) => {
onThemeChange(key, 'theme')
}}
>
<Tab key="system" title="自动" />
<Tab key="dark" title="深色" />
<Tab key="gray" title="灰色" />
<Tab key="light" title="浅色" />
</Tabs>
</SettingItem>
{appTheme !== 'system' && (
<SettingItem title="主题色">
<SettingItem title="自动检查更新" divider>
<Switch
size="sm"
isSelected={autoCheckUpdate}
onValueChange={(v) => {
patchAppConfig({ autoCheckUpdate: v })
}}
/>
</SettingItem>
<SettingItem title="静默启动" divider>
<Switch
size="sm"
isSelected={silentStart}
onValueChange={(v) => {
patchAppConfig({ silentStart: v })
}}
/>
</SettingItem>
{platform === 'darwin' && (
<>
<SettingItem title="显示Dock图标" divider>
<Switch
size="sm"
isSelected={useDockIcon}
onValueChange={async (v) => {
await patchAppConfig({ useDockIcon: v })
}}
/>
</SettingItem>
<SettingItem title="显示网速信息" divider>
<Switch
size="sm"
isSelected={showTraffic}
onValueChange={async (v) => {
await patchAppConfig({ showTraffic: v })
await restartCore()
}}
/>
</SettingItem>
</>
)}
{platform === 'win32' && (
<SettingItem title="数据存储路径" divider>
<Select
className="w-[150px]"
size="sm"
selectedKeys={new Set([portable ? 'portable' : 'data'])}
onSelectionChange={async (v) => {
try {
await setPortable(v.currentKey === 'portable')
} catch (e) {
alert(e)
} finally {
mutatePortable()
}
}}
>
<SelectItem key="data">AppData</SelectItem>
<SelectItem key="portable"></SelectItem>
</Select>
</SettingItem>
)}
<SettingItem title="背景色" divider={appTheme !== 'system'}>
<Tabs
size="sm"
color="primary"
selectedKey={appTheme.split('-')[1] || 'blue'}
selectedKey={appTheme.split('-')[0]}
onSelectionChange={(key) => {
onThemeChange(key, 'color')
onThemeChange(key, 'theme')
}}
>
<Tab key="blue" title="蓝色" />
<Tab key="pink" title="粉色" />
<Tab key="green" title="绿色" />
<Tab key="system" title="自动" />
<Tab key="dark" title="深色" />
<Tab key="gray" title="灰色" />
<Tab key="light" title="浅色" />
</Tabs>
</SettingItem>
)}
</SettingCard>
<SettingCard>
<SettingItem title="订阅拉取 UA" divider>
<Input
size="sm"
className="w-[60%]"
value={ua}
placeholder="默认 clash-meta"
onValueChange={(v) => {
setUa(v)
setUaDebounce(v)
}}
></Input>
</SettingItem>
<SettingItem title="延迟测试地址" divider>
<Input
size="sm"
className="w-[60%]"
value={url}
placeholder="默认https://www.gstatic.com/generate_204"
onValueChange={(v) => {
setUrl(v)
setUrlDebounce(v)
}}
></Input>
</SettingItem>
<SettingItem title="延迟测试超时时间" divider>
<Input
type="number"
size="sm"
className="w-[60%]"
value={delayTestTimeout?.toString()}
placeholder="默认5000"
onValueChange={(v) => {
patchAppConfig({ delayTestTimeout: parseInt(v) })
}}
/>
</SettingItem>
<SettingItem title="接管DNS设置" divider>
<Switch
size="sm"
isSelected={controlDns}
onValueChange={async (v) => {
await patchAppConfig({ controlDns: v })
await patchControledMihomoConfig({})
}}
/>
</SettingItem>
<SettingItem title="接管域名嗅探设置" divider>
<Switch
size="sm"
isSelected={controlSniff}
onValueChange={async (v) => {
await patchAppConfig({ controlSniff: v })
await patchControledMihomoConfig({})
}}
/>
</SettingItem>
<SettingItem title="自动断开连接">
<Switch
size="sm"
isSelected={autoCloseConnection}
onValueChange={(v) => {
patchAppConfig({ autoCloseConnection: v })
}}
/>
</SettingItem>
</SettingCard>
<SettingCard>
<SettingItem title="检查更新" divider>
<Button
size="sm"
onPress={async () => {
try {
const version = await checkUpdate()
{appTheme !== 'system' && (
<SettingItem title="主题色">
<Tabs
size="sm"
color="primary"
selectedKey={appTheme.split('-')[1] || 'blue'}
onSelectionChange={(key) => {
onThemeChange(key, 'color')
}}
>
<Tab key="blue" title="蓝色" />
<Tab key="pink" title="粉色" />
<Tab key="green" title="绿色" />
</Tabs>
</SettingItem>
)}
</SettingCard>
<SettingCard>
<SettingItem title="WebDav地址" divider>
<Input
size="sm"
className="w-[60%]"
value={webdav.webdavUrl}
onValueChange={(v) => {
setWebdav({ ...webdav, webdavUrl: v })
setWebdavDebounce({ ...webdav, webdavUrl: v })
}}
/>
</SettingItem>
<SettingItem title="WebDav用户名" divider>
<Input
size="sm"
className="w-[60%]"
value={webdav.webdavUsername}
onValueChange={(v) => {
setWebdav({ ...webdav, webdavUsername: v })
setWebdavDebounce({ ...webdav, webdavUsername: v })
}}
/>
</SettingItem>
<SettingItem title="WebDav密码" divider>
<Input
size="sm"
className="w-[60%]"
type="password"
value={webdav.webdavPassword}
onValueChange={(v) => {
setWebdav({ ...webdav, webdavPassword: v })
setWebdavDebounce({ ...webdav, webdavPassword: v })
}}
/>
</SettingItem>
<div className="flex justify0between">
<Button
isLoading={backuping}
fullWidth
size="sm"
className="mr-1"
onPress={handleBackup}
>
</Button>
<Button
isLoading={restoring}
fullWidth
size="sm"
className="ml-1"
onPress={handleRestore}
>
</Button>
</div>
</SettingCard>
<SettingCard>
<SettingItem title="订阅拉取 UA" divider>
<Input
size="sm"
className="w-[60%]"
value={ua}
placeholder="默认 clash-meta"
onValueChange={(v) => {
setUa(v)
setUaDebounce(v)
}}
></Input>
</SettingItem>
<SettingItem title="延迟测试地址" divider>
<Input
size="sm"
className="w-[60%]"
value={url}
placeholder="默认https://www.gstatic.com/generate_204"
onValueChange={(v) => {
setUrl(v)
setUrlDebounce(v)
}}
></Input>
</SettingItem>
<SettingItem title="延迟测试超时时间" divider>
<Input
type="number"
size="sm"
className="w-[60%]"
value={delayTestTimeout?.toString()}
placeholder="默认5000"
onValueChange={(v) => {
patchAppConfig({ delayTestTimeout: parseInt(v) })
}}
/>
</SettingItem>
<SettingItem title="接管DNS设置" divider>
<Switch
size="sm"
isSelected={controlDns}
onValueChange={async (v) => {
await patchAppConfig({ controlDns: v })
await patchControledMihomoConfig({})
}}
/>
</SettingItem>
<SettingItem title="接管域名嗅探设置" divider>
<Switch
size="sm"
isSelected={controlSniff}
onValueChange={async (v) => {
await patchAppConfig({ controlSniff: v })
await patchControledMihomoConfig({})
}}
/>
</SettingItem>
<SettingItem title="自动断开连接">
<Switch
size="sm"
isSelected={autoCloseConnection}
onValueChange={(v) => {
patchAppConfig({ autoCloseConnection: v })
}}
/>
</SettingItem>
</SettingCard>
<SettingCard>
<SettingItem title="检查更新" divider>
<Button
size="sm"
onPress={async () => {
try {
const version = await checkUpdate()
if (version) {
new window.Notification(`v${version}版本已发布`, {
body: '点击前往下载'
}).onclick = (): void => {
open(`https://github.com/pompurin404/mihomo-party/releases/tag/v${version}`)
if (version) {
new window.Notification(`v${version}版本已发布`, {
body: '点击前往下载'
}).onclick = (): void => {
open(`https://github.com/pompurin404/mihomo-party/releases/tag/v${version}`)
}
} else {
new window.Notification('当前已是最新版本', { body: '无需更新' })
}
} else {
new window.Notification('当前已是最新版本', { body: '无需更新' })
} catch (e) {
alert(e)
}
} catch (e) {
alert(e)
}
}}
>
</Button>
</SettingItem>
<SettingItem title="退出应用" divider>
<Button size="sm" onPress={quitApp}>
退
</Button>
</SettingItem>
<SettingItem title="应用版本">
<div>v{version}</div>
</SettingItem>
</SettingCard>
</BasePage>
}}
>
</Button>
</SettingItem>
<SettingItem title="退出应用" divider>
<Button size="sm" onPress={quitApp}>
退
</Button>
</SettingItem>
<SettingItem title="应用版本">
<div>v{version}</div>
</SettingItem>
</SettingCard>
</BasePage>
</>
)
}

View File

@ -253,6 +253,18 @@ export async function isPortable(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('isPortable'))
}
export async function webdavBackup(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('webdavBackup'))
}
export async function webdavRestore(filename: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('webdavRestore', filename))
}
export async function listWebdavBackups(): Promise<string[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('listWebdavBackups'))
}
export async function quitApp(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('quitApp'))
}

View File

@ -218,6 +218,9 @@ interface IAppConfig {
controlSniff?: boolean
useDockIcon?: boolean
showTraffic?: boolean
webdavUrl?: string
webdavUsername?: string
webdavPassword?: string
useNameserverPolicy: boolean
nameserverPolicy: { [key: string]: string | string[] }
}