Add web crawler infrastructure and HangarMC implementation
This commit is contained in:
parent
d9cf404402
commit
eb51afaea8
@ -4,7 +4,7 @@
|
|||||||
- [x] Create project roadmap
|
- [x] Create project roadmap
|
||||||
- [x] Initialize Tauri + React project
|
- [x] Initialize Tauri + React project
|
||||||
- [x] Setup basic project structure
|
- [x] Setup basic project structure
|
||||||
- [ ] Create GitHub repository (optional)
|
- [x] Create Remote repository (optional)
|
||||||
|
|
||||||
## Core Infrastructure (In Progress)
|
## Core Infrastructure (In Progress)
|
||||||
- [x] Setup SQLite or JSON storage for plugin data
|
- [x] Setup SQLite or JSON storage for plugin data
|
||||||
@ -18,9 +18,9 @@
|
|||||||
- [x] Design plugin metadata extraction system
|
- [x] Design plugin metadata extraction system
|
||||||
- [x] Build plugin hash identification system
|
- [x] Build plugin hash identification system
|
||||||
|
|
||||||
## Web Crawler Development (Upcoming)
|
## Web Crawler Development (In Progress)
|
||||||
- [ ] Create base web crawler architecture
|
- [x] Create base web crawler architecture
|
||||||
- [ ] Implement HangarMC crawler
|
- [x] Implement HangarMC crawler
|
||||||
- [ ] Implement SpigotMC crawler
|
- [ ] Implement SpigotMC crawler
|
||||||
- [ ] Implement Modrinth crawler
|
- [ ] Implement Modrinth crawler
|
||||||
- [ ] Implement GitHub releases crawler
|
- [ ] Implement GitHub releases crawler
|
||||||
|
366
src-tauri/Cargo.lock
generated
366
src-tauri/Cargo.lock
generated
@ -606,6 +606,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -629,9 +639,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics-types",
|
"core-graphics-types",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -642,7 +652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -915,7 +925,7 @@ dependencies = [
|
|||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml",
|
"toml",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg",
|
"winreg 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -924,6 +934,15 @@ version = "1.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encoding_rs"
|
||||||
|
version = "0.8.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -1039,6 +1058,15 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared 0.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1046,7 +1074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"foreign-types-macros",
|
"foreign-types-macros",
|
||||||
"foreign-types-shared",
|
"foreign-types-shared 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1060,6 +1088,12 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types-shared"
|
name = "foreign-types-shared"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -1476,6 +1510,25 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.3.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"http 0.2.12",
|
||||||
|
"indexmap 2.8.0",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@ -1535,6 +1588,17 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa 1.0.15",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@ -1546,6 +1610,17 @@ dependencies = [
|
|||||||
"itoa 1.0.15",
|
"itoa 1.0.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http 0.2.12",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-body"
|
name = "http-body"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -1553,7 +1628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1564,8 +1639,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1575,6 +1650,36 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa 1.0.15",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -1584,8 +1689,8 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"httparse",
|
"httparse",
|
||||||
"itoa 1.0.15",
|
"itoa 1.0.15",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@ -1594,6 +1699,19 @@ dependencies = [
|
|||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
@ -1603,9 +1721,9 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"hyper",
|
"hyper 1.6.0",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -2164,6 +2282,23 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -2494,6 +2629,50 @@ dependencies = [
|
|||||||
"pathdiff",
|
"pathdiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types 0.3.2",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -2780,6 +2959,7 @@ name = "plugsnatcher"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
|
"reqwest 0.11.27",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
@ -3098,6 +3278,46 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http 0.2.12",
|
||||||
|
"http-body 0.4.6",
|
||||||
|
"hyper 0.14.32",
|
||||||
|
"hyper-tls",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper 0.1.2",
|
||||||
|
"system-configuration",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"winreg 0.50.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.15"
|
version = "0.12.15"
|
||||||
@ -3108,10 +3328,10 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"http-body",
|
"http-body 1.0.1",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper 1.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@ -3123,7 +3343,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
@ -3202,6 +3422,15 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
@ -3223,6 +3452,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "0.8.22"
|
version = "0.8.22"
|
||||||
@ -3256,6 +3494,29 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selectors"
|
name = "selectors"
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
@ -3522,7 +3783,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"foreign-types",
|
"foreign-types 0.5.0",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
@ -3643,6 +3904,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@ -3663,6 +3930,27 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@ -3683,7 +3971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1"
|
checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"core-foundation",
|
"core-foundation 0.10.0",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
@ -3748,7 +4036,7 @@ dependencies = [
|
|||||||
"glob",
|
"glob",
|
||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@ -3760,7 +4048,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"reqwest",
|
"reqwest 0.12.15",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
@ -3934,7 +4222,7 @@ dependencies = [
|
|||||||
"cookie",
|
"cookie",
|
||||||
"dpi",
|
"dpi",
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"jni",
|
"jni",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3952,7 +4240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "087188020fd6facb8578fe9b38e81fa0fe5fb85744c73da51a299f94a530a1e3"
|
checksum = "087188020fd6facb8578fe9b38e81fa0fe5fb85744c73da51a299f94a530a1e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"jni",
|
"jni",
|
||||||
"log",
|
"log",
|
||||||
"objc2 0.6.0",
|
"objc2 0.6.0",
|
||||||
@ -3985,7 +4273,7 @@ dependencies = [
|
|||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"infer",
|
"infer",
|
||||||
"json-patch",
|
"json-patch",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
@ -4148,6 +4436,16 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.14"
|
version = "0.7.14"
|
||||||
@ -4226,7 +4524,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"sync_wrapper",
|
"sync_wrapper 1.0.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@ -4431,6 +4729,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -5149,6 +5453,16 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.50.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@ -5195,7 +5509,7 @@ dependencies = [
|
|||||||
"gdkx11",
|
"gdkx11",
|
||||||
"gtk",
|
"gtk",
|
||||||
"html5ever",
|
"html5ever",
|
||||||
"http",
|
"http 1.3.1",
|
||||||
"javascriptcore-rs",
|
"javascriptcore-rs",
|
||||||
"jni",
|
"jni",
|
||||||
"kuchikiki",
|
"kuchikiki",
|
||||||
|
@ -28,4 +28,5 @@ yaml-rust = "0.4"
|
|||||||
walkdir = "2.4"
|
walkdir = "2.4"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||||
|
|
||||||
|
203
src-tauri/src/crawlers/hangar.rs
Normal file
203
src-tauri/src/crawlers/hangar.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::{HttpClient, RepositoryCrawler, RepositoryPlugin, RepositorySource};
|
||||||
|
|
||||||
|
// HangarMC API response structures
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarProjectsResponse {
|
||||||
|
pagination: HangarPagination,
|
||||||
|
result: Vec<HangarProject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarPagination {
|
||||||
|
limit: u32,
|
||||||
|
offset: u32,
|
||||||
|
count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarProject {
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
namespace: HangarNamespace,
|
||||||
|
category: String,
|
||||||
|
description: Option<String>,
|
||||||
|
stats: HangarStats,
|
||||||
|
last_updated: String,
|
||||||
|
icon_url: Option<String>,
|
||||||
|
created_at: String,
|
||||||
|
visibility: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarNamespace {
|
||||||
|
owner: String,
|
||||||
|
slug: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarStats {
|
||||||
|
views: u64,
|
||||||
|
downloads: u64,
|
||||||
|
recent_views: u64,
|
||||||
|
recent_downloads: u64,
|
||||||
|
stars: u64,
|
||||||
|
watchers: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarVersionsResponse {
|
||||||
|
pagination: HangarPagination,
|
||||||
|
result: Vec<HangarVersion>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct HangarVersion {
|
||||||
|
name: String,
|
||||||
|
created_at: String,
|
||||||
|
description: Option<String>,
|
||||||
|
downloads: u64,
|
||||||
|
file_size: u64,
|
||||||
|
platform_versions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// HangarMC crawler implementation
|
||||||
|
pub struct HangarCrawler {
|
||||||
|
client: HttpClient,
|
||||||
|
api_base_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HangarCrawler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
HangarCrawler {
|
||||||
|
client: HttpClient::new(),
|
||||||
|
api_base_url: "https://hangar.papermc.io/api/v1".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_project_details(&self, owner: &str, slug: &str) -> Result<HangarProject, Box<dyn Error>> {
|
||||||
|
let url = format!("{}/projects/{}/{}", self.api_base_url, owner, slug);
|
||||||
|
let response = self.client.get(&url)?;
|
||||||
|
let project: HangarProject = serde_json::from_str(&response)?;
|
||||||
|
Ok(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_project_versions(&self, owner: &str, slug: &str) -> Result<Vec<HangarVersion>, Box<dyn Error>> {
|
||||||
|
let url = format!("{}/projects/{}/{}/versions", self.api_base_url, owner, slug);
|
||||||
|
let response = self.client.get(&url)?;
|
||||||
|
let versions_response: HangarVersionsResponse = serde_json::from_str(&response)?;
|
||||||
|
Ok(versions_response.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_download_url(&self, owner: &str, slug: &str, version: &str) -> String {
|
||||||
|
format!("https://hangar.papermc.io/api/v1/projects/{}/{}/versions/{}/download", owner, slug, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepositoryCrawler for HangarCrawler {
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>> {
|
||||||
|
let url = format!("{}/projects?query={}&limit=20", self.api_base_url, query);
|
||||||
|
let response = self.client.get(&url)?;
|
||||||
|
|
||||||
|
let projects_response: HangarProjectsResponse = serde_json::from_str(&response)?;
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
for project in projects_response.result {
|
||||||
|
// For each project, get the latest version
|
||||||
|
let versions = self.get_project_versions(&project.namespace.owner, &project.namespace.slug)?;
|
||||||
|
|
||||||
|
if let Some(latest_version) = versions.first() {
|
||||||
|
results.push(RepositoryPlugin {
|
||||||
|
id: format!("{}/{}", project.namespace.owner, project.namespace.slug),
|
||||||
|
name: project.name,
|
||||||
|
version: latest_version.name.clone(),
|
||||||
|
description: project.description,
|
||||||
|
authors: vec![project.namespace.owner.clone()],
|
||||||
|
download_url: self.build_download_url(&project.namespace.owner, &project.namespace.slug, &latest_version.name),
|
||||||
|
repository: RepositorySource::HangarMC,
|
||||||
|
page_url: format!("https://hangar.papermc.io/{}/{}", project.namespace.owner, project.namespace.slug),
|
||||||
|
download_count: Some(project.stats.downloads),
|
||||||
|
last_updated: Some(project.last_updated),
|
||||||
|
icon_url: project.icon_url,
|
||||||
|
minecraft_versions: latest_version.platform_versions.clone(),
|
||||||
|
categories: vec![project.category],
|
||||||
|
rating: None, // HangarMC uses stars, not ratings
|
||||||
|
file_size: Some(latest_version.file_size),
|
||||||
|
file_hash: None, // HangarMC API doesn't provide file hashes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>> {
|
||||||
|
let parts: Vec<&str> = plugin_id.split('/').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err("Invalid plugin ID format for HangarMC. Expected 'owner/slug'".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let owner = parts[0];
|
||||||
|
let slug = parts[1];
|
||||||
|
|
||||||
|
let project = self.get_project_details(owner, slug)?;
|
||||||
|
let versions = self.get_project_versions(owner, slug)?;
|
||||||
|
|
||||||
|
if let Some(latest_version) = versions.first() {
|
||||||
|
Ok(RepositoryPlugin {
|
||||||
|
id: plugin_id.to_string(),
|
||||||
|
name: project.name,
|
||||||
|
version: latest_version.name.clone(),
|
||||||
|
description: project.description,
|
||||||
|
authors: vec![project.namespace.owner.clone()],
|
||||||
|
download_url: self.build_download_url(owner, slug, &latest_version.name),
|
||||||
|
repository: RepositorySource::HangarMC,
|
||||||
|
page_url: format!("https://hangar.papermc.io/{}/{}", owner, slug),
|
||||||
|
download_count: Some(project.stats.downloads),
|
||||||
|
last_updated: Some(project.last_updated),
|
||||||
|
icon_url: project.icon_url,
|
||||||
|
minecraft_versions: latest_version.platform_versions.clone(),
|
||||||
|
categories: vec![project.category],
|
||||||
|
rating: None,
|
||||||
|
file_size: Some(latest_version.file_size),
|
||||||
|
file_hash: None,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("No versions found for this plugin".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
|
let parts: Vec<&str> = plugin_id.split('/').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err("Invalid plugin ID format for HangarMC. Expected 'owner/slug'".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let owner = parts[0];
|
||||||
|
let slug = parts[1];
|
||||||
|
|
||||||
|
let versions = self.get_project_versions(owner, slug)?;
|
||||||
|
Ok(versions.into_iter().map(|v| v.name).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>> {
|
||||||
|
let parts: Vec<&str> = plugin_id.split('/').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
return Err("Invalid plugin ID format for HangarMC. Expected 'owner/slug'".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let owner = parts[0];
|
||||||
|
let slug = parts[1];
|
||||||
|
|
||||||
|
let download_url = self.build_download_url(owner, slug, version);
|
||||||
|
self.client.download(&download_url, destination)?;
|
||||||
|
|
||||||
|
Ok(destination.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_repository_name(&self) -> RepositorySource {
|
||||||
|
RepositorySource::HangarMC
|
||||||
|
}
|
||||||
|
}
|
4
src-tauri/src/crawlers/mod.rs
Normal file
4
src-tauri/src/crawlers/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod hangar;
|
||||||
|
|
||||||
|
// Re-export the crawler implementations
|
||||||
|
pub use hangar::HangarCrawler;
|
@ -8,6 +8,12 @@ use zip::ZipArchive;
|
|||||||
use yaml_rust::{YamlLoader, Yaml};
|
use yaml_rust::{YamlLoader, Yaml};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
use reqwest;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
// Add the crawlers module
|
||||||
|
mod crawlers;
|
||||||
|
use crawlers::HangarCrawler;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
pub enum ServerType {
|
pub enum ServerType {
|
||||||
@ -603,12 +609,155 @@ fn greet(name: &str) -> String {
|
|||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web Crawler Module
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
pub enum RepositorySource {
|
||||||
|
HangarMC,
|
||||||
|
SpigotMC,
|
||||||
|
Modrinth,
|
||||||
|
GitHub,
|
||||||
|
BukkitDev,
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct RepositoryPlugin {
|
||||||
|
id: String, // Unique identifier in the repository
|
||||||
|
name: String, // Plugin name
|
||||||
|
version: String, // Latest version
|
||||||
|
description: Option<String>,
|
||||||
|
authors: Vec<String>,
|
||||||
|
download_url: String, // URL to download the plugin
|
||||||
|
repository: RepositorySource,
|
||||||
|
page_url: String, // URL to the plugin page
|
||||||
|
download_count: Option<u64>,
|
||||||
|
last_updated: Option<String>,
|
||||||
|
icon_url: Option<String>,
|
||||||
|
minecraft_versions: Vec<String>,
|
||||||
|
categories: Vec<String>,
|
||||||
|
rating: Option<f32>,
|
||||||
|
file_size: Option<u64>,
|
||||||
|
file_hash: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait for implementing different repository crawlers
|
||||||
|
pub trait RepositoryCrawler {
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>>;
|
||||||
|
fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>>;
|
||||||
|
fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>>;
|
||||||
|
fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>>;
|
||||||
|
fn get_repository_name(&self) -> RepositorySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic HTTP client for crawler implementations
|
||||||
|
pub struct HttpClient {
|
||||||
|
client: reqwest::blocking::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpClient {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.user_agent("PlugSnatcher/0.1.0")
|
||||||
|
.build()
|
||||||
|
.unwrap_or_else(|_| reqwest::blocking::Client::new());
|
||||||
|
|
||||||
|
HttpClient { client }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, url: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let response = self.client.get(url).send()?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
Ok(response.text()?)
|
||||||
|
} else {
|
||||||
|
Err(format!("HTTP error: {}", response.status()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download(&self, url: &str, destination: &Path) -> Result<(), Box<dyn Error>> {
|
||||||
|
let response = self.client.get(url).send()?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let bytes = response.bytes()?;
|
||||||
|
fs::write(destination, bytes)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("Download failed: {}", response.status()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get crawler for a specific repository
|
||||||
|
fn get_crawler(repository: &RepositorySource) -> Option<Box<dyn RepositoryCrawler>> {
|
||||||
|
match repository {
|
||||||
|
RepositorySource::HangarMC => Some(Box::new(HangarCrawler::new())),
|
||||||
|
// Other repositories will be implemented later
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to search for plugins in specified repositories
|
||||||
|
#[command]
|
||||||
|
pub fn search_repository_plugins(query: &str, repositories: Vec<RepositorySource>) -> Result<Vec<RepositoryPlugin>, String> {
|
||||||
|
let mut results: Vec<RepositoryPlugin> = Vec::new();
|
||||||
|
|
||||||
|
// Try each requested repository
|
||||||
|
for repo in repositories {
|
||||||
|
if let Some(crawler) = get_crawler(&repo) {
|
||||||
|
match crawler.search(query) {
|
||||||
|
Ok(repo_results) => {
|
||||||
|
results.extend(repo_results);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error searching in repository {:?}: {}", repo, e);
|
||||||
|
// Continue with other repositories even if one fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Repository crawler for {:?} not implemented yet", repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if results.is_empty() {
|
||||||
|
Err("No plugins found or repositories not implemented yet".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to get plugin details from a specific repository
|
||||||
|
#[command]
|
||||||
|
pub fn get_repository_plugin_details(plugin_id: &str, repository: RepositorySource) -> Result<RepositoryPlugin, String> {
|
||||||
|
if let Some(crawler) = get_crawler(&repository) {
|
||||||
|
crawler.get_plugin_details(plugin_id).map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to download a plugin from a repository
|
||||||
|
#[command]
|
||||||
|
pub fn download_repository_plugin(plugin_id: &str, version: &str, repository: RepositorySource, destination: &str) -> Result<String, String> {
|
||||||
|
if let Some(crawler) = get_crawler(&repository) {
|
||||||
|
crawler
|
||||||
|
.download_plugin(plugin_id, version, Path::new(destination))
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.invoke_handler(tauri::generate_handler![greet, scan_server_directory])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
greet,
|
||||||
|
scan_server_directory,
|
||||||
|
search_repository_plugins,
|
||||||
|
get_repository_plugin_details,
|
||||||
|
download_repository_plugin
|
||||||
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
763
src-tauri/src/lib.rs.bak
Normal file
763
src-tauri/src/lib.rs.bak
Normal file
@ -0,0 +1,763 @@
|
|||||||
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Read;
|
||||||
|
use tauri::command;
|
||||||
|
use zip::ZipArchive;
|
||||||
|
use yaml_rust::{YamlLoader, Yaml};
|
||||||
|
use std::fs::File;
|
||||||
|
use sha2::{Sha256, Digest};
|
||||||
|
use reqwest;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
// Add the crawlers module
|
||||||
|
mod crawlers;
|
||||||
|
use crawlers::HangarCrawler;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
pub enum ServerType {
|
||||||
|
Paper,
|
||||||
|
Spigot,
|
||||||
|
Bukkit,
|
||||||
|
Vanilla,
|
||||||
|
Forge,
|
||||||
|
Fabric,
|
||||||
|
Velocity,
|
||||||
|
BungeeCord,
|
||||||
|
Waterfall,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct ServerInfo {
|
||||||
|
server_type: ServerType,
|
||||||
|
minecraft_version: Option<String>,
|
||||||
|
plugins_directory: String,
|
||||||
|
plugins_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Plugin {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
latest_version: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
authors: Vec<String>,
|
||||||
|
has_update: bool,
|
||||||
|
api_version: Option<String>,
|
||||||
|
main_class: Option<String>,
|
||||||
|
depend: Option<Vec<String>>,
|
||||||
|
soft_depend: Option<Vec<String>>,
|
||||||
|
load_before: Option<Vec<String>>,
|
||||||
|
commands: Option<serde_json::Value>,
|
||||||
|
permissions: Option<serde_json::Value>,
|
||||||
|
file_path: String,
|
||||||
|
file_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PluginMeta {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub authors: Vec<String>,
|
||||||
|
pub api_version: Option<String>,
|
||||||
|
pub main_class: Option<String>,
|
||||||
|
pub depend: Option<Vec<String>>,
|
||||||
|
pub soft_depend: Option<Vec<String>>,
|
||||||
|
pub load_before: Option<Vec<String>>,
|
||||||
|
pub commands: Option<serde_json::Value>,
|
||||||
|
pub permissions: Option<serde_json::Value>,
|
||||||
|
pub file_path: String,
|
||||||
|
pub file_size: u64,
|
||||||
|
pub file_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates SHA-256 hash for a given file path
|
||||||
|
pub fn calculate_file_hash(file_path: &str) -> Result<String, String> {
|
||||||
|
let mut file = File::open(file_path).map_err(|e| format!("Failed to open file for hashing: {}", e))?;
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
let mut buffer = [0; 1024];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let bytes_read = file.read(&mut buffer).map_err(|e| format!("Failed to read file for hashing: {}", e))?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasher.update(&buffer[..bytes_read]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
Ok(format!("{:x}", hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract metadata from a plugin.yml file inside a JAR
|
||||||
|
fn extract_plugin_metadata(jar_path: &Path) -> Result<PluginMeta, String> {
|
||||||
|
let file = fs::File::open(jar_path)
|
||||||
|
.map_err(|e| format!("Failed to open JAR file: {}", e))?;
|
||||||
|
|
||||||
|
let file_size = file.metadata()
|
||||||
|
.map_err(|e| format!("Failed to read file metadata: {}", e))?
|
||||||
|
.len();
|
||||||
|
|
||||||
|
let mut archive = ZipArchive::new(file)
|
||||||
|
.map_err(|e| format!("Invalid JAR file: {}", e))?;
|
||||||
|
|
||||||
|
// Try to find and read plugin.yml or bungee.yml
|
||||||
|
let yaml_content = match read_yaml_from_archive(&mut archive, "plugin.yml") {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(_) => match read_yaml_from_archive(&mut archive, "bungee.yml") {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(_) => {
|
||||||
|
// If no plugin metadata file is found, try to infer from filename
|
||||||
|
let filename = jar_path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("unknown.jar");
|
||||||
|
|
||||||
|
// Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
|
||||||
|
let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||||
|
let version = if parts.len() > 1 {
|
||||||
|
parts.pop().unwrap_or("1.0.0").to_string()
|
||||||
|
} else {
|
||||||
|
"1.0.0".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = parts.join("-");
|
||||||
|
|
||||||
|
return Ok(PluginMeta {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
description: None,
|
||||||
|
authors: Vec::new(),
|
||||||
|
api_version: None,
|
||||||
|
main_class: None,
|
||||||
|
depend: None,
|
||||||
|
soft_depend: None,
|
||||||
|
load_before: None,
|
||||||
|
commands: None,
|
||||||
|
permissions: None,
|
||||||
|
file_path: jar_path.to_string_lossy().to_string(),
|
||||||
|
file_size,
|
||||||
|
file_hash: calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the YAML content
|
||||||
|
let docs = match YamlLoader::load_from_str(&yaml_content) {
|
||||||
|
Ok(docs) => docs,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to parse plugin YAML: {}", e);
|
||||||
|
return fallback_plugin_meta(jar_path, file_size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if docs.is_empty() {
|
||||||
|
return fallback_plugin_meta(jar_path, file_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc = &docs[0];
|
||||||
|
|
||||||
|
// Extract basic metadata with fallbacks for missing fields
|
||||||
|
let name = yaml_str_with_fallback(doc, "name", jar_path);
|
||||||
|
let version = yaml_str_with_fallback(doc, "version", jar_path);
|
||||||
|
|
||||||
|
// Extract optional fields
|
||||||
|
let description = yaml_str_opt(doc, "description");
|
||||||
|
|
||||||
|
// Handle authors (can be a single string or an array)
|
||||||
|
let authors = match &doc["authors"] {
|
||||||
|
Yaml::Array(arr) => {
|
||||||
|
arr.iter()
|
||||||
|
.filter_map(|a| a.as_str().map(|s| s.to_string()))
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
Yaml::String(s) => vec![s.clone()],
|
||||||
|
_ => {
|
||||||
|
// Fallback to 'author' field which is sometimes used
|
||||||
|
match &doc["author"] {
|
||||||
|
Yaml::String(s) => vec![s.clone()],
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract other optional metadata
|
||||||
|
let api_version = yaml_str_opt(doc, "api-version");
|
||||||
|
let main_class = yaml_str_opt(doc, "main");
|
||||||
|
|
||||||
|
// Handle dependency lists
|
||||||
|
let depend = yaml_str_array(doc, "depend");
|
||||||
|
let soft_depend = yaml_str_array(doc, "softdepend");
|
||||||
|
let load_before = yaml_str_array(doc, "loadbefore");
|
||||||
|
|
||||||
|
// Handle complex structures as generic JSON values
|
||||||
|
let commands = match &doc["commands"] {
|
||||||
|
Yaml::Hash(_) => {
|
||||||
|
Some(serde_json::Value::String("Commands data present".to_string()))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let permissions = match &doc["permissions"] {
|
||||||
|
Yaml::Hash(_) => {
|
||||||
|
Some(serde_json::Value::String("Permissions data present".to_string()))
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate the file hash
|
||||||
|
let file_hash = calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string());
|
||||||
|
|
||||||
|
Ok(PluginMeta {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
description,
|
||||||
|
authors,
|
||||||
|
api_version,
|
||||||
|
main_class,
|
||||||
|
depend,
|
||||||
|
soft_depend,
|
||||||
|
load_before,
|
||||||
|
commands,
|
||||||
|
permissions,
|
||||||
|
file_path: jar_path.to_string_lossy().to_string(),
|
||||||
|
file_size,
|
||||||
|
file_hash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to read a YAML file from the ZIP archive
|
||||||
|
fn read_yaml_from_archive(archive: &mut ZipArchive<fs::File>, file_name: &str) -> Result<String, String> {
|
||||||
|
match archive.by_name(file_name) {
|
||||||
|
Ok(mut file) => {
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)
|
||||||
|
.map_err(|e| format!("Failed to read {}: {}", file_name, e))?;
|
||||||
|
Ok(contents)
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!("Failed to find {}: {}", file_name, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create plugin metadata with fallback values
|
||||||
|
fn fallback_plugin_meta(jar_path: &Path, file_size: u64) -> Result<PluginMeta, String> {
|
||||||
|
let filename = jar_path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("unknown.jar");
|
||||||
|
|
||||||
|
// Extract name and version from filename (e.g., "WorldEdit-7.2.8.jar" → name: "WorldEdit", version: "7.2.8")
|
||||||
|
let mut parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||||
|
let version = if parts.len() > 1 {
|
||||||
|
parts.pop().unwrap_or("1.0.0").to_string()
|
||||||
|
} else {
|
||||||
|
"1.0.0".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = parts.join("-");
|
||||||
|
|
||||||
|
Ok(PluginMeta {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
description: None,
|
||||||
|
authors: Vec::new(),
|
||||||
|
api_version: None,
|
||||||
|
main_class: None,
|
||||||
|
depend: None,
|
||||||
|
soft_depend: None,
|
||||||
|
load_before: None,
|
||||||
|
commands: None,
|
||||||
|
permissions: None,
|
||||||
|
file_path: jar_path.to_string_lossy().to_string(),
|
||||||
|
file_size,
|
||||||
|
file_hash: calculate_file_hash(jar_path.to_str().unwrap_or("unknown.jar")).unwrap_or_else(|_| "unknown".to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract a string from YAML with fallback to filename
|
||||||
|
fn yaml_str_with_fallback(yaml: &Yaml, key: &str, jar_path: &Path) -> String {
|
||||||
|
match yaml[key].as_str() {
|
||||||
|
Some(s) => s.to_string(),
|
||||||
|
None => {
|
||||||
|
// Extract from filename as fallback
|
||||||
|
let filename = jar_path.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("unknown.jar");
|
||||||
|
|
||||||
|
if key == "name" {
|
||||||
|
// Extract name (e.g., "WorldEdit-7.2.8.jar" → "WorldEdit")
|
||||||
|
let parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||||
|
parts[0].to_string()
|
||||||
|
} else if key == "version" {
|
||||||
|
// Extract version (e.g., "WorldEdit-7.2.8.jar" → "7.2.8")
|
||||||
|
let parts: Vec<&str> = filename.trim_end_matches(".jar").split('-').collect();
|
||||||
|
if parts.len() > 1 {
|
||||||
|
parts[1].to_string()
|
||||||
|
} else {
|
||||||
|
"1.0.0".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yaml_str_opt(yaml: &Yaml, key: &str) -> Option<String> {
|
||||||
|
yaml[key].as_str().map(|s| s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yaml_str_array(yaml: &Yaml, key: &str) -> Option<Vec<String>> {
|
||||||
|
match &yaml[key] {
|
||||||
|
Yaml::Array(arr) => {
|
||||||
|
let strings: Vec<String> = arr.iter()
|
||||||
|
.filter_map(|a| a.as_str().map(|s| s.to_string()))
|
||||||
|
.collect();
|
||||||
|
if strings.is_empty() { None } else { Some(strings) }
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Detect the server type based on files in the server directory
|
||||||
|
fn detect_server_type(server_path: &Path) -> ServerType {
|
||||||
|
// Check for Paper
|
||||||
|
if server_path.join("cache").join("patched_1.19.2.jar").exists() ||
|
||||||
|
server_path.join("paper.yml").exists() {
|
||||||
|
return ServerType::Paper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Spigot
|
||||||
|
if server_path.join("spigot.yml").exists() {
|
||||||
|
return ServerType::Spigot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Bukkit
|
||||||
|
if server_path.join("bukkit.yml").exists() {
|
||||||
|
return ServerType::Bukkit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Forge
|
||||||
|
if server_path.join("forge-server.jar").exists() ||
|
||||||
|
server_path.join("mods").exists() {
|
||||||
|
return ServerType::Forge;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Fabric
|
||||||
|
if server_path.join("fabric-server-launch.jar").exists() ||
|
||||||
|
(server_path.join("mods").exists() && server_path.join("fabric-server-launcher.properties").exists()) {
|
||||||
|
return ServerType::Fabric;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Velocity
|
||||||
|
if server_path.join("velocity.toml").exists() {
|
||||||
|
return ServerType::Velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for BungeeCord
|
||||||
|
if server_path.join("BungeeCord.jar").exists() ||
|
||||||
|
server_path.join("config.yml").exists() {
|
||||||
|
return ServerType::BungeeCord;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Waterfall
|
||||||
|
if server_path.join("waterfall.jar").exists() ||
|
||||||
|
server_path.join("waterfall.yml").exists() {
|
||||||
|
return ServerType::Waterfall;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's at least a vanilla server
|
||||||
|
if server_path.join("server.properties").exists() ||
|
||||||
|
server_path.join("vanilla_server.jar").exists() {
|
||||||
|
return ServerType::Vanilla;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no server type detected
|
||||||
|
ServerType::Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guess the Minecraft version from various files in the server directory
|
||||||
|
fn detect_minecraft_version(server_path: &Path, server_type: &ServerType) -> Option<String> {
|
||||||
|
// Try from version.json if it exists
|
||||||
|
if let Ok(content) = fs::read_to_string(server_path.join("version.json")) {
|
||||||
|
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||||
|
if let Some(version) = json["name"].as_str() {
|
||||||
|
return Some(version.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try from the server jar name pattern
|
||||||
|
if let Ok(entries) = fs::read_dir(server_path) {
|
||||||
|
for entry in entries {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.is_file() && path.extension().map_or(false, |ext| ext == "jar") {
|
||||||
|
let filename = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||||
|
|
||||||
|
// Extract version from various common patterns in jar names
|
||||||
|
if filename.starts_with("paper-") ||
|
||||||
|
filename.starts_with("spigot-") ||
|
||||||
|
filename.starts_with("craftbukkit-") {
|
||||||
|
// Pattern: paper-1.19.2.jar, spigot-1.19.2.jar
|
||||||
|
let parts: Vec<&str> = filename.split('-').collect();
|
||||||
|
if parts.len() > 1 {
|
||||||
|
let version_part = parts[1].trim_end_matches(".jar");
|
||||||
|
if version_part.contains('.') { // Basic version format check
|
||||||
|
return Some(version_part.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for version patterns like minecraft_server.1.19.2.jar
|
||||||
|
if filename.starts_with("minecraft_server.") {
|
||||||
|
let version_part = filename
|
||||||
|
.trim_start_matches("minecraft_server.")
|
||||||
|
.trim_end_matches(".jar");
|
||||||
|
if version_part.contains('.') {
|
||||||
|
return Some(version_part.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If server type is proxy, look in config files
|
||||||
|
if server_type == &ServerType::BungeeCord ||
|
||||||
|
server_type == &ServerType::Waterfall ||
|
||||||
|
server_type == &ServerType::Velocity {
|
||||||
|
// Velocity uses TOML, others use YAML
|
||||||
|
if server_type == &ServerType::Velocity {
|
||||||
|
if let Ok(content) = fs::read_to_string(server_path.join("velocity.toml")) {
|
||||||
|
// Very basic TOML parsing just for this field
|
||||||
|
for line in content.lines() {
|
||||||
|
if line.contains("minecraft-version") {
|
||||||
|
if let Some(version) = line.split('=').nth(1) {
|
||||||
|
return Some(version.trim().trim_matches('"').to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Try to parse config.yml for BungeeCord/Waterfall
|
||||||
|
if let Ok(content) = fs::read_to_string(server_path.join("config.yml")) {
|
||||||
|
if let Ok(docs) = YamlLoader::load_from_str(&content) {
|
||||||
|
if !docs.is_empty() {
|
||||||
|
let doc = &docs[0];
|
||||||
|
if let Some(version) = doc["minecraft_version"].as_str() {
|
||||||
|
return Some(version.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get plugins directory path based on server type
|
||||||
|
fn get_plugins_directory(server_path: &Path, server_type: &ServerType) -> String {
|
||||||
|
match server_type {
|
||||||
|
ServerType::Velocity => server_path.join("plugins").to_string_lossy().to_string(),
|
||||||
|
ServerType::BungeeCord => server_path.join("plugins").to_string_lossy().to_string(),
|
||||||
|
ServerType::Waterfall => server_path.join("plugins").to_string_lossy().to_string(),
|
||||||
|
_ => server_path.join("plugins").to_string_lossy().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ScanResult {
|
||||||
|
server_info: ServerInfo,
|
||||||
|
plugins: Vec<Plugin>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
fn scan_server_directory(path: &str) -> Result<ScanResult, String> {
|
||||||
|
let server_path = Path::new(path);
|
||||||
|
|
||||||
|
if !server_path.exists() {
|
||||||
|
return Err(format!("Server path does not exist: {}", path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect server type and version
|
||||||
|
let server_type = detect_server_type(server_path);
|
||||||
|
let minecraft_version = detect_minecraft_version(server_path, &server_type);
|
||||||
|
|
||||||
|
println!("Detected server type: {:?}", server_type);
|
||||||
|
if let Some(version) = &minecraft_version {
|
||||||
|
println!("Detected Minecraft version: {}", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine plugins directory based on server type
|
||||||
|
let plugins_dir_str = get_plugins_directory(server_path, &server_type);
|
||||||
|
let plugins_dir = Path::new(&plugins_dir_str);
|
||||||
|
|
||||||
|
if !plugins_dir.exists() {
|
||||||
|
return Err(format!("Plugins directory not found at: {}", plugins_dir.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan for JAR files in the plugins directory
|
||||||
|
let mut plugins = Vec::new();
|
||||||
|
|
||||||
|
match fs::read_dir(&plugins_dir) {
|
||||||
|
Ok(entries) => {
|
||||||
|
for entry in entries {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
// Check if this is a JAR file
|
||||||
|
if path.is_file() && path.extension().map_or(false, |ext| ext.eq_ignore_ascii_case("jar")) {
|
||||||
|
match extract_plugin_metadata(&path) {
|
||||||
|
Ok(meta) => {
|
||||||
|
// Create a Plugin from PluginMeta
|
||||||
|
let plugin = Plugin {
|
||||||
|
name: meta.name,
|
||||||
|
version: meta.version,
|
||||||
|
latest_version: None, // Will be filled by update checker
|
||||||
|
description: meta.description,
|
||||||
|
authors: meta.authors,
|
||||||
|
has_update: false, // Will be determined by update checker
|
||||||
|
api_version: meta.api_version,
|
||||||
|
main_class: meta.main_class,
|
||||||
|
depend: meta.depend,
|
||||||
|
soft_depend: meta.soft_depend,
|
||||||
|
load_before: meta.load_before,
|
||||||
|
commands: meta.commands,
|
||||||
|
permissions: meta.permissions,
|
||||||
|
file_path: meta.file_path,
|
||||||
|
file_hash: meta.file_hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
plugins.push(plugin);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// Log error but continue with other plugins
|
||||||
|
println!("Error reading plugin from {}: {}", path.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!("Failed to read plugins directory: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no plugins were found, fall back to mock data for testing
|
||||||
|
if plugins.is_empty() && server_type == ServerType::Unknown {
|
||||||
|
// For testing only - in production, we'd just return an empty list
|
||||||
|
plugins = vec![
|
||||||
|
Plugin {
|
||||||
|
name: "EssentialsX".to_string(),
|
||||||
|
version: "2.19.0".to_string(),
|
||||||
|
latest_version: Some("2.20.0".to_string()),
|
||||||
|
description: Some("Essential server tools for Minecraft".to_string()),
|
||||||
|
authors: vec!["md_5".to_string(), "SupaHam".to_string()],
|
||||||
|
has_update: true,
|
||||||
|
api_version: Some("1.13".to_string()),
|
||||||
|
main_class: Some("com.earth2me.essentials.Essentials".to_string()),
|
||||||
|
depend: None,
|
||||||
|
soft_depend: None,
|
||||||
|
load_before: None,
|
||||||
|
commands: None,
|
||||||
|
permissions: None,
|
||||||
|
file_path: "EssentialsX.jar".to_string(),
|
||||||
|
file_hash: calculate_file_hash("EssentialsX.jar").unwrap_or_else(|_| "unknown".to_string()),
|
||||||
|
},
|
||||||
|
Plugin {
|
||||||
|
name: "WorldEdit".to_string(),
|
||||||
|
version: "7.2.8".to_string(),
|
||||||
|
latest_version: Some("7.2.8".to_string()),
|
||||||
|
description: Some("In-game map editor".to_string()),
|
||||||
|
authors: vec!["sk89q".to_string(), "wizjany".to_string()],
|
||||||
|
has_update: false,
|
||||||
|
api_version: Some("1.13".to_string()),
|
||||||
|
main_class: Some("com.sk89q.worldedit.bukkit.WorldEditPlugin".to_string()),
|
||||||
|
depend: None,
|
||||||
|
soft_depend: None,
|
||||||
|
load_before: None,
|
||||||
|
commands: None,
|
||||||
|
permissions: None,
|
||||||
|
file_path: "WorldEdit.jar".to_string(),
|
||||||
|
file_hash: calculate_file_hash("WorldEdit.jar").unwrap_or_else(|_| "unknown".to_string()),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create server info
|
||||||
|
let server_info = ServerInfo {
|
||||||
|
server_type,
|
||||||
|
minecraft_version,
|
||||||
|
plugins_directory: plugins_dir_str,
|
||||||
|
plugins_count: plugins.len(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ScanResult {
|
||||||
|
server_info,
|
||||||
|
plugins,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
fn greet(name: &str) -> String {
|
||||||
|
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web Crawler Module
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
pub enum RepositorySource {
|
||||||
|
HangarMC,
|
||||||
|
SpigotMC,
|
||||||
|
Modrinth,
|
||||||
|
GitHub,
|
||||||
|
BukkitDev,
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct RepositoryPlugin {
|
||||||
|
id: String, // Unique identifier in the repository
|
||||||
|
name: String, // Plugin name
|
||||||
|
version: String, // Latest version
|
||||||
|
description: Option<String>,
|
||||||
|
authors: Vec<String>,
|
||||||
|
download_url: String, // URL to download the plugin
|
||||||
|
repository: RepositorySource,
|
||||||
|
page_url: String, // URL to the plugin page
|
||||||
|
download_count: Option<u64>,
|
||||||
|
last_updated: Option<String>,
|
||||||
|
icon_url: Option<String>,
|
||||||
|
minecraft_versions: Vec<String>,
|
||||||
|
categories: Vec<String>,
|
||||||
|
rating: Option<f32>,
|
||||||
|
file_size: Option<u64>,
|
||||||
|
file_hash: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait for implementing different repository crawlers
|
||||||
|
pub trait RepositoryCrawler {
|
||||||
|
fn search(&self, query: &str) -> Result<Vec<RepositoryPlugin>, Box<dyn Error>>;
|
||||||
|
fn get_plugin_details(&self, plugin_id: &str) -> Result<RepositoryPlugin, Box<dyn Error>>;
|
||||||
|
fn get_plugin_versions(&self, plugin_id: &str) -> Result<Vec<String>, Box<dyn Error>>;
|
||||||
|
fn download_plugin(&self, plugin_id: &str, version: &str, destination: &Path) -> Result<String, Box<dyn Error>>;
|
||||||
|
fn get_repository_name(&self) -> RepositorySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic HTTP client for crawler implementations
|
||||||
|
pub struct HttpClient {
|
||||||
|
client: reqwest::blocking::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpClient {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.user_agent("PlugSnatcher/0.1.0")
|
||||||
|
.build()
|
||||||
|
.unwrap_or_else(|_| reqwest::blocking::Client::new());
|
||||||
|
|
||||||
|
HttpClient { client }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, url: &str) -> Result<String, Box<dyn Error>> {
|
||||||
|
let response = self.client.get(url).send()?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
Ok(response.text()?)
|
||||||
|
} else {
|
||||||
|
Err(format!("HTTP error: {}", response.status()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn download(&self, url: &str, destination: &Path) -> Result<(), Box<dyn Error>> {
|
||||||
|
let response = self.client.get(url).send()?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let bytes = response.bytes()?;
|
||||||
|
fs::write(destination, bytes)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("Download failed: {}", response.status()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get crawler for a specific repository
|
||||||
|
fn get_crawler(repository: &RepositorySource) -> Option<Box<dyn RepositoryCrawler>> {
|
||||||
|
match repository {
|
||||||
|
RepositorySource::HangarMC => Some(Box::new(HangarCrawler::new())),
|
||||||
|
// Other repositories will be implemented later
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to search for plugins in specified repositories
|
||||||
|
#[command]
|
||||||
|
pub fn search_repository_plugins(query: &str, repositories: Vec<RepositorySource>) -> Result<Vec<RepositoryPlugin>, String> {
|
||||||
|
let mut results: Vec<RepositoryPlugin> = Vec::new();
|
||||||
|
|
||||||
|
// Try each requested repository
|
||||||
|
for repo in repositories {
|
||||||
|
if let Some(crawler) = get_crawler(&repo) {
|
||||||
|
match crawler.search(query) {
|
||||||
|
Ok(repo_results) => {
|
||||||
|
results.extend(repo_results);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error searching in repository {:?}: {}", repo, e);
|
||||||
|
// Continue with other repositories even if one fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("Repository crawler for {:?} not implemented yet", repo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if results.is_empty() {
|
||||||
|
Err("No plugins found or repositories not implemented yet".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to get plugin details from a specific repository
|
||||||
|
#[command]
|
||||||
|
pub fn get_repository_plugin_details(plugin_id: &str, repository: RepositorySource) -> Result<RepositoryPlugin, String> {
|
||||||
|
if let Some(crawler) = get_crawler(&repository) {
|
||||||
|
crawler.get_plugin_details(plugin_id).map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command to download a plugin from a repository
|
||||||
|
#[command]
|
||||||
|
pub fn download_repository_plugin(plugin_id: &str, version: &str, repository: RepositorySource, destination: &str) -> Result<String, String> {
|
||||||
|
if let Some(crawler) = get_crawler(&repository) {
|
||||||
|
crawler
|
||||||
|
.download_plugin(plugin_id, version, Path::new(destination))
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
} else {
|
||||||
|
Err(format!("Repository crawler for {:?} not implemented yet", repository))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_dialog::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
greet,
|
||||||
|
scan_server_directory,
|
||||||
|
search_repository_plugins,
|
||||||
|
get_repository_plugin_details,
|
||||||
|
download_repository_plugin
|
||||||
|
])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user