From 155f4091a6d3970d6f05b68cbb4677f2171cf6de Mon Sep 17 00:00:00 2001 From: aecsocket Date: Tue, 3 Mar 2026 22:20:48 +0000 Subject: [PATCH] Tweak search sorting (#5464) * Tweak search sorting * Tweak search sorting * fix ping impl * remove port field, add server regions * fix compile * fix tests * update frontend banner upload size limit * feat: use server project region instead of country * remove java and bedrock port in frontend * add helper text * allow filtering by if server is online * add server status online offline filter * use region in instance * pre-collapse status in app discovery * pnpm prepr * remove server discovery flag * add servers into mobile nav tabs * parse port from address if present --------- Co-authored-by: tdgao --- Cargo.lock | 168 ++++++----------- Cargo.toml | 3 +- apps/app-frontend/src/pages/Browse.vue | 3 +- .../app-frontend/src/pages/instance/Index.vue | 6 +- .../ui/create/ProjectCreateModal.vue | 2 - apps/frontend/src/composables/featureFlags.ts | 1 - apps/frontend/src/layouts/default.vue | 5 +- apps/frontend/src/pages/[type]/[id].vue | 4 +- .../src/pages/[type]/[id]/settings/index.vue | 4 +- .../src/pages/[type]/[id]/settings/server.vue | 176 +++++------------- apps/frontend/src/pages/discover.vue | 1 - .../src/pages/discover/[type]/index.vue | 3 +- apps/labrinth/Cargo.toml | 3 +- apps/labrinth/src/clickhouse/mod.rs | 11 +- apps/labrinth/src/models/exp/compat.rs | 1 + apps/labrinth/src/models/exp/minecraft.rs | 49 +++-- apps/labrinth/src/queue/server_ping.rs | 144 +++++--------- .../src/routes/internal/server_ping.rs | 3 +- apps/labrinth/src/search/indexing/mod.rs | 1 + apps/labrinth/src/search/mod.rs | 2 +- .../api-client/src/modules/labrinth/types.ts | 10 +- .../src/data/nags/server-projects.ts | 2 +- .../project/ProjectSidebarServerInfo.vue | 8 +- .../components/project/card/ProjectCard.vue | 6 +- .../project/server/ServerRegion.vue | 27 ++- packages/ui/src/locales/en-US/index.json | 3 - packages/ui/src/utils/server-search.ts | 76 +++++--- 27 files changed, 280 insertions(+), 442 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07e0b9d3b..467e34285 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -643,10 +643,10 @@ dependencies = [ [[package]] name = "async-minecraft-ping" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668b459c14dd8d9ef21e296af3f2a3651ff7dc3536e092fb0b09e528daaa6d89" +source = "git+https://github.com/jsvana/async-minecraft-ping?rev=56a64a8a59de854fb81cc0f9f66c2285873d960c#56a64a8a59de854fb81cc0f9f66c2285873d960c" dependencies = [ "async-trait", + "hickory-resolver 0.24.4", "serde", "serde_json", "thiserror 1.0.69", @@ -1896,7 +1896,7 @@ checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" dependencies = [ "cookie 0.18.1", "document-features", - "idna 1.1.0", + "idna", "log", "publicsuffix", "serde", @@ -2698,24 +2698,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "elytra-ping" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "086ee116d28d0eb35ae108d1059d135e585dd1254e5df881c5b2505a6d67c445" -dependencies = [ - "bytes", - "chrono", - "mc-varint", - "rand 0.9.2", - "serde", - "serde_json", - "snafu", - "tokio", - "tracing", - "trust-dns-resolver", -] - [[package]] name = "email-encoding" version = "0.4.1" @@ -3806,6 +3788,30 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + [[package]] name = "hickory-proto" version = "0.25.2" @@ -3819,7 +3825,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 1.1.0", + "idna", "ipnet", "once_cell", "rand 0.9.2", @@ -3831,6 +3837,27 @@ dependencies = [ "url", ] +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto 0.24.4", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", +] + [[package]] name = "hickory-resolver" version = "0.25.2" @@ -3839,7 +3866,7 @@ checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" dependencies = [ "cfg-if", "futures-util", - "hickory-proto", + "hickory-proto 0.25.2", "ipconfig", "moka", "once_cell", @@ -4289,16 +4316,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.1.0" @@ -4824,7 +4841,6 @@ dependencies = [ "dotenv-build", "dotenvy", "either", - "elytra-ping", "eyre", "futures", "futures-util", @@ -4935,7 +4951,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna 1.1.0", + "idna", "mime", "nom 8.0.0", "percent-encoding", @@ -5252,12 +5268,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "mc-varint" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6407d50d4e371d5f450a4a5f1abd4531e48e9a1627409c051b5d4c7c84f6bb09" - [[package]] name = "md-5" version = "0.10.6" @@ -7178,7 +7188,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" dependencies = [ - "idna 1.1.0", + "idna", "psl-types", ] @@ -8918,28 +8928,6 @@ dependencies = [ "serde", ] -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "backtrace", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "socket2" version = "0.5.10" @@ -10086,7 +10074,7 @@ dependencies = [ "fs4", "futures", "heck 0.5.0", - "hickory-resolver", + "hickory-resolver 0.25.2", "indicatif", "itertools 0.14.0", "notify", @@ -10792,52 +10780,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand 0.8.5", - "smallvec", - "thiserror 1.0.69", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand 0.8.5", - "resolv-conf", - "smallvec", - "thiserror 1.0.69", - "tokio", - "tracing", - "trust-dns-proto", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -11057,7 +10999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", - "idna 1.1.0", + "idna", "percent-encoding", "serde", ] @@ -11205,7 +11147,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ - "idna 1.1.0", + "idna", "once_cell", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index dd4dd8fd3..04639aa10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ arc-swap = "1.7.1" argon2 = { version = "0.5.3", features = ["std"] } ariadne = { path = "packages/ariadne" } async-compression = { version = "0.4.32", default-features = false } -async-minecraft-ping = { version = "0.8.0" } +async-minecraft-ping = { git = "https://github.com/jsvana/async-minecraft-ping", rev = "56a64a8a59de854fb81cc0f9f66c2285873d960c" } async-recursion = "1.1.1" async-stripe = { version = "0.41.0", default-features = false, features = [ "runtime-tokio-hyper-rustls", @@ -72,7 +72,6 @@ dotenv-build = "0.1.1" dotenvy = "0.15.7" dunce = "1.0.5" either = "1.15.0" -elytra-ping = "6.0.1" encoding_rs = "0.8.35" enumset = "1.1.10" eyre = "0.6.12" diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index 05c7afcd6..cd25e243f 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -666,6 +666,7 @@ previousFilterState.value = JSON.stringify({ 'server_category_minecraft_server_meta', 'server_category_minecraft_server_community', 'server_game_version', + 'server_status', ].includes(filterType.id) " > @@ -810,7 +811,7 @@ previousFilterState.value = JSON.stringify({ :tags="project.categories" :link="`/project/${project.slug ?? project.project_id}`" :server-online-players="project.minecraft_java_server?.ping?.data?.players_online ?? 0" - :server-region-code="project.minecraft_server?.country" + :server-region="project.minecraft_server?.region" :server-recent-plays="project.minecraft_java_server?.verified_plays_4w ?? 0" :server-modpack-content="getServerModpackContent(project)" :server-ping="serverPings[project.project_id]" diff --git a/apps/app-frontend/src/pages/instance/Index.vue b/apps/app-frontend/src/pages/instance/Index.vue index 3167f204f..3800d242b 100644 --- a/apps/app-frontend/src/pages/instance/Index.vue +++ b/apps/app-frontend/src/pages/instance/Index.vue @@ -58,16 +58,16 @@
- +
diff --git a/apps/frontend/src/components/ui/create/ProjectCreateModal.vue b/apps/frontend/src/components/ui/create/ProjectCreateModal.vue index 66db57596..17f8766ca 100644 --- a/apps/frontend/src/components/ui/create/ProjectCreateModal.vue +++ b/apps/frontend/src/components/ui/create/ProjectCreateModal.vue @@ -417,11 +417,9 @@ async function createProject() { }, minecraft_java_server: { address: '', - port: 25565, }, minecraft_bedrock_server: { address: '', - port: 19132, }, }) createdProjectId = result.id diff --git a/apps/frontend/src/composables/featureFlags.ts b/apps/frontend/src/composables/featureFlags.ts index 1fbab75ba..96748eec5 100644 --- a/apps/frontend/src/composables/featureFlags.ts +++ b/apps/frontend/src/composables/featureFlags.ts @@ -38,7 +38,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({ newProjectGeneralSettings: false, newProjectEnvironmentSettings: true, hideRussiaCensorshipBanner: false, - serverDiscovery: true, disablePrettyProjectUrlRedirects: false, hidePreviewBanner: false, i18nDebug: false, diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue index 2121b197b..082f566e9 100644 --- a/apps/frontend/src/layouts/default.vue +++ b/apps/frontend/src/layouts/default.vue @@ -184,7 +184,6 @@ { id: 'servers', action: '/discover/servers', - shown: flags.serverDiscovery, }, ]" hoverable @@ -1002,6 +1001,10 @@ const navRoutes = computed(() => [ label: formatMessage(getProjectTypeMessage('modpack', true)), href: '/discover/modpacks', }, + { + label: formatMessage(getProjectTypeMessage('server', true)), + href: '/discover/servers', + }, ]) const userMenuOptions = computed(() => { diff --git a/apps/frontend/src/pages/[type]/[id].vue b/apps/frontend/src/pages/[type]/[id].vue index d4836dc11..9c94f42b2 100644 --- a/apps/frontend/src/pages/[type]/[id].vue +++ b/apps/frontend/src/pages/[type]/[id].vue @@ -1188,7 +1188,7 @@ const serverProject = computed(() => ({ numPlayers: projectV3.value?.minecraft_java_server?.ping?.data?.players_online, icon: project.value.icon_url, statusOnline: !!projectV3.value?.minecraft_java_server?.ping?.data, - region: projectV3.value?.minecraft_server?.country, + region: projectV3.value?.minecraft_server?.region, })) function handlePlayServerProject() { @@ -2528,8 +2528,6 @@ const navLinks = computed(() => { ? project.value.gallery.filter((item) => item.name === '__mc_server_banner__').length : project.value.gallery.length - console.log('galleryCount', galleryCount, !!currentMember.value) - return [ { label: formatMessage(messages.descriptionTab), diff --git a/apps/frontend/src/pages/[type]/[id]/settings/index.vue b/apps/frontend/src/pages/[type]/[id]/settings/index.vue index 264ce1fff..02b66082c 100644 --- a/apps/frontend/src/pages/[type]/[id]/settings/index.vue +++ b/apps/frontend/src/pages/[type]/[id]/settings/index.vue @@ -146,7 +146,9 @@ (e) => { const input = e.target if (input.files?.length) { - if (fileIsValid(input.files[0], { maxSize: 524288, alertOnInvalid: true })) + if ( + fileIsValid(input.files[0], { maxSize: 524288000, alertOnInvalid: true }) + ) showBannerPreview(Array.from(input.files)) } } diff --git a/apps/frontend/src/pages/[type]/[id]/settings/server.vue b/apps/frontend/src/pages/[type]/[id]/settings/server.vue index c3dddbc1d..af8881622 100644 --- a/apps/frontend/src/pages/[type]/[id]/settings/server.vue +++ b/apps/frontend/src/pages/[type]/[id]/settings/server.vue @@ -4,17 +4,17 @@
Server details
- +
-
@@ -48,7 +48,7 @@
-
.
+
+ If you have [SRV records] + , you do not need to add a port. Otherwise if you have a port which isn't 25565, you + can include it as :12345 +
@@ -140,16 +141,6 @@ wrapper-class="flex-grow" autocomplete="off" /> - @@ -168,7 +159,7 @@ diff --git a/apps/frontend/src/pages/discover/[type]/index.vue b/apps/frontend/src/pages/discover/[type]/index.vue index 05b21c908..af485f6c1 100644 --- a/apps/frontend/src/pages/discover/[type]/index.vue +++ b/apps/frontend/src/pages/discover/[type]/index.vue @@ -642,6 +642,7 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) => 'server_category_minecraft_server_meta', 'server_category_minecraft_server_community', 'server_game_version', + 'server_status', ].includes(filterType.id) " > @@ -800,7 +801,7 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) => project.minecraft_java_server?.ping?.data?.players_online ?? 0 " :server-recent-plays="project.minecraft_java_server?.verified_plays_2w ?? 0" - :server-region-code="project.minecraft_server?.country" + :server-region="project.minecraft_server?.region" :server-status-online="!!project.minecraft_java_server?.ping?.data" :server-modpack-content="getServerModpackContent(project)" :layout=" diff --git a/apps/labrinth/Cargo.toml b/apps/labrinth/Cargo.toml index efe3723a3..a8598d158 100644 --- a/apps/labrinth/Cargo.toml +++ b/apps/labrinth/Cargo.toml @@ -21,7 +21,7 @@ actix-ws = { workspace = true } arc-swap = { workspace = true } argon2 = { workspace = true } ariadne = { workspace = true } -async-minecraft-ping = { workspace = true } +async-minecraft-ping = { workspace = true, features = ["srv"] } async-stripe = { workspace = true, features = [ "billing", "checkout", @@ -44,7 +44,6 @@ deadpool-redis.workspace = true derive_more = { workspace = true, features = ["deref", "deref_mut"] } dotenvy = { workspace = true } either = { workspace = true } -elytra-ping = { workspace = true } eyre = { workspace = true } futures = { workspace = true } futures-util = { workspace = true } diff --git a/apps/labrinth/src/clickhouse/mod.rs b/apps/labrinth/src/clickhouse/mod.rs index 6830aed1a..3d922d0b9 100644 --- a/apps/labrinth/src/clickhouse/mod.rs +++ b/apps/labrinth/src/clickhouse/mod.rs @@ -172,7 +172,6 @@ pub async fn init_client_with_database( recorded DateTime64(4), project_id UInt64, address String, - port UInt16, online Bool, latency_ms Nullable(UInt32), description Nullable(String), @@ -219,5 +218,15 @@ pub async fn init_client_with_database( .execute() .await?; + client + .query(&format!( + " + ALTER TABLE {database}.{MINECRAFT_JAVA_SERVER_PINGS} {cluster_line} + DROP COLUMN IF EXISTS port + " + )) + .execute() + .await?; + Ok(client.with_database(database)) } diff --git a/apps/labrinth/src/models/exp/compat.rs b/apps/labrinth/src/models/exp/compat.rs index 562f71ddb..ba5f206b1 100644 --- a/apps/labrinth/src/models/exp/compat.rs +++ b/apps/labrinth/src/models/exp/compat.rs @@ -32,6 +32,7 @@ mod tests { minecraft_server: Some(ServerProject { max_players: None, country: None, + region: None, languages: vec![], active_version: None, }), diff --git a/apps/labrinth/src/models/exp/minecraft.rs b/apps/labrinth/src/models/exp/minecraft.rs index 1dcd9481b..a66333c76 100644 --- a/apps/labrinth/src/models/exp/minecraft.rs +++ b/apps/labrinth/src/models/exp/minecraft.rs @@ -101,6 +101,15 @@ component::define! { #[validate(length(min = 2, max = 2))] pub country: Option, #[base(serde(default))] + #[edit(serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_with::rust::double_option" + ))] + #[create(optional)] + /// Geographical region which this server is hosted in. + pub region: Option, + #[base(serde(default))] #[edit(serde(default))] #[create(default)] /// Languages which the owners of this server prefer. @@ -129,11 +138,6 @@ component::define! { /// Address (IP or domain name) of the Bedrock server, excluding port. #[validate(length(max = 255))] pub address: String, - #[base()] - #[edit(serde(default))] - #[create(required)] - /// Port which the server runs on. - pub port: u16, } } @@ -167,8 +171,6 @@ pub struct JavaServerProject { /// Address (IP or domain name) of the Java server, excluding port. #[validate(length(max = 255))] pub address: String, - /// Port which the server runs on. - pub port: u16, /// What game content this server is using. #[serde(default)] pub content: ServerContent, @@ -180,15 +182,12 @@ pub struct JavaServerProjectEdit { #[serde(default)] pub address: Option, #[serde(default)] - pub port: Option, - #[serde(default)] pub content: Option, } #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct JavaServerProjectQuery { pub address: String, - pub port: u16, pub content: ServerContentQuery, pub ping: Option, pub verified_plays_2w: Option, @@ -229,7 +228,6 @@ impl ComponentQuery for JavaServerProjectQuery { let analytics = context.minecraft_server_analytics.get(&project_id); Ok(Self { address: serial.address, - port: serial.port, content: match serial.content { ServerContent::Vanilla { supported_game_versions, @@ -276,7 +274,6 @@ impl ComponentEdit for JavaServerProjectEdit { fn create(self) -> Result { Ok(JavaServerProject { address: self.address.wrap_err("missing `address`")?, - port: self.port.wrap_err("missing `port`")?, content: self.content.unwrap_or_default(), }) } @@ -285,9 +282,6 @@ impl ComponentEdit for JavaServerProjectEdit { if let Some(address) = self.address { component.address = address; } - if let Some(port) = self.port { - component.port = port; - } if let Some(content) = self.content { component.content = content; } @@ -347,6 +341,29 @@ impl Default for ServerContent { } } +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + utoipa::ToSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum ServerRegion { + UsEast, + UsWest, + Europe, + Asia, + Australia, + SouthAmerica, + MiddleEast, + Russia, +} + /// Recorded ping attempt that Labrinth made to a Minecraft Java server project. #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct JavaServerPing { @@ -354,8 +371,6 @@ pub struct JavaServerPing { pub when: DateTime, /// Address of the server at the time of the ping. pub address: String, - /// Port of the server at the time of the ping. - pub port: u16, /// If the ping was successful, info on the ping response. pub data: Option, } diff --git a/apps/labrinth/src/queue/server_ping.rs b/apps/labrinth/src/queue/server_ping.rs index f03a1aea0..b08f21b10 100644 --- a/apps/labrinth/src/queue/server_ping.rs +++ b/apps/labrinth/src/queue/server_ping.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::Semaphore; use tokio::task::JoinSet; -use tracing::{Instrument, debug, info, info_span, trace, warn}; +use tracing::{Instrument, info, info_span, trace, warn}; pub struct ServerPingQueue { pub db: PgPool, @@ -44,17 +44,16 @@ impl ServerPingQueue { let pings = server_projects .into_iter() .map(|(project_id, java_server)| { - let address = java_server.address.to_string(); - let port = java_server.port; - let span = info_span!("ping", %project_id, %address, %port); + let span = info_span!("ping", %project_id, address = %java_server.address); let active_pings = active_pings.clone(); + let address = java_server.address; let task = async move { let _permit = active_pings.acquire().await.expect("semaphore should not be closed now"); let mut retries = ENV.SERVER_PING_RETRIES; let result = loop { - match ping_server(&address, port, None).await { + match ping_server(&address, None).await { Ok(ping) => { info!(?ping, "Received successful ping"); break Ok(ping); @@ -73,8 +72,7 @@ impl ServerPingQueue { (project_id, exp::minecraft::JavaServerPing { when: Utc::now(), - address: address.to_string(), - port, + address, data: result.ok(), }) }; @@ -108,7 +106,6 @@ impl ServerPingQueue { / 100_000, project_id: project_id.0, address: ping.address.clone(), - port: ping.port, latency_ms: data.map(|d| d.latency.as_millis() as u32), description: data.map(|d| d.description.clone()), version_name: data.map(|d| d.version_name.clone()), @@ -251,7 +248,6 @@ impl ServerPingQueue { pub async fn ping_server( address: &str, - port: u16, timeout: Option, ) -> eyre::Result { let start = Instant::now(); @@ -260,97 +256,45 @@ pub async fn ping_server( .map(|duration| duration.min(default_duration)) .unwrap_or(default_duration); - let task_ep = async move { - fn map_component(c: elytra_ping::parse::TextComponent) -> String { - match c { - elytra_ping::parse::TextComponent::Plain(t) => t, - elytra_ping::parse::TextComponent::Fancy(t) => { - t.text.unwrap_or_default() - } - elytra_ping::parse::TextComponent::Extra(e) => e - .into_iter() - .map(map_component) - .collect::>() - .join(""), - } + let (address, port) = match address.rsplit_once(':') { + Some((addr, port)) => { + let port = port.parse::().wrap_err("invalid port number")?; + (addr, port) } + None => (address, 25565), + }; - let (result, latency) = - elytra_ping::ping_or_timeout((address.to_string(), port), timeout) - .await?; + let task = async move { + let conn = async_minecraft_ping::ConnectionConfig::build(address) + .with_port(port) + .with_srv_lookup() + .connect() + .await + .wrap_err("failed to connect to server")?; + + let status = conn + .status() + .await + .wrap_err("failed to get server status")? + .status; eyre::Ok(exp::minecraft::JavaServerPingData { - latency, - version_name: result - .version - .as_ref() - .map(|v| v.name.to_string()) - .unwrap_or_default(), - version_protocol: result - .version - .as_ref() - .map(|v| v.protocol.cast_unsigned()) - .unwrap_or_default(), - description: map_component(result.description), - players_online: result - .players - .as_ref() - .map(|p| p.online) - .unwrap_or(0), - players_max: result.players.as_ref().map(|p| p.max).unwrap_or(0), + latency: start.elapsed(), + version_name: status.version.name, + version_protocol: status.version.protocol, + description: match status.description { + ServerDescription::Plain(text) + | ServerDescription::Object { text } => text, + }, + players_online: status.players.online, + players_max: status.players.max, }) }; - let task_amp = async move { - let task = async move { - let conn = async_minecraft_ping::ConnectionConfig::build(address) - .with_port(port) - .connect() - .await - .wrap_err("failed to connect to server")?; - - let status = conn - .status() - .await - .wrap_err("failed to get server status")? - .status; - - eyre::Ok(exp::minecraft::JavaServerPingData { - latency: start.elapsed(), - version_name: status.version.name, - version_protocol: status.version.protocol, - description: match status.description { - ServerDescription::Plain(text) - | ServerDescription::Object { text } => text, - }, - players_online: status.players.online, - players_max: status.players.max, - }) - }; - - tokio::time::timeout(timeout, task) - .await - .map_err(eyre::Error::new) - .flatten() - }; - - async move { - let (result_ep, result_amp) = (task_ep.await, task_amp.await); - - let result_ep = result_ep - .inspect(|_| debug!("Successful ping with `elytra_ping`")) - .inspect_err(|err| { - debug!("Failed to ping with `elytra_ping`: {err:#}") - }); - let result_amp = result_amp - .inspect(|_| debug!("Successful ping with `async_minecraft_ping`")) - .inspect_err(|err| { - debug!("Failed to ping with `async_minecraft_ping`: {err:#}") - }); - - result_ep.or(result_amp) - } - .await + tokio::time::timeout(timeout, task) + .await + .map_err(eyre::Error::new) + .flatten() } #[derive(Debug, Row, Serialize, Clone)] @@ -358,7 +302,6 @@ struct ServerPingRecord { recorded: i64, project_id: u64, address: String, - port: u16, latency_ms: Option, description: Option, version_name: Option, @@ -373,19 +316,22 @@ mod tests { #[actix_rt::test] async fn test_ping_server_success() { - let _status = ping_server("mc.hypixel.net", 25565, None).await.unwrap(); + let _status = ping_server("mc.hypixel.net", None).await.unwrap(); + } + + #[actix_rt::test] + async fn test_follow_srv_record() { + _ = ping_server("hypixel.net", None).await.unwrap(); } #[actix_rt::test] async fn test_ping_server_invalid_address() { - _ = ping_server("invalid.invalid", 25565, None) - .await - .unwrap_err(); + _ = ping_server("invalid.invalid", None).await.unwrap_err(); } #[actix_rt::test] async fn test_ping_zero_timeout() { - _ = ping_server("hypixel.net", 25565, Some(Duration::ZERO)) + _ = ping_server("mc.hypixel.net", Some(Duration::ZERO)) .await .unwrap_err(); } diff --git a/apps/labrinth/src/routes/internal/server_ping.rs b/apps/labrinth/src/routes/internal/server_ping.rs index 423cfc534..6f2d7e147 100644 --- a/apps/labrinth/src/routes/internal/server_ping.rs +++ b/apps/labrinth/src/routes/internal/server_ping.rs @@ -19,7 +19,6 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) { #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct PingRequest { pub address: String, - pub port: u16, pub timeout_ms: Option, } @@ -42,7 +41,7 @@ pub async fn ping_minecraft_java( .await?; let timeout = request.timeout_ms.map(Duration::from_millis); - server_ping::ping_server(&request.address, request.port, timeout) + server_ping::ping_server(&request.address, timeout) .await .wrap_request_err("failed to ping server")?; diff --git a/apps/labrinth/src/search/indexing/mod.rs b/apps/labrinth/src/search/indexing/mod.rs index 614ef6b2f..85e506088 100644 --- a/apps/labrinth/src/search/indexing/mod.rs +++ b/apps/labrinth/src/search/indexing/mod.rs @@ -646,6 +646,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[ "minecraft_java_server.content.supported_game_versions", "minecraft_java_server.content.recommended_game_version", "minecraft_java_server.verified_plays_2w", + "minecraft_java_server.ping.data", "minecraft_java_server.ping.data.players_online", ]; diff --git a/apps/labrinth/src/search/mod.rs b/apps/labrinth/src/search/mod.rs index f45df7096..4763b3f91 100644 --- a/apps/labrinth/src/search/mod.rs +++ b/apps/labrinth/src/search/mod.rs @@ -274,9 +274,9 @@ pub fn get_sort_index( "relevance" => ( projects_name, &[ - "downloads:desc", "minecraft_java_server.verified_plays_2w:desc", "minecraft_java_server.ping.data.players_online:desc", + "downloads:desc", ], ), "downloads" => (projects_filtered_name, &["downloads:desc"]), diff --git a/packages/api-client/src/modules/labrinth/types.ts b/packages/api-client/src/modules/labrinth/types.ts index 498b78d24..f3ecab2d6 100644 --- a/packages/api-client/src/modules/labrinth/types.ts +++ b/packages/api-client/src/modules/labrinth/types.ts @@ -408,9 +408,13 @@ export namespace Labrinth { export interface MinecraftServer { max_players?: number - country?: string + region?: string active_version?: string | null languages?: string[] + /** + * deprecated, use region instead + */ + country?: string } export interface ModpackContent { @@ -428,7 +432,6 @@ export namespace Labrinth { export interface MinecraftJavaServer { address?: string - port?: number content?: ModpackContent | VanillaContent verified_plays_4w?: number | null verified_plays_2w?: number | null @@ -437,7 +440,6 @@ export namespace Labrinth { export interface MinecraftBedrockServer { address?: string - port?: number } export interface CreateServerProjectRequest { @@ -817,7 +819,7 @@ export namespace Labrinth { export namespace Internal { export type MinecraftJavaPingRequest = { address: string - port: number + timeout_ms?: number } } } diff --git a/packages/moderation/src/data/nags/server-projects.ts b/packages/moderation/src/data/nags/server-projects.ts index b95e4e5b9..caf092fca 100644 --- a/packages/moderation/src/data/nags/server-projects.ts +++ b/packages/moderation/src/data/nags/server-projects.ts @@ -14,7 +14,7 @@ export const serverProjectsNags: Nag[] = [ }), status: 'required', shouldShow: (context: NagContext) => - !!context.projectV3?.minecraft_server && !context.projectV3?.minecraft_server.country, + !!context.projectV3?.minecraft_server && !context.projectV3?.minecraft_server.region, link: { path: 'settings/server', title: defineMessage({ diff --git a/packages/ui/src/components/project/ProjectSidebarServerInfo.vue b/packages/ui/src/components/project/ProjectSidebarServerInfo.vue index 206b6279f..a9f841ad3 100644 --- a/packages/ui/src/components/project/ProjectSidebarServerInfo.vue +++ b/packages/ui/src/components/project/ProjectSidebarServerInfo.vue @@ -55,10 +55,10 @@ -
-

Country

+
+

Region

- +
@@ -120,7 +120,7 @@ const props = withDefaults(defineProps(), { const ipAddress = computed(() => props.projectV3?.minecraft_java_server?.address ?? '') const languages = computed(() => props.projectV3?.minecraft_server?.languages ?? []) -const country = computed(() => props.projectV3?.minecraft_server?.country) +const region = computed(() => props.projectV3?.minecraft_server?.region) const recommendedVersions = computed(() => { if (props.recommendedVersion) return [props.recommendedVersion] diff --git a/packages/ui/src/components/project/card/ProjectCard.vue b/packages/ui/src/components/project/card/ProjectCard.vue index 2647fa72d..fa42dc40b 100644 --- a/packages/ui/src/components/project/card/ProjectCard.vue +++ b/packages/ui/src/components/project/card/ProjectCard.vue @@ -51,7 +51,7 @@
import { computed } from 'vue' -import { defineMessage, useVIntl } from '../../../composables' +import { TagItem } from '../../base' const { region } = defineProps<{ region: string }>() -const { formatMessage } = useVIntl() +const regionNames: Record = { + us_east: 'US East', + us_west: 'US West', + europe: 'Europe', + asia: 'Asia', + australia: 'Australia', + south_america: 'South America', + middle_east: 'Middle East', + russia: 'Russia', +} -const alt = defineMessage({ - id: 'project.server.region.alt', - defaultMessage: 'Region: {regionCode}', -}) - -const regionLower = computed(() => region.toLowerCase()) +const regionName = computed(() => regionNames[region] ?? region) diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json index 236d956ce..515442075 100644 --- a/packages/ui/src/locales/en-US/index.json +++ b/packages/ui/src/locales/en-US/index.json @@ -992,9 +992,6 @@ "project.server.ping.ms": { "defaultMessage": "{ping} ms" }, - "project.server.region.alt": { - "defaultMessage": "Region: {regionCode}" - }, "project.settings.analytics.title": { "defaultMessage": "Analytics" }, diff --git a/packages/ui/src/utils/server-search.ts b/packages/ui/src/utils/server-search.ts index 12c5306f9..88ac2d8cd 100644 --- a/packages/ui/src/utils/server-search.ts +++ b/packages/ui/src/utils/server-search.ts @@ -55,25 +55,15 @@ const SERVER_CATEGORY_ICON_MAP: Record = { 'world-resets': 'refresh-ccw', } -export const SERVER_COUNTRIES = [ - { code: 'US', name: 'United States' }, - { code: 'GB', name: 'United Kingdom' }, - { code: 'DE', name: 'Germany' }, - { code: 'FR', name: 'France' }, - { code: 'NL', name: 'Netherlands' }, - { code: 'PL', name: 'Poland' }, - { code: 'RU', name: 'Russia' }, - { code: 'BR', name: 'Brazil' }, - { code: 'CA', name: 'Canada' }, - { code: 'AU', name: 'Australia' }, - { code: 'SE', name: 'Sweden' }, - { code: 'FI', name: 'Finland' }, - { code: 'SG', name: 'Singapore' }, - { code: 'JP', name: 'Japan' }, - { code: 'KR', name: 'South Korea' }, - { code: 'TR', name: 'Turkey' }, - { code: 'IN', name: 'India' }, - { code: 'ZA', name: 'South Africa' }, +export const SERVER_REGIONS = [ + { code: 'us_east', name: 'US East' }, + { code: 'us_west', name: 'US West' }, + { code: 'europe', name: 'Europe' }, + { code: 'asia', name: 'Asia' }, + { code: 'australia', name: 'Australia' }, + { code: 'south_america', name: 'South America' }, + { code: 'middle_east', name: 'Middle East' }, + { code: 'russia', name: 'Russia' }, ] export const SERVER_LANGUAGES = [ @@ -106,7 +96,8 @@ export const SERVER_SORT_TYPES: SortType[] = [ const FILTER_FIELD_MAP: Record = { server_content_type: 'minecraft_java_server.content.kind', server_game_version: 'minecraft_java_server.content.supported_game_versions', - server_country: 'minecraft_server.country', + server_status: 'minecraft_java_server.ping.data', + server_region: 'minecraft_server.region', server_language: 'minecraft_server.languages', } @@ -128,7 +119,7 @@ export function useServerSearch(opts: { const route = useRoute() const serverCurrentSortType = ref(SERVER_SORT_TYPES[0]) - const serverCurrentFilters = ref([]) + const serverCurrentFilters = ref([{ type: 'server_status', option: 'online' }]) const serverToggledGroups = ref([]) const serverFilterTypes = computed(() => { @@ -206,18 +197,18 @@ export function useServerSearch(opts: { })), }, { - id: 'server_country', - formatted_name: 'Country', + id: 'server_region', + formatted_name: 'Region', supported_project_types: ['server'], - display: 'scrollable', - query_param: 'sco', + display: 'all', + query_param: 'sr', supports_negative_filter: true, - searchable: true, - options: SERVER_COUNTRIES.map((c) => ({ - id: c.code, - formatted_name: c.name, + searchable: false, + options: SERVER_REGIONS.map((r) => ({ + id: r.code, + formatted_name: r.name, method: 'or' as const, - value: c.code, + value: r.code, })), }, { @@ -235,6 +226,19 @@ export function useServerSearch(opts: { value: l.code, })), }, + { + id: 'server_status', + formatted_name: 'Status', + supported_project_types: ['server'], + display: 'all', + query_param: 'sst', + supports_negative_filter: false, + searchable: false, + options: [ + { id: 'online', formatted_name: 'Online', method: 'or', value: 'online' }, + { id: 'offline', formatted_name: 'Offline', method: 'or', value: 'offline' }, + ], + }, ] }) @@ -245,6 +249,18 @@ export function useServerSearch(opts: { const field = getFilterField(filterType.id) if (!field) continue const matched = serverCurrentFilters.value.filter((f) => f.type === filterType.id) + if (matched.length === 0) continue + + if (filterType.id === 'server_status') { + const selected = matched[0]?.option + if (selected === 'online') { + parts.push(`${field} EXISTS`) + } else if (selected === 'offline') { + parts.push(`${field} NOT EXISTS`) + } + continue + } + const included = matched.filter((f) => !f.negative) const excluded = matched.filter((f) => f.negative) if (included.length > 0) {