Skip to content

Commit d1fad04

Browse files
Genesis
0 parents  commit d1fad04

17 files changed

Lines changed: 4626 additions & 0 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

Dockerfile

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Use the official Node.js 20.17.0 image as a parent image
2+
FROM node:20.17.0
3+
4+
# Set the working directory in the container
5+
WORKDIR /usr/src/app
6+
7+
8+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
9+
10+
RUN apt-get update && apt-get install curl gnupg ffmpeg -y \
11+
&& curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
12+
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
13+
&& apt-get update \
14+
&& apt-get install google-chrome-stable -y --no-install-recommends \
15+
&& rm -rf /var/lib/apt/lists/*
16+
# Copy package.json and package-lock.json (if available)
17+
COPY package*.json ./
18+
19+
# Install dependencies
20+
RUN npm install
21+
22+
# Copy the rest of your app's source code
23+
COPY . .
24+
25+
# Expose the port your app runs on
26+
EXPOSE 3000
27+
28+
# Set environment variables
29+
ENV HOST=0.0.0.0
30+
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
31+
32+
# Create a non-root user
33+
RUN useradd -m appuser
34+
35+
# Change ownership of the working directory to the non-root user
36+
RUN chown -R appuser:appuser /usr/src/app
37+
38+
# Switch to the non-root user
39+
USER appuser
40+
41+
42+
# Start the application
43+
CMD ["node", "app/server.js"]

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Fast HTML2PDF API
2+
3+
Welcome to the **Fast HTML2PDF API**! 🚀
4+
5+
This is a lightweight version of the software used at [html2pdfapi.com](https://html2pdfapi.com).
6+
7+
It provides a basic yet performant wrapper along with additional features to enhance the standard Puppeteer experience.
8+
9+
For usage in commercial services, please refer to the `license.txt` file located in this repository.
10+
11+
We are a small team, and any support to further develop this product is greatly appreciated! 🙏
12+
13+
## Getting Started with Development
14+
15+
To get started, run the following commands:
16+
17+
```
18+
npm i
19+
npm run dev
20+
```
21+
22+
## Build and Run in Docker
23+
24+
### Prerequisites
25+
26+
- Docker installed on your system
27+
28+
### Build the Docker image
29+
30+
To build the Docker image, run the following command in the project root directory:
31+
32+
```
33+
docker build --platform linux/amd64 . -t render
34+
```
35+
36+
### Run the Docker container
37+
38+
To run the Docker container, use the following command:
39+
40+
```
41+
docker run --platform linux/amd64 -p 3000:3000 render
42+
```

app/controllers/render.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { PuppeteerWrapper } from "../core/wrapper.js";
2+
/**
3+
* Render controller for handling rendering requests
4+
* @param {import('fastify').FastifyRequest} request
5+
* @param {import('fastify').FastifyReply} reply
6+
*/
7+
export async function renderController(request, reply) {
8+
try {
9+
let config;
10+
if (request.method === "GET") {
11+
config = JSON.parse(request.query.config);
12+
} else {
13+
config = request.body;
14+
}
15+
16+
const wrapper = new PuppeteerWrapper(config);
17+
18+
try {
19+
await wrapper.initialize();
20+
const { content, contentType, filename } = await wrapper.captureOutput();
21+
22+
reply.header("Content-Type", contentType);
23+
reply.header("Content-Disposition", `attachment; filename="${filename}"`);
24+
return reply.send(content);
25+
} catch (error) {
26+
console.error("Rendering error:", error);
27+
return reply
28+
.code(500)
29+
.send({ error: "An error occurred during rendering" });
30+
} finally {
31+
await wrapper.close();
32+
}
33+
} catch (error) {
34+
let errorMessage = JSON.parse(error.message);
35+
return reply.code(400).send({ error: errorMessage });
36+
}
37+
}

app/core/constants.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
const ACCEPTED_WORDS = {
2+
en: [
3+
"accept",
4+
"agree",
5+
"ok",
6+
"got it",
7+
"i understand",
8+
"continue",
9+
"allow",
10+
"consent",
11+
"yes",
12+
"confirm",
13+
],
14+
de: [
15+
"akzeptieren",
16+
"einverstanden",
17+
"verstanden",
18+
"fortfahren",
19+
"ok",
20+
"ja",
21+
"zustimmen",
22+
"erlauben",
23+
"bestätigen",
24+
],
25+
fr: [
26+
"accepter",
27+
"j'accepte",
28+
"d'accord",
29+
"je comprends",
30+
"continuer",
31+
"ok",
32+
"oui",
33+
"permettre",
34+
"autoriser",
35+
"confirmer",
36+
],
37+
es: [
38+
"aceptar",
39+
"de acuerdo",
40+
"entiendo",
41+
"continuar",
42+
"ok",
43+
"sí",
44+
"permitir",
45+
"autorizar",
46+
"confirmar",
47+
],
48+
it: [
49+
"accetta",
50+
"accetto",
51+
"acconsento",
52+
"ho capito",
53+
"continua",
54+
"ok",
55+
"okay",
56+
"va bene",
57+
"consento",
58+
"consenti",
59+
"sì",
60+
"permetto",
61+
"confermo",
62+
"Consenti tutti i cookie",
63+
],
64+
nl: [
65+
"accepteren",
66+
"akkoord",
67+
"ik begrijp het",
68+
"doorgaan",
69+
"ok",
70+
"ja",
71+
"toestaan",
72+
"bevestigen",
73+
],
74+
pl: [
75+
"akceptuję",
76+
"zgadzam się",
77+
"rozumiem",
78+
"kontynuuj",
79+
"ok",
80+
"tak",
81+
"zezwalam",
82+
"potwierdzam",
83+
],
84+
sv: ["godkänn", "jag förstår", "fortsätt", "ok", "ja", "tillåt", "bekräfta"],
85+
da: ["accepter", "jeg forstår", "fortsæt", "ok", "ja", "tillad", "bekræft"],
86+
fi: ["hyväksy", "ymmärrän", "jatka", "ok", "kyllä", "salli", "vahvista"],
87+
pt: [
88+
"aceitar",
89+
"concordo",
90+
"entendo",
91+
"continuar",
92+
"ok",
93+
"sim",
94+
"permitir",
95+
"confirmar",
96+
],
97+
ru: [
98+
"принять",
99+
"согласен",
100+
"понимаю",
101+
"продолжить",
102+
"ок",
103+
"да",
104+
"разрешить",
105+
"подтвердить",
106+
],
107+
zh: ["接受", "同意", "我明白", "继续", "好的", "是的", "允许", "确认"],
108+
ja: [
109+
"受け入れる",
110+
"同意する",
111+
"理解しました",
112+
"続ける",
113+
"OK",
114+
"はい",
115+
"許可する",
116+
"確認する",
117+
],
118+
ko: [
119+
"수락",
120+
"동의",
121+
"이해했습니다",
122+
"계속",
123+
"확인",
124+
"네",
125+
"허용",
126+
"확인하다",
127+
],
128+
};
129+
130+
export { ACCEPTED_WORDS };

app/core/cookies.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ACCEPTED_WORDS } from "./constants.js";
2+
3+
export async function blockCookies(page) {
4+
await page.evaluate(async (ACCEPTED_WORDS) => {
5+
// try to press esc to close modals
6+
// Simulate pressing the Escape key to close modals
7+
document.dispatchEvent(
8+
new KeyboardEvent("keydown", {
9+
key: "Escape",
10+
code: "Escape",
11+
which: 27,
12+
keyCode: 27,
13+
bubbles: true,
14+
cancelable: true,
15+
})
16+
);
17+
18+
// Wait a short time for any potential animations to complete
19+
await new Promise((resolve) => setTimeout(resolve, 100));
20+
21+
const selectors = [
22+
// Priority selectors for buttons in modals
23+
'.modal button, .modal input[type="button"], .modal input[type="submit"]',
24+
'.modal [role="button"]',
25+
// Close buttons
26+
'button[class*="close"], button[aria-label="Close"], button.close',
27+
// Overlay buttons
28+
".modal-backdrop button, .modal-overlay button, .overlay button",
29+
"button",
30+
"div",
31+
"span",
32+
];
33+
34+
const flatAcceptWords = Object.values(ACCEPTED_WORDS).flat();
35+
36+
selectors.forEach((selector) => {
37+
const elements = document.querySelectorAll(selector);
38+
for (let i = elements.length - 1; i >= 0; i--) {
39+
const el = elements[i];
40+
if (el.tagName.toLowerCase() === "a") continue; // Skip links
41+
const text = el.innerText.toLowerCase();
42+
if (flatAcceptWords.some((word) => text.includes(word.toLowerCase()))) {
43+
console.log("Found accept button:", el);
44+
el.click();
45+
break;
46+
}
47+
}
48+
});
49+
}, ACCEPTED_WORDS);
50+
51+
// Wait for any animations to complete
52+
await new Promise((resolve) => setTimeout(resolve, 500));
53+
}

0 commit comments

Comments
 (0)