mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
add AI report types + fix report page fetching (#7175)
* add AI report types + fix report page fetching * use auth middleware on report
This commit is contained in:
@@ -4322,12 +4322,21 @@
|
|||||||
"report.not-for.dmca.description": {
|
"report.not-for.dmca.description": {
|
||||||
"message": "See our <policy-link>Copyright Policy</policy-link>."
|
"message": "See our <policy-link>Copyright Policy</policy-link>."
|
||||||
},
|
},
|
||||||
|
"report.note.ai-images.1": {
|
||||||
|
"message": "We've just updated our rules to prohibit AI-generated images in icons, galleries, and descriptions. We're giving creators a grace period to remove AI images from their projects before we begin accepting reports for them."
|
||||||
|
},
|
||||||
|
"report.note.check-back-later": {
|
||||||
|
"message": "Check back later once the grace period has ended. Reports of this type are not being accepted at this time."
|
||||||
|
},
|
||||||
"report.note.copyright.1": {
|
"report.note.copyright.1": {
|
||||||
"message": "Please note that you are *not* submitting a DMCA takedown request, but rather a report of reuploaded content."
|
"message": "Please note that you are *not* submitting a DMCA takedown request, but rather a report of reuploaded content."
|
||||||
},
|
},
|
||||||
"report.note.copyright.2": {
|
"report.note.copyright.2": {
|
||||||
"message": "If you meant to file a DMCA takedown request (which is a legal action) instead, please see our <copyright-policy-link>Copyright Policy</copyright-policy-link>."
|
"message": "If you meant to file a DMCA takedown request (which is a legal action) instead, please see our <copyright-policy-link>Copyright Policy</copyright-policy-link>."
|
||||||
},
|
},
|
||||||
|
"report.note.fully-ai-generated.1": {
|
||||||
|
"message": "We've just updated our rules to prohibit fully AI-generated projects. We're giving creators a grace period to update or take down fully AI-generated projects before we begin accepting reports for them."
|
||||||
|
},
|
||||||
"report.note.malicious.1": {
|
"report.note.malicious.1": {
|
||||||
"message": "Reports for malicious or deceptive content must include substantial evidence of the behavior, such as code samples."
|
"message": "Reports for malicious or deceptive content must include substantial evidence of the behavior, such as code samples."
|
||||||
},
|
},
|
||||||
@@ -4337,9 +4346,6 @@
|
|||||||
"report.note.missing-disclosure.1": {
|
"report.note.missing-disclosure.1": {
|
||||||
"message": "Content disclosures are a new feature we've just added. We're giving creators a grace period to get their disclosures up-to-date before we begin accepting reports for missing or incorrect disclosures."
|
"message": "Content disclosures are a new feature we've just added. We're giving creators a grace period to get their disclosures up-to-date before we begin accepting reports for missing or incorrect disclosures."
|
||||||
},
|
},
|
||||||
"report.note.missing-disclosure.2": {
|
|
||||||
"message": "Check back later once the grace period has ended. Reports of this type are not being accepted at this time."
|
|
||||||
},
|
|
||||||
"report.please-report": {
|
"report.please-report": {
|
||||||
"message": "Please report:"
|
"message": "Please report:"
|
||||||
},
|
},
|
||||||
|
|||||||
+127
-104
@@ -109,17 +109,7 @@
|
|||||||
<span class="text-lg font-semibold text-contrast">
|
<span class="text-lg font-semibold text-contrast">
|
||||||
{{ formatMessage(messages.whatContentType) }}
|
{{ formatMessage(messages.whatContentType) }}
|
||||||
</span>
|
</span>
|
||||||
<RadioButtons
|
<RadioButtons v-slot="{ item }" v-model="reportItem" :items="reportItems">
|
||||||
v-slot="{ item }"
|
|
||||||
v-model="reportItem"
|
|
||||||
:items="reportItems"
|
|
||||||
@update:model-value="
|
|
||||||
() => {
|
|
||||||
prefilled = false
|
|
||||||
fetchItem()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ capitalizeString(item) }}
|
{{ capitalizeString(item) }}
|
||||||
</RadioButtons>
|
</RadioButtons>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,13 +129,6 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:disabled="reportItem === ''"
|
:disabled="reportItem === ''"
|
||||||
wrapper-class="w-40"
|
wrapper-class="w-40"
|
||||||
@blur="
|
|
||||||
() => {
|
|
||||||
prefilled = false
|
|
||||||
reportItemID = reportItemID.trim()
|
|
||||||
fetchItem()
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
<div v-if="checkingId || checkedId" class="flex items-center gap-1">
|
<div v-if="checkingId || checkedId" class="flex items-center gap-1">
|
||||||
<template v-if="checkingId">
|
<template v-if="checkingId">
|
||||||
@@ -163,8 +146,8 @@
|
|||||||
class="flex items-center gap-1 font-semibold text-contrast hover:underline"
|
class="flex items-center gap-1 font-semibold text-contrast hover:underline"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
v-if="typeof itemIcon === 'string'"
|
v-if="typeof itemIcon === 'string' || !itemIcon"
|
||||||
:src="itemIcon"
|
:src="itemIcon ?? null"
|
||||||
:alt="itemName"
|
:alt="itemName"
|
||||||
size="24px"
|
size="24px"
|
||||||
:circle="reportItem === 'user'"
|
:circle="reportItem === 'user'"
|
||||||
@@ -303,22 +286,22 @@ import {
|
|||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { Project, Report, User, Version } from '@modrinth/utils'
|
import type { Project, Report, User, Version } from '@modrinth/utils'
|
||||||
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
|
|
||||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: 'auth',
|
||||||
|
})
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
|
|
||||||
const tags = useGeneratedState()
|
const tags = useGeneratedState()
|
||||||
const route = useNativeRoute()
|
const route = useNativeRoute()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
if (!auth.value.user) {
|
|
||||||
router.push('/auth/sign-in?redirect=' + encodeURIComponent(route.fullPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessQuery = (id: string): string => {
|
const accessQuery = (id: string): string => {
|
||||||
return route.query?.[id]?.toString() || ''
|
return route.query?.[id]?.toString() || ''
|
||||||
}
|
}
|
||||||
@@ -368,58 +351,83 @@ async function fetchExistingReports() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchItem() {
|
const fetchItemDebounced = useDebounceFn(fetchItem, 500)
|
||||||
if (reportItem.value && reportItemID.value) {
|
|
||||||
checkingId.value = true
|
|
||||||
itemIcon.value = undefined
|
|
||||||
itemName.value = undefined
|
|
||||||
itemLink.value = undefined
|
|
||||||
itemId.value = undefined
|
|
||||||
itemIssueTracker.value = undefined
|
|
||||||
try {
|
|
||||||
if (reportItem.value === 'project') {
|
|
||||||
const project = (await useBaseFetch(`project/${reportItemID.value}`)) as Project
|
|
||||||
currentProject.value = project
|
|
||||||
|
|
||||||
itemIcon.value = project.icon_url
|
watch([reportItem, reportItemID], ([type, id], [prevType]) => {
|
||||||
itemName.value = project.title
|
prefilled.value = false
|
||||||
itemLink.value = `/project/${project.id}`
|
if (!id.trim() || type !== prevType) {
|
||||||
itemId.value = project.id
|
fetchItem()
|
||||||
itemIssueTracker.value = project.issues_url
|
} else {
|
||||||
} else if (reportItem.value === 'version') {
|
fetchItemDebounced()
|
||||||
const version = (await useBaseFetch(`version/${reportItemID.value}`)) as Version
|
|
||||||
currentVersion.value = version
|
|
||||||
|
|
||||||
itemIcon.value = VersionIcon
|
|
||||||
itemName.value = version.version_number
|
|
||||||
itemLink.value = `project/${version.project_id}/version/${version.id}`
|
|
||||||
itemId.value = version.id
|
|
||||||
} else if (reportItem.value === 'user') {
|
|
||||||
const user = (await useBaseFetch(`user/${reportItemID.value}`)) as User
|
|
||||||
currentUser.value = user
|
|
||||||
|
|
||||||
itemIcon.value = user.avatar_url
|
|
||||||
itemName.value = user.username
|
|
||||||
itemLink.value = `/user/${user.username}`
|
|
||||||
itemId.value = user.id
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
checkedId.value = true
|
|
||||||
checkingId.value = false
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function fetchItem() {
|
||||||
|
const type = reportItem.value
|
||||||
|
const id = reportItemID.value.trim()
|
||||||
|
|
||||||
|
currentProject.value = null
|
||||||
|
currentVersion.value = null
|
||||||
|
currentUser.value = null
|
||||||
|
itemIcon.value = undefined
|
||||||
|
itemName.value = undefined
|
||||||
|
itemLink.value = undefined
|
||||||
|
itemId.value = undefined
|
||||||
|
itemIssueTracker.value = undefined
|
||||||
|
|
||||||
|
if (!type || !id) {
|
||||||
|
checkedId.value = false
|
||||||
|
checkingId.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingId.value = true
|
||||||
|
try {
|
||||||
|
if (type === 'project') {
|
||||||
|
const project = (await useBaseFetch(`project/${id}`)) as Project
|
||||||
|
currentProject.value = project
|
||||||
|
itemIcon.value = project.icon_url
|
||||||
|
itemName.value = project.title
|
||||||
|
itemLink.value = `/project/${project.id}`
|
||||||
|
itemId.value = project.id
|
||||||
|
itemIssueTracker.value = project.issues_url
|
||||||
|
} else if (type === 'version') {
|
||||||
|
const version = (await useBaseFetch(`version/${id}`)) as Version
|
||||||
|
currentVersion.value = version
|
||||||
|
itemIcon.value = VersionIcon
|
||||||
|
itemName.value = version.version_number
|
||||||
|
itemLink.value = `project/${version.project_id}/version/${version.id}`
|
||||||
|
itemId.value = version.id
|
||||||
|
} else if (type === 'user') {
|
||||||
|
const user = (await useBaseFetch(`user/${id}`)) as User
|
||||||
|
currentUser.value = user
|
||||||
|
itemIcon.value = user.avatar_url
|
||||||
|
itemName.value = user.username
|
||||||
|
itemLink.value = `/user/${user.username}`
|
||||||
|
itemId.value = user.id
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
checkedId.value = true
|
||||||
|
checkingId.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportItems = ['project', 'version', 'user']
|
const reportItems = ['project', 'version', 'user']
|
||||||
|
const dummyProjectReportTypes = ['missing-disclosure', 'ai-images', 'fully-ai-generated'] as const
|
||||||
const reportTypes = computed(() => {
|
const reportTypes = computed(() => {
|
||||||
const types = [...tags.value.reportTypes]
|
const types = [...tags.value.reportTypes]
|
||||||
if (reportItem.value === 'project') {
|
if (reportItem.value === 'project') {
|
||||||
types.push('missing-disclosure')
|
const otherIndex = types.indexOf('other')
|
||||||
|
if (otherIndex === -1) {
|
||||||
|
types.push(...dummyProjectReportTypes)
|
||||||
|
} else {
|
||||||
|
types.splice(otherIndex, 0, ...dummyProjectReportTypes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return types
|
return types
|
||||||
})
|
})
|
||||||
const disableReporting = computed(() => reportType.value === 'missing-disclosure')
|
const disableReporting = computed(() => dummyProjectReportTypes.includes(reportType.value))
|
||||||
|
|
||||||
const canSubmit = computed(() => {
|
const canSubmit = computed(() => {
|
||||||
return (
|
return (
|
||||||
@@ -531,44 +539,6 @@ const onImageUpload = async (file: File) => {
|
|||||||
return item.url
|
return item.url
|
||||||
}
|
}
|
||||||
|
|
||||||
const warnings: Record<string, MessageDescriptor[]> = {
|
|
||||||
copyright: [
|
|
||||||
defineMessage({
|
|
||||||
id: 'report.note.copyright.1',
|
|
||||||
defaultMessage:
|
|
||||||
'Please note that you are *not* submitting a DMCA takedown request, but rather a report of reuploaded content.',
|
|
||||||
}),
|
|
||||||
defineMessage({
|
|
||||||
id: 'report.note.copyright.2',
|
|
||||||
defaultMessage:
|
|
||||||
'If you meant to file a DMCA takedown request (which is a legal action) instead, please see our <copyright-policy-link>Copyright Policy</copyright-policy-link>.',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
malicious: [
|
|
||||||
defineMessage({
|
|
||||||
id: 'report.note.malicious.1',
|
|
||||||
defaultMessage:
|
|
||||||
'Reports for malicious or deceptive content must include substantial evidence of the behavior, such as code samples.',
|
|
||||||
}),
|
|
||||||
defineMessage({
|
|
||||||
id: 'report.note.malicious.2',
|
|
||||||
defaultMessage:
|
|
||||||
'Summaries from Microsoft Defender, VirusTotal, or AI malware detection are not sufficient forms of evidence and will not be accepted.',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
'missing-disclosure': [
|
|
||||||
defineMessage({
|
|
||||||
id: 'report.note.missing-disclosure.1',
|
|
||||||
defaultMessage: `Content disclosures are a new feature we've just added. We're giving creators a grace period to get their disclosures up-to-date before we begin accepting reports for missing or incorrect disclosures.`,
|
|
||||||
}),
|
|
||||||
defineMessage({
|
|
||||||
id: 'report.note.missing-disclosure.2',
|
|
||||||
defaultMessage:
|
|
||||||
'Check back later once the grace period has ended. Reports of this type are not being accepted at this time.',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
reportContent: {
|
reportContent: {
|
||||||
id: 'report.report-content',
|
id: 'report.report-content',
|
||||||
@@ -662,7 +632,60 @@ const messages = defineMessages({
|
|||||||
id: 'report.submit',
|
id: 'report.submit',
|
||||||
defaultMessage: 'Submit report',
|
defaultMessage: 'Submit report',
|
||||||
},
|
},
|
||||||
|
checkBackLater: {
|
||||||
|
id: 'report.note.check-back-later',
|
||||||
|
defaultMessage:
|
||||||
|
'Check back later once the grace period has ended. Reports of this type are not being accepted at this time.',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const warnings: Record<string, MessageDescriptor[]> = {
|
||||||
|
copyright: [
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.copyright.1',
|
||||||
|
defaultMessage:
|
||||||
|
'Please note that you are *not* submitting a DMCA takedown request, but rather a report of reuploaded content.',
|
||||||
|
}),
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.copyright.2',
|
||||||
|
defaultMessage:
|
||||||
|
'If you meant to file a DMCA takedown request (which is a legal action) instead, please see our <copyright-policy-link>Copyright Policy</copyright-policy-link>.',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
malicious: [
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.malicious.1',
|
||||||
|
defaultMessage:
|
||||||
|
'Reports for malicious or deceptive content must include substantial evidence of the behavior, such as code samples.',
|
||||||
|
}),
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.malicious.2',
|
||||||
|
defaultMessage:
|
||||||
|
'Summaries from Microsoft Defender, VirusTotal, or AI malware detection are not sufficient forms of evidence and will not be accepted.',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
'missing-disclosure': [
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.missing-disclosure.1',
|
||||||
|
defaultMessage: `Content disclosures are a new feature we've just added. We're giving creators a grace period to get their disclosures up-to-date before we begin accepting reports for missing or incorrect disclosures.`,
|
||||||
|
}),
|
||||||
|
messages.checkBackLater,
|
||||||
|
],
|
||||||
|
'ai-images': [
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.ai-images.1',
|
||||||
|
defaultMessage: `We've just updated our rules to prohibit AI-generated images in icons, galleries, and descriptions. We're giving creators a grace period to remove AI images from their projects before we begin accepting reports for them.`,
|
||||||
|
}),
|
||||||
|
messages.checkBackLater,
|
||||||
|
],
|
||||||
|
'fully-ai-generated': [
|
||||||
|
defineMessage({
|
||||||
|
id: 'report.note.fully-ai-generated.1',
|
||||||
|
defaultMessage: `We've just updated our rules to prohibit fully AI-generated projects. We're giving creators a grace period to update or take down fully AI-generated projects before we begin accepting reports for them.`,
|
||||||
|
}),
|
||||||
|
messages.checkBackLater,
|
||||||
|
],
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -3995,9 +3995,15 @@
|
|||||||
"report.item-type.version": {
|
"report.item-type.version": {
|
||||||
"defaultMessage": "version"
|
"defaultMessage": "version"
|
||||||
},
|
},
|
||||||
|
"report.type.ai-images": {
|
||||||
|
"defaultMessage": "AI-generated images in icon, gallery, or description"
|
||||||
|
},
|
||||||
"report.type.copyright": {
|
"report.type.copyright": {
|
||||||
"defaultMessage": "Reuploaded work"
|
"defaultMessage": "Reuploaded work"
|
||||||
},
|
},
|
||||||
|
"report.type.fully-ai-generated": {
|
||||||
|
"defaultMessage": "Fully AI-generated project"
|
||||||
|
},
|
||||||
"report.type.inappropriate": {
|
"report.type.inappropriate": {
|
||||||
"defaultMessage": "Inappropriate"
|
"defaultMessage": "Inappropriate"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1049,6 +1049,14 @@ export const reportTypeMessages = defineMessages({
|
|||||||
id: 'report.type.missing-disclosure',
|
id: 'report.type.missing-disclosure',
|
||||||
defaultMessage: 'Missing or incorrect disclosure',
|
defaultMessage: 'Missing or incorrect disclosure',
|
||||||
},
|
},
|
||||||
|
'ai-images': {
|
||||||
|
id: 'report.type.ai-images',
|
||||||
|
defaultMessage: 'AI-generated images in icon, gallery, or description',
|
||||||
|
},
|
||||||
|
'fully-ai-generated': {
|
||||||
|
id: 'report.type.fully-ai-generated',
|
||||||
|
defaultMessage: 'Fully AI-generated project',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export function formatReportType(formatMessage: FormatMessage, type: string | undefined): string {
|
export function formatReportType(formatMessage: FormatMessage, type: string | undefined): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user