Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: deploy to production environment #94

Merged
merged 6 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 1 addition & 105 deletions api/http.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
import type { VercelRequest, VercelResponse } from '@vercel/node'
import axios from 'axios'
import colors from 'picocolors'
import escapeRegExp from 'lodash.escaperegexp'

const PROD = process.env.NODE_ENV === 'production'

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(';')
}
}
import { ajax } from './utils.js'

export default async function (req: VercelRequest, res: VercelResponse) {
if (!isAccepted(req)) {
Expand All @@ -36,96 +22,6 @@ export default async function (req: VercelRequest, res: VercelResponse) {
}
}

export const ajax = axios.create({
baseURL: 'https://www.pixiv.net/',
params: {},
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69',
},
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'] =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69'
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 (!PROD) {
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 = replaceUrlInObject(ctx.data?.body ?? ctx.data))
if (!PROD) {
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 replaceUrlInString(str: string): string {
const { VITE_PXIMG_BASEURL_I: i, VITE_PXIMG_BASEURL_S: s } = process.env
if (i) {
str = str.replace(/https:\/\/i\.pximg\.net\//g, i.replace(/\/$/, '') + '/')
}
if (s) {
str = str.replace(/https:\/\/s\.pximg\.net\//g, s.replace(/\/$/, '') + '/')
}
return str
}

export function replaceUrlInObject(
obj: Record<string, any> | string
): Record<string, any> | string {
if (typeof obj === 'string') return replaceUrlInString(obj)

for (const key in obj) {
if (
typeof obj[key] === 'string' &&
/^https:\/\/[is]\.pximg\.net\//.test(obj[key])
) {
obj[key] = replaceUrlInString(obj[key])
} else if (typeof obj[key] === 'object') {
obj[key] = replaceUrlInObject(obj[key])
}
}
return obj
}

function isAccepted(req: VercelRequest) {
const { UA_BLACKLIST = '[]' } = process.env
try {
Expand Down
4 changes: 2 additions & 2 deletions api/image.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VercelRequest, VercelResponse } from '@vercel/node'
import axios from 'axios'
import { USER_AGENT } from './utils.js'

export default async (req: VercelRequest, res: VercelResponse) => {
const { __PREFIX, __PATH } = req.query
Expand All @@ -14,8 +15,7 @@ export default async (req: VercelRequest, res: VercelResponse) => {
responseType: 'arraybuffer',
headers: {
referer: 'https://www.pixiv.net/',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0',
'user-agent': USER_AGENT,
},
})
.then(
Expand Down
12 changes: 6 additions & 6 deletions api/random.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VercelRequest, VercelResponse } from '@vercel/node'
import { formatInTimeZone } from 'date-fns-tz'
import { ajax } from './http.js'
import { PXIMG_BASEURL_I, ajax } from './utils.js'
import { Artwork } from '../src/types/Artworks.js'

type ArtworkOrAd = Artwork | { isAdContainer: boolean }
Expand Down Expand Up @@ -30,11 +30,11 @@ export default async (req: VercelRequest, res: VercelResponse) => {
'yyyy/MM/dd/HH/mm/ss'
)}/${value.id}`
value.urls = {
mini: `/-/c/48x48/img-master/${middle}_p0_square1200.jpg`,
thumb: `/-/c/250x250_80_a2/img-master/${middle}_p0_square1200.jpg`,
small: `/-/c/540x540_70/img-master/${middle}_p0_master1200.jpg`,
regular: `/-/img-master/${middle}_p0_master1200.jpg`,
original: `/-/img-original/${middle}_p0.jpg`,
mini: `${PXIMG_BASEURL_I}c/48x48/img-master/${middle}_p0_square1200.jpg`,
thumb: `${PXIMG_BASEURL_I}c/250x250_80_a2/img-master/${middle}_p0_square1200.jpg`,
small: `${PXIMG_BASEURL_I}c/540x540_70/img-master/${middle}_p0_master1200.jpg`,
regular: `${PXIMG_BASEURL_I}img-master/${middle}_p0_master1200.jpg`,
original: `${PXIMG_BASEURL_I}img-original/${middle}_p0.jpg`,
}
})
if (requestImage) {
Expand Down
4 changes: 2 additions & 2 deletions api/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VercelRequest, VercelResponse } from '@vercel/node'
import { load } from 'cheerio'
import { ajax, replaceUrlInObject } from './http.js'
import { ajax, replacePximgUrlsInObject } from './utils.js'

export default async (req: VercelRequest, res: VercelResponse) => {
const token = req.cookies.PHPSESSID || req.query.token
Expand Down Expand Up @@ -46,7 +46,7 @@ export default async (req: VercelRequest, res: VercelResponse) => {
'set-cookie',
`CSRFTOKEN=${meta.token}; path=/; secure; sameSite=Lax`
)
res.send(replaceUrlInObject(meta))
res.send(replacePximgUrlsInObject(meta))
})
.catch((err) => {
return res
Expand Down
163 changes: 163 additions & 0 deletions api/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
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
Dismissed Show dismissed Hide dismissed
return str
.replace(/https:\/\/i\.pximg\.net\//g, PXIMG_BASEURL_I)
.replace(/https:\/\/s\.pximg\.net\//g, PXIMG_BASEURL_S)
}

export function replacePximgUrlsInObject(
obj: Record<string, any> | string
): Record<string, any> | string {
if (typeof obj === 'string') return replacePximgUrlsInString(obj)
if (['arraybuffer', 'blob'].includes(obj.constructor.name.toLowerCase())) {
return obj
}

return JSON.parse(safelyStringify(obj), (key: string, val: any) => {
if (typeof val === 'string' && val.includes('pximg.net')) {
Dismissed Show dismissed Hide dismissed
return replacePximgUrlsInString(val)
}
return val
})
}

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
}
}
2 changes: 2 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ declare module 'vue' {
NButton: typeof import('naive-ui')['NButton']
NFlex: typeof import('naive-ui')['NFlex']
NProgress: typeof import('./src/components/NProgress.vue')['default']
NSpace: typeof import('naive-ui')['NSpace']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTabsPane: typeof import('naive-ui')['NTabsPane']
NTag: typeof import('naive-ui')['NTag']
Placeholder: typeof import('./src/components/Placeholder.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Expand Down
Loading
Loading