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