import { Video, OrionVideo, Category, Schedule } from "./types"; import fs from "fs"; import axios from "axios"; import moment from "moment"; import dotenv from "dotenv"; dotenv.config(); const orionHeader = process.env.orionHeader; const orionUrl = `https://orion.joystream.org/graphql`; const videosFile = "./videos.json"; const scheduleFile = "./schedule.json"; if (!orionHeader) { console.log(`Please set orionHeader in .env`); process.exit(1); } const getDay = (daysFromNow: number = 0) => moment().add(daysFromNow, "days").format("YYYY-MM-DD"); export const getOrionVideo = ({ videoId }: Video): OrionVideo => { return { videoId, videoCutUrl: `https://eu-central-1.linodeobjects.com/atlas-featured-content/category-featured-videos/1/video-cut-${videoId}.mp4`, }; }; const loadSchedule = () => require(scheduleFile); const loadVideos = (): { videos: Video[]; categories: number[] } => { // load videos and extract category IDs const categories: number[] = []; const videos: Video[] = require(videosFile); videos.forEach( ({ categoryId }) => categories.includes(categoryId) || categories.push(categoryId) ); return { videos, categories }; }; const selectVideos = (videos: Video[], count: number): OrionVideo[] => { let selected: OrionVideo[] = []; for (let n = 0; n < count; ++n) { // remove selected videos const available: Video[] = videos.filter( ({ videoId }) => !selected.find((v) => v.videoId === videoId) ); // select random video const id = Math.floor(Math.random() * available.length); if (available.length && available[id]) selected.push(getOrionVideo(available[id])); else break; } return selected; }; const generateVideoList = (file: string) => { console.log(`Indexing video files ..`); const videos = fs .readFileSync(`./list`, `utf-8`) .split(`\n`) .reduce((list: Video[], path: string): Video[] => { const match = path.match(/\.\/([^\/]+) (\d+)\/(\d+)-(\d+)\.mp4/); if (match) { const category = match[1]; const count = Number(match[2]); const categoryId = Number(match[3]); const videoId = Number(match[4]); const video: Video = { category, count, categoryId, videoId }; return list.concat(video); } else return list; }, []); fs.writeFileSync(file, JSON.stringify(videos)); console.log(`Wrote ${videos.length} videos to ${file}`); }; const generateSchedule = (maxDays: number = 7): Schedule => { const loaded = loadVideos(); const days: number[] = []; for (let day = 0; day <= maxDays; ++day) { days.push(day); } const left: Video[][] = []; const schedule: Schedule = {}; days.forEach((day) => { schedule[getDay(day)] = { categories: loaded.categories.map((categoryId: number) => { const available: Video[] = left[categoryId] ? left[categoryId] : loaded.videos.filter((v) => v.categoryId === categoryId); const videos: OrionVideo[] = selectVideos(available, 3); // select n videos left[categoryId] = available.filter( ({ videoId }) => !videos.find((v) => v.videoId === videoId) ); return { categoryId, videos }; }), }; }); return schedule; }; const getCategoryFeaturedVideos = (): Promise => { const data = { query: "query GetCategoriesFeaturedVideos {\n allCategoriesFeaturedVideos {\n categoryId\n videos {\n videoId\n videoCutUrl\n }\n }\n}", }; return axios .post(orionUrl, data) .then(({ data }: any) => { fs.writeFileSync(`featured.json`, JSON.stringify(data)); console.log(`Wrote featured.json`); return data; }) .catch((error: any) => error.message + error); }; const setCategoryVideos = (categoryId: number): string => ` mutation SetFeaturedVideos ($videos: [FeaturedVideoInput!]!) { setCategoryFeaturedVideos(categoryId: "${categoryId}", videos: $videos) { videoId videoCutUrl } } `; const setCategoryFeaturedVideos = async ( categoryId: number, videos: OrionVideo[] ) => { const headers = { Authorization: orionHeader }; const query = setCategoryVideos(categoryId); axios .post(orionUrl, { query, variables: { videos } }, { headers }) .then(async ({ status, statusText }: any) => console.log(`Set videos for category ${categoryId}:`, status, statusText) ) .catch((error: any) => { console.error( `Failed to set featured videos for category ${categoryId}:`, error.response.data.errors.map( (e: any) => e.message + JSON.stringify(e.locations) ) ); process.exit(1); }); }; const main = async (args: string[]) => { switch (args[0]) { case "get": getCategoryFeaturedVideos(); break; case "set": try { const schedule: Schedule = require(scheduleFile); if (!schedule || !schedule[getDay()]) { console.error(`Current day not found in schedule. Run update again.`); process.exit(1); } const { categories } = schedule[getDay()]; categories .sort((a, b) => a.categoryId - b.categoryId) .map(({ categoryId, videos }) => setCategoryFeaturedVideos(categoryId, videos) ); } catch (e) { console.warn(`Did you run: yarn run schedule`); } break; case "schedule": fs.writeFileSync(scheduleFile, JSON.stringify(generateSchedule())); console.log(`Wrote schedule to ${scheduleFile}.`); break; case "update": generateVideoList(videosFile); break; default: console.log(`Available commands: [get, set, schedule, update]`); } }; main(process.argv.slice(2));