auto sync, dep updates, other stuff

This commit is contained in:
Tanner Sommers 2024-08-13 18:10:30 -05:00
parent 0577a0311a
commit c76ff2ac36
19 changed files with 1217 additions and 342 deletions

12
debug.log Normal file
View File

@ -0,0 +1,12 @@
[0813/143821.763:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143822.005:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143823.091:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143823.102:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143823.114:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143823.926:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143914.910:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143915.186:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143917.426:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143917.460:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143917.472:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143917.910:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)

155
package-lock.json generated
View File

@ -1,27 +1,29 @@
{
"name": "fanslysync-desktop",
"version": "0.0.1",
"version": "0.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fanslysync-desktop",
"version": "0.0.1",
"version": "0.1.3",
"dependencies": {
"@tauri-apps/api": "^2.0.0-rc.0",
"@tauri-apps/plugin-autostart": "^2.0.0-rc.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.0",
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
"@tauri-apps/plugin-log": "^2.0.0-rc.0",
"@tauri-apps/plugin-notification": "^2.0.0-rc.0",
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
"@tauri-apps/plugin-updater": "^2.0.0-rc.0"
"@tauri-apps/plugin-updater": "^2.0.0-rc.0",
"svelte-french-toast": "^1.2.0"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tauri-apps/cli": "^2.0.0-rc.0",
"@tauri-apps/cli": "^2.0.0-rc.3",
"@types/eslint": "^8.56.7",
"autoprefixer": "^10.4.19",
"eslint": "^9.0.0",
@ -56,7 +58,6 @@
"version": "2.3.0",
"resolved": "https://npm.hep.gg/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@ -610,7 +611,6 @@
"version": "0.3.5",
"resolved": "https://npm.hep.gg/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@ -624,7 +624,6 @@
"version": "3.1.2",
"resolved": "https://npm.hep.gg/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -633,7 +632,6 @@
"version": "1.2.1",
"resolved": "https://npm.hep.gg/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
@ -641,14 +639,12 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://npm.hep.gg/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://npm.hep.gg/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@ -1020,9 +1016,9 @@
}
},
"node_modules/@tauri-apps/cli": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli/-/cli-2.0.0-rc.0.tgz",
"integrity": "sha512-X9N/R7e3zeTpU0HgQi2kaNYncMFoSTzXstnUyGOcKrJcCkR4ebbA2nYqhGvIPfglLKHNFRTZwfPNhixx2Ftxxg==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli/-/cli-2.0.0-rc.3.tgz",
"integrity": "sha512-iNF95pieBmverl1EmQyqh+fhcIClS544fN5Ex5lAbYLTiHZ/gm3lOfVBhF6NPaKd/sfLuy7K1tfDXlHztBfANw==",
"dev": true,
"bin": {
"tauri": "tauri.js"
@ -1035,22 +1031,22 @@
"url": "https://opencollective.com/tauri"
},
"optionalDependencies": {
"@tauri-apps/cli-darwin-arm64": "2.0.0-rc.0",
"@tauri-apps/cli-darwin-x64": "2.0.0-rc.0",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-rc.0",
"@tauri-apps/cli-linux-arm64-gnu": "2.0.0-rc.0",
"@tauri-apps/cli-linux-arm64-musl": "2.0.0-rc.0",
"@tauri-apps/cli-linux-x64-gnu": "2.0.0-rc.0",
"@tauri-apps/cli-linux-x64-musl": "2.0.0-rc.0",
"@tauri-apps/cli-win32-arm64-msvc": "2.0.0-rc.0",
"@tauri-apps/cli-win32-ia32-msvc": "2.0.0-rc.0",
"@tauri-apps/cli-win32-x64-msvc": "2.0.0-rc.0"
"@tauri-apps/cli-darwin-arm64": "2.0.0-rc.3",
"@tauri-apps/cli-darwin-x64": "2.0.0-rc.3",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-rc.3",
"@tauri-apps/cli-linux-arm64-gnu": "2.0.0-rc.3",
"@tauri-apps/cli-linux-arm64-musl": "2.0.0-rc.3",
"@tauri-apps/cli-linux-x64-gnu": "2.0.0-rc.3",
"@tauri-apps/cli-linux-x64-musl": "2.0.0-rc.3",
"@tauri-apps/cli-win32-arm64-msvc": "2.0.0-rc.3",
"@tauri-apps/cli-win32-ia32-msvc": "2.0.0-rc.3",
"@tauri-apps/cli-win32-x64-msvc": "2.0.0-rc.3"
}
},
"node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-rc.0.tgz",
"integrity": "sha512-jpQc++6ESFUuBpoXvJNPTRtrmToMIByOynZ4K5SAwI9RkKUikSaDmlRZV0GisOVPT7TT08khk7MPtAQeFyjqwA==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-rc.3.tgz",
"integrity": "sha512-szYCSr/ChbCF+S6Wnr15TYpI2cZR07d+AQOiFGuScP0preM8Pbsk/sb0hfLwqzepjVFFNVWQba9sG7FEW2Y2XA==",
"cpu": [
"arm64"
],
@ -1064,9 +1060,9 @@
}
},
"node_modules/@tauri-apps/cli-darwin-x64": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-rc.0.tgz",
"integrity": "sha512-EnqsgcNUwH29YjQ4JdBID5kILQQdIJgZ36VQgtju/BBvnd1lr+6Bswdk1/4y41hhAVz9WyHeMCnTH33CSKvNxw==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-rc.3.tgz",
"integrity": "sha512-BJv6EJOY1DJbRzVtfg8CcBAlnS5OjhBAc5YKjh4BT7EyOcop8HStBSxhL6yjWrUP7eLR1iIsW/uSehVJwzYIdQ==",
"cpu": [
"x64"
],
@ -1080,9 +1076,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-rc.0.tgz",
"integrity": "sha512-YKg/qThAmQYRToiV8lx/DvMf0VaUJAutIfv8ALq9Or4zdg7mZlAlKvF+7lZQQRmKhQ62+0gnoJS2HyN8feaagw==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-rc.3.tgz",
"integrity": "sha512-fwx805/xL4sF/EdMYqcUHQHzMYwo+OVTBTz5x/JWK8D57rnmLHAP+ZhnfFsZQLRo2QRT2l1Ye3bDyU+QRA1JFA==",
"cpu": [
"arm"
],
@ -1096,9 +1092,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-rc.0.tgz",
"integrity": "sha512-UnflWuNhH/u41GSmG1jm6qhWUM4o1AB463QCaf2fAjLo5GWiI78U6rJOOXZvOOmWIQuzsmAUZjNDORWW13+osA==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-rc.3.tgz",
"integrity": "sha512-3KauzO1Ls4kuY0nr82S4X8XFxlQAMN+Mqp8LLqvQ+PPMp92XQAkPH7osQdoHIEoW5gsE69U2JaiQ5tHSqNM9og==",
"cpu": [
"arm64"
],
@ -1112,9 +1108,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-rc.0.tgz",
"integrity": "sha512-sWRaDyb332gtHWHr0KvoLzkzvXqRpZ0vpQYxKF2/mIZtaUuMtU56GmRwFVX4VjQYgWb3yWmzTr+tEKjCjXbjng==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-rc.3.tgz",
"integrity": "sha512-ngHS0foffm1xO5gqnDKGeYMKj8ceGmrFP5dDldoaaMQubw1SyFa0pRUjb7fZSYiO7F4SOSa8NYeMqlF9peZmnQ==",
"cpu": [
"arm64"
],
@ -1128,9 +1124,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-rc.0.tgz",
"integrity": "sha512-1dokW+9ZfhwzwYz42jYR5A0/VnGoEGVW7HyN1N6KvHeL1FpSbh0LoTsDODjVjZSjXd62+Aac/stbJqBZChQWbA==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-rc.3.tgz",
"integrity": "sha512-0/am9pVvuUHGmz32M8ffz1fpLnc08j3nzcRe5wUdL2AxfT+wKMII+Dn99GtCVgcdDW4jSXDMRUwrBkGocGC2OA==",
"cpu": [
"x64"
],
@ -1144,9 +1140,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-musl": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-rc.0.tgz",
"integrity": "sha512-7tb34p3vLEGS4pGRpmxcz7eq37yD7DVe4XFfgj8ZO0KsaRBVivzNK2snNDLnd0dVZcx/lJjZc8Cf0B5W1/x/Jw==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-rc.3.tgz",
"integrity": "sha512-r7mRi8q8TqTFVjb9kAsU7IgwUgno2s8Ip4xwq9psQhlRE3JGEZQmSEcy1jqTjfl6KFh6lJcDR7l+9/EMhL/D3Q==",
"cpu": [
"x64"
],
@ -1160,9 +1156,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-rc.0.tgz",
"integrity": "sha512-Ilw3Gro8ZBew9yfBXcaET9lLj0g4jehWDMH6j2Wdje1rnvBbQShMaSn3WNMfJypyet5nqM88nGA3YipzmVcIBA==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-rc.3.tgz",
"integrity": "sha512-2J6KjmDIQCw6HF1X6/yPcd+JLl7pxrH2zVMGmNllaoWhHeByvRobqFWnT7gcdHaA3dGTo432CwWvOgTgrINQpQ==",
"cpu": [
"arm64"
],
@ -1176,9 +1172,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-rc.0.tgz",
"integrity": "sha512-AWzgDlvv0BssWH3aNsDXLRkKbGynWdm5X6DenSQKtZm5dmDBZsNlZRMgkyPb8WSHK/7ARznKs2OdnkuHWuh9ww==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-rc.3.tgz",
"integrity": "sha512-8q75CsHDSEDdgi6xPwim+BaQZFCswK2Dn/qL38V3Mh9kmVvC8oGJMPC66bC20dF+v3KWeFm2FNNGQqOSXCveHg==",
"cpu": [
"ia32"
],
@ -1192,9 +1188,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-rc.0.tgz",
"integrity": "sha512-zmXDUkBmzZ6lk6jT6AoOAE2w+kmkLX5uf/vaJHHzE6WsrZ6UymH0HTLwbRXhaplvpnzXGVyLuSlv3/BK6Q69aQ==",
"version": "2.0.0-rc.3",
"resolved": "https://npm.hep.gg/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-rc.3.tgz",
"integrity": "sha512-qeBRJYalahxEXolekcpZJ/HBrIJacG2NWJBGhhi797mIwnbmlpbHMc8blIJtNNNwVUb2BjXuxKQVfojQ5YYrcg==",
"cpu": [
"x64"
],
@ -1207,6 +1203,14 @@
"node": ">= 10"
}
},
"node_modules/@tauri-apps/plugin-autostart": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/plugin-autostart/-/plugin-autostart-2.0.0-rc.0.tgz",
"integrity": "sha512-V49lm++vhrMPPDGMtmOcbJLF4TYu78ZmAiMhyd4FFnbYlgin6ZTjiMCFEl4JKVy2lqP3C8DQvXf/gkUMuER7Iw==",
"dependencies": {
"@tauri-apps/api": "^2.0.0-rc.0"
}
},
"node_modules/@tauri-apps/plugin-clipboard-manager": {
"version": "2.0.0-rc.0",
"resolved": "https://npm.hep.gg/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.0.tgz",
@ -1274,8 +1278,7 @@
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://npm.hep.gg/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
@ -1511,7 +1514,6 @@
"version": "8.12.1",
"resolved": "https://npm.hep.gg/acorn/-/acorn-8.12.1.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
@ -1603,7 +1605,6 @@
"version": "5.3.0",
"resolved": "https://npm.hep.gg/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"dependencies": {
"dequal": "^2.0.3"
}
@ -1658,7 +1659,6 @@
"version": "4.1.0",
"resolved": "https://npm.hep.gg/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@ -1838,7 +1838,6 @@
"version": "1.0.4",
"resolved": "https://npm.hep.gg/code-red/-/code-red-1.0.4.tgz",
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1",
@ -1907,7 +1906,6 @@
"version": "2.3.1",
"resolved": "https://npm.hep.gg/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"dev": true,
"dependencies": {
"mdn-data": "2.0.30",
"source-map-js": "^1.0.1"
@ -1964,7 +1962,6 @@
"version": "2.0.3",
"resolved": "https://npm.hep.gg/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"engines": {
"node": ">=6"
}
@ -2291,7 +2288,6 @@
"version": "3.0.3",
"resolved": "https://npm.hep.gg/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0"
}
@ -2725,7 +2721,6 @@
"version": "3.0.2",
"resolved": "https://npm.hep.gg/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"dev": true,
"dependencies": {
"@types/estree": "*"
}
@ -2845,8 +2840,7 @@
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://npm.hep.gg/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
},
"node_modules/locate-path": {
"version": "6.0.0",
@ -2879,7 +2873,6 @@
"version": "0.30.10",
"resolved": "https://npm.hep.gg/magic-string/-/magic-string-0.30.10.tgz",
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
@ -2887,8 +2880,7 @@
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://npm.hep.gg/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"dev": true
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
},
"node_modules/merge2": {
"version": "1.4.1",
@ -3200,7 +3192,6 @@
"version": "3.1.0",
"resolved": "https://npm.hep.gg/periscopic/-/periscopic-3.1.0.tgz",
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^3.0.0",
@ -3729,7 +3720,6 @@
"version": "1.2.0",
"resolved": "https://npm.hep.gg/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -3942,7 +3932,6 @@
"version": "4.2.18",
"resolved": "https://npm.hep.gg/svelte/-/svelte-4.2.18.tgz",
"integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15",
@ -4055,6 +4044,17 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/svelte-french-toast": {
"version": "1.2.0",
"resolved": "https://npm.hep.gg/svelte-french-toast/-/svelte-french-toast-1.2.0.tgz",
"integrity": "sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==",
"dependencies": {
"svelte-writable-derived": "^3.1.0"
},
"peerDependencies": {
"svelte": "^3.57.0 || ^4.0.0"
}
},
"node_modules/svelte-hmr": {
"version": "0.16.0",
"resolved": "https://npm.hep.gg/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
@ -4129,6 +4129,17 @@
}
}
},
"node_modules/svelte-writable-derived": {
"version": "3.1.1",
"resolved": "https://npm.hep.gg/svelte-writable-derived/-/svelte-writable-derived-3.1.1.tgz",
"integrity": "sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw==",
"funding": {
"url": "https://ko-fi.com/pixievoltno1"
},
"peerDependencies": {
"svelte": "^3.2.1 || ^4.0.0-next.1 || ^5.0.0-next.94"
}
},
"node_modules/tailwindcss": {
"version": "3.4.6",
"resolved": "https://npm.hep.gg/tailwindcss/-/tailwindcss-3.4.6.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "fanslysync-desktop",
"version": "0.1.3",
"version": "0.1.4",
"private": true,
"scripts": {
"dev": "vite dev",
@ -17,7 +17,7 @@
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@tauri-apps/cli": "^2.0.0-rc.0",
"@tauri-apps/cli": "^2.0.0-rc.3",
"@types/eslint": "^8.56.7",
"autoprefixer": "^10.4.19",
"eslint": "^9.0.0",
@ -38,11 +38,13 @@
"type": "module",
"dependencies": {
"@tauri-apps/api": "^2.0.0-rc.0",
"@tauri-apps/plugin-autostart": "^2.0.0-rc.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.0",
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
"@tauri-apps/plugin-log": "^2.0.0-rc.0",
"@tauri-apps/plugin-notification": "^2.0.0-rc.0",
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
"@tauri-apps/plugin-updater": "^2.0.0-rc.0"
"@tauri-apps/plugin-updater": "^2.0.0-rc.0",
"svelte-french-toast": "^1.2.0"
}
}

391
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.3"
version = "0.1.4"
description = "A Tauri App"
authors = ["SticksDev"]
license = "MIT"
@ -12,26 +12,29 @@ rust-version = "1.80"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.0-rc", features = [] }
tauri-build = { version = "2.0.0-rc.2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-rc", features = [] }
tauri = { version = "2.0.0-rc.2", features = [] }
dirs = "5.0.1"
reqwest = { version = "0.11.18", features = ["json"] }
lazy_static = "1.5.0"
tokio = { version = "1.29.1", features = ["full"] }
tokio-macros = "2.3.0"
tauri-plugin-os = "2.0.0-alpha.2"
tauri-plugin-dialog = "2.0.0-alpha.2"
tauri-plugin-clipboard-manager = "2.0.0-alpha.2"
tauri-plugin-notification = "2.0.0-alpha.3"
tauri-plugin-updater = "2.0.0-alpha.3"
tauri-plugin-log = "2.0.0-rc.0"
tauri-plugin-os = { version = "2.0.0-rc" }
tauri-plugin-dialog = { version = "2.0.0-rc" }
tauri-plugin-clipboard-manager = { version = "2.0.0-rc" }
tauri-plugin-notification = { version = "2.0.0-rc" }
tauri-plugin-updater = { version = "2.0.0-rc" }
tauri-plugin-log = { version = "2.0.0-rc" }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.0.0-rc.0"

View File

@ -1,33 +1,32 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": [
"main"
],
"permissions": [
"core:default",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm",
"notification:default",
"os:allow-platform",
"os:allow-version",
"os:allow-os-type",
"os:allow-family",
"os:allow-arch",
"os:allow-exe-extension",
"os:allow-locale",
"os:allow-hostname",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"core:app:allow-app-show",
"core:app:allow-app-hide",
"os:default",
"dialog:default",
"clipboard-manager:default",
"notification:default",
"updater:default",
"log:default"
]
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm",
"notification:default",
"os:allow-platform",
"os:allow-version",
"os:allow-os-type",
"os:allow-family",
"os:allow-arch",
"os:allow-exe-extension",
"os:allow-locale",
"os:allow-hostname",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"core:app:allow-app-show",
"core:app:allow-app-hide",
"os:default",
"dialog:default",
"clipboard-manager:default",
"notification:default",
"updater:default",
"log:default",
"autostart:default"
]
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm","notification:default","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","core:app:allow-app-show","core:app:allow-app-hide","os:default","dialog:default","clipboard-manager:default","notification:default","updater:default","log:default"]}}
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm","notification:default","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","core:app:allow-app-show","core:app:allow-app-hide","os:default","dialog:default","clipboard-manager:default","notification:default","updater:default","log:default","autostart:default"]}}

View File

@ -171,6 +171,55 @@
},
"Identifier": {
"oneOf": [
{
"description": "autostart:default -> This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n",
"type": "string",
"enum": [
"autostart:default"
]
},
{
"description": "autostart:allow-disable -> Enables the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-disable"
]
},
{
"description": "autostart:allow-enable -> Enables the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-enable"
]
},
{
"description": "autostart:allow-is-enabled -> Enables the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-is-enabled"
]
},
{
"description": "autostart:deny-disable -> Denies the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-disable"
]
},
{
"description": "autostart:deny-enable -> Denies the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-enable"
]
},
{
"description": "autostart:deny-is-enabled -> Denies the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-is-enabled"
]
},
{
"description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
"type": "string",

View File

@ -171,6 +171,55 @@
},
"Identifier": {
"oneOf": [
{
"description": "autostart:default -> This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n",
"type": "string",
"enum": [
"autostart:default"
]
},
{
"description": "autostart:allow-disable -> Enables the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-disable"
]
},
{
"description": "autostart:allow-enable -> Enables the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-enable"
]
},
{
"description": "autostart:allow-is-enabled -> Enables the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-is-enabled"
]
},
{
"description": "autostart:deny-disable -> Denies the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-disable"
]
},
{
"description": "autostart:deny-enable -> Denies the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-enable"
]
},
{
"description": "autostart:deny-is-enabled -> Denies the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-is-enabled"
]
},
{
"description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
"type": "string",

View File

@ -3,6 +3,7 @@ use crate::{
structs::{FanslyAccountResponse, FanslyBaseResponse, SyncDataResponse},
};
use lazy_static::lazy_static;
use serde_json::Value;
use tokio::sync::Mutex;
lazy_static! {
@ -26,9 +27,34 @@ pub async fn fansly_get_me() -> Result<FanslyBaseResponse<FanslyAccountResponse>
}
#[tauri::command]
pub async fn fansly_sync() -> Result<SyncDataResponse, String> {
pub async fn fansly_sync(auto: bool) -> Result<SyncDataResponse, String> {
let fansly = FANSLY.lock().await;
let response = fansly.sync().await;
let response = fansly.sync(auto).await;
match response {
Ok(response) => Ok(response),
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
pub async fn fansly_upload_auto_sync_data(
data: SyncDataResponse,
token: String,
) -> Result<(), String> {
let fansly: tokio::sync::MutexGuard<Fansly> = FANSLY.lock().await;
let response = fansly.upload_auto_sync_data(data, token).await;
match response {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
pub async fn fansly_check_sync_token(token: String) -> Result<Value, String> {
let fansly: tokio::sync::MutexGuard<Fansly> = FANSLY.lock().await;
let response = fansly.check_sync_token(token).await;
match response {
Ok(response) => Ok(response),

View File

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use crate::structs::{FanslyFollowersResponse, Subscription};
const CURRENT_VERSION: i32 = 1; // Set the current version of the config
const CURRENT_VERSION: i32 = 2; // Set the current version of the config
#[derive(Debug, Serialize, Deserialize)]
pub struct SyncData {
@ -18,6 +18,8 @@ pub struct Config {
pub version: i32, // Add a version field to the config (1, 2, 3, etc.)
pub is_first_run: bool,
pub fansly_token: String,
pub auto_sync_enabled: bool,
pub sync_token: String,
pub sync_interval: u64,
pub last_sync: u64,
pub last_sync_data: SyncData,
@ -31,6 +33,8 @@ impl Default for Config {
fansly_token: String::new(), // Fansly token is stored as a string
sync_interval: 1, // Every hour - sync interval is interpreted as hours
last_sync: 0, // Last sync time is stored as a UNIX timestamp
auto_sync_enabled: false, // Auto sync is disabled by default
sync_token: String::new(), // Sync token is stored as a string
last_sync_data: SyncData {
followers: Vec::new(),
subscribers: Vec::new(),
@ -42,17 +46,89 @@ impl Default for Config {
impl Config {
pub fn load_or_create(path: &Path) -> io::Result<Self> {
if path.exists() {
let mut config: Self = serde_json::from_str(std::fs::read_to_string(path)?.as_str())
.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Could not parse config file: {}", e),
)
})?;
let config_result: Result<Self, _> =
serde_json::from_str(&std::fs::read_to_string(path)?);
let config = match config_result {
Ok(config) => config,
Err(_) => {
// Load raw JSON and attempt to parse it as a JSON object
let config_raw = std::fs::read_to_string(path)?;
let config_json: serde_json::Value = serde_json::from_str(&config_raw)?;
println!("[config::migrate] Migrating config file to latest version...");
println!(
"[config::migrate] [DEBUG] config is_object: {}",
config_json.is_object()
);
// Check if the JSON object is valid, if not, return an error
if !config_json.is_object() {
println!(
"[config::migrate] [ERROR] Found invalid JSON object in config file"
);
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Tried to migrate a config file, but found an invalid JSON object",
));
}
// Get the version field from the JSON object
let version = config_json["version"].as_i64().unwrap_or(0) as i32;
// Check if the version field is a valid integer, if not, return an error
if version == 0 {
println!(
"[config::migrate] [ERROR] Found invalid version field in config JSON"
);
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Tried to migrate a config file, but found an invalid version field",
));
}
println!(
"[config::migrate] Found version field in config JSON: {}",
version
);
// Now create a new Config object and set the version field to the value we found
let mut config = Config::default();
config.version = version;
// Retain important fields from the JSON object
config.is_first_run = config_json["is_first_run"].as_bool().unwrap_or(true);
config.fansly_token = config_json["fansly_token"]
.as_str()
.unwrap_or("")
.to_string();
config.sync_token =
config_json["sync_token"].as_str().unwrap_or("").to_string();
config.sync_interval =
config_json["sync_interval"].as_i64().unwrap_or(1) as u64;
// Run migrations on the config object and save it
config = config.migrate()?;
config.save(path)?;
println!(
"[config::migrate] Successfully migrated config file to latest version"
);
// Recursively call load_or_create to load the migrated config
return Config::load_or_create(path);
}
};
if config.version != CURRENT_VERSION {
config = config.migrate()?;
config.save(path)?;
// Should have been migrated by now, error out because it wasn't
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Config version mismatch: expected {}, got {}. Please try removing the config file and restarting the application.",
CURRENT_VERSION, config.version
),
));
}
Ok(config)
} else {
let saved_config = Config::default().save(path);
@ -66,8 +142,12 @@ impl Config {
while self.version < CURRENT_VERSION {
self = match self.version {
1 => {
// If we're on version 1, migrate to version 2 (not implemented)
self.version += 1;
// Migrate from version 1 to version 2
self.version = 2;
self.auto_sync_enabled = false;
self.sync_token = String::new();
self.sync_interval = 1;
self
}
_ => {

View File

@ -5,6 +5,7 @@ use crate::structs::{
FanslySubscriptionsResponse, Subscription, SyncDataResponse,
};
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use serde_json::Value;
pub struct Fansly {
client: reqwest::Client,
@ -195,7 +196,73 @@ impl Fansly {
Ok(format!("https://paste.hep.gg/{}", key))
}
pub async fn sync(&self) -> Result<SyncDataResponse, String> {
pub async fn upload_auto_sync_data(
&self,
data: SyncDataResponse,
token: String,
) -> Result<(), reqwest::Error> {
let url = "http://localhost:5001/sync";
// Set our content type to application/json
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONTENT_TYPE,
"application/json".parse().unwrap(),
);
// Add our auth token to the headers
headers.insert("Authorization", format!("{}", token).parse().unwrap());
let response = self
.client
.post(url)
.headers(headers)
.json(&data)
.send()
.await?;
if !response.status().is_success() {
eprintln!("[sync::process::upload_auto_sync_data] Failed to upload sync data.");
return Err(response.error_for_status().unwrap_err());
}
Ok(())
}
pub async fn check_sync_token(&self, token: String) -> Result<Value, reqwest::Error> {
// Check if the token is valid (GET /checkSyncToken with Authorization header)
// If it is, return the data back from the API
// If it isn't, return an error
let url = "http://localhost:5001/checkSyncToken";
// Set our content type to application/json
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONTENT_TYPE,
"application/json".parse().unwrap(),
);
// Add our auth token to the headers
headers.insert("Authorization", format!("{}", token).parse().unwrap());
let response = self.client.get(url).headers(headers).send().await;
// If successful, return the data, otherwise return an error
match response {
Ok(response) => {
if !response.status().is_success() {
eprintln!("[sync::process::check_sync_token] Failed to check sync token.");
return Err(response.error_for_status().unwrap_err());
}
let json: serde_json::Value = response.json().await?;
Ok(json)
}
Err(e) => Err(e),
}
}
pub async fn sync(&self, auto: bool) -> Result<SyncDataResponse, String> {
// Fetch profile
println!("[sync::process] Fetching profile...");
let profile = self.get_profile().await.map_err(|e| e.to_string())?;
@ -280,20 +347,29 @@ impl Fansly {
println!("[sync::process] Uploading sync data to paste.hep.gg for processing...");
// Upload sync data to paste.hep.gg
let paste_url = self
.upload_sync_data(SyncDataResponse {
followers: followers.clone(),
subscribers: subscribers.clone(),
if !auto {
let paste_url = self
.upload_sync_data(SyncDataResponse {
followers: followers.clone(),
subscribers: subscribers.clone(),
sync_data_url: "".to_string(),
})
.await
.map_err(|e| e.to_string())?;
// Return JSON of what we fetched
Ok(SyncDataResponse {
followers,
subscribers,
sync_data_url: paste_url,
})
} else {
// Return JSON of what we fetched
Ok(SyncDataResponse {
followers,
subscribers,
sync_data_url: "".to_string(),
})
.await
.map_err(|e| e.to_string())?;
// Return JSON of what we fetched
Ok(SyncDataResponse {
followers,
subscribers,
sync_data_url: paste_url,
})
}
}
}

View File

@ -9,8 +9,12 @@ use std::fs;
use std::io;
use commands::config::{get_config, init_config, save_config};
use commands::fansly::{fansly_get_me, fansly_set_token, fansly_sync};
use commands::fansly::{
fansly_check_sync_token, fansly_get_me, fansly_set_token, fansly_sync,
fansly_upload_auto_sync_data,
};
use commands::utils::quit;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_log::{Target, TargetKind};
fn get_log_path() -> io::Result<String> {
@ -31,6 +35,10 @@ fn get_log_path() -> io::Result<String> {
#[tokio::main]
async fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
None,
))
.plugin(tauri_plugin_log::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_clipboard_manager::init())
@ -55,7 +63,9 @@ async fn main() {
quit,
fansly_set_token,
fansly_get_me,
fansly_sync
fansly_sync,
fansly_upload_auto_sync_data,
fansly_check_sync_token
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@ -2,6 +2,8 @@ export type Config = {
version: number;
is_first_run: boolean;
fansly_token: string;
auto_sync_enabled: boolean;
sync_token: string;
sync_interval: number;
last_sync: number;
last_sync_data: SyncData;

View File

@ -1,5 +1,7 @@
<script>
import '../app.css';
import { Toaster } from 'svelte-french-toast';
</script>
<Toaster position='bottom-center' />
<slot />

View File

@ -87,8 +87,20 @@
info(`[FanslySync::init] Not first run. Setting Fansly token...`);
await invoke('fansly_set_token', { token: config.fansly_token });
info(`[FanslySync::init] Fansly token set.`);
info(`[FanslySync::init] Initialization complete. Redirecting to /home...`);
window.location.href = '/home';
info(`[FanslySync::init] Checking token validity...`);
const [valid, validError] = await awaiter(invoke('fansly_get_me'));
if (validError) {
error(`[FanslySync::init] Token invalid. Redirecting to /setup...`);
await message(
`Your Fansly token is invalid. Please re-enter your token in the setup page.`,
{ title: 'FanslySync | Token Invalid', kind: 'error' }
);
window.location.href = '/setup';
} else {
info(`[FanslySync::init] Token valid. Redirecting to /dashboard...`);
window.location.href = '/dashboard';
}
}
});
</script>

View File

@ -3,7 +3,7 @@
import { awaiter } from '$lib/utils';
import { onMount } from 'svelte';
import type { Config, SyncData } from '$lib/types';
import { slide } from 'svelte/transition';
import { fade, fly, slide } from 'svelte/transition';
import { sendNotification } from '@tauri-apps/plugin-notification';
import { platform } from '@tauri-apps/plugin-os';
import { check, Update } from '@tauri-apps/plugin-updater';
@ -11,6 +11,8 @@
import { invoke } from '@tauri-apps/api/core';
import { ask, message } from '@tauri-apps/plugin-dialog';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { isEnabled, enable } from '@tauri-apps/plugin-autostart';
import { toast } from 'svelte-french-toast';
let loadingSync = true;
let upToDate: boolean | null = null;
@ -28,7 +30,20 @@
message: ''
};
let isAutoSyncConfigModalOpen = false;
let canSave = false;
let config: Config | null = null;
let syncInterval: number | null = null;
let autoSyncConfig = {
interval: 0,
syncToken: ''
};
let autoSyncConfigState = {
validatingToken: false,
tokenValid: false
};
onMount(async () => {
info(`[FanslySync::page_init:home] onMount() called. Starting page initialization...`);
@ -55,6 +70,8 @@
`[FanslySync::page_init:home] Configuration initialized successfully. Checking for updates...`
);
config = configData;
autoSyncConfig.interval = config.sync_interval;
autoSyncConfig.syncToken = config.sync_token;
loadingSync = false;
const updateStatus = await check();
@ -72,19 +89,80 @@
`[FanslySync::page_init:home] App and Tauri versions fetched. We are running App version: ${versionData.appVersion}, atop Tauri version: ${versionData.tauriVersion}`
);
info(`[FanslySync::page_init:home] Setting up autosync interval...`);
syncInterval = setInterval(
async () => {
if (config?.auto_sync_enabled) {
info(`[FanslySync::autoSyncProcess] Auto Sync enabled - syncing data automatically.`);
const nextIntervalTime = new Date(
Date.now() + (config?.sync_interval ?? 1) * 60 * 60 * 1000
);
const nextIntervalTimeString = nextIntervalTime.toLocaleTimeString();
const returnedData = await syncNow(true);
if (!returnedData || returnedData === null) {
error(`[FanslySync::autoSyncProcess] Failed to sync data automatically.`);
// Send error notification
await sendNotification({
title: 'FanslySync: Auto Sync Failed!',
body: `An error occurred while syncing data automatically. We will automatically retry at ${nextIntervalTimeString}.`
});
} else {
info(
`[FanslySync::autoSyncProcess] Synced data automatically - preparing to send to server.`
);
const [_, uploadErr] = await awaiter(
invoke('fansly_upload_auto_sync_data', {
token: config?.sync_token,
data: returnedData
})
);
if (uploadErr) {
error(
`[FanslySync::autoSyncProcess] Failed to upload data to server. Error: ${uploadErr}`
);
// Send error notification
await sendNotification({
title: 'FanslySync: Auto Sync Failed!',
body: `An error occurred while uploading data to the server. We will automatically retry at ${nextIntervalTimeString}.`
});
return;
}
// Send success notification
await sendNotification({
title: 'FanslySync: Auto Sync Successful!',
body: `Data synced and uploaded successfully. Next sync will occur at ${nextIntervalTimeString}.`
});
}
} else {
info(`[FanslySync::autoSyncProcess] Auto Sync disabled - skipping this sync.`);
}
},
(config?.sync_interval ?? 1) * 60 * 60 * 1000
);
info(`[FanslySync::page_init:home] Autosync interval set successfully.`);
info(`[FanslySync::page_init:home] Page initialization completed successfully.`);
});
async function syncNow() {
async function syncNow(auto: boolean = false) {
info(`[FanslySync::syncNow] Starting manual sync...`);
syncState.error = false;
syncState.success = false;
syncState.syncing = true;
syncState.show = true;
syncState.show = !auto;
const [syncData, syncError] = await awaiter(invoke('fansly_sync') as Promise<SyncData>);
console.log(syncData, syncError);
const [syncData, syncError] = await awaiter(
invoke('fansly_sync', {
auto
}) as Promise<SyncData>
);
if (syncError || syncData === null) {
error(
@ -93,6 +171,13 @@
syncState.syncing = false;
syncState.error = true;
syncState.message = syncError ?? 'Sync data was null';
// Send failure notification
await sendNotification({
title: 'FanslySync: Sync Failed!',
body: 'An error occurred while syncing data. Please check the app for more details.'
});
return;
}
@ -110,6 +195,12 @@
syncState.syncing = false;
syncState.error = true;
syncState.message = saveConfigError ?? 'Save config data was null';
// Send failure notification
await sendNotification({
title: 'FanslySync: Sync Failed!',
body: 'An error occurred while syncing data. Please check the app for more details.'
});
return;
}
@ -127,13 +218,17 @@
soundName = 'completion-sucess';
}
await sendNotification({
title: 'FanslySync: Sync Successful!',
body: 'Data synced successfully. Please look at the app for more details.',
sound: soundName
});
if (!auto)
await sendNotification({
title: 'FanslySync: Sync Successful!',
body: 'Data synced successfully. Please look at the app for more details.',
sound: soundName
});
info(`[FanslySync::syncNow] Manual sync completed successfully.`);
if (auto) return syncData;
else return null;
}
async function doUpdate() {
@ -162,13 +257,357 @@
console.log('User declined update');
}
}
async function enableAutoSync() {
const isAutoStartEnabled = await isEnabled();
if (!isAutoStartEnabled) {
// Required to enable autosync. Ask user for permission
const confirm = await ask(
`We've detected that FanslySync is not set to start automatically with the system. To enable Auto Sync, we need to enable this feature. Would you like to enable this feature now?`,
{
title: 'FanslySync | Enable Auto Start',
okLabel: 'Yes',
cancelLabel: 'No',
kind: 'warning'
}
);
if (!confirm) {
// Error out
await message(
`Auto Sync cannot be enabled without enabling the Auto Start feature. Please enable the Auto Start feature and try again.`,
{ title: 'FanslySync | Auto Sync Error', kind: 'error' }
);
return;
} else {
await toast.promise(enable(), {
loading: 'Enabling Auto Start...',
success: 'Auto Start enabled successfully.',
error: 'Failed to enable Auto Start. Please try again.'
});
}
}
// Ensure they have a sync token set
if (!config?.sync_token || config?.sync_token.length === 0) {
await message(
`Auto Sync cannot be enabled without a valid sync token. Please set a sync token in settings and try again.`,
{ title: 'FanslySync | Auto Sync Error', kind: 'error' }
);
return;
}
// Enable autosync
config!.auto_sync_enabled = !config?.auto_sync_enabled;
const [_, saveConfigError] = await awaiter(invoke('save_config', { config }));
if (saveConfigError) {
await message(
`Failed to save Auto Sync configuration. Please try again. Error: ${saveConfigError}`,
{ title: 'FanslySync | Auto Sync Error', kind: 'error' }
);
error(
`[FanslySync::enableAutoSync] Failed to save Auto Sync configuration. Error: ${saveConfigError}`
);
return;
}
// Clear interval if autosync is disabled, set if enabled
if (config?.auto_sync_enabled) {
syncInterval = setInterval(
async () => {
if (config?.auto_sync_enabled) {
info(`[FanslySync::autoSyncProcess] Auto Sync enabled - syncing data automatically.`);
const nextIntervalTime = new Date(
Date.now() + (config?.sync_interval ?? 1) * 60 * 60 * 1000
);
const nextIntervalTimeString = nextIntervalTime.toLocaleTimeString();
const returnedData = await syncNow(true);
if (!returnedData || returnedData === null) {
error(`[FanslySync::autoSyncProcess] Failed to sync data automatically.`);
// Send error notification
await sendNotification({
title: 'FanslySync: Auto Sync Failed!',
body: `An error occurred while syncing data automatically. We will automatically retry at ${nextIntervalTimeString}.`
});
} else {
info(
`[FanslySync::autoSyncProcess] Synced data automatically - preparing to send to server.`
);
const [_, uploadErr] = await awaiter(
invoke('fansly_upload_auto_sync_data', {
token: config?.sync_token,
data: returnedData
})
);
if (uploadErr) {
error(
`[FanslySync::autoSyncProcess] Failed to upload data to server. Error: ${uploadErr}`
);
// Send error notification
await sendNotification({
title: 'FanslySync: Auto Sync Failed!',
body: `An error occurred while uploading data to the server. We will automatically retry at ${nextIntervalTimeString}.`
});
return;
}
// Send success notification
await sendNotification({
title: 'FanslySync: Auto Sync Successful!',
body: `Data synced and uploaded successfully. Next sync will occur at ${nextIntervalTimeString}.`
});
}
} else {
info(`[FanslySync::autoSyncProcess] Auto Sync disabled - skipping this sync.`);
}
},
(config?.sync_interval ?? 1) * 60 * 60 * 1000
);
// Run a auto sync as soon as it's enabled
const returnedData = await syncNow(true);
if (!returnedData || returnedData === null) {
error(`[FanslySync::autoSyncProcess] Failed to sync data automatically.`);
// Disable autosync, resave, and error out on the UI
config!.auto_sync_enabled = false;
const [_, saveConfigError] = await awaiter(invoke('save_config', { config }));
if (saveConfigError)
toast.error('Failed to save Auto Sync configuration. Please try again.', {
duration: 5000
});
toast.error(
'Self test of Auto Sync failed (SYNC_ERR). Please check the logs for more details.',
{
duration: 5000
}
);
return;
} else {
info(
`[FanslySync::autoSyncProcess] Synced data automatically - preparing to send to server.`
);
const [_, uploadErr] = await awaiter(
invoke('fansly_upload_auto_sync_data', {
token: config?.sync_token,
data: returnedData
})
);
if (uploadErr) {
error(
`[FanslySync::autoSyncProcess] Failed to upload data to server. Error: ${uploadErr}`
);
// Disable autosync, resave, and error out on the UI
config!.auto_sync_enabled = false;
const [_, saveConfigError] = await awaiter(invoke('save_config', { config }));
if (saveConfigError)
toast.error('Failed to save Auto Sync configuration. Please try again.', {
duration: 5000
});
toast.error(
'Self test of Auto Sync failed (UPLOAD_ERR). Please check the logs for more details.',
{
duration: 5000
}
);
return;
}
}
} else {
if (syncInterval) {
clearInterval(syncInterval);
}
}
info(`[FanslySync::enableAutoSync] Auto Sync configuration saved successfully.`);
const nextInterval = new Date(Date.now() + (config?.sync_interval ?? 1) * 60 * 60 * 1000);
const nextIntervalString = nextInterval.toLocaleTimeString();
toast.success(
`${
config?.auto_sync_enabled ? 'Enabled' : 'Disabled'
} Auto Sync successfully. ${config?.auto_sync_enabled ? `The next sync will occur at ${nextIntervalString}.` : ''}`,
{
duration: 5000
}
);
}
async function onSyncTokenEntered() {
// Check if the sync token is valid
autoSyncConfigState.validatingToken = true;
const [_, tokenError] = await awaiter(
invoke('fansly_check_sync_token', { token: autoSyncConfig.syncToken })
);
if (tokenError) {
error(`[FanslySync::onSyncTokenEntered] Failed to validate sync token. Error: ${tokenError}`);
autoSyncConfigState.validatingToken = false;
autoSyncConfigState.tokenValid = false;
canSave = false;
return;
} else {
info(`[FanslySync::onSyncTokenEntered] Sync token validated successfully.`);
autoSyncConfigState.validatingToken = false;
autoSyncConfigState.tokenValid = true;
canSave = true;
}
}
async function onAutoSyncSave() {
// Close the modal
isAutoSyncConfigModalOpen = false;
const savingToast = await toast.loading('Saving Auto Sync configuration...');
config!.sync_interval = autoSyncConfig.interval;
config!.sync_token = autoSyncConfig.syncToken;
const [_, saveConfigError] = await awaiter(invoke('save_config', { config }));
if (saveConfigError) {
await toast.error('Failed to save Auto Sync configuration. Please try again.', {
id: savingToast
});
error(
`[FanslySync::onAutoSyncSave] Failed to save Auto Sync configuration. Error: ${saveConfigError}`
);
return;
} else {
info(`[FanslySync::onAutoSyncSave] Auto Sync configuration saved successfully.`);
await toast.success('Auto Sync configuration saved successfully.', { id: savingToast });
}
}
</script>
<div class="container bg-zinc-800 w-screen h-screen">
<!-- Top header, Bambu Connect at the left side, settings icon on the right -->
<!-- Create config modal -->
{#if isAutoSyncConfigModalOpen}
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-10"
transition:fade={{ duration: 300 }}
>
<div
class="bg-zinc-900 p-4 rounded-lg"
in:fly={{ y: 20, duration: 300 }}
out:fly={{ y: 20, duration: 200 }}
>
<h1 class="text-2xl font-bold text-gray-200">Auto Sync Configuration</h1>
<p class="text-gray-400 mt-1">
Configure the Auto Sync feature here. Please ensure you have a valid sync token set in
settings.
</p>
<div class="flex flex-col mt-2">
<label for="syncInterval" class="text-gray-200">Sync Interval (in hours)</label>
<input
type="number"
id="syncInterval"
class="bg-zinc-700 text-gray-200 p-2 rounded-lg mt-1"
placeholder="Enter sync interval in hours"
bind:value={autoSyncConfig.interval}
min="1"
/>
<p class="text-gray-400 mt-1">
How often should the app sync data automatically? Please enter a number in hours. The
minimum value is 1 hour.
</p>
<label for="syncToken" class="text-gray-200 mt-2">Sync Token</label>
<div class="relative flex items-center">
<!-- Sync token input. Check in the input for valid token, x not, question mark empty -->
<input
type="text"
id="syncToken"
class="bg-zinc-700 text-gray-200 p-2 rounded-lg mt-1 w-full pr-10"
placeholder="Enter sync token"
bind:value={autoSyncConfig.syncToken}
on:change={onSyncTokenEntered}
/>
{#if !autoSyncConfigState.validatingToken && autoSyncConfigState.tokenValid}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="absolute right-2 bottom-2 w-6 h-6 text-green-500"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
{:else if !autoSyncConfigState.validatingToken && !autoSyncConfigState.tokenValid}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="absolute right-2 bottom-2 w-6 h-6 text-red-500"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
{:else if autoSyncConfigState.validatingToken}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="absolute right-2 bottom-2 w-6 h-6 text-gray-400 animate-spin"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
{/if}
</div>
<p class="text-gray-400 mt-1">
Unfocus (click out of) the input to validate the token. A green checkmark means the
token is valid.
</p>
<div class="flex mt-2">
<button
class="bg-blue-600 text-white px-4 py-2 rounded-lg w-full disabled:opacity-50 disabled:cursor-not-allowed hover:bg-blue-700 transition-all duration-200 ease-in-out"
disabled={!canSave}
on:click={onAutoSyncSave}
>
{canSave ? 'Save' : 'Please enter a valid sync token'}
</button>
<button
class="bg-red-500 text-white px-4 py-2 rounded-lg w-full ml-2 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-red-600 transition-all duration-200 ease-in-out"
on:click={() => {
isAutoSyncConfigModalOpen = false;
}}
>
Cancel
</button>
</div>
</div>
</div>
</div>
{/if}
<!-- Top header, FanslySync at the left side, settings icon on the right -->
<div class="flex justify-between items-center h-16 px-4 bg-zinc-900">
<div class="flex items-center">
<img src="/fanslySync.png" alt="FanslySynct" class="w-8 h-8" />
<img src="/fanslySync.png" alt="FanslySync" class="w-8 h-8" />
<h1 class="text-2xl font-bold text-gray-200 ml-2">FanslySync</h1>
<span class="text-gray-400 ml-2"
>v{versionData.appVersion} (runtime: {versionData.tauriVersion})</span
@ -240,40 +679,54 @@
<!-- Automatic sync card -->
<div class="relative">
<div class="bg-zinc-700 p-4 rounded-lg">
<h1 class="text-xl font-bold text-gray-200">Automatic Sync</h1>
<div class="flex items-center space-x-2">
<h1 class="text-xl font-bold text-gray-200">Automatic Sync</h1>
<!-- Status badge -->
<span
class={`px-2 py-1 rounded-lg text-xs font-bold ${
config?.auto_sync_enabled ? 'bg-green-500' : 'bg-red-500'
}`}
>
{config?.auto_sync_enabled ? 'Enabled' : 'Disabled'}
</span>
</div>
<p class="text-gray-400 mt-1">
Sync content automatically every {config?.sync_interval} hours. Please ensure you have
a stable internet connection.
Sync content automatically every {config?.sync_interval}
{(config?.sync_interval ?? 0 > 1) ? 'hour' : 'hours'}. Please ensure you have a
stable internet connection.
</p>
<div class="flex mt-2">
<button
class="bg-blue-600 text-white px-4 py-2 rounded-lg w-full"
on:click={() => console.log('Automatic sync clicked')}
class={` text-white px-4 py-2 rounded-lg w-full ${
!config?.auto_sync_enabled
? 'bg-green-500 hover:bg-green-600'
: 'bg-red-500 hover:bg-red-600'
} disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ease-in-out`}
on:click={enableAutoSync}
disabled={syncState.syncing}
>
Enable
{syncState.syncing
? 'Sync in progress. Please wait.'
: config?.auto_sync_enabled
? 'Disable'
: 'Enable'}
{!syncState.syncing ? 'Auto Sync' : ''}
</button>
<button
class="bg-blue-600 text-white px-4 py-2 rounded-lg w-full ml-2 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ease-in-out hover:bg-blue-700"
on:click={() => {
isAutoSyncConfigModalOpen = true;
}}
disabled={config?.auto_sync_enabled || syncState.syncing}
>
{syncState.syncing
? 'Sync in progress. Please wait.'
: config?.auto_sync_enabled
? 'Disable Auto Sync To Edit'
: 'Edit Auto Sync Configuration'}
</button>
</div>
</div>
<div
class="absolute top-0 left-0 right-0 bottom-0 bg-white/30 backdrop-blur-sm flex flex-col justify-center items-center rounded-lg"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-10 text-blue-400"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
<h1 class="text-xl font-bold text-gray-200 ml-2">Automatic Sync is coming soon!</h1>
<p class="text-white mt-1">Stay tuned for updates.</p>
</div>
</div>
<!-- Manual sync card -->
@ -285,7 +738,9 @@
<div class="flex mt-2">
<button
class="bg-blue-600 text-white px-4 py-2 rounded-lg w-full disabled:opacity-50 disabled:cursor-not-allowed hover:bg-blue-700 transition-all duration-200 ease-in-out"
on:click={syncNow}
on:click={() => {
syncNow(false);
}}
disabled={syncState.syncing}
>
{syncState.syncing ? 'Syncing...' : 'Sync Now'}

View File

@ -0,0 +1,2 @@
[0813/143825.367:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)
[0813/143918.868:ERROR:registration_protocol_win.cc(108)] CreateFile: The system cannot find the file specified. (0x2)