import { PDFDocument, PDFPage, rgb, } from "https://cdn.skypack.dev/pdf-lib@^1.11.1?dts"; import fontkit from "https://cdn.skypack.dev/@pdf-lib/fontkit@^1.0.0?dts"; type Result = { id: number; word: string; answer: string; }; async function generateVoca(levels: number[], day: number) { const re = /

(?\d+?)\. (?.+?)<\/p>.*?(?.+?)<\/span>/gis; const res = await fetch( `https://www.hackers.co.kr/?c=s_toeic/new_voca_toeic_testpaper/toeic_study/new_paper&mode=new_view&level=${ levels.join(",") }&level_type=&lang_text=2&question=1000&day3=${day}&day4=${day}&day_auto=N&index=1`, ); const body = await res.text(); const matches = [...body.matchAll(re)].map((m) => ({ ...(m.groups as unknown as Result), id: Number.parseInt(m.groups?.id as string), })); const pdfDoc = await PDFDocument.create(); const fontBytes = await Deno.readFile("./NanumGothic.ttf"); pdfDoc.registerFontkit(fontkit); const customFont = await pdfDoc.embedFont(fontBytes); const fontSize = 12; const margin = 20; const perColumn = 25; const perPage = 50; let page!: PDFPage; let col = -1; let yCursor = 1; // 문제 생성 for (let i = 0; i < matches.length; ++i) { const data = matches[i]; if (i % perPage === 0) { page = pdfDoc.addPage(); yCursor = 1; col = -1; } if (i % perColumn === 0) { col++; yCursor = 1; } page.drawText(`${data.id}. ${data.word}`, { x: margin + (page.getWidth() / 2) * col, y: page.getHeight() - (fontSize + margin) * yCursor, size: fontSize, font: customFont, color: rgb(0, 0, 0), }); page.drawText("_________________", { x: margin + (page.getWidth() / 2) * col + 140, y: page.getHeight() - (fontSize + margin) * yCursor - 3, size: fontSize, font: customFont, color: rgb(0, 0, 0), }); yCursor++; } // 답 생성 for (let i = 0; i < matches.length; ++i) { const data = matches[i]; if (i % perPage === 0) { page = pdfDoc.addPage(); yCursor = 1; col = -1; } if (i % perColumn === 0) { col++; yCursor = 1; } page.drawText(`${data.id}. ${data.word}`, { x: margin + (page.getWidth() / 2) * col, y: page.getHeight() - (fontSize + margin) * yCursor, size: fontSize, font: customFont, color: rgb(0, 0, 0), }); page.drawText("_________________", { x: margin + (page.getWidth() / 2) * col + 140, y: page.getHeight() - (fontSize + margin) * yCursor - 3, size: fontSize, font: customFont, color: rgb(0, 0, 0), }); page.drawText(data.answer, { x: margin + (page.getWidth() / 2) * col + 140, y: page.getHeight() - (fontSize + margin) * yCursor, size: 8, font: customFont, color: rgb(1, 0, 0), }); yCursor++; } const pdfBytes = await pdfDoc.save(); return pdfBytes; } const port = 8055; const server = Deno.listen({ port }); console.log( `HTTP webserver running. Access it at: http://localhost:${port}/`, ); for await (const conn of server) { serveHttp(conn); } async function serveHttp(conn: Deno.Conn) { const httpConn = Deno.serveHttp(conn); for await (const requestEvent of httpConn) { const { searchParams, pathname } = new URL(requestEvent.request.url); if (pathname.startsWith("/generate")) { if (!searchParams.has("level") || !searchParams.has("day")) { requestEvent.respondWith( new Response("Not Found", { status: 404, }), ); continue; } const levels = searchParams.getAll("level").map((l) => Number.parseInt(l) ); const day = Number.parseInt(searchParams.get("day")!) ?? 1; const data = await generateVoca(levels, day); requestEvent.respondWith( new Response(data, { status: 200, headers: { "Content-Disposition": 'attachment; filename="voca.pdf"', }, }), ); } else if (pathname.startsWith("/") || pathname.startsWith("/index.html")) { const file = await Deno.readFile("./index.html"); requestEvent.respondWith( new Response(file, { headers: { "content-type": "text/html", }, }), ); } } }