diff --git a/.github/workflows/turbo-ci.yml b/.github/workflows/turbo-ci.yml index 6f82db1b2..82e9f333a 100644 --- a/.github/workflows/turbo-ci.yml +++ b/.github/workflows/turbo-ci.yml @@ -80,4 +80,4 @@ jobs: - name: 🔍 Verify intl:extract has been run run: | pnpm intl:extract - git diff --exit-code */*/src/locales/en-US/index.json + git diff --exit-code --color */*/src/locales/en-US/index.json diff --git a/apps/app-frontend/src/helpers/skins.ts b/apps/app-frontend/src/helpers/skins.ts index 9b5953f53..e31b67b1c 100644 --- a/apps/app-frontend/src/helpers/skins.ts +++ b/apps/app-frontend/src/helpers/skins.ts @@ -67,9 +67,8 @@ export async function determineModelType(texture: string): Promise<'SLIM' | 'CLA const armWidth = 2 const armHeight = 12 const imageData = context.getImageData(armX, armY, armWidth, armHeight).data - for (let index = 1; index <= imageData.length; index++) { - //every fourth value in RGBA is the alpha channel - if (index % 4 == 0 && imageData[index - 1] !== 0) { + for (let alphaIndex = 3; alphaIndex < imageData.length; alphaIndex += 4) { + if (imageData[alphaIndex] !== 0) { resolve('CLASSIC') return } diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index f11e2c044..16bfefac1 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -220,7 +220,7 @@ async function refreshSearch() { } } results.value = rawResults.result - currentPage.value = Math.max(1, Math.min(pageCount.value, currentPage.value)) + currentPage.value = 1 const persistentParams: LocationQuery = {} @@ -266,6 +266,7 @@ async function onSearchChangeToTop() { function clearSearch() { query.value = '' + currentPage.value = 1 } watch( diff --git a/apps/frontend/src/pages/admin/billing/[id].vue b/apps/frontend/src/pages/admin/billing/[id].vue index 40ba9da61..fbbaae9c6 100644 --- a/apps/frontend/src/pages/admin/billing/[id].vue +++ b/apps/frontend/src/pages/admin/billing/[id].vue @@ -150,9 +150,26 @@ + + Ended: + + Ends: + Issued: + Due: {{ dayjs(charge.due).format("MMMM D, YYYY [at] h:mma") }} ({{ formatRelativeTime(charge.due) }}) + + Last attempt: + Charged: + {{ dayjs(charge.last_attempt).format("MMMM D, YYYY [at] h:mma") }} + ({{ formatRelativeTime(charge.last_attempt) }}) + +
{{ charge.status }} ⋅ diff --git a/apps/frontend/src/pages/servers/index.vue b/apps/frontend/src/pages/servers/index.vue index 1d11bd55f..2ac52cbe8 100644 --- a/apps/frontend/src/pages/servers/index.vue +++ b/apps/frontend/src/pages/servers/index.vue @@ -45,8 +45,9 @@

- Modrinth Servers is the easiest way to host your own Minecraft server. Seamlessly install - and play your favorite mods and modpacks, all within the Modrinth platform. + Modrinth Servers is the easiest way to host your own Minecraft: Java Edition server. + Seamlessly install and play your favorite mods and modpacks, all within the Modrinth + platform.

-
+
@@ -480,7 +481,7 @@

-
+
@@ -491,6 +492,24 @@ All prices are listed in United States Dollars (USD).

+ +
+ + + + + What Minecraft versions and loaders can be used? + +

+ Modrinth Servers can run any version of Minecraft: Java Edition going all the way + back to version 1.2.5, including snapshot versions. +

+

+ We also support a wide range of mod and plugin loaders, including Fabric, Quilt, + Forge, and NeoForge for mods, as well as Paper and Purpur for plugins. Availability + depends on whether the mod or plugin loader supports the selected Minecraft version. +

+
diff --git a/apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json b/apps/labrinth/.sqlx/query-cf020daa52a1316e5f60d197196b880b72c0b2a576e470d9fd7182558103d055.json similarity index 94% rename from apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json rename to apps/labrinth/.sqlx/query-cf020daa52a1316e5f60d197196b880b72c0b2a576e470d9fd7182558103d055.json index c7e1c1a3c..47805a8b3 100644 --- a/apps/labrinth/.sqlx/query-99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2.json +++ b/apps/labrinth/.sqlx/query-cf020daa52a1316e5f60d197196b880b72c0b2a576e470d9fd7182558103d055.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')", + "query": "\n SELECT\n id, user_id, price_id, amount, currency_code, status, due, last_attempt,\n charge_type, subscription_id,\n -- Workaround for https://github.com/launchbadge/sqlx/issues/3336\n subscription_interval AS \"subscription_interval?\",\n payment_platform,\n payment_platform_id AS \"payment_platform_id?\",\n parent_charge_id AS \"parent_charge_id?\",\n net AS \"net?\"\n FROM charges\n WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled' OR status = 'failed')", "describe": { "columns": [ { @@ -102,5 +102,5 @@ true ] }, - "hash": "99cca53fd3f35325e2da3b671532bf98b8c7ad8e7cb9158e4eb9c5bac66d20b2" + "hash": "cf020daa52a1316e5f60d197196b880b72c0b2a576e470d9fd7182558103d055" } diff --git a/apps/labrinth/src/auth/mod.rs b/apps/labrinth/src/auth/mod.rs index e2dc16ff8..a22fd65cf 100644 --- a/apps/labrinth/src/auth/mod.rs +++ b/apps/labrinth/src/auth/mod.rs @@ -43,7 +43,9 @@ pub enum AuthenticationError { InvalidAuthMethod, #[error("GitHub Token from incorrect Client ID")] InvalidClientId, - #[error("User email/account is already registered on Modrinth")] + #[error( + "User email is already registered on Modrinth. Try 'Forgot password' to access your account." + )] DuplicateUser, #[error("Invalid state sent, you probably need to get a new websocket")] SocketError, diff --git a/apps/labrinth/src/database/models/charge_item.rs b/apps/labrinth/src/database/models/charge_item.rs index 5acf4c69d..2ca8b9edb 100644 --- a/apps/labrinth/src/database/models/charge_item.rs +++ b/apps/labrinth/src/database/models/charge_item.rs @@ -197,7 +197,7 @@ impl DBCharge { ) -> Result, DatabaseError> { let user_subscription_id = user_subscription_id.0; let res = select_charges_with_predicate!( - "WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled')", + "WHERE subscription_id = $1 AND (status = 'open' OR status = 'cancelled' OR status = 'failed')", user_subscription_id ) .fetch_optional(exec) diff --git a/apps/labrinth/src/routes/internal/flows.rs b/apps/labrinth/src/routes/internal/flows.rs index e8d97a65a..281f85be0 100644 --- a/apps/labrinth/src/routes/internal/flows.rs +++ b/apps/labrinth/src/routes/internal/flows.rs @@ -223,8 +223,8 @@ impl TempUser { stripe_customer_id: None, totp_secret: None, username, - email: self.email, - email_verified: true, + email: self.email.clone(), + email_verified: self.email.is_some(), avatar_url, raw_avatar_url, bio: self.bio, @@ -1419,15 +1419,15 @@ pub async fn create_account_with_password( .hash_password(new_account.password.as_bytes(), &salt)? .to_string(); - if crate::database::models::DBUser::get_by_email( + if !crate::database::models::DBUser::get_by_case_insensitive_email( &new_account.email, &**pool, ) .await? - .is_some() + .is_empty() { return Err(ApiError::InvalidInput( - "Email is already registered on Modrinth!".to_string(), + "Email is already registered on Modrinth! Try 'Forgot password' to access your account.".to_string(), )); } @@ -2220,6 +2220,18 @@ pub async fn set_email( .await? .1; + if !crate::database::models::DBUser::get_by_case_insensitive_email( + &email.email, + &**pool, + ) + .await? + .is_empty() + { + return Err(ApiError::InvalidInput( + "Email is already registered on Modrinth! Try 'Forgot password' in incognito to access and delete your other account.".to_string(), + )); + } + let mut transaction = pool.begin().await?; sqlx::query!( diff --git a/package.json b/package.json index 19dc488e5..e9919f3e9 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "app:intl:extract": "pnpm run --filter=@modrinth/app-frontend intl:extract", "blog:fix": "turbo run fix --filter=@modrinth/blog", "pages:build": "NITRO_PRESET=cloudflare-pages pnpm --filter frontend run build", + "moderation:fix": "turbo run fix --filter=@modrinth/moderation", "build": "turbo run build --continue", "lint": "turbo run lint --continue", "test": "turbo run test --continue", diff --git a/packages/app-lib/src/api/pack/import/mmc.rs b/packages/app-lib/src/api/pack/import/mmc.rs index 030ad003d..2abbf16b1 100644 --- a/packages/app-lib/src/api/pack/import/mmc.rs +++ b/packages/app-lib/src/api/pack/import/mmc.rs @@ -284,6 +284,12 @@ async fn import_mmc_unmanaged( component.version.clone().unwrap_or_default(), )); } + if component.uid.starts_with("net.neoforged") { + return Some(( + PackDependency::NeoForge, + component.version.clone().unwrap_or_default(), + )); + } if component.uid.starts_with("org.quiltmc.quilt-loader") { return Some(( PackDependency::QuiltLoader, diff --git a/packages/moderation/data/messages/checklist-text/license/id.md b/packages/moderation/data/messages/checklist-text/license/id.md deleted file mode 100644 index 37e3ac376..000000000 --- a/packages/moderation/data/messages/checklist-text/license/id.md +++ /dev/null @@ -1 +0,0 @@ -**License id:** %PROJECT_LICENSE_ID% \ diff --git a/packages/moderation/data/messages/checklist-text/license/link.md b/packages/moderation/data/messages/checklist-text/licensing.md similarity index 50% rename from packages/moderation/data/messages/checklist-text/license/link.md rename to packages/moderation/data/messages/checklist-text/licensing.md index ff5f4c4c3..87b7d0783 100644 --- a/packages/moderation/data/messages/checklist-text/license/link.md +++ b/packages/moderation/data/messages/checklist-text/licensing.md @@ -1 +1,2 @@ +**License id:** %PROJECT_LICENSE_ID% \ **License Link:** %PROJECT_LICENSE_URL% diff --git a/packages/moderation/data/messages/checklist-text/links/donation/donation.md b/packages/moderation/data/messages/checklist-text/links/donation/donation.md index 154b77d20..fce119787 100644 --- a/packages/moderation/data/messages/checklist-text/links/donation/donation.md +++ b/packages/moderation/data/messages/checklist-text/links/donation/donation.md @@ -1 +1 @@ -> **{PLATFORM}:** {URL} +> **{PLATFORM}:** {URL}
diff --git a/packages/moderation/data/messages/checklist-text/links/donation/donations.md b/packages/moderation/data/messages/checklist-text/links/donation/donations.md index 7dc9615c1..29f39bade 100644 --- a/packages/moderation/data/messages/checklist-text/links/donation/donations.md +++ b/packages/moderation/data/messages/checklist-text/links/donation/donations.md @@ -1 +1,2 @@ -**Donation Links:** +
+**Donation Links:**
diff --git a/packages/moderation/data/messages/description/non-english.md b/packages/moderation/data/messages/description/non-english.md index 5ceb22ad8..06ba85141 100644 --- a/packages/moderation/data/messages/description/non-english.md +++ b/packages/moderation/data/messages/description/non-english.md @@ -1,5 +1,5 @@ ## No English Description -Per section 2.2 of %RULES% a project's Summary and Description must be in English, unless meant exclusively for non-English use, such as translations. +Per section 2.2 of %RULES% a project's [Summary](%PROJECT_SETTINGS_LINK%) and %PROJECT_DESCRIPTION_FLINK% must be in English, unless meant exclusively for non-English use, such as translations. -You may include your non-English Description if you would like but we ask that you also add an English translation of the Description to your Description page, if you would like to use an online translator to do this, we recommend [DeepL](https://www.deepl.com/translator). +You may include your non-English Description if you would like but we ask that you also add an English translation of the Description to your project page, if you would like to use an online translator to do this, we recommend [DeepL](https://www.deepl.com/translator). diff --git a/packages/moderation/data/messages/reupload/identity_verification.md b/packages/moderation/data/messages/reupload/identity_verification.md new file mode 100644 index 000000000..198504cd1 --- /dev/null +++ b/packages/moderation/data/messages/reupload/identity_verification.md @@ -0,0 +1,6 @@ +## Identity Verification + +**Welcome to Modrinth!** We're happy to see you here, we just want to make sure you're you. + +Since this project already exists on the internet we ask that you provide evidence you are the rightful owner of this content. +For instance, by submitting a screenshot accessing the settings of the project's existing pages such as %PLATFORM%. diff --git a/packages/moderation/data/messages/status-alerts/tec/source_request-bins.md b/packages/moderation/data/messages/status-alerts/tec/source_request-bins.md new file mode 100644 index 000000000..5e1731902 --- /dev/null +++ b/packages/moderation/data/messages/status-alerts/tec/source_request-bins.md @@ -0,0 +1,5 @@ +## Source Code Requested + +To ensure the safety of all Modrinth users, we ask that you provide the source code for this project before resubmission so that it can be reviewed by our Moderation Team. +We also ask that you provide the source for any included binary files, as well as detailed build instructions allowing us to verify that the compiled code you are distributing matches the provided source. +We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub. diff --git a/packages/moderation/data/messages/status-alerts/tec/source_request-obfs.md b/packages/moderation/data/messages/status-alerts/tec/source_request-obfs.md new file mode 100644 index 000000000..37f486a4a --- /dev/null +++ b/packages/moderation/data/messages/status-alerts/tec/source_request-obfs.md @@ -0,0 +1,4 @@ +## Source Code Requested + +To ensure the safety of all Modrinth users, we ask that you provide the source code for this project and the process you used to obfuscate it before resubmission so that it can be reviewed by our Moderation Team. +We understand that you may not want to publish the source code for this project, so you are welcome to share it privately to the [Modrinth Content Moderation Team](https://github.com/ModrinthModeration) on GitHub. diff --git a/packages/moderation/data/stages/license.ts b/packages/moderation/data/stages/license.ts index 6a8ff0fdd..9ae50173e 100644 --- a/packages/moderation/data/stages/license.ts +++ b/packages/moderation/data/stages/license.ts @@ -20,14 +20,7 @@ const licensesNotRequiringSource: string[] = [ const licenseStage: Stage = { title: 'Is this license and link valid?', - text: async (project) => { - let text = '' - text += (await import('../messages/checklist-text/license/id.md?raw')).default - if (project.license.url) - text += (await import('../messages/checklist-text/license/link.md?raw')).default - - return text - }, + text: async () => (await import('../messages/checklist-text/licensing.md?raw')).default, id: 'license', icon: BookTextIcon, guidance_url: 'https://modrinth.com/legal/rules#miscellaneous', @@ -55,33 +48,24 @@ const licenseStage: Stage = { }, ], }, - // { - // id: 'license_no_source', - // type: 'conditional-button', - // label: 'No Source', - // fallbackWeight: 602, - // suggestedStatus: 'rejected', - // severity: 'medium', - // fallbackMessage: async () => (await import('../messages/license/no_source.md?raw')).default, - // messageVariants: [ - // { - // conditions: { - // requiredActions: ['reupload_unclear_fork'], - // }, - // weight: 602, - // message: async () => (await import('../messages/license/no_source-fork.md?raw')).default, - // }, - // ], - // }, { id: 'license_no_source', - type: 'button', + type: 'conditional-button', label: 'No Source', - weight: 602, suggestedStatus: 'rejected', severity: 'medium', shouldShow: (project) => !licensesNotRequiringSource.includes(project.license.id), - message: async () => (await import('../messages/license/no_source.md?raw')).default, + messageVariants: [ + { + conditions: { + excludedActions: ['license_no_source-fork'], + }, + weight: 602, + message: async () => (await import('../messages/license/no_source.md?raw')).default, + }, + ], + fallbackWeight: 602, + fallbackMessage: async () => '', enablesActions: [ { id: 'license_no_source-fork', diff --git a/packages/moderation/data/stages/links.ts b/packages/moderation/data/stages/links.ts index 73d6209e4..5cea34b5b 100644 --- a/packages/moderation/data/stages/links.ts +++ b/packages/moderation/data/stages/links.ts @@ -61,7 +61,8 @@ const links: Stage = { { id: 'links_unaccessible_options', type: 'multi-select-chips', - label: 'Warn of unaccessible link?', + label: 'Warn of inaccessible link?', + shouldShow: (project) => Boolean(project.source_url || project.discord_url), options: [ { label: 'Source', diff --git a/packages/moderation/data/stages/reupload.ts b/packages/moderation/data/stages/reupload.ts index 24c637669..d7a49d45e 100644 --- a/packages/moderation/data/stages/reupload.ts +++ b/packages/moderation/data/stages/reupload.ts @@ -20,6 +20,7 @@ const reupload: Stage = { 'reupload_unclear_fork', 'reupload_insufficient_fork', 'reupload_request_proof', + 'reupload_identity_verification', ], relevantExtraInput: [ { @@ -44,11 +45,12 @@ const reupload: Stage = { suggestedStatus: 'rejected', severity: 'high', message: async () => (await import('../messages/reupload/fork.md?raw')).default, - // disablesActions: [ - // 'reupload_reupload', - // 'reupload_insufficient_fork', - // 'reupload_request_proof', - // ], + disablesActions: [ + 'reupload_reupload', + 'reupload_insufficient_fork', + 'reupload_request_proof', + 'reupload_identity_verification', + ], } as ButtonAction, { id: 'reupload_insufficient_fork', @@ -58,7 +60,12 @@ const reupload: Stage = { suggestedStatus: 'rejected', severity: 'high', message: async () => (await import('../messages/reupload/insufficient_fork.md?raw')).default, - // disablesActions: ['reupload_unclear_fork', 'reupload_reupload', 'reupload_request_proof'], + disablesActions: [ + 'reupload_unclear_fork', + 'reupload_reupload', + 'reupload_request_proof', + 'reupload_identity_verification', + ], } as ButtonAction, { id: 'reupload_request_proof', @@ -69,7 +76,34 @@ const reupload: Stage = { severity: 'high', message: async () => (await import('../messages/reupload/proof_of_permissions.md?raw')).default, - // disablesActions: ['reupload_reupload', 'reupload_unclear_fork', 'reupload_insufficient_fork'], + disablesActions: [ + 'reupload_reupload', + 'reupload_unclear_fork', + 'reupload_insufficient_fork', + 'reupload_identity_verification', + ], + }, + { + id: 'reupload_identity_verification', + type: 'button', + label: 'Verify Identity', + weight: 1100, + suggestedStatus: 'rejected', + severity: 'high', + message: async () => + (await import('../messages/reupload/identity_verification.md?raw')).default, + relevantExtraInput: [ + { + label: 'Where else can the project be found?', + variable: 'PLATFORM', + required: true, + }, + ], + disablesActions: [ + 'reupload_reupload', + 'reupload_insufficient_fork', + 'reupload_request_proof', + ], }, ], } diff --git a/packages/moderation/data/stages/status-alerts.ts b/packages/moderation/data/stages/status-alerts.ts index fd6d1c63d..6c8dbc53f 100644 --- a/packages/moderation/data/stages/status-alerts.ts +++ b/packages/moderation/data/stages/status-alerts.ts @@ -1,5 +1,5 @@ import type { Stage } from '../../types/stage' -import type { ButtonAction } from '../../types/actions' +import type { ButtonAction, DropdownAction, DropdownActionOption } from '../../types/actions' import { TriangleAlertIcon } from '@modrinth/assets' const statusAlerts: Stage = { @@ -39,6 +39,41 @@ const statusAlerts: Stage = { message: async () => (await import('../messages/status-alerts/account_issues.md?raw')).default, } as ButtonAction, + { + id: 'status_tec_source_request', + type: 'button', + label: `Request Source`, + suggestedStatus: 'rejected', + severity: 'critical', + disablesActions: ['status_corrections_applied', 'status_private_use'], + shouldShow: (project) => + project.project_type === 'mod' || + project.project_type === 'shader' || + project.project_type.toString() === 'plugin', + weight: -999999, + message: async () => '', + enablesActions: [ + { + id: 'status_tec_source_request_options', + type: 'dropdown', + label: 'Why are you requesting source?', + options: [ + { + label: 'Obfuscated', + weight: 999999, + message: async () => + (await import('../messages/status-alerts/tec/source_request-obfs.md?raw')).default, + } as DropdownActionOption, + { + label: 'Binaries', + weight: 999000, + message: async () => + (await import('../messages/status-alerts/tec/source_request-bins.md?raw')).default, + } as DropdownActionOption, + ], + } as DropdownAction, + ], + } as ButtonAction, { id: 'status_automod_confusion', type: 'button', diff --git a/packages/moderation/data/stages/versions.ts b/packages/moderation/data/stages/versions.ts index e93840adc..951ac2879 100644 --- a/packages/moderation/data/stages/versions.ts +++ b/packages/moderation/data/stages/versions.ts @@ -25,6 +25,8 @@ const versions: Stage = { label: 'Incorrect Project Type', suggestedStatus: 'rejected', severity: 'medium', + weight: -999999, + message: async () => '', enablesActions: [ { id: 'versions_incorrect_project_type_options', @@ -62,6 +64,8 @@ const versions: Stage = { label: 'Alternate Versions', suggestedStatus: 'flagged', severity: 'medium', + weight: -999999, + message: async () => '', enablesActions: [ { id: 'versions_incorrect_project_type_options', @@ -110,7 +114,7 @@ const versions: Stage = { weight: 1002, suggestedStatus: 'rejected', severity: 'high', - shouldShow: (project) => project.project_type === `modpack`, + shouldShow: (project) => project.project_type === 'modpack', message: async () => (await import('../messages/versions/alternate_versions-zip.md?raw')).default, } as DropdownActionOption, diff --git a/packages/utils/changelog.ts b/packages/utils/changelog.ts index 0383db464..d6e7d983d 100644 --- a/packages/utils/changelog.ts +++ b/packages/utils/changelog.ts @@ -10,6 +10,13 @@ export type VersionEntry = { } const VERSIONS: VersionEntry[] = [ + { + date: `2025-07-19T15:20:00-07:00`, + product: 'web', + body: `### Improvements +- Removed Tumblr icon from footer as we no longer use it. +- Reverted changes to publishing checklist since they need more work.`, + }, { date: `2025-07-16T12:40:00-07:00`, product: 'web',