clean up and refactor open graph
This commit is contained in:
@@ -53,7 +53,6 @@
|
|||||||
"@tiptap/starter-kit": "^2.0.4",
|
"@tiptap/starter-kit": "^2.0.4",
|
||||||
"@tiptap/suggestion": "^2.0.4",
|
"@tiptap/suggestion": "^2.0.4",
|
||||||
"@void-cat/api": "^1.0.7",
|
"@void-cat/api": "^1.0.7",
|
||||||
"cheerio": "1.0.0-rc.12",
|
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"destr": "^1.2.2",
|
"destr": "^1.2.2",
|
||||||
"get-urls": "^11.0.0",
|
"get-urls": "^11.0.0",
|
||||||
|
|||||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@@ -109,9 +109,6 @@ dependencies:
|
|||||||
'@void-cat/api':
|
'@void-cat/api':
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7
|
version: 1.0.7
|
||||||
cheerio:
|
|
||||||
specifier: 1.0.0-rc.12
|
|
||||||
version: 1.0.0-rc.12
|
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.9
|
specifier: ^1.11.9
|
||||||
version: 1.11.9
|
version: 1.11.9
|
||||||
@@ -2763,10 +2760,6 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/boolbase@1.0.0:
|
|
||||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/brace-expansion@1.1.11:
|
/brace-expansion@1.1.11:
|
||||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2888,30 +2881,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/cheerio-select@2.1.0:
|
|
||||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
|
||||||
dependencies:
|
|
||||||
boolbase: 1.0.0
|
|
||||||
css-select: 5.1.0
|
|
||||||
css-what: 6.1.0
|
|
||||||
domelementtype: 2.3.0
|
|
||||||
domhandler: 5.0.3
|
|
||||||
domutils: 3.1.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/cheerio@1.0.0-rc.12:
|
|
||||||
resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dependencies:
|
|
||||||
cheerio-select: 2.1.0
|
|
||||||
dom-serializer: 2.0.0
|
|
||||||
domhandler: 5.0.3
|
|
||||||
domutils: 3.1.0
|
|
||||||
htmlparser2: 8.0.2
|
|
||||||
parse5: 7.1.2
|
|
||||||
parse5-htmlparser2-tree-adapter: 7.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/chokidar@3.5.3:
|
/chokidar@3.5.3:
|
||||||
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
|
||||||
engines: {node: '>= 8.10.0'}
|
engines: {node: '>= 8.10.0'}
|
||||||
@@ -3060,21 +3029,6 @@ packages:
|
|||||||
shebang-command: 2.0.0
|
shebang-command: 2.0.0
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
/css-select@5.1.0:
|
|
||||||
resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
|
|
||||||
dependencies:
|
|
||||||
boolbase: 1.0.0
|
|
||||||
css-what: 6.1.0
|
|
||||||
domhandler: 5.0.3
|
|
||||||
domutils: 3.1.0
|
|
||||||
nth-check: 2.1.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/css-what@6.1.0:
|
|
||||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/cssesc@3.0.0:
|
/cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -5439,12 +5393,6 @@ packages:
|
|||||||
set-blocking: 2.0.0
|
set-blocking: 2.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/nth-check@2.1.1:
|
|
||||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
|
||||||
dependencies:
|
|
||||||
boolbase: 1.0.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/object-assign@4.1.1:
|
/object-assign@4.1.1:
|
||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -5620,19 +5568,6 @@ packages:
|
|||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/parse5-htmlparser2-tree-adapter@7.0.0:
|
|
||||||
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
|
|
||||||
dependencies:
|
|
||||||
domhandler: 5.0.3
|
|
||||||
parse5: 7.1.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/parse5@7.1.2:
|
|
||||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
|
||||||
dependencies:
|
|
||||||
entities: 4.5.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/parseley@0.12.1:
|
/parseley@0.12.1:
|
||||||
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
156
src-tauri/Cargo.lock
generated
156
src-tauri/Cargo.lock
generated
@@ -1123,6 +1123,36 @@ dependencies = [
|
|||||||
"cipher 0.3.0",
|
"cipher 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curl"
|
||||||
|
version = "0.4.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
|
||||||
|
dependencies = [
|
||||||
|
"curl-sys",
|
||||||
|
"libc",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"socket2 0.4.9",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curl-sys"
|
||||||
|
version = "0.4.65+curl-8.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libz-sys",
|
||||||
|
"openssl-sys",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "curve25519-dalek"
|
name = "curve25519-dalek"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
@@ -2148,7 +2178,21 @@ checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"mac",
|
"mac",
|
||||||
"markup5ever",
|
"markup5ever 0.10.1",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "html5ever"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"mac",
|
||||||
|
"markup5ever 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
@@ -2538,7 +2582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
|
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"html5ever",
|
"html5ever 0.25.2",
|
||||||
"matches",
|
"matches",
|
||||||
"selectors",
|
"selectors",
|
||||||
]
|
]
|
||||||
@@ -2621,6 +2665,18 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libz-sys"
|
||||||
|
version = "1.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "line-wrap"
|
name = "line-wrap"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2701,6 +2757,7 @@ dependencies = [
|
|||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-upload",
|
"tauri-plugin-upload",
|
||||||
"tauri-plugin-window",
|
"tauri-plugin-window",
|
||||||
|
"webpage",
|
||||||
"window-vibrancy",
|
"window-vibrancy",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2740,12 +2797,38 @@ checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"phf 0.8.0",
|
"phf 0.8.0",
|
||||||
"phf_codegen",
|
"phf_codegen 0.8.0",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
"string_cache_codegen",
|
"string_cache_codegen",
|
||||||
"tendril",
|
"tendril",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markup5ever"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"phf 0.10.1",
|
||||||
|
"phf_codegen 0.10.0",
|
||||||
|
"string_cache",
|
||||||
|
"string_cache_codegen",
|
||||||
|
"tendril",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markup5ever_rcdom"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2"
|
||||||
|
dependencies = [
|
||||||
|
"html5ever 0.26.0",
|
||||||
|
"markup5ever 0.11.0",
|
||||||
|
"tendril",
|
||||||
|
"xml5ever",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3123,6 +3206,24 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.91"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-stream"
|
name = "ordered-stream"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -3297,6 +3398,16 @@ dependencies = [
|
|||||||
"phf_shared 0.8.0",
|
"phf_shared 0.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_codegen"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator 0.10.0",
|
||||||
|
"phf_shared 0.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_generator"
|
name = "phf_generator"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -4034,6 +4145,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scoped-tls"
|
name = "scoped-tls"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -4069,7 +4189,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"matches",
|
"matches",
|
||||||
"phf 0.8.0",
|
"phf 0.8.0",
|
||||||
"phf_codegen",
|
"phf_codegen 0.8.0",
|
||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
"servo_arc",
|
"servo_arc",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@@ -5308,7 +5428,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
"heck",
|
"heck",
|
||||||
"html5ever",
|
"html5ever 0.25.2",
|
||||||
"infer",
|
"infer",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"kuchiki",
|
"kuchiki",
|
||||||
@@ -5949,6 +6069,19 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpage"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8598785beeb5af95abe95e7bb20c7e747d1188347080d6811d5a56d2b9a5f368"
|
||||||
|
dependencies = [
|
||||||
|
"curl",
|
||||||
|
"html5ever 0.26.0",
|
||||||
|
"markup5ever_rcdom",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@@ -6385,7 +6518,7 @@ dependencies = [
|
|||||||
"gio",
|
"gio",
|
||||||
"glib",
|
"glib",
|
||||||
"gtk",
|
"gtk",
|
||||||
"html5ever",
|
"html5ever 0.25.2",
|
||||||
"http",
|
"http",
|
||||||
"javascriptcore-rs",
|
"javascriptcore-rs",
|
||||||
"kuchiki",
|
"kuchiki",
|
||||||
@@ -6481,6 +6614,17 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml5ever"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"mac",
|
||||||
|
"markup5ever 0.11.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "3.14.1"
|
version = "3.14.1"
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ sqlx-cli = { version = "0.7.0", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
rust-argon2 = "1.0"
|
rust-argon2 = "1.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
webpage = { version = "1.1", features = ["serde"] }
|
||||||
|
|
||||||
[dependencies.tauri-plugin-sql]
|
[dependencies.tauri-plugin-sql]
|
||||||
git = "https://github.com/tauri-apps/plugins-workspace"
|
git = "https://github.com/tauri-apps/plugins-workspace"
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
// use rand::distributions::{Alphanumeric, DistString};
|
// use rand::distributions::{Alphanumeric, DistString};
|
||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
use tauri_plugin_autostart::MacosLauncher;
|
use tauri_plugin_autostart::MacosLauncher;
|
||||||
use tauri_plugin_sql::{Migration, MigrationKind};
|
use tauri_plugin_sql::{Migration, MigrationKind};
|
||||||
|
use webpage::{Webpage, WebpageOptions};
|
||||||
use window_vibrancy::{apply_mica, apply_vibrancy, NSVisualEffectMaterial};
|
use window_vibrancy::{apply_mica, apply_vibrancy, NSVisualEffectMaterial};
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize)]
|
#[derive(Clone, serde::Serialize)]
|
||||||
@@ -15,6 +18,71 @@ struct Payload {
|
|||||||
cwd: String,
|
cwd: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct OpenGraphResponse {
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
url: String,
|
||||||
|
image: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_opengraph(url: String) -> OpenGraphResponse {
|
||||||
|
let options = WebpageOptions {
|
||||||
|
allow_insecure: false,
|
||||||
|
max_redirections: 3,
|
||||||
|
timeout: Duration::from_secs(15),
|
||||||
|
useragent: "lume - desktop app".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = match Webpage::from_url(&url, options) {
|
||||||
|
Ok(webpage) => webpage,
|
||||||
|
Err(_) => {
|
||||||
|
return OpenGraphResponse {
|
||||||
|
title: "".to_string(),
|
||||||
|
description: "".to_string(),
|
||||||
|
url: "".to_string(),
|
||||||
|
image: "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = result.html;
|
||||||
|
|
||||||
|
return OpenGraphResponse {
|
||||||
|
title: html
|
||||||
|
.opengraph
|
||||||
|
.properties
|
||||||
|
.get("title")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
description: html
|
||||||
|
.opengraph
|
||||||
|
.properties
|
||||||
|
.get("description")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
url: html
|
||||||
|
.opengraph
|
||||||
|
.properties
|
||||||
|
.get("url")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
image: html
|
||||||
|
.opengraph
|
||||||
|
.images
|
||||||
|
.get(0)
|
||||||
|
.and_then(|i| Some(i.url.clone()))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn opengraph(url: String) -> OpenGraphResponse {
|
||||||
|
let result = fetch_opengraph(url).await;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn close_splashscreen(window: tauri::Window) {
|
async fn close_splashscreen(window: tauri::Window) {
|
||||||
// Close splashscreen
|
// Close splashscreen
|
||||||
@@ -179,7 +247,7 @@ fn main() {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![close_splashscreen])
|
.invoke_handler(tauri::generate_handler![close_splashscreen, opengraph])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,8 @@
|
|||||||
"exceptionDomain": "",
|
"exceptionDomain": "",
|
||||||
"frameworks": [],
|
"frameworks": [],
|
||||||
"providerShortName": null,
|
"providerShortName": null,
|
||||||
"signingIdentity": null
|
"signingIdentity": null,
|
||||||
|
"minimumSystemVersion": "10.15.0"
|
||||||
},
|
},
|
||||||
"resources": [],
|
"resources": [],
|
||||||
"shortDescription": "",
|
"shortDescription": "",
|
||||||
|
|||||||
@@ -31,12 +31,26 @@ export default class TauriAdapter implements NDKCacheAdapter {
|
|||||||
const event = await this.store.get(result as string);
|
const event = await this.store.get(result as string);
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
|
console.log('cache hit: ', result);
|
||||||
const ndkEvent = new NDKEvent(subscription.ndk, JSON.parse(event as string));
|
const ndkEvent = new NDKEvent(subscription.ndk, JSON.parse(event as string));
|
||||||
subscription.eventReceived(ndkEvent, undefined, true);
|
subscription.eventReceived(ndkEvent, undefined, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filter.ids) {
|
||||||
|
for (const id of filter.ids) {
|
||||||
|
const key = id;
|
||||||
|
const event = await this.store.get(key);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
console.log('cache hit: ', id);
|
||||||
|
const ndkEvent = new NDKEvent(subscription.ndk, JSON.parse(event as string));
|
||||||
|
subscription.eventReceived(ndkEvent, undefined, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setEvent(event: NDKEvent): Promise<void> {
|
public async setEvent(event: NDKEvent): Promise<void> {
|
||||||
@@ -1,452 +0,0 @@
|
|||||||
import { fetch } from '@tauri-apps/plugin-http';
|
|
||||||
import * as cheerio from 'cheerio';
|
|
||||||
|
|
||||||
import { OPENGRAPH } from '@stores/constants';
|
|
||||||
|
|
||||||
interface ILinkPreviewOptions {
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
imagesPropertyType?: string;
|
|
||||||
proxyUrl?: string;
|
|
||||||
timeout?: number;
|
|
||||||
followRedirects?: `follow` | `error` | `manual`;
|
|
||||||
resolveDNSHost?: (url: string) => Promise<string>;
|
|
||||||
handleRedirects?: (baseURL: string, forwardedURL: string) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IPreFetchedResource {
|
|
||||||
headers: Record<string, string>;
|
|
||||||
status?: number;
|
|
||||||
imagesPropertyType?: string;
|
|
||||||
proxyUrl?: string;
|
|
||||||
url: string;
|
|
||||||
data: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwOnLoopback(address: string) {
|
|
||||||
if (OPENGRAPH.REGEX_LOOPBACK.test(address)) {
|
|
||||||
throw new Error('SSRF request detected, trying to query host');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function metaTag(doc: cheerio.CheerioAPI, type: string, attr: string) {
|
|
||||||
const nodes = doc(`meta[${attr}='${type}']`);
|
|
||||||
return nodes.length ? nodes : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function metaTagContent(doc: cheerio.CheerioAPI, type: string, attr: string) {
|
|
||||||
return doc(`meta[${attr}='${type}']`).attr(`content`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTitle(doc: cheerio.CheerioAPI) {
|
|
||||||
let title =
|
|
||||||
metaTagContent(doc, `og:title`, `property`) ||
|
|
||||||
metaTagContent(doc, `og:title`, `name`);
|
|
||||||
if (!title) {
|
|
||||||
title = doc(`title`).text();
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSiteName(doc: cheerio.CheerioAPI) {
|
|
||||||
const siteName =
|
|
||||||
metaTagContent(doc, `og:site_name`, `property`) ||
|
|
||||||
metaTagContent(doc, `og:site_name`, `name`);
|
|
||||||
return siteName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDescription(doc: cheerio.CheerioAPI) {
|
|
||||||
const description =
|
|
||||||
metaTagContent(doc, `description`, `name`) ||
|
|
||||||
metaTagContent(doc, `Description`, `name`) ||
|
|
||||||
metaTagContent(doc, `og:description`, `property`);
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMediaType(doc: cheerio.CheerioAPI) {
|
|
||||||
const node = metaTag(doc, `medium`, `name`);
|
|
||||||
if (node) {
|
|
||||||
const content = node.attr(`content`);
|
|
||||||
return content === `image` ? `photo` : content;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
metaTagContent(doc, `og:type`, `property`) || metaTagContent(doc, `og:type`, `name`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImages(
|
|
||||||
doc: cheerio.CheerioAPI,
|
|
||||||
rootUrl: string,
|
|
||||||
imagesPropertyType?: string
|
|
||||||
) {
|
|
||||||
let images: string[] = [];
|
|
||||||
let nodes: cheerio.Cheerio<cheerio.Element> | null;
|
|
||||||
let src: string | undefined;
|
|
||||||
let dic: Record<string, boolean> = {};
|
|
||||||
|
|
||||||
const imagePropertyType = imagesPropertyType ?? `og`;
|
|
||||||
nodes =
|
|
||||||
metaTag(doc, `${imagePropertyType}:image`, `property`) ||
|
|
||||||
metaTag(doc, `${imagePropertyType}:image`, `name`);
|
|
||||||
|
|
||||||
if (nodes) {
|
|
||||||
nodes.each((_: number, node: cheerio.Element) => {
|
|
||||||
if (node.type === `tag`) {
|
|
||||||
src = node.attribs.content;
|
|
||||||
if (src) {
|
|
||||||
src = new URL(src, rootUrl).href;
|
|
||||||
images.push(src);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (images.length <= 0 && !imagesPropertyType) {
|
|
||||||
src = doc(`link[rel=image_src]`).attr(`href`);
|
|
||||||
if (src) {
|
|
||||||
src = new URL(src, rootUrl).href;
|
|
||||||
images = [src];
|
|
||||||
} else {
|
|
||||||
nodes = doc(`img`);
|
|
||||||
|
|
||||||
if (nodes?.length) {
|
|
||||||
dic = {};
|
|
||||||
images = [];
|
|
||||||
nodes.each((_: number, node: cheerio.Element) => {
|
|
||||||
if (node.type === `tag`) src = node.attribs.src;
|
|
||||||
if (src && !dic[src]) {
|
|
||||||
dic[src] = true;
|
|
||||||
// width = node.attribs.width;
|
|
||||||
// height = node.attribs.height;
|
|
||||||
images.push(new URL(src, rootUrl).href);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return images;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVideos(doc: cheerio.CheerioAPI) {
|
|
||||||
const videos = [];
|
|
||||||
let nodeTypes;
|
|
||||||
let nodeSecureUrls;
|
|
||||||
let nodeType;
|
|
||||||
let nodeSecureUrl;
|
|
||||||
let video;
|
|
||||||
let videoType;
|
|
||||||
let videoSecureUrl;
|
|
||||||
let width;
|
|
||||||
let height;
|
|
||||||
let videoObj;
|
|
||||||
let index;
|
|
||||||
|
|
||||||
const nodes = metaTag(doc, `og:video`, `property`) || metaTag(doc, `og:video`, `name`);
|
|
||||||
|
|
||||||
if (nodes?.length) {
|
|
||||||
nodeTypes =
|
|
||||||
metaTag(doc, `og:video:type`, `property`) || metaTag(doc, `og:video:type`, `name`);
|
|
||||||
nodeSecureUrls =
|
|
||||||
metaTag(doc, `og:video:secure_url`, `property`) ||
|
|
||||||
metaTag(doc, `og:video:secure_url`, `name`);
|
|
||||||
width =
|
|
||||||
metaTagContent(doc, `og:video:width`, `property`) ||
|
|
||||||
metaTagContent(doc, `og:video:width`, `name`);
|
|
||||||
height =
|
|
||||||
metaTagContent(doc, `og:video:height`, `property`) ||
|
|
||||||
metaTagContent(doc, `og:video:height`, `name`);
|
|
||||||
|
|
||||||
for (index = 0; index < nodes.length; index += 1) {
|
|
||||||
const node = nodes[index];
|
|
||||||
if (node.type === `tag`) video = node.attribs.content;
|
|
||||||
|
|
||||||
nodeType = nodeTypes?.[index];
|
|
||||||
if (nodeType?.type === `tag`) {
|
|
||||||
videoType = nodeType ? nodeType.attribs.content : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeSecureUrl = nodeSecureUrls?.[index];
|
|
||||||
if (nodeSecureUrl?.type === `tag`) {
|
|
||||||
videoSecureUrl = nodeSecureUrl ? nodeSecureUrl.attribs.content : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
videoObj = {
|
|
||||||
url: video,
|
|
||||||
secureUrl: videoSecureUrl,
|
|
||||||
type: videoType,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
if (videoType && videoType.indexOf(`video/`) === 0) {
|
|
||||||
videos.splice(0, 0, videoObj);
|
|
||||||
} else {
|
|
||||||
videos.push(videoObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns default favicon (//hostname/favicon.ico) for a url
|
|
||||||
function getDefaultFavicon(rootUrl: string) {
|
|
||||||
return `${new URL(rootUrl).origin}/favicon.ico`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns an array of URLs to favicon images
|
|
||||||
function getFavicons(doc: cheerio.CheerioAPI, rootUrl: string) {
|
|
||||||
const images = [];
|
|
||||||
let nodes: cheerio.Cheerio<cheerio.Element> | never[] = [];
|
|
||||||
let src: string | undefined;
|
|
||||||
|
|
||||||
const relSelectors = [`rel=icon`, `rel="shortcut icon"`, `rel=apple-touch-icon`];
|
|
||||||
|
|
||||||
relSelectors.forEach((relSelector) => {
|
|
||||||
// look for all icon tags
|
|
||||||
nodes = doc(`link[${relSelector}]`);
|
|
||||||
|
|
||||||
// collect all images from icon tags
|
|
||||||
if (nodes.length) {
|
|
||||||
nodes.each((_: number, node: cheerio.Element) => {
|
|
||||||
if (node.type === `tag`) src = node.attribs.href;
|
|
||||||
if (src) {
|
|
||||||
src = new URL(src, rootUrl).href;
|
|
||||||
images.push(src);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if no icon images, use default favicon location
|
|
||||||
if (images.length <= 0) {
|
|
||||||
images.push(getDefaultFavicon(rootUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
return images;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseImageResponse(url: string, contentType: string) {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
mediaType: `image`,
|
|
||||||
contentType,
|
|
||||||
favicons: [getDefaultFavicon(url)],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAudioResponse(url: string, contentType: string) {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
mediaType: `audio`,
|
|
||||||
contentType,
|
|
||||||
favicons: [getDefaultFavicon(url)],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseVideoResponse(url: string, contentType: string) {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
mediaType: `video`,
|
|
||||||
contentType,
|
|
||||||
favicons: [getDefaultFavicon(url)],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseApplicationResponse(url: string, contentType: string) {
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
mediaType: `application`,
|
|
||||||
contentType,
|
|
||||||
favicons: [getDefaultFavicon(url)],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseTextResponse(
|
|
||||||
body: string,
|
|
||||||
url: string,
|
|
||||||
options: ILinkPreviewOptions = {},
|
|
||||||
contentType?: string
|
|
||||||
) {
|
|
||||||
const doc = cheerio.load(body);
|
|
||||||
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
title: getTitle(doc),
|
|
||||||
siteName: getSiteName(doc),
|
|
||||||
description: getDescription(doc),
|
|
||||||
mediaType: getMediaType(doc) || `website`,
|
|
||||||
contentType,
|
|
||||||
images: getImages(doc, url, options.imagesPropertyType),
|
|
||||||
videos: getVideos(doc),
|
|
||||||
favicons: getFavicons(doc, url),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseUnknownResponse(
|
|
||||||
body: string,
|
|
||||||
url: string,
|
|
||||||
options: ILinkPreviewOptions = {},
|
|
||||||
contentType?: string
|
|
||||||
) {
|
|
||||||
return parseTextResponse(body, url, options, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseResponse(response: IPreFetchedResource, options?: ILinkPreviewOptions) {
|
|
||||||
try {
|
|
||||||
let contentType = response.headers[`content-type`];
|
|
||||||
// console.warn(`original content type`, contentType);
|
|
||||||
if (contentType?.indexOf(`;`)) {
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
contentType = contentType.split(`;`)[0];
|
|
||||||
// console.warn(`splitting content type`, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contentType) {
|
|
||||||
return parseUnknownResponse(response.data, response.url, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((contentType as any) instanceof Array) {
|
|
||||||
// eslint-disable-next-line no-param-reassign, prefer-destructuring
|
|
||||||
contentType = contentType[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse response depending on content type
|
|
||||||
if (OPENGRAPH.REGEX_CONTENT_TYPE_IMAGE.test(contentType)) {
|
|
||||||
return parseImageResponse(response.url, contentType);
|
|
||||||
}
|
|
||||||
if (OPENGRAPH.REGEX_CONTENT_TYPE_AUDIO.test(contentType)) {
|
|
||||||
return parseAudioResponse(response.url, contentType);
|
|
||||||
}
|
|
||||||
if (OPENGRAPH.REGEX_CONTENT_TYPE_VIDEO.test(contentType)) {
|
|
||||||
return parseVideoResponse(response.url, contentType);
|
|
||||||
}
|
|
||||||
if (OPENGRAPH.REGEX_CONTENT_TYPE_TEXT.test(contentType)) {
|
|
||||||
const htmlString = response.data;
|
|
||||||
return parseTextResponse(htmlString, response.url, options, contentType);
|
|
||||||
}
|
|
||||||
if (OPENGRAPH.REGEX_CONTENT_TYPE_APPLICATION.test(contentType)) {
|
|
||||||
return parseApplicationResponse(response.url, contentType);
|
|
||||||
}
|
|
||||||
const htmlString = response.data;
|
|
||||||
return parseUnknownResponse(htmlString, response.url, options);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(
|
|
||||||
`link-preview-js could not fetch link information ${(e as any).toString()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the text, extracts the first link it finds and does a HTTP request
|
|
||||||
* to fetch the website content, afterwards it tries to parse the internal HTML
|
|
||||||
* and extract the information via meta tags
|
|
||||||
* @param text string, text to be parsed
|
|
||||||
* @param options ILinkPreviewOptions
|
|
||||||
*/
|
|
||||||
export async function getLinkPreview(text: string, options?: ILinkPreviewOptions) {
|
|
||||||
if (!text || typeof text !== `string`) {
|
|
||||||
throw new Error(`link-preview-js did not receive a valid url or text`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const detectedUrl = text
|
|
||||||
.replace(/\n/g, ` `)
|
|
||||||
.split(` `)
|
|
||||||
.find((token) => OPENGRAPH.REGEX_VALID_URL.test(token));
|
|
||||||
|
|
||||||
if (!detectedUrl) {
|
|
||||||
throw new Error(`link-preview-js did not receive a valid a url or text`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.followRedirects === `manual` && !options?.handleRedirects) {
|
|
||||||
throw new Error(
|
|
||||||
`link-preview-js followRedirects is set to manual, but no handleRedirects function was provided`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.resolveDNSHost) {
|
|
||||||
const resolvedUrl = await options.resolveDNSHost(detectedUrl);
|
|
||||||
|
|
||||||
throwOnLoopback(resolvedUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = options?.timeout ?? 3000; // 3 second timeout default
|
|
||||||
const controller = new AbortController();
|
|
||||||
const timeoutCounter = setTimeout(() => controller.abort(), timeout);
|
|
||||||
|
|
||||||
const fetchOptions = {
|
|
||||||
headers: options?.headers ?? {},
|
|
||||||
redirect: options?.followRedirects ?? `error`,
|
|
||||||
signal: controller.signal,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchUrl = options?.proxyUrl ? options.proxyUrl.concat(detectedUrl) : detectedUrl;
|
|
||||||
|
|
||||||
// Seems like fetchOptions type definition is out of date
|
|
||||||
// https://github.com/node-fetch/node-fetch/issues/741
|
|
||||||
let response = await fetch(fetchUrl, fetchOptions as any).catch((e) => {
|
|
||||||
if (e.name === `AbortError`) {
|
|
||||||
throw new Error(`Request timeout`);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(timeoutCounter);
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
response.status > 300 &&
|
|
||||||
response.status < 309 &&
|
|
||||||
fetchOptions.redirect === `manual` &&
|
|
||||||
options?.handleRedirects
|
|
||||||
) {
|
|
||||||
const forwardedUrl = response.headers.get(`location`) || ``;
|
|
||||||
|
|
||||||
if (!options.handleRedirects(fetchUrl, forwardedUrl)) {
|
|
||||||
throw new Error(`link-preview-js could not handle redirect`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.resolveDNSHost) {
|
|
||||||
const resolvedUrl = await options.resolveDNSHost(forwardedUrl);
|
|
||||||
|
|
||||||
throwOnLoopback(resolvedUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
response = await fetch(forwardedUrl, fetchOptions as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(timeoutCounter);
|
|
||||||
|
|
||||||
const headers: Record<string, string> = {};
|
|
||||||
response.headers.forEach((header, key) => {
|
|
||||||
headers[key] = header;
|
|
||||||
});
|
|
||||||
|
|
||||||
const normalizedResponse: IPreFetchedResource = {
|
|
||||||
url: options?.proxyUrl ? response.url.replace(options.proxyUrl, ``) : response.url,
|
|
||||||
headers,
|
|
||||||
data: await response.text(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return parseResponse(normalizedResponse, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip the library fetching the website for you, instead pass a response object
|
|
||||||
* from whatever source you get and use the internal parsing of the HTML to return
|
|
||||||
* the necessary information
|
|
||||||
* @param response Preview Response
|
|
||||||
* @param options IPreviewLinkOptions
|
|
||||||
*/
|
|
||||||
export async function getPreviewFromContent(
|
|
||||||
response: IPreFetchedResource,
|
|
||||||
options?: ILinkPreviewOptions
|
|
||||||
) {
|
|
||||||
if (!response || typeof response !== `object`) {
|
|
||||||
throw new Error(`link-preview-js did not receive a valid response object`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.url) {
|
|
||||||
throw new Error(`link-preview-js did not receive a valid response object`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseResponse(response, options);
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,11 @@ export async function connect(): Promise<Database> {
|
|||||||
if (db) {
|
if (db) {
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
db = await Database.load('sqlite:lume.db');
|
try {
|
||||||
|
db = await Database.load('sqlite:lume.db');
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Failed to connect to database, error: ', e);
|
||||||
|
}
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,26 +48,12 @@ export async function getActiveAccount() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all accounts
|
|
||||||
export async function getAccounts() {
|
|
||||||
const db = await connect();
|
|
||||||
const result: Array<Account> = await db.select(
|
|
||||||
'SELECT * FROM accounts WHERE is_active = 0 ORDER BY created_at DESC;'
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create account
|
// create account
|
||||||
export async function createAccount(
|
export async function createAccount(npub: string, pubkey: string, follows?: string[][]) {
|
||||||
npub: string,
|
|
||||||
pubkey: string,
|
|
||||||
follows?: string[][],
|
|
||||||
is_active?: number
|
|
||||||
) {
|
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
const res = await db.execute(
|
const res = await db.execute(
|
||||||
'INSERT OR IGNORE INTO accounts (npub, pubkey, privkey, follows, is_active) VALUES (?, ?, ?, ?, ?);',
|
'INSERT OR IGNORE INTO accounts (npub, pubkey, privkey, follows, is_active) VALUES (?, ?, ?, ?, ?);',
|
||||||
[npub, pubkey, 'privkey is stored in secure storage', follows || '', is_active || 0]
|
[npub, pubkey, 'privkey is stored in secure storage', follows || '', 1]
|
||||||
);
|
);
|
||||||
if (res) {
|
if (res) {
|
||||||
await createWidget(
|
await createWidget(
|
||||||
@@ -86,13 +76,6 @@ export async function updateAccount(column: string, value: string | string[]) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// count total notes
|
|
||||||
export async function countTotalChannels() {
|
|
||||||
const db = await connect();
|
|
||||||
const result = await db.select('SELECT COUNT(*) AS "total" FROM channels;');
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// count total notes
|
// count total notes
|
||||||
export async function countTotalNotes() {
|
export async function countTotalNotes() {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
@@ -127,21 +110,6 @@ export async function getNotes(limit: number, offset: number) {
|
|||||||
return notes;
|
return notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all notes by pubkey
|
|
||||||
export async function getNotesByPubkey(pubkey: string) {
|
|
||||||
const db = await connect();
|
|
||||||
|
|
||||||
const query: LumeEvent[] = await db.select(
|
|
||||||
`SELECT * FROM notes WHERE pubkey == "${pubkey}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC;`
|
|
||||||
);
|
|
||||||
|
|
||||||
query.forEach(
|
|
||||||
(el) => (el.tags = typeof el.tags === 'string' ? destr(el.tags) : el.tags)
|
|
||||||
);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all notes by authors
|
// get all notes by authors
|
||||||
export async function getNotesByAuthors(authors: string, limit: number, offset: number) {
|
export async function getNotesByAuthors(authors: string, limit: number, offset: number) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
@@ -228,90 +196,6 @@ export async function createReplyNote(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all pubkeys in db
|
|
||||||
export async function getAllPubkeys() {
|
|
||||||
const db = await connect();
|
|
||||||
const notes: any = await db.select('SELECT DISTINCT pubkey FROM notes');
|
|
||||||
const replies: any = await db.select('SELECT DISTINCT pubkey FROM replies');
|
|
||||||
const chats: any = await db.select('SELECT DISTINCT sender_pubkey FROM chats');
|
|
||||||
return [...notes, ...replies, ...chats];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all channels
|
|
||||||
export async function getChannels() {
|
|
||||||
const db = await connect();
|
|
||||||
const result: any = await db.select('SELECT * FROM channels ORDER BY created_at DESC;');
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get channel by id
|
|
||||||
export async function getChannel(id: string) {
|
|
||||||
const db = await connect();
|
|
||||||
const result = await db.select(`SELECT * FROM channels WHERE event_id = "${id}";`);
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// create channel
|
|
||||||
export async function createChannel(
|
|
||||||
event_id: string,
|
|
||||||
pubkey: string,
|
|
||||||
name: string,
|
|
||||||
picture: string,
|
|
||||||
about: string,
|
|
||||||
created_at: number
|
|
||||||
) {
|
|
||||||
const db = await connect();
|
|
||||||
return await db.execute(
|
|
||||||
'INSERT OR IGNORE INTO channels (event_id, pubkey, name, picture, about, created_at) VALUES (?, ?, ?, ?, ?, ?);',
|
|
||||||
[event_id, pubkey, name, picture, about, created_at]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update channel metadata
|
|
||||||
export async function updateChannelMetadata(event_id: string, value: string) {
|
|
||||||
const db = await connect();
|
|
||||||
const data = JSON.parse(value);
|
|
||||||
|
|
||||||
return await db.execute(
|
|
||||||
'UPDATE channels SET name = ?, picture = ?, about = ? WHERE event_id = ?;',
|
|
||||||
[data.name, data.picture, data.about, event_id]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// create channel messages
|
|
||||||
export async function createChannelMessage(
|
|
||||||
channel_id: string,
|
|
||||||
event_id: string,
|
|
||||||
pubkey: string,
|
|
||||||
kind: number,
|
|
||||||
content: string,
|
|
||||||
tags: string[][],
|
|
||||||
created_at: number
|
|
||||||
) {
|
|
||||||
const db = await connect();
|
|
||||||
return await db.execute(
|
|
||||||
'INSERT OR IGNORE INTO channel_messages (channel_id, event_id, pubkey, kind, content, tags, created_at) VALUES (?, ?, ?, ?, ?, ?, ?);',
|
|
||||||
[channel_id, event_id, pubkey, kind, content, tags, created_at]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get channel messages by channel id
|
|
||||||
export async function getChannelMessages(channel_id: string) {
|
|
||||||
const db = await connect();
|
|
||||||
return await db.select(
|
|
||||||
`SELECT * FROM channel_messages WHERE channel_id = "${channel_id}" ORDER BY created_at ASC;`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get channel users
|
|
||||||
export async function getChannelUsers(channel_id: string) {
|
|
||||||
const db = await connect();
|
|
||||||
const result: any = await db.select(
|
|
||||||
`SELECT DISTINCT pubkey FROM channel_messages WHERE channel_id = "${channel_id}";`
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all chats by pubkey
|
// get all chats by pubkey
|
||||||
export async function getChats() {
|
export async function getChats() {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
@@ -35,10 +35,10 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{data.images?.[0] && (
|
{data.image && (
|
||||||
<Image
|
<Image
|
||||||
src={data.images?.[0] || 'https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW'}
|
src={data.image}
|
||||||
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW"
|
fallback="https://void.cat/d/XTmrMkpid8DGLjv1AzdvcW.jpg"
|
||||||
alt={urls[0]}
|
alt={urls[0]}
|
||||||
className="h-44 w-full rounded-t-lg object-cover"
|
className="h-44 w-full rounded-t-lg object-cover"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
|
|
||||||
import { getLinkPreview } from '@libs/openGraph';
|
import { Opengraph } from '@utils/types';
|
||||||
|
|
||||||
export function useOpenGraph(url: string) {
|
export function useOpenGraph(url: string) {
|
||||||
const { status, data, error, isFetching } = useQuery(
|
const { status, data, error, isFetching } = useQuery(
|
||||||
['preview', url],
|
['preview', url],
|
||||||
async () => {
|
async () => {
|
||||||
const res = await getLinkPreview(url);
|
const res: Opengraph = await invoke('opengraph', { url });
|
||||||
if (!res) {
|
if (!res) {
|
||||||
throw new Error('fetch preview failed');
|
throw new Error('fetch preview failed');
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/utils/types.d.ts
vendored
7
src/utils/types.d.ts
vendored
@@ -62,3 +62,10 @@ export interface Relays {
|
|||||||
relay: string;
|
relay: string;
|
||||||
purpose?: string;
|
purpose?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Opengraph {
|
||||||
|
url: string;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
image?: string;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user