refactor: use zyachel's code
This commit is contained in:
parent
fda79adc52
commit
a410bc4264
@ -1,164 +1,50 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import redis from '../../utils/redis'
|
import redis from '../../utils/redis'
|
||||||
import crypto from 'crypto'
|
import axiosInstance from '../../utils/axiosInstance'
|
||||||
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
const acceptableExtensions = ['.jpg', '.png', '.gif', '.webp', '.mp4']
|
const regex =
|
||||||
|
/^https:\/\/((m\.)?media-amazon\.com|imdb-video\.media-imdb\.com).*\.(jpg|jpeg|png|mp4|gif|webp).*$/
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const userIp =
|
const mediaUrl = req.query.url as string | undefined
|
||||||
(req.headers['cf-connecting-ip'] as string) ||
|
|
||||||
(req.headers['x-real-ip'] as string) ||
|
|
||||||
req.socket.remoteAddress ||
|
|
||||||
null
|
|
||||||
|
|
||||||
if (!userIp) {
|
if (!mediaUrl || !regex.test(mediaUrl))
|
||||||
res.status(500)
|
return res.status(400).json({
|
||||||
res.json({
|
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Unable to enforce ratelimit',
|
message: 'Invalid query',
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash ip with md5 (for speed)
|
const cachedMedia = await redis.getBuffer(mediaUrl)
|
||||||
const ipHash = crypto.createHash('md5').update(userIp).digest('hex')
|
|
||||||
|
|
||||||
const key = `ip_ratelimit:${ipHash}`
|
|
||||||
|
|
||||||
// check if ip is in redis
|
|
||||||
let ipInRedis = await redis.get(key)
|
|
||||||
|
|
||||||
if (!ipInRedis) {
|
|
||||||
// if not, set it to 1
|
|
||||||
await redis.setex(key, 30, '1')
|
|
||||||
ipInRedis = '1'
|
|
||||||
}
|
|
||||||
|
|
||||||
const ipReqNumber = Number(ipInRedis)
|
|
||||||
|
|
||||||
if (ipReqNumber > 60) {
|
|
||||||
res.status(429)
|
|
||||||
res.setHeader('x-cringe', 'stop abusing a FOSS service')
|
|
||||||
res.json({
|
|
||||||
success: false,
|
|
||||||
message: 'Too many requests',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment ip in redis
|
|
||||||
await redis.set(key, String(ipReqNumber + 1))
|
|
||||||
|
|
||||||
// get query param
|
|
||||||
const mediaUrl = (req.query as { url: string }).url
|
|
||||||
|
|
||||||
if (!mediaUrl) {
|
|
||||||
res.status(400)
|
|
||||||
res.json({
|
|
||||||
success: false,
|
|
||||||
message: 'Missing query',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let mediaUrlParsed: URL
|
|
||||||
|
|
||||||
try {
|
|
||||||
mediaUrlParsed = new URL(mediaUrl)
|
|
||||||
} catch {
|
|
||||||
res.status(400)
|
|
||||||
res.json({
|
|
||||||
success: false,
|
|
||||||
message: 'Invalid URL',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get media domain
|
|
||||||
const mediaDomain = mediaUrlParsed.hostname
|
|
||||||
|
|
||||||
if (
|
|
||||||
!mediaDomain.endsWith('media-amazon.com') &&
|
|
||||||
mediaDomain !== 'imdb-video.media-imdb.com'
|
|
||||||
) {
|
|
||||||
res.status(400)
|
|
||||||
res.json({
|
|
||||||
success: false,
|
|
||||||
message: 'Unauthorized domain',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaUrlParsed.protocol !== 'https:') {
|
|
||||||
res.status(400)
|
|
||||||
res.json({
|
|
||||||
success: false,
|
|
||||||
message: 'Unauthorized protocol',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let validExtension = false
|
|
||||||
|
|
||||||
for (const acceptableExtension of acceptableExtensions) {
|
|
||||||
if (mediaUrlParsed.pathname.endsWith(acceptableExtension)) {
|
|
||||||
validExtension = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validExtension) {
|
|
||||||
res.status(400)
|
|
||||||
res.json({
|
|
||||||
success: false,
|
|
||||||
message: 'Unauthorized extension',
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash mediaUrl with blake3
|
|
||||||
const mediaUrlHash = crypto
|
|
||||||
.createHash('sha256')
|
|
||||||
.update(mediaUrl)
|
|
||||||
.digest('base64')
|
|
||||||
|
|
||||||
// try to find mediaUrlHash in redis
|
|
||||||
const cacheKey = `media_proxy:${mediaUrlHash}`
|
|
||||||
|
|
||||||
const cachedMedia = await redis.get(cacheKey)
|
|
||||||
|
|
||||||
if (cachedMedia) {
|
if (cachedMedia) {
|
||||||
res.status(302)
|
|
||||||
res.setHeader('x-cached', 'true')
|
res.setHeader('x-cached', 'true')
|
||||||
res.send(cachedMedia)
|
res.status(302).send(cachedMedia)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader('x-cached', 'false')
|
let mediaRes: AxiosResponse
|
||||||
|
try {
|
||||||
// download media
|
mediaRes = await axiosInstance(mediaUrl, { responseType: 'arraybuffer' })
|
||||||
const mediaRes = await fetch(mediaUrl)
|
} catch {
|
||||||
|
res.status(404)
|
||||||
if (!mediaRes.ok) {
|
|
||||||
res.status(mediaRes.status)
|
|
||||||
res.json({
|
res.json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Error from Amazon',
|
message: 'Error from IMDb',
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaBuffer = Buffer.from(await mediaRes.arrayBuffer())
|
const data = mediaRes.data
|
||||||
|
|
||||||
// save in redis for 30 minutes
|
// save in redis for 30 minutes
|
||||||
await redis.setex(cacheKey, 60 * 30, mediaBuffer)
|
await redis.setex(mediaUrl, 30 * 60, Buffer.from(data))
|
||||||
|
|
||||||
// send media
|
// send media
|
||||||
res.send(mediaBuffer)
|
res.setHeader('x-cached', 'false')
|
||||||
|
res.send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user