index.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. const express = require("express");
  2. const fs = require("node:fs");
  3. const path = require("node:path");
  4. const supabase = require("@supabase/supabase-js");
  5. const cookie_parser = require("cookie-parser");
  6. const multer = require("multer");
  7. const app = express();
  8. require("dotenv").config();
  9. const webhookURL = process.env.WEBHOOK_URL;
  10. const utils = require("./utils.js");
  11. const url = "https://skibidihub.buttplugstudios.xyz"
  12. let ipBlacklist = JSON.parse(fs.readFileSync("./ipbans.json"));
  13. fs.watch("./ipbans.json", () => {
  14. console.log("Reloading IP Bans...");
  15. ipBlacklist = JSON.parse(fs.readFileSync("./ipbans.json"));
  16. });
  17. const port = 3000;
  18. const storage = multer.diskStorage({
  19. destination: function (req, file, cb) {
  20. try {
  21. // Validations
  22. if (!utils.checkBodyVideo(req.body)) throw new Error("invalid video body");
  23. if (!utils.checkToken(req, "/api/upload multer")) throw new Error("unauthorized");
  24. if(!req.skibidihub_id) req.skibidihub_id = utils.nanoid(7);
  25. console.log("Video ID:", req.skibidihub_id);
  26. // Check if the directory exists, and create it if it doesn't
  27. const dir = path.join(__dirname, "videos", req.skibidihub_id);
  28. if (!utils.videoExists(req.skibidihub_id)) {
  29. console.log("Directory does not exist, creating:", dir);
  30. fs.mkdirSync(dir, { recursive: true });
  31. }
  32. // Set the destination path for multer
  33. cb(null, dir);
  34. } catch (err) {
  35. console.log("Error in destination function:", err);
  36. cb(err);
  37. }
  38. },
  39. filename: function (req, file, cb) {
  40. let filename;
  41. if (file.fieldname === "video") {
  42. filename = "video.mp4";
  43. } else if (file.fieldname === "thumbnail") {
  44. filename = "thumbnail.jpg";
  45. }
  46. console.log("Saving file with filename:", filename);
  47. cb(null, filename);
  48. }
  49. });
  50. const upload = multer({
  51. storage: storage,
  52. limits: { fileSize: 1000000 * 250 /* 250MB in bytes */ }
  53. });
  54. const client = supabase.createClient(
  55. process.env.SUPABASE_URL,
  56. process.env.SUPABASE_KEY
  57. );
  58. const ipwareObject = require("@fullerstack/nax-ipware");
  59. const sunset = JSON.parse(fs.readFileSync(path.join(__dirname, "sunset.json")));
  60. const ipware = new ipwareObject.Ipware();
  61. app.use(function(req, res, next) {
  62. req.ipInfo = ipware.getClientIP(req);
  63. if (new Date().getTime() >= sunset.timestamp && sunset.sunset == true) {
  64. if(req.path == "/assets/piss baby.mp4") {
  65. if (ipBlacklist.includes(req.ipInfo.ip)) {
  66. res.setHeader("Cache-Control", "no-cache");
  67. return res.sendFile(path.join(__dirname, path.join("www", "down.html")));
  68. }
  69. } else {
  70. return res.sendFile(path.join(__dirname, path.join("www", "sunset.html")));
  71. }
  72. }
  73. if(ipBlacklist.includes(req.ipInfo.ip)) {
  74. res.setHeader("Cache-Control", "no-cache");
  75. return res.sendFile(path.join(__dirname, path.join("www", "down.html")));
  76. }
  77. next();
  78. });
  79. app.use(express.static("www")); // Static folder
  80. app.use(express.json()); // JSON body parser
  81. app.use(cookie_parser()); // Cookie parser
  82. // User facing URL's
  83. app.get("/", (req, res) => {
  84. res.sendFile(path.join(__dirname, path.join("www", "index.html")));
  85. });
  86. app.get("/login", (req, res) => {
  87. res.sendFile(path.join(__dirname, path.join("www", "login.html")));
  88. });
  89. app.get("/upload", (req, res) => {
  90. res.sendFile(path.join(__dirname, path.join("www", "upload.html")));
  91. })
  92. app.get("/contact", (req, res) => {
  93. res.sendFile(path.join(__dirname, path.join("www", "contact.html")));
  94. })
  95. app.get("/editProfile", (req, res) => {
  96. res.sendFile(path.join(__dirname, path.join("www", "editProfile.html")));
  97. });
  98. app.get("/search", (req, res) => {
  99. res.sendFile(path.join(__dirname, path.join("www", "search.html")));
  100. });
  101. app.set('view engine', 'ejs');
  102. app.get("/video/:id", async (req, res) => {
  103. if(utils.discordCheck(req) && utils.videoExists(req.params.id)) {
  104. const videoInfo = await utils.videoInfo(req.params.id);
  105. if(isNaN(videoInfo)) {
  106. return res.render("video", {
  107. video: `${url}/api/video/${req.params.id}.mp4`,
  108. video_name: videoInfo.title,
  109. description: videoInfo.description,
  110. url: url,
  111. author_url: `${url}/api/oembed/?author_name=${videoInfo.uploader}&author_url=${url}/user/${encodeURIComponent(videoInfo.uploader)}`
  112. })
  113. }
  114. }
  115. if(!utils.videoExists(req.params.id) && utils.checkToken(req, "/video/:id")) {
  116. return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
  117. }
  118. res.sendFile(path.join(__dirname, path.join("www", "video.html")));
  119. });
  120. app.get("/user/:user", (req, res) => {
  121. if(!utils.checkToken(req, "/user/:user")) {
  122. return res.sendFile(path.join(__dirname, path.join("www", "user.html")));
  123. }
  124. // Check if user exists
  125. client.from("users").select().eq("name", decodeURIComponent(req.params.user)).then(data => {
  126. if(data.error) {
  127. return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
  128. } else if (data.status != 200) {
  129. return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
  130. }
  131. if(!data.data[0]) return res.sendFile(path.join(__dirname, path.join("www", "404.html")));
  132. res.sendFile(path.join(__dirname, path.join("www", "user.html")));
  133. })
  134. });
  135. // API //
  136. function handleVideoAPI(req, res) {
  137. if(!utils.checkToken(req, "/api/video/:id") && !utils.discordCheck(req)) {
  138. return res.sendFile(
  139. path.join(
  140. __dirname,
  141. path.join("www", path.join("assets", path.join("troll", "video.mp4")))
  142. )
  143. );
  144. }
  145. const videoPath = path.join(__dirname, path.join("videos", path.join(req.params.id, "video.mp4")));
  146. if(!fs.existsSync(videoPath)) return res.sendStatus(404);
  147. const stat = fs.statSync(videoPath);
  148. const fileSize = stat.size;
  149. const range = req.headers.range;
  150. console.log('Requested Range:', range); // Log the range for debugging
  151. if (range) {
  152. const parts = range.replace(/bytes=/, "").split("-");
  153. let start = parseInt(parts[0], 10);
  154. let end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
  155. // Handle the case where range is 0-1
  156. if (start === 0 && end === 1) {
  157. end = 1;
  158. }
  159. const chunksize = (end - start) + 1;
  160. const file = fs.createReadStream(videoPath, {start, end});
  161. const head = {
  162. 'Content-Range': `bytes ${start}-${end}/${fileSize}`,
  163. 'Accept-Ranges': 'bytes',
  164. 'Content-Length': chunksize,
  165. 'Content-Type': 'video/mp4',
  166. };
  167. res.writeHead(206, head);
  168. file.pipe(res);
  169. } else {
  170. const head = {
  171. 'Content-Length': fileSize,
  172. 'Content-Type': 'video/mp4',
  173. 'Accept-Ranges': 'bytes',
  174. };
  175. res.writeHead(200, head);
  176. fs.createReadStream(videoPath).pipe(res);
  177. }
  178. }
  179. // Get an mp4 file according to its video ID.
  180. app.get('/api/video/:id*.mp4', handleVideoAPI);
  181. app.get("/api/video/:id", handleVideoAPI);
  182. // OEmbed
  183. const oembed = (provider_name, provider_url, author_name, author_url, url) => {
  184. const baseObject = {
  185. version: '1.0',
  186. };
  187. if (provider_name && provider_url) {
  188. baseObject.provider_name = provider_name;
  189. baseObject.provider_url = provider_url;
  190. }
  191. if (author_name) {
  192. baseObject.author_name = author_name;
  193. }
  194. if (author_url) {
  195. baseObject.author_url = author_url;
  196. }
  197. if (url) {
  198. baseObject.url = url;
  199. }
  200. return baseObject;
  201. };
  202. app.get('/api/oembed', (req, res) => {
  203. const {
  204. provider_name,
  205. provider_url,
  206. author_name,
  207. author_url,
  208. url,
  209. } = req.query;
  210. return res.status(200).send(oembed(provider_name, provider_url, author_name, author_url, url));
  211. });
  212. // Get a videos thumbnail according to its video ID.
  213. app.get("/api/thumbnail/:id", (req, res) => {
  214. if (!utils.checkToken(req, "/api/thumbnail/:id")) {
  215. let images = fs.readdirSync(
  216. path.join(__dirname, path.join("www", path.join("assets", "troll")))
  217. );
  218. res.sendFile(
  219. path.join(
  220. __dirname,
  221. path.join(
  222. "www",
  223. path.join(
  224. "assets",
  225. path.join("troll", images[utils.getRandomInt(images.length - 1)])
  226. )
  227. )
  228. )
  229. );
  230. return;
  231. }
  232. const thumbnail = utils.getThumbnail(req.params.id);
  233. if(thumbnail) return res.sendFile(thumbnail);
  234. if(!thumbnail) return res.sendStatus(404);
  235. });
  236. // Get a videos thumbnail according to its video ID.
  237. app.get("/api/webhookThumbnail/:id", (req, res) => {
  238. const thumbnail = utils.getThumbnail(req.params.id);
  239. if(thumbnail) return res.sendFile(thumbnail);
  240. if(!thumbnail) return res.sendStatus(404);
  241. });
  242. // Get the info for a video according to its video ID.
  243. app.get("/api/videoInfo/:id", async (req, res) => {
  244. if(!utils.checkToken(req, "/api/videoInfo/:id")) {
  245. let newData = {};
  246. newData.title = utils.fakeTitleList[utils.getRandomInt(utils.fakeTitleList.length)]
  247. newData.description = "SIGN IN to see this EPIC content"
  248. newData.likes = "69"
  249. newData.dislikes = "0"
  250. newData.uploader = "SIGN IN to see this EPIC content"
  251. newData.uploaded_at = new Date().toISOString().toString();
  252. return res.send(newData);
  253. }
  254. if(!utils.videoExists(req.params.id)) return res.sendStatus(404);
  255. const videoInfo = await utils.videoInfo(req.params.id);
  256. if(isNaN(videoInfo)) {
  257. return res.send(videoInfo);
  258. } else {
  259. return res.sendStatus(videoInfo);
  260. }
  261. });
  262. // Pulls the user information from the database and returns it.
  263. app.get("/api/userInfo/:id", (req, res) => {
  264. if(!utils.checkToken(req, "/api/userInfo/:id")) {
  265. let data = {
  266. name: "SIGN IN to see this EPIC CONTENT",
  267. subscribers: 999,
  268. social_score: 999,
  269. description: "SIGN IN to see this EPIC content",
  270. verified: true
  271. }
  272. return res.send(data);
  273. }
  274. client.from("users").select().eq("name", decodeURIComponent(req.params.id)).then(data => {
  275. if(data.error) {
  276. return res.sendStatus(400);
  277. } else if (data.status != 200) {
  278. return res.sendStatus(data.status);
  279. }
  280. if(!data.data[0]) return res.sendStatus(400);
  281. return res.send(data.data[0]);
  282. })
  283. })
  284. // Subscribes to a user
  285. app.get("/api/subscribe/:id", async (req, res) => {
  286. if(!utils.checkToken(req)) return res.sendStatus(401);
  287. if(!utils.userExists(decodeURIComponent(req.params.id))) return res.sendStatus(400);
  288. const subscribersData = await client
  289. .from("users")
  290. .select("subscribers")
  291. .eq("name", decodeURIComponent(req.params.id));
  292. const subscribers = subscribersData["data"][0]["subscribers"] + 1;
  293. client.from("users").update({
  294. subscribers: subscribers
  295. }).eq("name", decodeURIComponent(req.params.id)).then(data => {
  296. if(data.error) {
  297. return res.sendStatus(400);
  298. } else if (data.status != 204) {
  299. return res.sendStatus(data.status);
  300. }
  301. return res.sendStatus(200);
  302. })
  303. });
  304. // Login endpoints (Just handles user creation)
  305. app.post("/api/login", async (req, res) => {
  306. if(!req.body.user) return res.sendStatus(400);
  307. const ipInfo = ipware.getClientIP(req);
  308. // If user doesn't exist, create the user.
  309. if(!await utils.userExists(req.body.user)) {
  310. console.log(`User ${req.body.user} doesn't exist. Creating the user...`);
  311. await client.from("users").insert({
  312. name: req.body.user
  313. }).then(data => {
  314. if(data.error) {
  315. return res.status(500).send(data);
  316. } else if(data.status != 201) {
  317. return res.status(500).send(data);
  318. }
  319. console.log(`User created successfully. IP: ${ipInfo.ip}`);
  320. return res.sendStatus(200);
  321. });
  322. } else {
  323. console.log(`User ${req.body.user} already exists. Logging in... IP: ${ipInfo.ip}`);
  324. return res.sendStatus(200);
  325. }
  326. });
  327. app.post("/api/editUser", async (req, res) => {
  328. // Validate request
  329. const user = utils.checkUserToken(req, "/api/editUser");
  330. if(!user.value) return res.status(401).json({message: "you need to login to edit your user page"});
  331. if(!req.body.description && !req.body.website) return res.status(400).json({message: "invalid form data"});
  332. if(req.body.website && !utils.isValidUrl(req.body.website)) return res.status(400).json({message: "invalid website"});
  333. // Update user
  334. await client.from("users").update({
  335. description: req.body.description,
  336. website: req.body.website,
  337. }).eq("name", user.user).then(data => {
  338. if(data.error) {
  339. return res.status(500).send(data);
  340. } else if(data.status != 204) {
  341. return res.status(500).send(data);
  342. }
  343. return res.sendStatus(200);
  344. })
  345. });
  346. // Get the comments for a video according to its video ID.
  347. app.get("/api/comments/:videoID", (req, res) => {
  348. if(!utils.checkToken(req, "/api/comments/:videoID")) {
  349. let comments = [];
  350. for(let i = 0; i < 8; i++) {
  351. let comment = {};
  352. comment.text = utils.fakeCommentList[utils.getRandomInt(utils.fakeCommentList.length)]
  353. comment.commenter = "SIGN IN to see this EPIC content!";
  354. let date = new Date();
  355. date.setTime(1005286084);
  356. comment.created_at = date.toISOString();
  357. comments.push(comment);
  358. }
  359. return res.send(comments);
  360. }
  361. if(!utils.videoExists(req.params.videoID)) return res.sendStatus(404);
  362. client
  363. .from("comments")
  364. .select()
  365. .eq("video_id", req.params.videoID)
  366. .then((data) => {
  367. if (data.error) {
  368. res.sendStatus(400);
  369. } else if (data.status != 200) {
  370. res.sendStatus(data.status);
  371. }
  372. res.send(data["data"]);
  373. });
  374. });
  375. // Get a list of all videos
  376. app.get("/api/getAllVideos", (req, res) => {
  377. if(!utils.checkToken(req, "/api/getAllVideos/")) {
  378. let newData = [];
  379. for(let i = 0; i < 12; i++) {
  380. let temp = {};
  381. temp.description = "SIGN IN to see this EPIC content"
  382. temp.title = utils.fakeTitleList[utils.getRandomInt(utils.fakeTitleList.length)]
  383. temp.uploader = "SIGN IN to see this EPIC content";
  384. temp.id = utils.nanoid(7);
  385. newData.push(temp);
  386. }
  387. return res.send(newData);
  388. }
  389. client
  390. .from("videos")
  391. .select()
  392. .then((data) => {
  393. if (data.error) {
  394. return res.sendStatus(400);
  395. } else if (data.status != 200) {
  396. return res.sendStatus(data.status);
  397. }
  398. return res.send(data["data"]);
  399. });
  400. });
  401. // Search endpoint
  402. app.post("/api/search", (req, res) => {
  403. if(!utils.checkToken(req)) return res.sendStatus(401);
  404. if(!req.body.query) return res.sendStatus(400);
  405. client
  406. .from("videos")
  407. .select()
  408. .then((response) => {
  409. if (response.error) {
  410. return res.sendStatus(400);
  411. } else if (response.status != 200) {
  412. return res.sendStatus(response.status);
  413. }
  414. let results = [];
  415. response.data.forEach(video => {
  416. if(video.title.startsWith(req.body.query.toLowerCase()) || video.uploader.startsWith(req.body.query.toLowerCase())) results.push(video);
  417. })
  418. return res.send(results);
  419. });
  420. })
  421. // Send a comment
  422. app.post("/api/comment", async (req, res) => {
  423. if (!utils.checkToken(req, "/api/comment")) return res.sendStatus(401);
  424. // Sanity chekcs
  425. if(!req.body.commenter) return res.sendStatus(400);
  426. if(!req.body.videoID) return res.sendStatus(400);
  427. if(!req.body.text) return res.sendStatus(400);
  428. if(req.body.text.trim() == "") return res.sendStatus(400);
  429. if(!utils.videoExists(req.body.videoID)) return res.sendStatus(404);
  430. client
  431. .from("comments")
  432. .insert({
  433. commenter: req.body.commenter,
  434. video_id: req.body.videoID,
  435. text: req.body.text,
  436. })
  437. .then((data) => {
  438. res.send(data);
  439. // Discord webhook
  440. utils.sendWebhook(
  441. "new comment guys",
  442. "New COMMENT!!!!!",
  443. [
  444. {
  445. "id": 220464536,
  446. "description": req.body.text.trim(),
  447. "fields": [],
  448. "title": `New comment on video ${req.body.videoID}!`,
  449. "author": {
  450. "name": req.body.commenter,
  451. "url": `http://skibidihub.buttplugstudios.xyz/user/${encodeURIComponent(req.body.commenter)}`
  452. },
  453. "url": `http://skibidihub.buttplugstudios.xyz/video/${req.body.videoID}`,
  454. "color": 917248
  455. }
  456. ],
  457. webhookURL
  458. )
  459. });
  460. });
  461. // Like a video
  462. app.post("/api/like/:id", async (req, res) => {
  463. if(!utils.checkToken(req, "/api/like/:id")) return res.sendStatus(401);
  464. if(!utils.videoExists(req.params.id)) return res.sendStatus(404);
  465. const likesData = await client
  466. .from("videos")
  467. .select("likes")
  468. .eq("id", req.params.id);
  469. const likes = likesData["data"][0]["likes"] + 1;
  470. const video = await client
  471. .from("videos")
  472. .update({ likes: likes })
  473. .eq("id", req.params.id);
  474. res.send(video);
  475. });
  476. // Dislike a video
  477. app.post("/api/dislike/:id", async (req, res) => {
  478. if(!utils.checkToken(req, "/api/dislike/:id")) return res.sendStatus(401);
  479. if(!utils.videoExists(req.params.id)) return res.sendStatus(404);
  480. const dislikesData = await client
  481. .from("videos")
  482. .select("dislikes")
  483. .eq("id", req.params.id);
  484. const dislikes = dislikesData["data"][0]["dislikes"] + 1;
  485. const video = await client
  486. .from("videos")
  487. .update({ dislikes: dislikes })
  488. .eq("id", req.params.id);
  489. res.send(video);
  490. });
  491. app.get("/api/userVideos/:id", async (req, res) => {
  492. const data = await client
  493. .from("videos")
  494. .select()
  495. .eq("uploader", decodeURIComponent(req.params.id));
  496. res.send(data);
  497. });
  498. app.get("/api/sunset", async (req, res) => {
  499. const file = fs.readFileSync(path.join(__dirname, "sunset.json"))
  500. return res.send(JSON.parse(file))
  501. })
  502. app.post("/api/upload", upload.fields([
  503. { name: 'video' }, { name: 'thumbnail' }
  504. ]), utils.multerErrorHandler, async (req, res) => {
  505. if(!utils.checkBodyVideo(req.body)) return res.status(400).json({ message: "invalid video body" });
  506. if(!utils.checkToken(req, "/api/upload")) return res.status(401).json({ message: "SIGN IN to UPLOAD videos!!!" });
  507. await client.from("videos").insert({
  508. id: req.skibidihub_id,
  509. likes: 0,
  510. dislikes: 0,
  511. description: req.body.description,
  512. title: req.body.title,
  513. uploader: req.body.uploader
  514. }).then(data => {
  515. res.status(201).json({
  516. "id": req.skibidihub_id
  517. })
  518. // Discord webhook
  519. utils.sendWebhook(
  520. `new video guys <@&1274653503448678440> \`\`${req.skibidihub_id}\`\``,
  521. "New UPLOAD!!!!",
  522. [
  523. {
  524. "id": 220464536,
  525. "description": req.body.description,
  526. "fields": [],
  527. "title": req.body.title,
  528. "author": {
  529. "name": req.body.uploader,
  530. "url": `http://skibidihub.buttplugstudios.xyz/user/${encodeURIComponent(req.body.uploader)}`
  531. },
  532. "url": `http://skibidihub.buttplugstudios.xyz/video/${req.skibidihub_id}`,
  533. "color": 9830655,
  534. "image": {
  535. "url": `https://skibidihub.buttplugstudios.xyz/api/webhookThumbnail/${req.skibidihub_id}`
  536. }
  537. }
  538. ],
  539. webhookURL
  540. )
  541. })
  542. })
  543. // Start App
  544. app.listen(port, () => {
  545. console.log(`skibidihub listening on port ${port}`);
  546. });