feat: 接入皮肤站公告 API
Sync source to CNB / Sync Git ref (push) Skipped

This commit is contained in:
AxTps committed 2026-10-10 22:34:00 +08:00
1 parent c54acd2e36
commit cadb7bbdcd
3 files changed
+208 -12

No files matched your search

@@ -5,6 +5,7 @@ export type RemoteAnnouncement = {
content: string
type: 'modal' | 'notification'
priority: 'low' | 'normal' | 'high' | 'critical'
pinned?: boolean
starts_at: string
ends_at: string | null
published_at: string
@@ -32,18 +33,28 @@ export function parseAnnouncements(value: unknown): RemoteAnnouncement[] | null
if (!entry || typeof entry !== 'object') continue
const item = entry as Record<string, unknown>
if (
typeof item.id !== 'string' || item.id.length > 100 || ids.has(item.id) ||
typeof item.title !== 'string' || !item.title.trim() || item.title.length > 120 ||
typeof item.content !== 'string' || item.content.length > 20000 ||
typeof item.id !== 'string' ||
item.id.length > 100 ||
ids.has(item.id) ||
typeof item.title !== 'string' ||
!item.title.trim() ||
item.title.length > 120 ||
typeof item.content !== 'string' ||
item.content.length > 20000 ||
(item.summary != null && (typeof item.summary !== 'string' || item.summary.length > 300)) ||
!['modal', 'notification'].includes(String(item.type)) ||
!['low', 'normal', 'high', 'critical'].includes(String(item.priority)) ||
typeof item.starts_at !== 'string' || !Number.isFinite(Date.parse(item.starts_at)) ||
typeof item.published_at !== 'string' || !Number.isFinite(Date.parse(item.published_at)) ||
(item.ends_at != null && (typeof item.ends_at !== 'string' || !Number.isFinite(Date.parse(item.ends_at)))) ||
(item.action_label != null && (typeof item.action_label !== 'string' || item.action_label.length > 80)) ||
typeof item.starts_at !== 'string' ||
!Number.isFinite(Date.parse(item.starts_at)) ||
typeof item.published_at !== 'string' ||
!Number.isFinite(Date.parse(item.published_at)) ||
(item.ends_at != null &&
(typeof item.ends_at !== 'string' || !Number.isFinite(Date.parse(item.ends_at)))) ||
(item.action_label != null &&
(typeof item.action_label !== 'string' || item.action_label.length > 80)) ||
(item.action_url != null && !safeAnnouncementUrl(item.action_url))
) continue
)
continue
ids.add(item.id)
items.push({
...item,
@@ -54,7 +65,12 @@ export function parseAnnouncements(value: unknown): RemoteAnnouncement[] | null
} as RemoteAnnouncement)
}
const rank = { low: 0, normal: 1, high: 2, critical: 3 }
return items.sort((first, second) => rank[second.priority] - rank[first.priority] || Date.parse(second.published_at) - Date.parse(first.published_at))
return items.sort(
(first, second) =>
Number(second.pinned ?? false) - Number(first.pinned ?? false) ||
rank[second.priority] - rank[first.priority] ||
Date.parse(second.published_at) - Date.parse(first.published_at),
)
}
export function isAnnouncementActive(item: RemoteAnnouncement, now = Date.now()) {
@@ -67,3 +83,73 @@ export function announcementKey(item: RemoteAnnouncement) {
export const REMOTE_ANNOUNCEMENTS_UPDATED_EVENT = 'starlight:remote-announcements-updated'
export const OPEN_REMOTE_ANNOUNCEMENT_CENTER_EVENT = 'starlight:open-remote-announcement-center'
type BroadcastAnnouncementEntry = {
title?: unknown
subtitle?: unknown
content?: unknown
updateAt?: unknown
star?: unknown
importantLevel?: unknown
id?: unknown
}
function broadcastPriority(level: number): RemoteAnnouncement['priority'] {
if (level >= 9) return 'critical'
if (level >= 6) return 'high'
if (level >= 3) return 'normal'
return 'low'
}
export function mapBroadcastAnnouncements(payload: unknown): RemoteAnnouncement[] | null {
if (!Array.isArray(payload) || payload.length > 200) return null
const items: RemoteAnnouncement[] = []
const seen = new Set<string>()
for (const entry of payload) {
if (!entry || typeof entry !== 'object') continue
const item = entry as BroadcastAnnouncementEntry
if (
typeof item.id !== 'number' ||
!Number.isFinite(item.id) ||
typeof item.title !== 'string' ||
!item.title.trim() ||
item.title.length > 120 ||
typeof item.content !== 'string' ||
item.content.length > 20000 ||
typeof item.updateAt !== 'number' ||
!Number.isFinite(item.updateAt) ||
(item.subtitle != null && typeof item.subtitle !== 'string')
)
continue
const id = String(item.id)
if (seen.has(id)) continue
seen.add(id)
const level =
typeof item.importantLevel === 'number' && Number.isFinite(item.importantLevel)
? Math.max(0, Math.min(10, Math.floor(item.importantLevel)))
: 0
const starred = item.star === true
const published = new Date(item.updateAt).toISOString()
const summary = typeof item.subtitle === 'string' && item.subtitle.trim() ? item.subtitle : null
items.push({
id,
title: item.title,
summary,
content: item.content,
type: starred || level >= 8 ? 'modal' : 'notification',
priority: broadcastPriority(level),
pinned: starred,
starts_at: published,
ends_at: null,
published_at: published,
action_url: null,
action_label: null,
})
}
const rank = { low: 0, normal: 1, high: 2, critical: 3 }
return items.sort(
(first, second) =>
Number(second.pinned ?? false) - Number(first.pinned ?? false) ||
rank[second.priority] - rank[first.priority] ||
Date.parse(second.published_at) - Date.parse(first.published_at),
)
}