diff --git a/.dockerignore b/.dockerignore index 1dcef2d9..70059f59 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,3 @@ node_modules -.env \ No newline at end of file +.env +/tmp diff --git a/.gitignore b/.gitignore index 300ff5c3..ba570396 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ !/db/.gitkeep yarn-error.log **/.DS_Store -/config/dev \ No newline at end of file +/config/dev +/tmp diff --git a/Dockerfile b/Dockerfile index e8662255..3a26b151 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,11 @@ FROM node:18-alpine # Create app directory WORKDIR /usr/app +RUN mkdir ./tmp +# Install the dependencies needed for pdf2pic and calipers +RUN apk add --update ghostscript +RUN apk add --update graphicsmagick + # Install app dependencies COPY package.json yarn.lock ./ RUN yarn --frozen-lockfile diff --git a/config/production/vars.json b/config/production/vars.json index b3313eb3..12d97025 100644 --- a/config/production/vars.json +++ b/config/production/vars.json @@ -3,5 +3,6 @@ "COFFEE_ROLE_ID": "936899047816589354", "NOTIF_CHANNEL_ID": "872305316522508329", "ANNOUNCEMENTS_CHANNEL_ID": "669294029804011540", - "OFFICE_STATUS_CHANNEL_ID": "997926133083422740" + "OFFICE_STATUS_CHANNEL_ID": "997926133083422740", + "RESUME_CHANNEL_ID": "824068952933924924" } diff --git a/config/staging/vars.json b/config/staging/vars.json index 05a29370..96e482de 100644 --- a/config/staging/vars.json +++ b/config/staging/vars.json @@ -3,5 +3,6 @@ "COFFEE_ROLE_ID": "861744590775779358", "NOTIF_CHANNEL_ID": "866861080301797386", "ANNOUNCEMENTS_CHANNEL_ID": "892952559520710677", - "OFFICE_CHANNEL_ID": "866861080301797386" + "OFFICE_CHANNEL_ID": "866861080301797386", + "RESUME_CHANNEL_ID": "866861080301797386" } diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index fca8348e..4733a521 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -1,6 +1,9 @@ # Stage 1: Typescript build app FROM node:18-alpine AS ts-build WORKDIR /usr/app +RUN apk add --update ghostscript +RUN apk add --update graphicsmagick +RUN mkdir ./tmp COPY package.json yarn.lock ./ RUN yarn --frozen-lockfile COPY . . diff --git a/docker/staging/Dockerfile b/docker/staging/Dockerfile index c439953a..06a50f2c 100644 --- a/docker/staging/Dockerfile +++ b/docker/staging/Dockerfile @@ -1,6 +1,9 @@ # Stage 1: Typescript build app FROM node:18-alpine AS ts-build WORKDIR /usr/app +RUN apk add --update ghostscript +RUN apk add --update graphicsmagick +RUN mkdir ./tmp COPY package.json yarn.lock ./ RUN yarn --frozen-lockfile COPY . . diff --git a/package.json b/package.json index e81b4a49..1e8704d9 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "lodash": "^4.17.21", "moment": "^2.29.1", "node-fetch": "^2.6.2", + "pdf-lib": "^1.17.1", + "pdf2pic": "^2.1.4", "sqlite": "^4.0.22", "sqlite3": "^5.0.2", "stable-marriage": "^1.0.2", diff --git a/src/listeners/messageCreate.ts b/src/listeners/messageCreate.ts index 6c2983e6..1ed01c5f 100644 --- a/src/listeners/messageCreate.ts +++ b/src/listeners/messageCreate.ts @@ -4,8 +4,15 @@ import { Message } from 'discord.js'; import { applyBonusByUserId } from '../components/coin'; import { vars } from '../config'; import { sendKickEmbed } from '../utils/embeds'; +import { convertPdfToPic } from '../utils/pdfToPic'; + +import { readFileSync } from 'fs'; +import { writeFile } from 'fs/promises'; +import axios from 'axios'; +import { PDFDocument } from 'pdf-lib'; const ANNOUNCEMENTS_CHANNEL_ID: string = vars.ANNOUNCEMENTS_CHANNEL_ID; +const RESUME_CHANNEL_ID: string = vars.RESUME_CHANNEL_ID; /* * If honeypot is to exist again, then add HONEYPOT_CHANNEL_ID to the config @@ -60,6 +67,35 @@ const punishSpammersAndTrolls = async (message: Message): Promise => { return false; }; +/** + * Convert any pdfs sent in the #resumes channel to an image. + */ +const convertResumePdfsIntoImages = async (message: Message): Promise | undefined> => { + // If no resume pdf is provided, do nothing + const attachments = message.attachments; + if (attachments.size === 0) return; + + // Get resume pdf from message and write locally to tmp + const pdfLink = Array.from(attachments.values()).map((file) => file.attachment)[0]; + const pdfResponse = await axios.get(pdfLink, { responseType: 'stream' }); + const pdfContent = pdfResponse.data; + await writeFile('tmp/resume.pdf', pdfContent); + + // Get the size of the pdf + const pdfDocument = await PDFDocument.load(readFileSync('tmp/resume.pdf')); + const { width, height } = pdfDocument.getPage(0).getSize(); + if (pdfDocument.getPageCount() > 10) { + return await message.channel.send('Resumes must be less than 10 pages.'); + } + + // Convert the resume pdf into image + const imgResponse = await convertPdfToPic('tmp/resume.pdf', 'resume', width * 2, height * 2); + // Send the image back to the channel + return await message.channel.send({ + files: imgResponse.map((img) => img.path) + }); +}; + @ApplyOptions({ event: 'messageCreate' }) @@ -80,6 +116,11 @@ export class MessageCreateListener extends Listener { return; } + // If channel is in resumes, convert the message attachment to an image + if (message.channelId === RESUME_CHANNEL_ID) { + await convertResumePdfsIntoImages(message); + } + // Ignore DMs; include announcements, thread, and regular text channels if (message.channel.type !== 'DM') { await applyBonusByUserId(message.author.id); diff --git a/src/utils/pdfToPic.ts b/src/utils/pdfToPic.ts new file mode 100644 index 00000000..864762fc --- /dev/null +++ b/src/utils/pdfToPic.ts @@ -0,0 +1,22 @@ +import { fromPath } from 'pdf2pic'; +import { WriteImageResponse } from 'pdf2pic/dist/types/writeImageResponse'; + +export const convertPdfToPic = async ( + filePath: string, + saveFileName: string, + width: number, + height: number +): Promise => { + const options = { + density: 500, + saveFilename: saveFileName, + savePath: './tmp', + format: 'png', + width: width, + height: height + }; + + const convert = fromPath(filePath, options); + const res = await convert.bulk!(-1); + return res; +}; diff --git a/yarn.lock b/yarn.lock index 26444889..e3cf2d08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -143,6 +143,20 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@pdf-lib/standard-fonts@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz#8ba691c4421f71662ed07c9a0294b44528af2d7f" + integrity sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA== + dependencies: + pako "^1.0.6" + +"@pdf-lib/upng@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@pdf-lib/upng/-/upng-1.0.1.tgz#7dc9c636271aca007a9df4deaf2dd7e7960280cb" + integrity sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ== + dependencies: + pako "^1.0.10" + "@sapphire/async-queue@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.3.1.tgz#9d861e626dbffae02d808e13f823d4510e450a78" @@ -514,6 +528,16 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-parallel@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/array-parallel/-/array-parallel-0.1.3.tgz#8f785308926ed5aa478c47e64d1b334b6c0c947d" + integrity sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w== + +array-series@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/array-series/-/array-series-0.1.5.tgz#df5d37bfc5c2ef0755e2aa4f92feae7d4b5a972f" + integrity sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -529,6 +553,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + axios@^0.27.2: version "0.27.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" @@ -714,6 +743,14 @@ cron@^1.8.2: dependencies: luxon "^1.23.x" +cross-spawn@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + integrity sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA== + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -735,7 +772,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3: dependencies: ms "2.1.2" -debug@^3.2.7: +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -1101,6 +1138,16 @@ from@~0: resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -1195,7 +1242,17 @@ globby@^11.0.3: merge2 "^1.4.1" slash "^3.0.0" -graceful-fs@^4.2.6: +gm@^1.23.1: + version "1.23.1" + resolved "https://registry.yarnpkg.com/gm/-/gm-1.23.1.tgz#2edeeb958084d0f8ea7988e5d995b1c7dfc14777" + integrity sha512-wYGVAa8/sh9ggF5qWoOs6eArcAgwEPkDNvf637jHRHkMUznvs7m/Q2vrc0KLN6B8px3nnRJqJcXK4mTK6lLFmg== + dependencies: + array-parallel "~0.1.3" + array-series "~0.1.5" + cross-spawn "^4.0.0" + debug "^3.1.0" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -1381,6 +1438,15 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -1414,6 +1480,14 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1695,6 +1769,11 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" +pako@^1.0.10, pako@^1.0.11, pako@^1.0.6: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -1724,6 +1803,24 @@ pause-stream@0.0.11: dependencies: through "~2.3" +pdf-lib@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/pdf-lib/-/pdf-lib-1.17.1.tgz#9e7dd21261a0c1fb17992580885b39e7d08f451f" + integrity sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw== + dependencies: + "@pdf-lib/standard-fonts" "^1.0.0" + "@pdf-lib/upng" "^1.0.1" + pako "^1.0.11" + tslib "^1.11.1" + +pdf2pic@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/pdf2pic/-/pdf2pic-2.1.4.tgz#5c6cbd7809a60b11f6504eb85c386aa2012b1dcf" + integrity sha512-8QOCvxZlYs7wsmBQbqpIApSWD/eqgXMQU+4VM0vWrXifLgXS+Ns3484sUa1Q7ddr+gwnruq/xKHzu7x1scqX6w== + dependencies: + fs-extra "^9.1.0" + gm "^1.23.1" + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -1771,6 +1868,11 @@ ps-tree@^1.2.0: dependencies: event-stream "=3.3.4" +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -2129,7 +2231,7 @@ tsc-watch@^4.4.0: string-argv "^0.1.1" strip-ansi "^6.0.0" -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -2182,6 +2284,11 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -2212,6 +2319,13 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -2255,6 +2369,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"