#!/usr/bin/env node require("dotenv").config(); const db = require("./db"); const fs = require("fs").promises; // WARNING: UGLY AND INEFFICIENT! // Coding rule: Everything is acceptable if the total execution time doesn't exceed 5s const sanitize = (text) => text .replace(/(\r\n|\n|\r)/gm, "") .replace(/\\/g, "\\\\") .replace(/%/g, "\\%") .replace(/&/g, "\\&") .replace(/_/g, "\\_") .replace(/#/g, "\\#") .replace(/\^/g, "\\^") .replace( /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g, "{ \\emojifont $&}", ) .replace(/~/g, "$\\mathtt{\\sim}$"); // Quotes in QR codes destroy everything const sanitizeQR = (text) => text.replace(/"/g, "").replace(/ /g, "\\ "); let hay; const answer = (needle) => { const e = hay.find((e) => e.question === needle); if (e && e.answer && e.answer.length > 1) return sanitize(e.answer); else return "nichts"; }; const classes = ["teacher", "TGM13.1", "TGM13.2", "TGTM13", "TGI13.1", "TGI13.2"]; // Be aware, I'm a longtime rhyme primer db.dump().then(async (data) => { const y10progs = JSON.parse(await fs.readFile(__dirname + "/progs.json", "utf8")); await data.users.forEach(async (user) => { hay = data.profile.filter((e) => e.user_id === user.id); let comments = user.comments.map((c) => ({ comment: sanitize(c.comment) })); const chars = user.chars; const obj = { id: user.id, name: `${user.name} ${user.middlename || ""} ${user.surname}`, birthday: answer("Geburtsdatum"), favsub: answer("Lieblingsfach"), hatesub: answer("Hassfach"), hobbies: answer("Hobbies"), music: answer("Lieblingsbands/-musiker/-genre"), missing: answer("Am meisten werde ich vermissen"), motivation: answer("Ohne das hätte ich die Oberstufe nicht geschafft"), quote: answer("Lebensmotto/Seniorquote"), future: answer("Zukunftspläne"), }; obj.birthday = new Date(obj.birthday).toLocaleDateString("de"); if (obj.birthday == "Invalid Date") obj.birthday = "unbekannt"; // QR-Code obj.qrcode = sanitizeQR(answer("QR-Code Text (z.B. Social Media Links, random Text, whatever)")); if (obj.qrcode === "nichts") obj.qrcode = ""; // 5head let textex = "\\student"; for (elem of Object.keys(obj)) { textex += `\\def\\std${elem}{${obj[elem]}}`; } textex += `\\studentbackground{${obj.id}}{${obj.qrcode}}\n\n`; // Characteristics olympics kinetics acoustics textex += "\\begin{center}\\begin{minipage}{0.75\\paperwidth}\\begin{center}\n"; if (chars && chars.length > 0) { chars.forEach((char, ind) => { textex += `\\studentchar{${sanitize(char.txt)}`; if (chars[ind + 1]) textex += " $\\circ$"; textex += "}"; }); } textex += "\\end{center}\\end{minipage}\\end{center}\n"; textex += "\\divider"; if (y10progs[user.id] && y10progs[user.id].length > 0) comments = [ ...comments, ...y10progs[user.id].map((comment) => ({ comment: `\\textbf{${sanitize(comment)}}` })), ]; comments = comments.sort((a, b) => a.comment.length - b.comment.length); // Comments contents intents indents events if (comments && comments.length > 0) { textex += "\n\n\\begin{small}\\renewcommand{\\arraystretch}{1.5}\\hspace*{\\commentsx}\\begin{tabularx}{\\commentswidth}{*{2}{>{\\RaggedRight\\arraybackslash}X}}"; for (let i = 0; i < comments.length; i += 2) { const first = comments[i].comment; if (comments[i] && !comments[i + 1]) { textex += `\\multicolumn{2}{p{\\commentswidth}}{\\RaggedRight{${first}}}`; break; } const second = comments[i + 1] ? comments[i + 1].comment : " "; textex += `${first} & ${second} \\\\\n`; if (i + 2 < comments.length) textex += " \\specialrule{.03em}{0em}{0em}\n"; } textex += "\\end{tabularx}\\renewcommand{\\arraystretch}{1}\\end{small}"; } await fs.writeFile( __dirname + "/zeitung/parts/generated/students/" + user.class + "/" + user.username + ".tex", textex, ); }); // Stats chats hats cats rats let textex = ""; const questions = [...new Set(data.questions.map((a) => a[0].id))]; const statrad = 2.5; const statxinc = 8, statyinc = 6.4; let statx = 0, staty = 0; await questions.forEach((q) => { const options = data.questions[q - 1].sort((a, b) => b.count - a.count); textex += `\\node at (${statx}, ${staty + statrad / 2 + 1.8}) {${options[0].question}};`; textex += `\\pie[hide number, sum=auto, text=inside, pos={${statx},${staty}}, radius=${statrad}]{`; options.forEach((option, ind) => { textex += `${option.count}/${sanitize(option.option)}`; if (options[ind + 1]) textex += ", "; }); textex += "}\n"; if (statx == statxinc) { staty += statyinc; statx = 0; } else { statx = statxinc; } }); await fs.writeFile(__dirname + "/zeitung/parts/generated/stats/perc.tex", textex); // Ranking pranking banking yanking // Confusion ftw - don't ask :P const rankingStart = "\\ranking\n\\begin{longtable}{R R R}\n\n"; const rankingEnd = "\\end{longtable}\n"; const rankingtex = ["", "", "", "", "", ""]; data.ranking.forEach((q) => { const answers = [[], [], [], [], [], []]; q.answers.forEach((a) => { answers[classes.indexOf(a.class)].push({ name: `${a.name} ${a.surname}`, count: a.count, }); }); answers.forEach((elem, ind) => { if ((q.type != "teacher" || ind == 0) && (q.type == "teacher" || ind != 0)) { const relevant = elem.slice(0, 3); const total_votes = relevant.reduce((a, b) => a + b.count, 0); let catted = ""; relevant.forEach((e, eind) => { let fitted = Math.ceil((e.count / total_votes) * 3); // 3 is max bottle count fitted = fitted == 0 ? 1 : fitted > 3 ? 3 : fitted; // Adjust float errors catted += `${e.name} @ \\rankingbottles{${fitted}}`; if (eind < 2) catted += "BLABLAB\n"; else catted += "\n\\vspace*{0.5cm}"; }); rankingtex[ind] += `\\rankingquestion{${q.question}}\n\\begin{tabular}{l l}\n${catted}\\end{tabular}`; // This is 10head const cntamp = rankingtex[ind].split("&").length - 1; const cntslash = rankingtex[ind].split("\\\\\n").length; if ((cntamp + cntslash) % 3 == 0) rankingtex[ind] += "\\\\\n"; else rankingtex[ind] += "&\n"; } }); }); await rankingtex.forEach(async (tex, ind) => { await fs.writeFile( __dirname + `/zeitung/parts/generated/ranking/${classes[ind]}.tex`, rankingStart + tex.replace(/@/g, "&").replace(/BLABLAB/g, "\\\\") + rankingEnd, ); }); // Quotes boats coats floats goats oats const genAuthor = (quote) => (quote ? `${quote.name} ${quote.middlename || ""} ${quote.surname}` : ""); textex = "\\def\\quoteclass{TGM13.1}\n\\quotepage"; let i = 0; for (const quote of data.quotes) { if (i > 1 && quote.class !== data.quotes[i - 1].class) { await fs.writeFile(__dirname + `/zeitung/parts/generated/quotes/${data.quotes[i - 1].class}.tex`, textex); textex = `\\def\\quoteclass{${quote.class}}\n\\quotepage`; } const author = genAuthor(quote); if (i === 0 || author !== genAuthor(data.quotes[i - 1])) { textex += `\\quoteauthor{${author}}\\begin{itemize}\n`; } textex += `\\item \\quoteadd{${sanitize(quote.quote)}}\n`; if (author !== genAuthor(data.quotes[i + 1])) { textex += `\\end{itemize}\n`; } i++; } // Lol await fs.writeFile(__dirname + "/zeitung/parts/generated/quotes/teacher.tex", textex); // SECRET!! textex = "\\renewcommand{\\arraystretch}{1.5}\n\\begin{longtable}{R R R}\n\n"; await data.secrets.forEach(async (secret, ind) => { textex += `{\\small ${secret.secret}}`; textex += ((ind + 1) % 3 == 0 ? "\\\\" : "&") + "\n"; if ((ind + 1) % 3 == 0) textex += "\\specialrule{.03em}{0em}{0em}\n"; }); textex += "\\end{longtable}\n\\renewcommand{\\arraystretch}{1}\n"; await fs.writeFile(__dirname + "/zeitung/parts/generated/secrets.tex", textex); // Final spinal vinyl textex = "\\begin{tabular}{l l l}\n\n"; await data.users.forEach(async (user, ind) => { textex += `{\\large ${user.name} ${user.middlename || ""} ${user.surname}}`; textex += ((ind + 1) % 3 == 0 ? "\\\\" : "&") + "\n"; }); textex += "\\end{tabular}\n"; await fs.writeFile(__dirname + "/zeitung/parts/generated/final.tex", textex); textex = ""; const profs = (await fs.readFile(__dirname + "/stecks.csv", "utf8")).split("\n").slice(1, -1); let flip = 1; for (const prof of profs) { const [t, a, p, s] = prof.split(";").map(sanitize); textex += `\\def\\profname{${t}}\\def\\profabi{${a}}\\def\\profprof{${p}}\\def\\profsecret{${s}}\\def\\profnum{${flip}}\\def\\proflast{${+( flip % 3 === 0 )}}\\teacherprofile\n`; if (flip % 3 === 0) { textex += "\\clearpage\n"; } flip++; } await fs.writeFile(__dirname + "/zeitung/parts/generated/teacherprofiles.tex", textex, "utf8"); setTimeout(() => { console.log("Finished!"); process.exit(0); }, 50); });