feat:移除了弹窗,服务器添加sls
This commit is contained in:
commit
6a295f9a7a
4082 files changed
+1322534
No files matched your search
@@ -0,0 +1,250 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export type ContentIdentityProvider = 'modrinth' | 'curseforge'
|
||||
export type ContentIdentitySource = 'curated_mapping' | 'sha1' | 'heuristic'
|
||||
export type ContentIdentityConfidence = 'exact' | 'high' | 'possible'
|
||||
|
||||
export interface ContentIdentityInput {
|
||||
provider: ContentIdentityProvider
|
||||
projectId: string
|
||||
contentType: string
|
||||
slug?: string | null
|
||||
title?: string | null
|
||||
fileName?: string | null
|
||||
sha1?: string | null
|
||||
}
|
||||
|
||||
export interface ContentIdentitySnapshotItem {
|
||||
projectType: string
|
||||
provider: ContentIdentityProvider | null
|
||||
providerProjectId: string | null
|
||||
expectedRelativePath: string
|
||||
content: {
|
||||
id: string
|
||||
file_name: string
|
||||
project_type: string
|
||||
project?: { slug?: string | null; title?: string | null } | null
|
||||
provider_refs: Array<{
|
||||
provider: ContentIdentityProvider
|
||||
project_id: string | number
|
||||
}>
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface ContentIdentityProjectMetadata {
|
||||
slug?: string | null
|
||||
title?: string | null
|
||||
}
|
||||
|
||||
export type ContentIdentityProjectMetadataByProvider = Partial<
|
||||
Record<ContentIdentityProvider, ReadonlyMap<string, ContentIdentityProjectMetadata>>
|
||||
>
|
||||
|
||||
export interface ContentIdentityCounterpart {
|
||||
provider: ContentIdentityProvider
|
||||
projectId: string
|
||||
slug?: string
|
||||
}
|
||||
|
||||
export interface ContentIdentity {
|
||||
key?: string
|
||||
source?: ContentIdentitySource
|
||||
confidence?: ContentIdentityConfidence
|
||||
counterparts?: ContentIdentityCounterpart[]
|
||||
provider: ContentIdentityProvider
|
||||
projectId: string
|
||||
contentType: string
|
||||
slug?: string
|
||||
title?: string
|
||||
fileName?: string
|
||||
sha1?: string
|
||||
normalizedSlug?: string
|
||||
normalizedTitle?: string
|
||||
normalizedFileName?: string
|
||||
ambiguous?: boolean
|
||||
}
|
||||
|
||||
interface ContentIdentityRecord {
|
||||
key: string
|
||||
counterparts: ContentIdentityCounterpart[]
|
||||
}
|
||||
|
||||
interface ContentIdentityLookup {
|
||||
modrinth: Record<string, ContentIdentityRecord[]>
|
||||
curseforge: Record<string, ContentIdentityRecord[]>
|
||||
}
|
||||
|
||||
export interface ContentIdentityMatch {
|
||||
source: ContentIdentitySource
|
||||
confidence: ContentIdentityConfidence
|
||||
identity: ContentIdentity
|
||||
}
|
||||
|
||||
export function contentIdentityInputsFromSnapshot(
|
||||
items: ContentIdentitySnapshotItem[],
|
||||
projectMetadata: ContentIdentityProjectMetadataByProvider = {},
|
||||
): ContentIdentityInput[] {
|
||||
const inputs: ContentIdentityInput[] = []
|
||||
for (const item of items) {
|
||||
const references = new Map<string, { provider: ContentIdentityProvider; projectId: string }>()
|
||||
if (item.provider && item.providerProjectId) {
|
||||
const reference = { provider: item.provider, projectId: item.providerProjectId }
|
||||
references.set(`${reference.provider}:${reference.projectId}`, reference)
|
||||
}
|
||||
for (const providerReference of item.content?.provider_refs ?? []) {
|
||||
const reference = {
|
||||
provider: providerReference.provider,
|
||||
projectId: String(providerReference.project_id),
|
||||
}
|
||||
references.set(`${reference.provider}:${reference.projectId}`, reference)
|
||||
}
|
||||
|
||||
for (const reference of references.values()) {
|
||||
const metadata = projectMetadata[reference.provider]?.get(reference.projectId)
|
||||
inputs.push({
|
||||
...reference,
|
||||
contentType: item.projectType || item.content?.project_type || 'mod',
|
||||
slug: metadata?.slug ?? item.content?.project?.slug,
|
||||
title: metadata?.title ?? item.content?.project?.title,
|
||||
fileName:
|
||||
item.content?.file_name ??
|
||||
item.expectedRelativePath.split(/[\\/]/u).pop() ??
|
||||
item.expectedRelativePath,
|
||||
sha1: item.content?.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
return inputs
|
||||
}
|
||||
|
||||
function normalizeText(value: string | null | undefined) {
|
||||
return (value ?? '')
|
||||
.toLocaleLowerCase()
|
||||
.replace(/\.(?:jar|zip|mrpack|litemod)(?:\.disabled)?$/u, '')
|
||||
.replace(/(?:[-_. ]+)(?:v?\d[\w.-]*)$/u, '')
|
||||
.replace(/(?:[-_. ]+)(?:fabric|forge|quilt|neoforge|neo|liteloader)$/u, '')
|
||||
.replace(/[^a-z0-9]+/gu, '')
|
||||
}
|
||||
|
||||
export function normalizeContentIdentityText(value: string | null | undefined) {
|
||||
return normalizeText(value)
|
||||
}
|
||||
|
||||
function identitySlug(input: ContentIdentityInput) {
|
||||
return input.slug ? normalizeText(input.slug) : ''
|
||||
}
|
||||
|
||||
function identityTitle(input: ContentIdentityInput) {
|
||||
return input.title ? normalizeText(input.title) : ''
|
||||
}
|
||||
|
||||
function identityFileName(input: ContentIdentityInput) {
|
||||
return input.fileName ? normalizeText(input.fileName) : ''
|
||||
}
|
||||
|
||||
export async function resolveContentIdentities(
|
||||
inputs: ContentIdentityInput[],
|
||||
): Promise<ContentIdentity[]> {
|
||||
const modrinthSlugs = [
|
||||
...new Set(
|
||||
inputs
|
||||
.filter((input) => input.provider === 'modrinth' && input.slug)
|
||||
.map((input) => input.slug as string),
|
||||
),
|
||||
]
|
||||
const curseforgeSlugs = [
|
||||
...new Set(
|
||||
inputs
|
||||
.filter((input) => input.provider === 'curseforge' && input.slug)
|
||||
.map((input) => input.slug as string),
|
||||
),
|
||||
]
|
||||
let lookup: ContentIdentityLookup = { modrinth: {}, curseforge: {} }
|
||||
if (modrinthSlugs.length || curseforgeSlugs.length) {
|
||||
lookup = await invoke<ContentIdentityLookup>(
|
||||
'plugin:content-search|lookup_content_identities',
|
||||
{
|
||||
modrinthSlugs,
|
||||
curseforgeSlugs,
|
||||
},
|
||||
).catch(() => lookup)
|
||||
}
|
||||
|
||||
return inputs.map((input) => {
|
||||
const records = input.slug
|
||||
? ((input.provider === 'modrinth'
|
||||
? (lookup.modrinth[input.slug] ?? lookup.modrinth[input.slug.toLowerCase()])
|
||||
: (lookup.curseforge[input.slug] ?? lookup.curseforge[input.slug.toLowerCase()])) ?? [])
|
||||
: []
|
||||
const uniqueKeys = [...new Set(records.map((record) => record.key))]
|
||||
const mapping =
|
||||
uniqueKeys.length === 1 ? records.find((record) => record.key === uniqueKeys[0]) : undefined
|
||||
return {
|
||||
...input,
|
||||
slug: input.slug ?? undefined,
|
||||
title: input.title ?? undefined,
|
||||
fileName: input.fileName ?? undefined,
|
||||
sha1: input.sha1?.toLowerCase() || undefined,
|
||||
key: mapping?.key,
|
||||
source: mapping ? 'curated_mapping' : undefined,
|
||||
confidence: mapping ? 'exact' : undefined,
|
||||
counterparts: mapping?.counterparts,
|
||||
normalizedSlug: identitySlug(input),
|
||||
normalizedTitle: identityTitle(input),
|
||||
normalizedFileName: identityFileName(input),
|
||||
ambiguous: uniqueKeys.length > 1,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function sameProvider(left: ContentIdentity, right: ContentIdentity) {
|
||||
return left.provider === right.provider
|
||||
}
|
||||
|
||||
function sameContentType(left: ContentIdentity, right: ContentIdentity) {
|
||||
return left.contentType === right.contentType
|
||||
}
|
||||
|
||||
export function compareContentIdentities(
|
||||
left: ContentIdentity,
|
||||
right: ContentIdentity,
|
||||
): ContentIdentityMatch | null {
|
||||
if (sameProvider(left, right) || !sameContentType(left, right)) return null
|
||||
if (left.key && right.key && left.key === right.key && !left.ambiguous && !right.ambiguous) {
|
||||
return { source: 'curated_mapping', confidence: 'exact', identity: right }
|
||||
}
|
||||
if (left.sha1 && right.sha1 && left.sha1 === right.sha1) {
|
||||
return { source: 'sha1', confidence: 'exact', identity: right }
|
||||
}
|
||||
if (left.normalizedSlug && right.normalizedSlug && left.normalizedSlug === right.normalizedSlug) {
|
||||
return { source: 'heuristic', confidence: 'high', identity: right }
|
||||
}
|
||||
if (
|
||||
left.normalizedTitle &&
|
||||
right.normalizedTitle &&
|
||||
left.normalizedTitle === right.normalizedTitle
|
||||
) {
|
||||
return { source: 'heuristic', confidence: 'possible', identity: right }
|
||||
}
|
||||
if (
|
||||
left.normalizedFileName &&
|
||||
right.normalizedFileName &&
|
||||
left.normalizedFileName === right.normalizedFileName
|
||||
) {
|
||||
return { source: 'heuristic', confidence: 'possible', identity: right }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function contentIdentityFromInput(input: ContentIdentityInput): ContentIdentity {
|
||||
return {
|
||||
...input,
|
||||
slug: input.slug ?? undefined,
|
||||
title: input.title ?? undefined,
|
||||
fileName: input.fileName ?? undefined,
|
||||
sha1: input.sha1?.toLowerCase() || undefined,
|
||||
normalizedSlug: identitySlug(input),
|
||||
normalizedTitle: identityTitle(input),
|
||||
normalizedFileName: identityFileName(input),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user