feat: new modpack permissions system (#6005)

* Begin external projects moderator database frontend

* add copy link button

* begin project page permissions settings

* MEL database backend routes

* include filename in external files

* wip: when uploading a version file, fetch its overrides as a list

* wip: override license checks

* improve FileHost ref counting

* file host read capability

* scan files when inserting version file

* add dependency sha1 field

* clean up version files

* wip: attributions

* update s3 file host

* attribution scanning basic works

* works

* insert attribution info after resolving

* add routes

* remove dep sha1 stuff

* prepr

* wip: override file sources

* add files_missing_attributions to versions

* return extended version info + attributed at/by

* hook up frontend to backend (mostly)

* expose version date published

* withholding version visibility

* frontend work

* prepr

* use api-client for img upload

* moar frontend

* prepr

* Add schema to attribution resolution and Flame project results

* sqlx prepare

* changes

* remove feature flag, fix optional proof images

* fix schema

* fmt

* fix deletion and file fetch

* prepare

* fix admonition

* update frontend stuff to new schema

* prepr

* attribution on dependencies

* fixes

* sqlx prepare

* fixes

* routes

* fix routes

* Version grandfathering

* prepare

* wip: bulk routes

* pushing what i've got rn

* include link in NoPermission

* change hash insert to bulk route

* query flame even if entry in MEL

* delete file with weird name

* Prioritise putting override files in existing groups even with ExternalLicense

* fix how hex bytes are handled in route

* feat: coolbot moderation changes (#6215)

* Update moderator checklist

* move permissions stage order

* Updated nagContext.versions to v3, added nag for permissions

* Update permissions.vue default messages

* prepr

---------

Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>

* QA

* prepr

* should group by project

* return attribution resolution correctly

* updated by moderator info

* Track what moderator reviewed an attribution moderation status

* default deser FMA field

* new version page

* clean up fetching + add a couple missing features

* qa items

* prepr

* provide moderation package stuff with DI

* format?

* don't redact moderated_at

* move supplementary resources

* Reorganize moderation messages.

* Quick replies for external content permissions.

* prepare

* QA

* allow exempting projects

* Ignore Flame projects which 404

* fix ci

* fix cross project attribution stuff

* Fix permission error

* change what files get cscanned

* add more logging

* QA Jun 22

* fix

* idempotency

* Expose route for rescanning

* update blog link

---------

Co-authored-by: aecsocket <aecsocket@tutanota.com>
Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>
Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
This commit is contained in:
Prospector
2026-06-23 21:27:51 +02:00
committed by GitHub
co-authored by coolbot100s aecsocket aecsocket
parent a686a93858
commit e7926083fb
315 changed files with 11106 additions and 2209 deletions
+4
View File
@@ -20,6 +20,7 @@ import { KyrosUploadSessionsV1Module } from './kyros/upload-sessions/v1'
import { LabrinthVersionsV2Module, LabrinthVersionsV3Module } from './labrinth'
import { LabrinthAffiliateInternalModule } from './labrinth/affiliate/internal'
import { LabrinthAnalyticsV3Module } from './labrinth/analytics/v3'
import { LabrinthAttributionInternalModule } from './labrinth/attribution/internal'
import { LabrinthAuthInternalModule } from './labrinth/auth/internal'
import { LabrinthAuthV2Module } from './labrinth/auth/v2'
import { LabrinthBillingInternalModule } from './labrinth/billing/internal'
@@ -28,6 +29,7 @@ import { LabrinthCollectionsModule } from './labrinth/collections'
import { LabrinthExternalProjectsInternalModule } from './labrinth/external-projects/internal'
import { LabrinthFriendsV3Module } from './labrinth/friends/v3'
import { LabrinthGlobalsInternalModule } from './labrinth/globals/internal'
import { LabrinthImagesV3Module } from './labrinth/images/v3'
import { LabrinthLimitsV3Module } from './labrinth/limits/v3'
import { LabrinthModerationInternalModule } from './labrinth/moderation/internal'
import { LabrinthNotificationsV2Module } from './labrinth/notifications/v2'
@@ -91,12 +93,14 @@ export const MODULE_REGISTRY = {
labrinth_analytics_v3: LabrinthAnalyticsV3Module,
labrinth_auth_internal: LabrinthAuthInternalModule,
labrinth_auth_v2: LabrinthAuthV2Module,
labrinth_attribution_internal: LabrinthAttributionInternalModule,
labrinth_billing_internal: LabrinthBillingInternalModule,
labrinth_campaign_internal: LabrinthCampaignInternalModule,
labrinth_collections: LabrinthCollectionsModule,
labrinth_external_projects_internal: LabrinthExternalProjectsInternalModule,
labrinth_friends_v3: LabrinthFriendsV3Module,
labrinth_globals_internal: LabrinthGlobalsInternalModule,
labrinth_images_v3: LabrinthImagesV3Module,
labrinth_moderation_internal: LabrinthModerationInternalModule,
labrinth_notifications_v2: LabrinthNotificationsV2Module,
labrinth_oauth_internal: LabrinthOAuthInternalModule,
@@ -0,0 +1,103 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
/**
* Decode a base62-encoded ID string into a number.
* The backend serializes attribution group IDs as base62 strings in responses,
* but the assign/update endpoints expect raw integer IDs in their request payloads.
*/
function decodeBase62Id(id: string): number {
let value = 0
for (const char of id) {
const digit = BASE62_CHARS.indexOf(char)
if (digit < 0) {
throw new Error(`Invalid base62 character "${char}" in id "${id}"`)
}
value = value * 62 + digit
if (!Number.isSafeInteger(value)) {
throw new Error(`Base62 id "${id}" exceeds safe integer range`)
}
}
return value
}
export class LabrinthAttributionInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_attribution_internal'
}
/**
* List attribution groups for a project
* GET /_internal/attribution/{project_id}
*/
public async listProjectAttribution(
projectId: string,
): Promise<Labrinth.Attribution.Internal.AttributionGroup[]> {
return this.client.request<Labrinth.Attribution.Internal.AttributionGroup[]>(
`/attribution/${projectId}`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
}
/**
* Update an attribution group's attribution payload.
* PATCH /_internal/attribution/group/{group_id}
*
* @param groupId - The base62 attribution group id (as returned from listProjectAttribution).
*/
public async updateGroup(
groupId: string,
body: Labrinth.Attribution.Internal.UpdateGroupRequest,
): Promise<void> {
const numericId = decodeBase62Id(groupId)
return this.client.request<void>(`/attribution/group/${numericId}`, {
api: 'labrinth',
version: 'internal',
method: 'PATCH',
body,
})
}
/**
* Reassign a file (by sha1) to another attribution group within the same project.
* POST /_internal/attribution/assign
*
* @param body.target_group_id - The base62 id of the attribution group to assign the file to.
*/
public async assignFileToGroup(body: {
sha1: string
target_group_id: string
project_id: string
}): Promise<void> {
const wireBody: Labrinth.Attribution.Internal.AssignRequest = {
sha1: body.sha1,
target_group_id: decodeBase62Id(body.target_group_id),
project_id: body.project_id,
}
return this.client.request<void>('/attribution/assign', {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: wireBody,
})
}
/**
* Split a file (by sha1) out of its current attribution group into a new group.
* POST /_internal/attribution/split
*/
public async splitFile(body: Labrinth.Attribution.Internal.SplitRequest): Promise<void> {
return this.client.request<void>('/attribution/split', {
api: 'labrinth',
version: 'internal',
method: 'POST',
body,
})
}
}
@@ -47,4 +47,18 @@ export class LabrinthExternalProjectsInternalModule extends AbstractModule {
},
)
}
public async addFile(
data: Labrinth.ExternalProjects.Internal.AddFileRequest,
): Promise<Labrinth.ExternalProjects.Internal.ExternalProject> {
return this.client.request<Labrinth.ExternalProjects.Internal.ExternalProject>(
'/moderation/external-license/file',
{
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
},
)
}
}
@@ -0,0 +1,47 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { UploadHandle } from '../../../types/upload'
import type { Labrinth } from '../types'
function buildImageQueryParams(
ext: Labrinth.Images.v3.ImageExtension,
target: Labrinth.Images.v3.UploadImageParams,
): Record<string, string> {
const params: Record<string, string> = {
ext,
context: target.context,
}
switch (target.context) {
case 'project':
params.project_id = target.project_id
break
case 'version':
params.version_id = target.version_id
break
case 'thread_message':
params.thread_message_id = target.thread_message_id
break
case 'report':
params.report_id = target.report_id
break
}
return params
}
export class LabrinthImagesV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_images_v3'
}
public uploadImage(
file: File | Blob,
ext: Labrinth.Images.v3.ImageExtension,
target: Labrinth.Images.v3.UploadImageParams,
): UploadHandle<Labrinth.Images.v3.UploadedImage> {
return this.client.upload<Labrinth.Images.v3.UploadedImage>('/image', {
api: 'labrinth',
version: 3,
file,
params: buildImageQueryParams(ext, target),
})
}
}
@@ -1,4 +1,5 @@
export * from './analytics/v3'
export * from './attribution/internal'
export * from './auth/internal'
export * from './auth/v2'
export * from './billing/internal'
@@ -6,6 +7,7 @@ export * from './collections'
export * from './external-projects/internal'
export * from './friends/v3'
export * from './globals/internal'
export * from './images/v3'
export * from './limits/v3'
export * from './moderation/internal'
export * from './notifications/v2'
@@ -57,4 +57,15 @@ export class LabrinthModerationInternalModule extends AbstractModule {
},
)
}
public async setProjectJudgements(
judgements: Labrinth.Moderation.Internal.ProjectJudgements,
): Promise<void> {
return this.client.request<void>('/moderation/project', {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: judgements,
})
}
}
@@ -249,6 +249,144 @@ export namespace Labrinth {
}
}
export namespace Attribution {
export namespace Internal {
export type AttributionPermissionKind =
| 'license'
| 'my_project'
| 'special_permissions'
| 'globally_allowed'
| 'no_permission'
export type AttributionResolutionKind = AttributionPermissionKind
export type AttributionLicense = string | { name: string }
export type AttributionModerationStatusKind = 'not_allowed' | 'approved' | 'bad_proof'
export type AttributionModerationStatus = {
kind: AttributionModerationStatusKind
reason: string
moderated_at?: string
moderated_by?: string
}
export type AttributionResolutionBase = {
notes: string
image_urls: string[]
moderation_status?: AttributionModerationStatus | null
updated_by_moderator: boolean
}
export type AttributionResolution =
| (AttributionResolutionBase & {
kind: 'license'
license: AttributionLicense
link_to_work: string
})
| (AttributionResolutionBase & {
kind: 'my_project'
license: AttributionLicense
})
| (AttributionResolutionBase & {
kind: 'special_permissions'
link_to_work: string
})
| (AttributionResolutionBase & {
kind: 'globally_allowed'
link_to_work: string
})
| (AttributionResolutionBase & {
kind: 'no_permission'
link_to_work?: string
})
export type FlameProject = {
id: number
title: string
url: string
icon_url: string
}
export type AttributionFile = {
name: string
sha1: string
versions: string[]
moderation_external_license_id?: number
moderation_external_license?: ExternalProjects.Internal.ExternalProject
}
export type AttributionVersionInfo = {
id: string
name: string
version_number: string
date_created: string
}
export type AttributionGroup = {
id: string
flame_project: FlameProject | null
attribution: AttributionResolution | null
attributed_at: string | null
attributed_by: string | null
files: AttributionFile[]
versions: AttributionVersionInfo[]
}
export type UpdateGroupRequest = {
attribution: AttributionResolution
}
export type AssignRequest = {
sha1: string
target_group_id: number
project_id: string
}
export type SplitRequest = {
sha1: string
project_id: string
}
}
}
export namespace Images {
export namespace v3 {
/** Extensions accepted by POST /v3/image (Labrinth image pipeline). */
export type ImageExtension = 'bmp' | 'gif' | 'jpeg' | 'jpg' | 'png' | 'webp'
/** `context` query values accepted by POST /v3/image. */
export type ImageUploadContext = 'project' | 'version' | 'thread_message' | 'report'
export type UploadedImage = {
id: string
url: string
size: number
created: string
owner_id: string
} & (
| { context: 'project'; project_id: string }
| { context: 'version'; version_id: string }
| { context: 'thread_message'; thread_message_id: string }
| { context: 'report'; report_id: string }
)
export type UploadedImageFor<C extends ImageUploadContext> = Extract<
UploadedImage,
{ context: C }
>
/**
* Target for POST /v3/image (per-context id query params, plus `context`).
* `ext` is passed as a separate argument on the client module.
*/
export type UploadImageParams =
| { context: 'project'; project_id: string }
| { context: 'version'; version_id: string }
| { context: 'thread_message'; thread_message_id: string }
| { context: 'report'; report_id: string }
}
}
export namespace Analytics {
export namespace v3 {
export type AnalyticsEventId = number
@@ -1147,11 +1285,47 @@ export namespace Labrinth {
// TODO: consolidate duplicated types between v2 and v3 versions
export namespace v3 {
export type FlameProject = {
id: number
title: string
url: string
icon_url: string
}
export type DependencyAttribution = {
flame_project?: FlameProject
resolution?: DependencyAttributionResolution
}
export type DependencyAttributionResolution =
| {
kind: 'license'
license: Labrinth.Attribution.Internal.AttributionLicense
link_to_work: string
}
| {
kind: 'globally_allowed'
link_to_work: string
}
| {
kind: 'my_project'
license: Labrinth.Attribution.Internal.AttributionLicense
}
| {
kind: 'special_permissions'
link_to_work: string
}
| {
kind: 'no_permission'
link_to_work?: string
}
export interface Dependency {
dependency_type: Labrinth.Versions.v2.DependencyType
project_id?: string
file_name?: string
version_id?: string
attribution?: DependencyAttribution
}
export interface GetProjectVersionsParams {
@@ -1174,12 +1348,12 @@ export namespace Labrinth {
| 'signature'
| 'unknown'
export interface VersionFileHash {
sha512: string
sha1: string
export type FileHashType = 'sha512' | 'sha1'
export type VersionFileHash = {
[key in FileHashType]: string
}
interface VersionFile {
export interface VersionFile {
hashes: VersionFileHash
url: string
filename: string
@@ -1211,6 +1385,7 @@ export namespace Labrinth {
date_published: string
downloads: number
files: VersionFile[]
files_missing_attribution?: string[]
environment?: Labrinth.Projects.v3.Environment
mrpack_loaders?: string[]
@@ -1658,6 +1833,28 @@ export namespace Labrinth {
export type ReleaseLockResponse = {
success: boolean
}
export type ProjectJudgementStatus = ExternalProjects.Internal.ExternalLicenseStatus
export type FlameJudgement = {
type: 'flame'
id: number
status: ProjectJudgementStatus
link: string
title: string
}
export type UnknownJudgement = {
type: 'unknown'
status: ProjectJudgementStatus
proof?: string
link?: string
title?: string
}
export type ProjectJudgement = FlameJudgement | UnknownJudgement
export type ProjectJudgements = Record<string, ProjectJudgement>
}
}
@@ -1819,7 +2016,7 @@ export namespace Labrinth {
inserted_by: number | null
updated_at: string | null
updated_by: number | null
linked_files: LinkedFile[]
linked_files?: LinkedFile[]
}
export type SearchRequest = {
@@ -1835,6 +2032,11 @@ export namespace Labrinth {
proof?: string
flame_project_id?: number
}
export type AddFileRequest = {
hashes: string[]
license_id: number
}
}
}
+14
View File
@@ -11,8 +11,10 @@ import _ArchiveIcon from './icons/archive.svg?component'
import _ArrowBigRightDashIcon from './icons/arrow-big-right-dash.svg?component'
import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component'
import _ArrowDownIcon from './icons/arrow-down.svg?component'
import _ArrowDown10Icon from './icons/arrow-down-1-0.svg?component'
import _ArrowDownAZIcon from './icons/arrow-down-a-z.svg?component'
import _ArrowDownLeftIcon from './icons/arrow-down-left.svg?component'
import _ArrowDownWideNarrowIcon from './icons/arrow-down-wide-narrow.svg?component'
import _ArrowDownZAIcon from './icons/arrow-down-z-a.svg?component'
import _ArrowLeftIcon from './icons/arrow-left.svg?component'
import _ArrowLeftRightIcon from './icons/arrow-left-right.svg?component'
@@ -78,6 +80,7 @@ import _ChevronRightIcon from './icons/chevron-right.svg?component'
import _ChevronUpIcon from './icons/chevron-up.svg?component'
import _CircleAlertIcon from './icons/circle-alert.svg?component'
import _CircleArrowRightIcon from './icons/circle-arrow-right.svg?component'
import _CircleDashedIcon from './icons/circle-dashed.svg?component'
import _CircleUserIcon from './icons/circle-user.svg?component'
import _ClearIcon from './icons/clear.svg?component'
import _ClientIcon from './icons/client.svg?component'
@@ -120,6 +123,7 @@ import _FilePlusIcon from './icons/file-plus.svg?component'
import _FileTextIcon from './icons/file-text.svg?component'
import _FilterIcon from './icons/filter.svg?component'
import _FilterXIcon from './icons/filter-x.svg?component'
import _FoldVerticalIcon from './icons/fold-vertical.svg?component'
import _FolderIcon from './icons/folder.svg?component'
import _FolderArchiveIcon from './icons/folder-archive.svg?component'
import _FolderCogIcon from './icons/folder-cog.svg?component'
@@ -203,6 +207,7 @@ import _PackageOpenIcon from './icons/package-open.svg?component'
import _PackagePlusIcon from './icons/package-plus.svg?component'
import _PaintbrushIcon from './icons/paintbrush.svg?component'
import _PaletteIcon from './icons/palette.svg?component'
import _PauseIcon from './icons/pause.svg?component'
import _PencilIcon from './icons/pencil.svg?component'
import _PickaxeIcon from './icons/pickaxe.svg?component'
import _PinIcon from './icons/pin.svg?component'
@@ -246,6 +251,7 @@ import _SortAscIcon from './icons/sort-asc.svg?component'
import _SortDescIcon from './icons/sort-desc.svg?component'
import _SparklesIcon from './icons/sparkles.svg?component'
import _SpinnerIcon from './icons/spinner.svg?component'
import _SplitIcon from './icons/split.svg?component'
import _StarIcon from './icons/star.svg?component'
import _StopCircleIcon from './icons/stop-circle.svg?component'
import _StoreIcon from './icons/store.svg?component'
@@ -403,6 +409,7 @@ import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
import _UnderlineIcon from './icons/underline.svg?component'
import _UndoIcon from './icons/undo.svg?component'
import _UnfoldHorizontalIcon from './icons/unfold-horizontal.svg?component'
import _UnfoldVerticalIcon from './icons/unfold-vertical.svg?component'
import _UnknownIcon from './icons/unknown.svg?component'
import _UnknownDonationIcon from './icons/unknown-donation.svg?component'
import _UnlinkIcon from './icons/unlink.svg?component'
@@ -434,8 +441,10 @@ export const ArchiveIcon = _ArchiveIcon
export const ArrowBigRightDashIcon = _ArrowBigRightDashIcon
export const ArrowBigUpDashIcon = _ArrowBigUpDashIcon
export const ArrowDownIcon = _ArrowDownIcon
export const ArrowDown10Icon = _ArrowDown10Icon
export const ArrowDownAZIcon = _ArrowDownAZIcon
export const ArrowDownLeftIcon = _ArrowDownLeftIcon
export const ArrowDownWideNarrowIcon = _ArrowDownWideNarrowIcon
export const ArrowDownZAIcon = _ArrowDownZAIcon
export const ArrowLeftIcon = _ArrowLeftIcon
export const ArrowLeftRightIcon = _ArrowLeftRightIcon
@@ -501,6 +510,7 @@ export const ChevronRightIcon = _ChevronRightIcon
export const ChevronUpIcon = _ChevronUpIcon
export const CircleAlertIcon = _CircleAlertIcon
export const CircleArrowRightIcon = _CircleArrowRightIcon
export const CircleDashedIcon = _CircleDashedIcon
export const CircleUserIcon = _CircleUserIcon
export const ClearIcon = _ClearIcon
export const ClientIcon = _ClientIcon
@@ -543,6 +553,7 @@ export const FilePlusIcon = _FilePlusIcon
export const FileTextIcon = _FileTextIcon
export const FilterIcon = _FilterIcon
export const FilterXIcon = _FilterXIcon
export const FoldVerticalIcon = _FoldVerticalIcon
export const FolderIcon = _FolderIcon
export const FolderArchiveIcon = _FolderArchiveIcon
export const FolderCogIcon = _FolderCogIcon
@@ -626,6 +637,7 @@ export const PackageOpenIcon = _PackageOpenIcon
export const PackagePlusIcon = _PackagePlusIcon
export const PaintbrushIcon = _PaintbrushIcon
export const PaletteIcon = _PaletteIcon
export const PauseIcon = _PauseIcon
export const PencilIcon = _PencilIcon
export const PickaxeIcon = _PickaxeIcon
export const PinIcon = _PinIcon
@@ -669,6 +681,7 @@ export const SortAscIcon = _SortAscIcon
export const SortDescIcon = _SortDescIcon
export const SparklesIcon = _SparklesIcon
export const SpinnerIcon = _SpinnerIcon
export const SplitIcon = _SplitIcon
export const StarIcon = _StarIcon
export const StopCircleIcon = _StopCircleIcon
export const StoreIcon = _StoreIcon
@@ -826,6 +839,7 @@ export const TriangleAlertIcon = _TriangleAlertIcon
export const UnderlineIcon = _UnderlineIcon
export const UndoIcon = _UndoIcon
export const UnfoldHorizontalIcon = _UnfoldHorizontalIcon
export const UnfoldVerticalIcon = _UnfoldVerticalIcon
export const UnknownIcon = _UnknownIcon
export const UnknownDonationIcon = _UnknownDonationIcon
export const UnlinkIcon = _UnlinkIcon
+19
View File
@@ -0,0 +1,19 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-arrow-down-1-0"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m3 16 4 4 4-4" />
<path d="M7 20V4" />
<path d="M17 10V4h-2" />
<path d="M15 10h4" />
<rect x="15" y="14" width="4" height="6" ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 450 B

@@ -0,0 +1,19 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-arrow-down-wide-narrow"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m3 16 4 4 4-4" />
<path d="M7 20V4" />
<path d="M11 4h10" />
<path d="M11 8h7" />
<path d="M11 12h4" />
</svg>

After

Width:  |  Height:  |  Size: 425 B

+22
View File
@@ -0,0 +1,22 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-circle-dashed"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10.1 2.182a10 10 0 0 1 3.8 0" />
<path d="M13.9 21.818a10 10 0 0 1-3.8 0" />
<path d="M17.609 3.721a10 10 0 0 1 2.69 2.7" />
<path d="M2.182 13.9a10 10 0 0 1 0-3.8" />
<path d="M20.279 17.609a10 10 0 0 1-2.7 2.69" />
<path d="M21.818 10.1a10 10 0 0 1 0 3.8" />
<path d="M3.721 6.391a10 10 0 0 1 2.7-2.69" />
<path d="M6.391 20.279a10 10 0 0 1-2.69-2.7" />
</svg>

After

Width:  |  Height:  |  Size: 675 B

+22
View File
@@ -0,0 +1,22 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-fold-vertical"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 22v-6" />
<path d="M12 8V2" />
<path d="M4 12H2" />
<path d="M10 12H8" />
<path d="M16 12h-2" />
<path d="M22 12h-2" />
<path d="m15 19-3-3-3 3" />
<path d="m15 5-3 3-3-3" />
</svg>

After

Width:  |  Height:  |  Size: 497 B

+16
View File
@@ -0,0 +1,16 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-pause"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="14" y="3" width="5" height="18" rx="1" />
<rect x="5" y="3" width="5" height="18" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 390 B

+18
View File
@@ -0,0 +1,18 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-split"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M16 3h5v5" />
<path d="M8 3H3v5" />
<path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3" />
<path d="m15 9 6-6" />
</svg>

After

Width:  |  Height:  |  Size: 412 B

+22
View File
@@ -0,0 +1,22 @@
<!-- @license lucide-static v0.562.0 - ISC -->
<svg
class="lucide lucide-unfold-vertical"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M12 22v-6" />
<path d="M12 8V2" />
<path d="M4 12H2" />
<path d="M10 12H8" />
<path d="M16 12h-2" />
<path d="M22 12h-2" />
<path d="m15 19-3 3-3-3" />
<path d="m15 5-3-3-3 3" />
</svg>

After

Width:  |  Height:  |  Size: 499 B

@@ -0,0 +1,59 @@
import type { QuickReply } from '../types/quick-reply'
export default [
{
label: '✅ Corrections Applied',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/corrections-applied.md?raw'))
.default,
private: false,
},
{
label: '🚫 Bad Proofs',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/illegitimate-evidence.md?raw'))
.default,
private: false,
},
{
label: '⛔ Inaccessible Proofs',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/inaccessible-evidence.md?raw'))
.default,
private: false,
},
{
label: '🌐 Not Permission to Distribute',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/but-its-online.md?raw'))
.default,
private: false,
},
{
label: '🍴 Forks',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/forks.md?raw')).default,
private: false,
},
{
label: '💲 Premium Content',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/premium-content.md?raw'))
.default,
private: false,
},
{
label: '⚖️ Prohibited Content',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/prohibited-content.md?raw'))
.default,
private: false,
},
{
label: '🧑‍💻 Cheats and Hacks',
message: async () =>
(await import('./messages/quick-replies/externals-permissions/cheats-and-hacks.md?raw'))
.default,
private: false,
},
] as ReadonlyArray<QuickReply>
+2 -2
View File
@@ -1,5 +1,4 @@
import type { Stage } from '../types/stage'
import modpackPermissionsStage from './modpack-permissions-stage'
import categories from './stages/categories'
import description from './stages/description'
import environment from './stages/environment/environment'
@@ -7,6 +6,7 @@ import environmentMultiple from './stages/environment/environment-multiple'
import gallery from './stages/gallery'
import license from './stages/license'
import links from './stages/links'
import permissions from './stages/permissions'
import postApproval from './stages/post-approval'
import reupload from './stages/reupload'
import ruleFollowing from './stages/rule-following'
@@ -28,8 +28,8 @@ export default [
gallery,
versions,
reupload,
permissions,
ruleFollowing,
modpackPermissionsStage,
statusAlerts,
undefinedProject,
postApproval,
@@ -0,0 +1,4 @@
## Proof of Permissions
You may not have the necessary rights or permissions to distribute some of the external content included in your Modpack. </br>
Per section 4 of %RULES%, we ask that you please complete all steps and address all notes left by moderators if any in your project's %PROJECT_PERMISSIONS_FLINK%.
@@ -0,0 +1,3 @@
## Permissions Incomplete
Per section 4 of %RULES%, we ask that you complete all steps requested in your project's %PROJECT_PERMISSIONS_FLINK%.
@@ -0,0 +1,6 @@
## Prohibited External Content
Some external content in your mod was found to be prohibited.</br>
This means you cannot include these files in your Modpack on Modrinth, likely because it violates one of %RULES%.
Please view your project's %PROJECT_PERMISSIONS_FLINK% for a list of what content must be removed from your pack before distribution on Modrinth.

Some files were not shown because too many files have changed in this diff Show More