fp/services/scout/src/fastify.ts

160 lines
3.9 KiB
TypeScript

import Fastify, { FastifyPluginCallback, FastifyReply, FastifyRequest, FastifyReplyContext, FastifyPluginAsync, FastifyServerOptions } from 'fastify'
import fastifySwagger from '@fastify/swagger'
import fastifySwaggerUi from '@fastify/swagger-ui'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'url'
import { dirname, join } from 'node:path'
import { Config } from './config'
import { VtuberRecord, VtuberResponse, VtuberDataScrape } from './schemas.ts'
import scrapeVtuberData from './scrapeVtuberData.ts'
import { getPlaylistUrl } from './ytdlp.ts'
import { getRandomRoom } from './cb.ts'
import { getPackageVersion } from '@futureporn/utils/file.ts'
type VtuberDataRequest = FastifyRequest<{
Querystring: { url: string }
}>
const __dirname = dirname(fileURLToPath(import.meta.url));
const swaggerDarkCss = readFileSync(join(__dirname, './css/SwaggerDark.css'), { encoding: 'utf-8' })
const version = getPackageVersion(join(__dirname, '../package.json'))
async function fastifySetup(configs: Config) {
const fastify = Fastify({
logger: true
})
await fastify.register(fastifySwagger, {
openapi: {
info: {
title: '@futureporn/scout',
description: 'Vtuber data acquisition API',
version
},
}
})
await fastify.register(fastifySwaggerUi, {
theme: {
title: '@fp/scout',
css: [
{ filename: 'SwaggerDark.css', content: swaggerDarkCss }
]
},
routePrefix: '/',
uiConfig: {
docExpansion: 'list',
deepLinking: true
},
uiHooks: {
onRequest: function (request: FastifyRequest, reply: FastifyReply, next: any) { next() },
preHandler: function (request: FastifyRequest, reply: FastifyReply, next: any) { next() }
},
staticCSP: true,
transformStaticCSP: (header: any) => header,
transformSpecification: (swaggerObject: any, request: FastifyRequest, reply: FastifyReply) => { return swaggerObject },
transformSpecificationClone: true
})
fastify.addSchema(VtuberResponse)
fastify.addSchema(VtuberRecord)
fastify.addSchema(VtuberDataScrape)
fastify.get('/chaturbate/random-room', {
schema: {
response: {
'2xx': {
type: 'object'
}
},
tags: ['chaturbate']
}
}, async (req, reply) => {
const room = await getRandomRoom()
console.log(room)
reply.type('application/json').send(JSON.stringify(room))
})
fastify.get('/ytdlp/playlist-url', {
schema: {
querystring: {
type: 'object',
properties: {
url: {
type: 'string'
}
}
},
response: {
'2xx': {
error: { type: 'boolean' },
message: { type: 'string' },
data: { type: 'object', properties: {
url: { type: 'string' }
}}
}
},
tags: ['yt-dlp']
}
}, async (req: VtuberDataRequest, reply) => {
try {
const playlistUrl = await getPlaylistUrl(req.query.url)
console.log(`playlistUrl=${playlistUrl}`)
reply.type('application/json').send(JSON.stringify({ data: { url: playlistUrl } }))
} catch (e) {
reply.type('application/json').send(JSON.stringify({ data: null, error: e }))
}
})
fastify.get('/vtuber/data', {
schema: {
querystring: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL of a vtuber profile on Chaturbate or Fansly. ex: https://chaturbate.com/projektmelody'
}
}
},
response: {
'2xx': { $ref: 'VtuberDataScrape' }
},
tags: [
'vtuber'
]
}
}, async (req: VtuberDataRequest, reply) => {
console.log(`we received a request with url=${req.query.url}`)
const data = await scrapeVtuberData(req.query.url)
reply.type('application/json').send(data)
})
fastify.listen({ host: '0.0.0.0', port: configs.port }, function (err, address) {
console.log(`@futureporn/scout listening on ${address}`)
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
}
export default fastifySetup