diff --git a/controller/latest.js b/controller/latest.js
index 5ff748b..3924c77 100644
--- a/controller/latest.js
+++ b/controller/latest.js
@@ -1,24 +1,35 @@
const { PrismaClient } = require('@prisma/client')
+const redis = require('../utils/redis.js')
const prisma = new PrismaClient()
exports.getLatest = async (req, res) => {
- res.json(await prisma.videos.findMany({
- take: 30,
- orderBy: [
- {
- archived: 'desc'
+ let json
+ const cached = await redis.get('latest')
+
+ if (cached) {
+ json = JSON.parse(cached)
+ } else {
+ json = await prisma.videos.findMany({
+ take: 90,
+ orderBy: [
+ {
+ archived: 'desc'
+ }
+ ],
+ select: {
+ id: true,
+ title: true,
+ thumbnail: true,
+ published: true,
+ archived: true,
+ channel: true,
+ channelId: true,
+ channelAvatar: true,
+ channelVerified: true
}
- ],
- select: {
- id: true,
- title: true,
- thumbnail: true,
- published: true,
- archived: true,
- channel: true,
- channelId: true,
- channelAvatar: true,
- channelVerified: true
- }
- }))
+ })
+ await redis.set('latest', JSON.stringify(json), 'EX', 3600)
+ }
+
+ res.json(json)
}
\ No newline at end of file
diff --git a/controller/search.js b/controller/search.js
index e6a7c9d..6b5e18a 100644
--- a/controller/search.js
+++ b/controller/search.js
@@ -1,8 +1,22 @@
+const crypto = require('node:crypto')
const validate = require('../utils/validate.js')
+const redis = require('../utils/redis.js')
+const { RedisRateLimiter } = require('rolling-rate-limiter')
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
+const limiter = new RedisRateLimiter({
+ client: redis,
+ namespace: 'search:',
+ interval: 5 * 60 * 1000,
+ maxInInterval: 5
+})
+
exports.searchVideo = async (req, res) => {
+ const ipHash = crypto.createHash('sha256').update(req.headers['x-userip'] || '0.0.0.0').digest('hex')
+ const isLimited = await limiter.limit(ipHash)
+ if (isLimited) return res.status(429).send('error-You have been ratelimited.')
+
const id = await validate.validateVideoInput(req.query.search)
if (id.fail) {
const videos = await prisma.videos.findMany({
@@ -24,7 +38,7 @@ exports.searchPlaylist = async (req, res) => {
if (id.fail) {
res.status(500).send(id.message)
} else {
- res.send(``)
+ res.redirect(`${process.env.FRONTEND}/playlist?list=${id}`)
}
}
@@ -33,6 +47,6 @@ exports.searchChannel = async (req, res) => {
if (id.fail) {
res.status(500).send(id.message)
} else {
- res.send(``)
+ res.redirect(`${process.env.FRONTEND}/channel/${id}`)
}
}
\ No newline at end of file
diff --git a/controller/transparency.js b/controller/transparency.js
index e014334..554f038 100644
--- a/controller/transparency.js
+++ b/controller/transparency.js
@@ -1,14 +1,25 @@
const { PrismaClient } = require('@prisma/client')
+const redis = require('../utils/redis.js')
const prisma = new PrismaClient()
exports.getReports = async (req, res) => {
- res.json((await prisma.reports.findMany()).map(r => {
- return {
- ...r,
- details: (r.details).split('<').join('<').split('>').join('>'),
- date: (r.date).toISOString().slice(0,10)
- }
- }))
+ let json
+ const cached = await redis.get('transparency')
+
+ if (cached) {
+ json = JSON.parse(cached)
+ } else {
+ json = (await prisma.reports.findMany()).map(r => {
+ return {
+ ...r,
+ details: (r.details).split('<').join('<').split('>').join('>'),
+ date: (r.date).toISOString().slice(0,10)
+ }
+ })
+ await redis.set('transparency', JSON.stringify(json), 'EX', 3600)
+ }
+
+ res.json(json)
}
exports.getReport = async (req, res) => {
diff --git a/controller/video.js b/controller/video.js
index 808abdc..e735ffe 100644
--- a/controller/video.js
+++ b/controller/video.js
@@ -3,28 +3,37 @@ const prisma = new PrismaClient()
const DOMPurify = require('isomorphic-dompurify')
const metadata = require('../utils/metadata.js')
+const redis = require('../utils/redis.js')
exports.getVideo = async (req, res) => {
- const info = await prisma.videos.findFirst({
- where: {
- id: req.params.id
- },
- select: {
- title: true,
- description: true,
- thumbnail: true,
- source: true,
- published: true,
- archived: true,
- channel: true,
- channelId: true,
- channelAvatar: true,
- channelVerified: true,
- disabled: true
- }
- })
-
- if (!info) return res.json({ error: '404' })
+ let info
+ const cached = await redis.get(`video:${req.params.id}`)
+
+ if (cached) {
+ info = JSON.parse(cached)
+ } else {
+ info = await prisma.videos.findFirst({
+ where: {
+ id: req.params.id
+ },
+ select: {
+ title: true,
+ description: true,
+ thumbnail: true,
+ source: true,
+ published: true,
+ archived: true,
+ channel: true,
+ channelId: true,
+ channelAvatar: true,
+ channelVerified: true,
+ disabled: true
+ }
+ })
+
+ if (!info) return res.json({ error: '404' })
+ await redis.set(`video:${req.params.id}`, JSON.stringify(info), 'EX', 3600)
+ }
res.json({
...info,
@@ -33,11 +42,17 @@ exports.getVideo = async (req, res) => {
}
exports.getChannel = async (req, res) => {
- const videos = await metadata.getChannelVideos(req.params.id)
- const channel = await metadata.getChannel(req.params.id)
+ const cached = await redis.get(`channel:${req.params.id}`)
+ if (cached) return res.json(JSON.parse(cached))
- if (!videos || !channel) return res.json({ error: '500' })
- if (videos.error) return res.json({ error: '404' })
+ const [videos, channel] = await Promise.all([
+ metadata.getChannelVideos(req.params.id),
+ metadata.getChannel(req.params.id)
+ ])
+
+ if (!videos || !channel || videos.error) {
+ return res.json({ error: '404' });
+ }
const archived = await prisma.videos.findMany({
where: {
@@ -52,43 +67,66 @@ exports.getChannel = async (req, res) => {
}
})
- var allVideos = []
- allVideos = allVideos.concat((videos).map(video => {
- return {
- id: video.url.replace('/watch?v=', ''),
- published: (new Date(video.uploaded)).toISOString().slice(0,10),
- ...video
- }
- }))
-
- await Promise.all(archived.map(async (v) => {
- const allVideo = allVideos.find(o => o.id == v.id)
- if (allVideo) {
- const index = allVideos.findIndex(o => o.id == v.id)
- allVideos[index] = v
+ const processedVideos = videos.map(video => ({
+ id: video.url.replace('/watch?v=', ''),
+ published: new Date(video.uploaded).toISOString().slice(0, 10),
+ ...video
+ }));
+
+ archived.forEach(v => {
+ const existingVideoIndex = processedVideos.findIndex(video => video.id === v.id);
+ if (existingVideoIndex !== -1) {
+ processedVideos[existingVideoIndex] = v;
} else {
- allVideos.push({
- ...v,
- deleted: undefined
- })
+ processedVideos.push({ ...v, deleted: undefined });
}
- }))
-
- allVideos.sort((a, b) => new Date(b.published) - new Date(a.published))
-
- res.json({
- name: channel.author,
+ });
+
+ processedVideos.sort((a, b) => new Date(b.published) - new Date(a.published));
+
+ const json = {
+ name: channel.author,
avatar: channel.authorThumbnails[1].url,
verified: channel.authorVerified,
- videos: allVideos
+ videos: processedVideos
+ }
+ await redis.set(`channel:${req.params.id}`, JSON.stringify(json), 'EX', 3600)
+ res.json(json)
+}
+
+exports.getOnlyChannelVideos = async (req, res) => {
+ const cached = await redis.get(`channelVideos:${req.params.id}`)
+ if (cached) return res.json(JSON.parse(cached))
+
+ const archived = await prisma.videos.findMany({
+ where: {
+ channelId: req.params.id
+ },
+ select: {
+ id: true,
+ title: true,
+ thumbnail: true,
+ published: true,
+ archived: true
+ },
+ orderBy: {
+ published: 'desc'
+ }
})
+
+ const json = {
+ videos: archived
+ }
+ await redis.set(`channelVideos:${req.params.id}`, JSON.stringify(json), 'EX', 3600)
+ res.json(json)
}
exports.getPlaylist = async (req, res) => {
+ const cached = await redis.get(`playlist:${req.params.id}`)
+ if (cached) return res.json(JSON.parse(cached))
+
const playlist = await metadata.getPlaylistVideos(req.params.id)
-
- if (!playlist) return res.json({ error: '500' })
- if (playlist.error) return res.json({ error: '404' })
+ if (!playlist || playlist.error) return res.json({ error: '404' })
const playlistArchived = await prisma.videos.findMany({
where: {
@@ -103,59 +141,54 @@ exports.getPlaylist = async (req, res) => {
}
})
- var allVideos = []
- allVideos = allVideos.concat((playlist.relatedStreams).map(video => {
- return {
- id: video.url.replace('/watch?v=', ''),
- published: (new Date(video.uploaded)).toISOString().slice(0,10),
- ...video
- }
- }))
+ const allVideos = playlist.relatedStreams.map(video => ({
+ id: video.url.replace('/watch?v=', ''),
+ published: new Date(video.uploaded).toISOString().slice(0, 10),
+ ...video
+ }));
await Promise.all(playlistArchived.map(async (v) => {
- const allVideo = allVideos.find(o => o.id == v.id)
+ const allVideo = allVideos.find(o => o.id == v.id);
if (allVideo) {
- const index = allVideos.findIndex(o => o.id == v.id)
- allVideos[index] = v
+ const index = allVideos.findIndex(o => o.id == v.id);
+ allVideos[index] = v;
} else {
- const live = await metadata.getVideoMetadata(v.id)
-
+ const live = await metadata.getVideoMetadata(v.id);
allVideos.push({
- ...v,
+ ...v,
deleted: live.error ? true : false
- })
+ });
}
- }))
-
- await Promise.all(allVideos.map(async (v) => {
- if (!v.archived) {
- const video = await prisma.videos.findFirst({
- where: {
- id: v.id
- },
- select: {
- id: true,
- title: true,
- thumbnail: true,
- published: true,
- archived: true
- }
- })
-
- if (video) {
- const index = allVideos.findIndex(o => o.id == v.id)
- allVideos[index] = video
+ }));
+
+ await Promise.all(allVideos.filter(v => !v.archived).map(async (v) => {
+ const video = await prisma.videos.findFirst({
+ where: {
+ id: v.id
+ },
+ select: {
+ id: true,
+ title: true,
+ thumbnail: true,
+ published: true,
+ archived: true
}
+ });
+ if (video) {
+ const index = allVideos.findIndex(o => o.id == v.id);
+ allVideos[index] = video;
}
- }))
-
- allVideos.sort((a, b) => new Date(a.published) - new Date(b.published))
-
- res.json({
+ }));
+
+ allVideos.sort((a, b) => new Date(b.published) - new Date(a.published));
+
+ const json = {
name: playlist.name,
- channel: playlist.uploader,
+ channel: playlist.uploader,
url: playlist.uploaderUrl,
avatar: playlist.uploaderAvatar,
- videos: allVideos
- })
+ videos: allVideos
+ }
+ await redis.set(`playlist:${req.params.id}`, JSON.stringify(json), 'EX', 3600)
+ res.json(json)
}
\ No newline at end of file
diff --git a/index.js b/index.js
index 809c958..e29fe1f 100644
--- a/index.js
+++ b/index.js
@@ -19,6 +19,7 @@ app.use(cors())
app.get('/latest', latestController.getLatest)
app.get('/video/:id', videoController.getVideo)
app.get('/channel/:id', videoController.getChannel)
+app.get('/channel/:id/videos', videoController.getOnlyChannelVideos)
app.get('/playlist/:id', videoController.getPlaylist)
app.get('/search/video', searchController.searchVideo)
diff --git a/utils/ytdlp.js b/utils/ytdlp.js
index 17430bb..4852b5d 100644
--- a/utils/ytdlp.js
+++ b/utils/ytdlp.js
@@ -21,13 +21,13 @@ async function downloadVideo(url, ws) {
})
child.on("close", async (code, signal) => {
- if (code == 2) {
+ if (code == 0) { // https://itsfoss.com/linux-exit-codes/
reject({
- fail: true
+ fail: false
})
} else {
resolve({
- fail: false
+ fail: true
})
}
})