Merge branch 'main' into cal/dev-124-project-validation

This commit is contained in:
IMB11 2025-07-21 18:35:59 +01:00 committed by GitHub
commit 89351b4be4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 200 additions and 62 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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(

View File

@ -150,9 +150,26 @@
</template>
</span>
<span class="text-sm text-secondary">
<span
v-if="charge.status === 'cancelled' && $dayjs(charge.due).isBefore($dayjs())"
class="font-bold"
>
Ended:
</span>
<span v-else-if="charge.status === 'cancelled'" class="font-bold">Ends:</span>
<span v-else-if="charge.type === 'refund'" class="font-bold">Issued:</span>
<span v-else class="font-bold">Due:</span>
{{ dayjs(charge.due).format("MMMM D, YYYY [at] h:mma") }}
<span class="text-secondary">({{ formatRelativeTime(charge.due) }}) </span>
</span>
<span v-if="charge.last_attempt != null" class="text-sm text-secondary">
<span v-if="charge.status === 'failed'" class="font-bold">Last attempt:</span>
<span v-else class="font-bold">Charged:</span>
{{ dayjs(charge.last_attempt).format("MMMM D, YYYY [at] h:mma") }}
<span class="text-secondary"
>({{ formatRelativeTime(charge.last_attempt) }})
</span>
</span>
<div class="flex w-full items-center gap-1 text-xs text-secondary">
{{ charge.status }}

View File

@ -45,8 +45,9 @@
<h2
class="relative m-0 max-w-2xl text-base font-normal leading-[155%] text-secondary md:text-[1.2rem]"
>
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.
</h2>
<div class="relative flex w-full flex-wrap items-center gap-8 align-middle sm:w-fit">
<div
@ -459,7 +460,7 @@
</p>
</details>
<details pyro-hash="players" class="group" :open="$route.hash === '#players'">
<details pyro-hash="performance" class="group" :open="$route.hash === '#performance'">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
@ -480,7 +481,7 @@
</p>
</details>
<details pyro-hash="players" class="group" :open="$route.hash === '#prices'">
<details pyro-hash="prices" class="group" :open="$route.hash === '#prices'">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
@ -491,6 +492,24 @@
All prices are listed in United States Dollars (USD).
</p>
</details>
<details pyro-hash="versions" class="group" :open="$route.hash === '#versions'">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
What Minecraft versions and loaders can be used?
</summary>
<p class="m-0 ml-6 leading-[160%]">
Modrinth Servers can run any version of Minecraft: Java Edition going all the way
back to version 1.2.5, including snapshot versions.
</p>
<p class="m-0 ml-6 mt-3 leading-[160%]">
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.
</p>
</details>
</div>
</div>
</div>

View File

@ -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"
}

View File

@ -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,

View File

@ -197,7 +197,7 @@ impl DBCharge {
) -> Result<Option<DBCharge>, 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)

View File

@ -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!(

View File

@ -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",

View File

@ -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,

View File

@ -1 +0,0 @@
**License id:** %PROJECT_LICENSE_ID% \

View File

@ -1 +1,2 @@
**License id:** %PROJECT_LICENSE_ID% \
**License Link:** %PROJECT_LICENSE_URL%

View File

@ -1 +1 @@
> **{PLATFORM}:** {URL}
> **{PLATFORM}:** {URL}<br />

View File

@ -1 +1,2 @@
<u>**Donation Links:**</u>
<br />
<u>**Donation Links:**</u><br />

View File

@ -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).

View File

@ -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%.

View File

@ -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.

View File

@ -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.

View File

@ -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),
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',

View File

@ -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',

View File

@ -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',
],
},
],
}

View File

@ -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',

View File

@ -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,

View File

@ -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',