From fed41546c272e560732ce408934af95ed807528f Mon Sep 17 00:00:00 2001 From: dragon-fish Date: Fri, 5 Sep 2025 05:24:45 +0800 Subject: [PATCH 001/143] refactor: batch rewrites --- api/image.ts | 88 +- src/auto-imports.d.ts | 66 +- src/components.d.ts | 54 +- src/components/LazyLoad.vue | 43 +- src/components/UgoiraViewer.vue | 7 +- src/utils/UgoiraPlayer.ts | 471 +++++----- src/utils/ZipDownloader.ts | 1444 +++++++++++++------------------ vercel.json | 8 +- 8 files changed, 1021 insertions(+), 1160 deletions(-) diff --git a/api/image.ts b/api/image.ts index 8c87b104..93831deb 100644 --- a/api/image.ts +++ b/api/image.ts @@ -8,33 +8,75 @@ export default async (req: VercelRequest, res: VercelResponse) => { return res.status(400).send({ message: 'Missing param(s)' }) } + let url = '' + switch (__PREFIX) { + case '-': { + url = `https://i.pximg.net/${__PATH}` + break + } case '~': { - return axios - .get(`https://s.pximg.net/${__PATH}`, { - responseType: 'arraybuffer', - headers: { - referer: 'https://www.pixiv.net/', - 'user-agent': USER_AGENT, - }, - }) - .then( - ({ data, headers }) => { - res.setHeader('Content-Type', headers['content-type']) - res.setHeader( - 'Cache-Control', - `public, max-age=${12 * 60 * 60 * 3600}` - ) - res.status(200).send(Buffer.from(data)) - }, - (err) => { - return res - .status(err?.response?.status || 500) - .send(err?.response?.data || err) - } - ) + url = `https://s.pximg.net/${__PATH}` + break } default: return res.status(400).send({ message: 'Invalid request' }) } + + const proxyHeaders = [ + 'accept', + 'accept-encoding', + 'accept-language', + 'range', + 'if-range', + 'if-none-match', + 'if-modified-since', + 'cache-control', + ] + + const headers = {} as Record + for (const h of proxyHeaders) { + if (typeof req.headers[h] === 'string') { + headers[h] = req.headers[h] + } + } + Object.assign(headers, { + referer: 'https://www.pixiv.net/', + 'user-agent': USER_AGENT, + }) + + console.log('Proxy image:', url, headers) + + return axios + .get(url, { + responseType: 'arraybuffer', + headers, + }) + .then( + ({ data, headers, status }) => { + const exposeHeaders = [ + 'content-type', + 'content-length', + 'cache-control', + 'content-disposition', + 'last-modified', + 'etag', + 'accept-ranges', + 'content-range', + 'vary', + ] + for (const h of exposeHeaders) { + if (typeof headers[h] === 'string') { + res.setHeader(h, headers[h]) + } + } + res.status(status).send(Buffer.from(data)) + }, + (err) => { + console.error('Image proxy error:', err) + return res + .status(err?.response?.status || 500) + .send(err?.response?.data || err) + } + ) } diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 71dd70a1..2e06bfcf 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -7,15 +7,15 @@ export {} declare global { const EffectScope: typeof import('vue')['EffectScope'] - const IllustType: typeof import('./src/types/Artworks')['IllustType'] - const UgoiraPlayer: typeof import('./src/utils/UgoiraPlayer')['UgoiraPlayer'] - const UserPrivacyLevel: typeof import('./src/types/Users')['UserPrivacyLevel'] - const UserXRestrict: typeof import('./src/types/Users')['UserXRestrict'] - const ZipDownloader: typeof import('./src/utils/ZipDownloader')['ZipDownloader'] - const addBookmark: typeof import('./src/utils/artworkActions')['addBookmark'] - const addUserFollow: typeof import('./src/utils/userActions')['addUserFollow'] - const ajax: typeof import('./src/utils/ajax')['ajax'] - const ajaxPostWithFormData: typeof import('./src/utils/ajax')['ajaxPostWithFormData'] + const IllustType: typeof import('./types/Artworks')['IllustType'] + const UgoiraPlayer: typeof import('./utils/UgoiraPlayer')['UgoiraPlayer'] + const UserPrivacyLevel: typeof import('./types/Users')['UserPrivacyLevel'] + const UserXRestrict: typeof import('./types/Users')['UserXRestrict'] + const ZipDownloader: typeof import('./utils/ZipDownloader')['ZipDownloader'] + const addBookmark: typeof import('./utils/artworkActions')['addBookmark'] + const addUserFollow: typeof import('./utils/userActions')['addUserFollow'] + const ajax: typeof import('./utils/ajax')['ajax'] + const ajaxPostWithFormData: typeof import('./utils/ajax')['ajaxPostWithFormData'] const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] const axios: typeof import('axios')['default'] @@ -40,32 +40,32 @@ declare global { const customRef: typeof import('vue')['customRef'] const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] - const defaultArtwork: typeof import('./src/utils/index')['defaultArtwork'] + const defaultArtwork: typeof import('./utils/index')['defaultArtwork'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import('vue')['defineComponent'] const demonstrateOptimizedPlayer: typeof import('./src/utils/UgoiraPlayerExample')['demonstrateOptimizedPlayer'] const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] const effectScope: typeof import('vue')['effectScope'] - const exampleSessionId: typeof import('./src/components/userData')['exampleSessionId'] - const existsSessionId: typeof import('./src/components/userData')['existsSessionId'] + const exampleSessionId: typeof import('./components/userData')['exampleSessionId'] + const existsSessionId: typeof import('./components/userData')['existsSessionId'] const extendRef: typeof import('@vueuse/core')['extendRef'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentWatcher: typeof import('vue')['getCurrentWatcher'] const h: typeof import('vue')['h'] const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] - const initUser: typeof import('./src/components/userData')['initUser'] + const initUser: typeof import('./components/userData')['initUser'] const inject: typeof import('vue')['inject'] const injectLocal: typeof import('@vueuse/core')['injectLocal'] - const isArtwork: typeof import('./src/utils/artworkActions')['isArtwork'] + const isArtwork: typeof import('./utils/artworkActions')['isArtwork'] const isDefined: typeof import('@vueuse/core')['isDefined'] const isProxy: typeof import('vue')['isProxy'] const isReactive: typeof import('vue')['isReactive'] const isReadonly: typeof import('vue')['isReadonly'] const isRef: typeof import('vue')['isRef'] const isShallow: typeof import('vue')['isShallow'] - const login: typeof import('./src/components/userData')['login'] - const logout: typeof import('./src/components/userData')['logout'] + const login: typeof import('./components/userData')['login'] + const logout: typeof import('./components/userData')['logout'] const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] const markRaw: typeof import('vue')['markRaw'] const nextTick: typeof import('vue')['nextTick'] @@ -106,16 +106,16 @@ declare global { const refDefault: typeof import('@vueuse/core')['refDefault'] const refThrottled: typeof import('@vueuse/core')['refThrottled'] const refWithControl: typeof import('@vueuse/core')['refWithControl'] - const removeBookmark: typeof import('./src/utils/artworkActions')['removeBookmark'] - const removeUserFollow: typeof import('./src/utils/userActions')['removeUserFollow'] + const removeBookmark: typeof import('./utils/artworkActions')['removeBookmark'] + const removeUserFollow: typeof import('./utils/userActions')['removeUserFollow'] const resolveComponent: typeof import('vue')['resolveComponent'] const resolveRef: typeof import('@vueuse/core')['resolveRef'] const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] - const setTitle: typeof import('./src/utils/setTitle')['setTitle'] + const setTitle: typeof import('./utils/setTitle')['setTitle'] const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowRef: typeof import('vue')['shallowRef'] - const sortArtList: typeof import('./src/utils/artworkActions')['sortArtList'] + const sortArtList: typeof import('./utils/artworkActions')['sortArtList'] const syncRef: typeof import('@vueuse/core')['syncRef'] const syncRefs: typeof import('@vueuse/core')['syncRefs'] const templateRef: typeof import('@vueuse/core')['templateRef'] @@ -261,7 +261,7 @@ declare global { const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] const useShare: typeof import('@vueuse/core')['useShare'] - const useSideNavStore: typeof import('./src/composables/states')['useSideNavStore'] + const useSideNavStore: typeof import('./composables/states')['useSideNavStore'] const useSlots: typeof import('vue')['useSlots'] const useSorted: typeof import('@vueuse/core')['useSorted'] const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] @@ -293,7 +293,7 @@ declare global { const useTransition: typeof import('@vueuse/core')['useTransition'] const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] - const useUserStore: typeof import('./src/composables/states')['useUserStore'] + const useUserStore: typeof import('./composables/states')['useUserStore'] const useVModel: typeof import('@vueuse/core')['useVModel'] const useVModels: typeof import('@vueuse/core')['useVModels'] const useVibrate: typeof import('@vueuse/core')['useVibrate'] @@ -306,7 +306,7 @@ declare global { const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] - const validateSessionId: typeof import('./src/components/userData')['validateSessionId'] + const validateSessionId: typeof import('./components/userData')['validateSessionId'] const watch: typeof import('vue')['watch'] const watchArray: typeof import('@vueuse/core')['watchArray'] const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] @@ -330,18 +330,18 @@ declare global { export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') // @ts-ignore - export type { UgoiraPlayer, UgoiraPlayerOptions, UgoiraFrame, UgoiraMeta } from './src/utils/UgoiraPlayer' - import('./src/utils/UgoiraPlayer') + export type { UgoiraPlayer, UgoiraPlayerOptions, UgoiraFrame, UgoiraMeta } from './utils/UgoiraPlayer' + import('./utils/UgoiraPlayer') // @ts-ignore - export type { ZipDownloader, ZipDownloaderOptions, ZipEntry, ZipEntryWithData, ZipOverview, DataRange } from './src/utils/ZipDownloader' - import('./src/utils/ZipDownloader') + export type { ZipDownloader, FetchLike, ZipDownloaderOptions, ZipEntry, ZipEntryWithData, ZipOverview, DataRange } from './utils/ZipDownloader' + import('./utils/ZipDownloader') // @ts-ignore - export type { IllustType, ArtworkUrls, ArtworkPageUrls, ArtworkTag, ArtworkGallery, ArtworkInfo, ArtworkInfoOrAd, ArtworkRank, Artwork, IllustType } from './src/types/Artworks' - import('./src/types/Artworks') + export type { IllustType, ArtworkUrls, ArtworkPageUrls, ArtworkTag, ArtworkGallery, ArtworkInfo, ArtworkInfoOrAd, ArtworkRank, Artwork, IllustType } from './types/Artworks' + import('./types/Artworks') // @ts-ignore - export type { Comments } from './src/types/Comment' - import('./src/types/Comment') + export type { Comments } from './types/Comment' + import('./types/Comment') // @ts-ignore - export type { UserXRestrict, UserPrivacyLevel, User, PixivUser, UserListItem, UserXRestrict, UserPrivacyLevel } from './src/types/Users' - import('./src/types/Users') + export type { UserXRestrict, UserPrivacyLevel, User, PixivUser, UserListItem, UserXRestrict, UserPrivacyLevel } from './types/Users' + import('./types/Users') } diff --git a/src/components.d.ts b/src/components.d.ts index 0792812a..f521eda0 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -8,24 +8,24 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { - ArtTag: typeof import('./src/components/ArtTag.vue')['default'] - ArtworkCard: typeof import('./src/components/ArtworksList/ArtworkCard.vue')['default'] - ArtworkLargeCard: typeof import('./src/components/ArtworksList/ArtworkLargeCard.vue')['default'] - ArtworkLargeList: typeof import('./src/components/ArtworksList/ArtworkLargeList.vue')['default'] - ArtworkList: typeof import('./src/components/ArtworksList/ArtworkList.vue')['default'] - ArtworksByUser: typeof import('./src/components/ArtworksList/ArtworksByUser.vue')['default'] - AuthorCard: typeof import('./src/components/AuthorCard.vue')['default'] - Card: typeof import('./src/components/Card.vue')['default'] - Comment: typeof import('./src/components/Comment/Comment.vue')['default'] - CommentsArea: typeof import('./src/components/Comment/CommentsArea.vue')['default'] - CommentSubmit: typeof import('./src/components/Comment/CommentSubmit.vue')['default'] - ErrorPage: typeof import('./src/components/ErrorPage.vue')['default'] - ExternalLink: typeof import('./src/components/ExternalLink.vue')['default'] - FollowUserCard: typeof import('./src/components/FollowUserCard.vue')['default'] - Gallery: typeof import('./src/components/Gallery.vue')['default'] - LazyLoad: typeof import('./src/components/LazyLoad.vue')['default'] - ListLink: typeof import('./src/components/SideNav/ListLink.vue')['default'] - NaiveuiProvider: typeof import('./src/components/NaiveuiProvider.vue')['default'] + ArtTag: typeof import('./components/ArtTag.vue')['default'] + ArtworkCard: typeof import('./components/ArtworksList/ArtworkCard.vue')['default'] + ArtworkLargeCard: typeof import('./components/ArtworksList/ArtworkLargeCard.vue')['default'] + ArtworkLargeList: typeof import('./components/ArtworksList/ArtworkLargeList.vue')['default'] + ArtworkList: typeof import('./components/ArtworksList/ArtworkList.vue')['default'] + ArtworksByUser: typeof import('./components/ArtworksList/ArtworksByUser.vue')['default'] + AuthorCard: typeof import('./components/AuthorCard.vue')['default'] + Card: typeof import('./components/Card.vue')['default'] + Comment: typeof import('./components/Comment/Comment.vue')['default'] + CommentsArea: typeof import('./components/Comment/CommentsArea.vue')['default'] + CommentSubmit: typeof import('./components/Comment/CommentSubmit.vue')['default'] + ErrorPage: typeof import('./components/ErrorPage.vue')['default'] + ExternalLink: typeof import('./components/ExternalLink.vue')['default'] + FollowUserCard: typeof import('./components/FollowUserCard.vue')['default'] + Gallery: typeof import('./components/Gallery.vue')['default'] + LazyLoad: typeof import('./components/LazyLoad.vue')['default'] + ListLink: typeof import('./components/SideNav/ListLink.vue')['default'] + NaiveuiProvider: typeof import('./components/NaiveuiProvider.vue')['default'] NAlert: typeof import('naive-ui')['NAlert'] NButton: typeof import('naive-ui')['NButton'] NCard: typeof import('naive-ui')['NCard'] @@ -35,22 +35,22 @@ declare module 'vue' { NInput: typeof import('naive-ui')['NInput'] NLi: typeof import('naive-ui')['NLi'] NPagination: typeof import('naive-ui')['NPagination'] - NProgress: typeof import('./src/components/NProgress.vue')['default'] + NProgress: typeof import('./components/NProgress.vue')['default'] NSpace: typeof import('naive-ui')['NSpace'] NStatistic: typeof import('naive-ui')['NStatistic'] NTabPane: typeof import('naive-ui')['NTabPane'] NTabs: typeof import('naive-ui')['NTabs'] NTag: typeof import('naive-ui')['NTag'] NUl: typeof import('naive-ui')['NUl'] - Placeholder: typeof import('./src/components/Placeholder.vue')['default'] + Placeholder: typeof import('./components/Placeholder.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - SearchBox: typeof import('./src/components/SearchBox.vue')['default'] - ShowMore: typeof import('./src/components/ShowMore.vue')['default'] - SideNav: typeof import('./src/components/SideNav/SideNav.vue')['default'] - SiteFooter: typeof import('./src/components/SiteFooter.vue')['default'] - SiteHeader: typeof import('./src/components/SiteHeader.vue')['default'] - SiteNoticeBanner: typeof import('./src/components/SiteNoticeBanner.vue')['default'] - UgoiraViewer: typeof import('./src/components/UgoiraViewer.vue')['default'] + SearchBox: typeof import('./components/SearchBox.vue')['default'] + ShowMore: typeof import('./components/ShowMore.vue')['default'] + SideNav: typeof import('./components/SideNav/SideNav.vue')['default'] + SiteFooter: typeof import('./components/SiteFooter.vue')['default'] + SiteHeader: typeof import('./components/SiteHeader.vue')['default'] + SiteNoticeBanner: typeof import('./components/SiteNoticeBanner.vue')['default'] + UgoiraViewer: typeof import('./components/UgoiraViewer.vue')['default'] } } diff --git a/src/components/LazyLoad.vue b/src/components/LazyLoad.vue index 47e67547..772fe87a 100644 --- a/src/components/LazyLoad.vue +++ b/src/components/LazyLoad.vue @@ -1,13 +1,13 @@ @@ -20,25 +20,32 @@ const props = defineProps<{ const loaded = ref(false) const error = ref(false) -const imgRef = useTemplateRef('imgRef') +const imgRef = ref(null) -watch( - () => props.src, - () => { - loaded.value = false - error.value = false +const ob = useIntersectionObserver(imgRef, async ([{ isIntersecting }]) => { + if (isIntersecting) { + await nextTick() + loadImage() + ob.stop() } -) - -useEventListener(imgRef, 'load', () => { - loaded.value = true - error.value = false }) -useEventListener(imgRef, 'error', (e) => { - console.warn('error', e) + +function loadImage() { loaded.value = false - error.value = true -}) + error.value = false + + const img = new Image(props.width, props.height) + img.src = props.src + img.onload = () => { + loaded.value = true + error.value = false + imgRef.value = img + } + img.onerror = () => { + loaded.value = false + error.value = true + } +}