187 lines
4.8 KiB
TypeScript
187 lines
4.8 KiB
TypeScript
import { VercelRequest, VercelResponse } from '@vercel/node'
|
|
import axios from 'axios'
|
|
import colors from 'picocolors'
|
|
|
|
// HTTP handler
|
|
export default async function (req: VercelRequest, res: VercelResponse) {
|
|
res.status(404).send({
|
|
error: true,
|
|
message: 'Not Found',
|
|
body: null,
|
|
})
|
|
}
|
|
|
|
export const PROD = process.env.NODE_ENV === 'production'
|
|
export const DEV = !PROD
|
|
export const USER_AGENT =
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0'
|
|
export const PXIMG_BASEURL_I = (() => {
|
|
const i = process.env.VITE_PXIMG_BASEURL_I
|
|
return i ? i.replace(/\/$/, '') + '/' : 'https://i.pximg.net/'
|
|
})()
|
|
export const PXIMG_BASEURL_S = (() => {
|
|
const s = process.env.VITE_PXIMG_BASEURL_S
|
|
return s ? s.replace(/\/$/, '') + '/' : 'https://s.pximg.net/'
|
|
})()
|
|
|
|
export class CookieUtils {
|
|
static toJSON(raw: string) {
|
|
return Object.fromEntries(new URLSearchParams(raw.replace(/;\s*/g, '&')))
|
|
}
|
|
static toString(obj: any) {
|
|
return Object.keys(obj)
|
|
.map((i) => `${i}=${obj[i]}`)
|
|
.join(';')
|
|
}
|
|
}
|
|
|
|
export const ajax = axios.create({
|
|
baseURL: 'https://www.pixiv.net/',
|
|
params: {},
|
|
headers: {
|
|
'user-agent': USER_AGENT,
|
|
},
|
|
timeout: 9 * 1000,
|
|
})
|
|
ajax.interceptors.request.use((ctx) => {
|
|
// 去除内部参数
|
|
ctx.params = ctx.params || {}
|
|
delete ctx.params.__PATH
|
|
delete ctx.params.__PREFIX
|
|
|
|
const cookies = CookieUtils.toJSON(ctx.headers.cookie || '')
|
|
const csrfToken = ctx.headers['x-csrf-token'] ?? cookies.CSRFTOKEN ?? ''
|
|
// 强制覆写部分 headers
|
|
ctx.headers = ctx.headers || {}
|
|
ctx.headers.host = 'www.pixiv.net'
|
|
ctx.headers.origin = 'https://www.pixiv.net'
|
|
ctx.headers.referer = 'https://www.pixiv.net/'
|
|
ctx.headers['user-agent'] = USER_AGENT
|
|
ctx.headers['accept-language'] ??=
|
|
'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'
|
|
csrfToken && (ctx.headers['x-csrf-token'] = csrfToken)
|
|
|
|
if (DEV) {
|
|
console.info(
|
|
colors.green(`[${ctx.method?.toUpperCase()}] <`),
|
|
colors.cyan(ctx.url || '')
|
|
)
|
|
console.info({
|
|
params: ctx.params,
|
|
data: ctx.data,
|
|
cookies,
|
|
})
|
|
}
|
|
|
|
return ctx
|
|
})
|
|
ajax.interceptors.response.use((ctx) => {
|
|
typeof ctx.data === 'object' &&
|
|
(ctx.data = replacePximgUrlsInObject(ctx.data?.body ?? ctx.data))
|
|
if (DEV) {
|
|
const out: string =
|
|
typeof ctx.data === 'object'
|
|
? JSON.stringify(ctx.data, null, 2)
|
|
: ctx.data.toString().trim()
|
|
console.info(
|
|
colors.green('[SEND] >'),
|
|
colors.cyan(ctx.request?.path?.replace('https://www.pixiv.net', '')),
|
|
`\n${colors.yellow(typeof ctx.data)} ${
|
|
out.length >= 200 ? out.slice(0, 200).trim() + '\n...' : out
|
|
}`
|
|
)
|
|
}
|
|
return ctx
|
|
})
|
|
|
|
export function replacePximgUrlsInString(str: string): string {
|
|
if (!str.includes('pximg.net')) return str
|
|
return str
|
|
.replaceAll('https://i.pximg.net/', PXIMG_BASEURL_I)
|
|
.replaceAll('https://s.pximg.net/', PXIMG_BASEURL_S)
|
|
}
|
|
|
|
export function replacePximgUrlsInObject(
|
|
obj: Record<string, any> | string
|
|
): Record<string, any> | string {
|
|
if (typeof obj === 'string') return replacePximgUrlsInString(obj)
|
|
|
|
return deepReplaceString(obj, replacePximgUrlsInString)
|
|
}
|
|
|
|
function isObject(value: any): value is Record<string, any> {
|
|
return typeof value === 'object' && value !== null
|
|
}
|
|
|
|
export function deepReplaceString<T>(
|
|
obj: T,
|
|
replacer: (value: string) => string
|
|
): T {
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((value) =>
|
|
deepReplaceString(value, replacer)
|
|
) as unknown as T
|
|
} else if (isObject(obj)) {
|
|
if (
|
|
['arraybuffer', 'blob', 'formdata'].includes(
|
|
obj.constructor.name.toLowerCase()
|
|
)
|
|
) {
|
|
return obj
|
|
}
|
|
const result: Record<string, any> = {}
|
|
for (const [key, value] of Object.entries(obj)) {
|
|
result[key] = deepReplaceString(value, replacer)
|
|
}
|
|
return result as T
|
|
} else if (typeof obj === 'string') {
|
|
return replacer(obj) as unknown as T
|
|
}
|
|
return obj
|
|
}
|
|
|
|
export function safelyStringify(value: any, space?: number) {
|
|
const visited = new WeakSet()
|
|
|
|
const replacer = (key: string, val: any) => {
|
|
// 处理 BigInt
|
|
if (typeof val === 'bigint') {
|
|
return val.toString()
|
|
}
|
|
|
|
// 处理 Set
|
|
if (val instanceof Set) {
|
|
return Array.from(val)
|
|
}
|
|
|
|
// 处理 Map
|
|
if (val instanceof Map) {
|
|
return Array.from(val.entries())
|
|
}
|
|
|
|
// 处理 function
|
|
if (typeof val === 'function') {
|
|
return val.toString()
|
|
}
|
|
|
|
// 处理自循环引用
|
|
if (typeof val === 'object' && val !== null) {
|
|
if (visited.has(val)) {
|
|
return '<circular>'
|
|
}
|
|
visited.add(val)
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
return JSON.stringify(value, replacer, space)
|
|
}
|
|
|
|
JSON.safelyStringify = safelyStringify
|
|
declare global {
|
|
interface JSON {
|
|
safelyStringify: typeof safelyStringify
|
|
}
|
|
}
|