|
|
import express from 'express'; |
|
|
import { chromium } from 'playwright'; |
|
|
import cors from 'cors'; |
|
|
import axios from 'axios'; |
|
|
import dotenv from 'dotenv'; |
|
|
import os from 'os'; |
|
|
|
|
|
dotenv.config(); |
|
|
|
|
|
const config = { |
|
|
maxTextLength: 100, |
|
|
viewport: { width: 1920, height: 1080 }, |
|
|
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' |
|
|
}; |
|
|
|
|
|
const app = express(); |
|
|
app.use(express.json()); |
|
|
app.use(cors()); |
|
|
|
|
|
app.get('/', (req, res) => { |
|
|
res.send('<h1>Welcome to the Web Scraping API</h1>'); |
|
|
}); |
|
|
|
|
|
app.get('/discord', async (req, res) => { |
|
|
const { logo, text, text1, text2, text3, text4, text5, text6, text7, text8, text9 } = req.query; |
|
|
|
|
|
if (!(logo)) { |
|
|
return res.status(400).json({ error: 'Missing logo parameters' }); |
|
|
} |
|
|
const html = `<!DOCTYPE html> |
|
|
<html lang="en" > |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>Discord Profile Card With Webhook</title> |
|
|
<style> |
|
|
@import url("https://fonts.googleapis.com/css?family=Roboto:400,400i,700"); |
|
|
|
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
font-family: Roboto, sans-serif; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body, |
|
|
html { |
|
|
height: 100%; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
background: #0b0b0b; |
|
|
} |
|
|
|
|
|
.tooltip { |
|
|
display: block; |
|
|
position: absolute; |
|
|
color: #b6b7b7; |
|
|
background: #18191c; |
|
|
padding: 0.4rem; |
|
|
border-radius: 3px; |
|
|
max-width: 150px; |
|
|
width: max-content; |
|
|
font-size: 0.8rem; |
|
|
transform: scale(0); |
|
|
transition: 0.055s ease-in-out transform; |
|
|
z-index: 10; |
|
|
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.35); |
|
|
-webkit-box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.35); |
|
|
} |
|
|
|
|
|
.tooltip-up { |
|
|
bottom: 30px; |
|
|
} |
|
|
|
|
|
.tooltip-up::before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
bottom: -5px; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
width: 0; |
|
|
height: 0; |
|
|
border-left: 6px solid transparent; |
|
|
border-right: 6px solid transparent; |
|
|
border-top: 6px solid #18191c; |
|
|
} |
|
|
|
|
|
.btn { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
text-decoration: none; |
|
|
background-color: #535353; |
|
|
padding: 10px; |
|
|
border-radius: 3px; |
|
|
color: #fff; |
|
|
font-size: 0.85rem; |
|
|
transition: 0.2s ease-in-out; |
|
|
margin-top: 12px; |
|
|
} |
|
|
|
|
|
.btn:hover { |
|
|
background-color: #747474; |
|
|
} |
|
|
|
|
|
/* Card */ |
|
|
.card-container { |
|
|
display: flex; |
|
|
flex-direction: row; |
|
|
justify-content: space-between; |
|
|
width: 1450px; |
|
|
z-index: 0; |
|
|
} |
|
|
|
|
|
.card { |
|
|
background: #292b2f; |
|
|
width: 345px; |
|
|
max-height: 95%; |
|
|
height: max-content; |
|
|
border-radius: 9px; |
|
|
box-shadow: 0px 0px 16px 3px rgba(0, 0, 0, 0.2); |
|
|
-webkit-box-shadow: 0px 0px 16px 3px rgba(0, 0, 0, 0.2); |
|
|
scrollbar-width: none; |
|
|
} |
|
|
|
|
|
.card::-webkit-scrollbar { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.card-header .banner { |
|
|
width: 100%; |
|
|
height: 60px; |
|
|
background: #ef5b0d; |
|
|
border-radius: 6px 6px 0 0; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.card-header .banner-img { |
|
|
width: 100%; |
|
|
height: 120px; |
|
|
background-position: center !important; |
|
|
background-size: 100% auto !important; |
|
|
border-radius: 6px 6px 0 0; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.card-body { |
|
|
padding: 15px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.card-body .profile-header { |
|
|
position: absolute; |
|
|
display: flex; |
|
|
flex-direction: row; |
|
|
align-items: flex-end; |
|
|
justify-content: space-between; |
|
|
width: calc(100% - 30px); |
|
|
top: -50px; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .profil-logo { |
|
|
position: relative; |
|
|
border: 6px solid #292b2f; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .profil-logo img { |
|
|
display: block; |
|
|
width: 80px; |
|
|
height: 80px; |
|
|
border-radius: 50%; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .profil-logo::before { |
|
|
content: "VIEW PRPFILE"; |
|
|
position: absolute; |
|
|
right: 0; |
|
|
top: 0; |
|
|
cursor: pointer; |
|
|
opacity: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
color: #eeeeee; |
|
|
background: #000000; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
border-radius: 50%; |
|
|
font-size: 0.6rem; |
|
|
font-weight: 600; |
|
|
text-transform: uppercase; |
|
|
transition-duration: 0.15s; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .profil-logo:hover::before { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .badges-container { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
justify-content: flex-end; |
|
|
max-width: 220px; |
|
|
background: #18191c; |
|
|
border-radius: 7px; |
|
|
padding: 3px; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .badges-container .badge-item { |
|
|
position: relative; |
|
|
margin: 5px; |
|
|
width: 15px; |
|
|
height: 15px; |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .badges-container .badge-item img { |
|
|
height: 110%; |
|
|
} |
|
|
|
|
|
.card-body .profile-header .badges-container .badge-item:hover > .tooltip { |
|
|
transform: scale(1); |
|
|
} |
|
|
|
|
|
.card-body .profile-body { |
|
|
background: #18191c; |
|
|
border-radius: 7px; |
|
|
padding: 13px; |
|
|
margin-top: 40px; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .username { |
|
|
color: #eeeeee; |
|
|
font-weight: 600; |
|
|
font-size: 1.3rem; |
|
|
display: flex; |
|
|
flex-direction: row; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .username span { |
|
|
color: #b9bbbe; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .username .badge { |
|
|
font-size: 0.65rem; |
|
|
background-color: #5865f2; |
|
|
text-transform: uppercase; |
|
|
font-weight: 300; |
|
|
width: max-content; |
|
|
padding: 2px 5px; |
|
|
margin-left: 5px; |
|
|
border-radius: 3px; |
|
|
} |
|
|
|
|
|
.card-body .profile-body hr { |
|
|
border: none; |
|
|
border-top: 0.5px solid #33353b; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .category-title { |
|
|
color: #d6d6d6; |
|
|
font-weight: 700; |
|
|
text-transform: uppercase; |
|
|
font-size: 0.8rem; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .basic-infos { |
|
|
margin-bottom: 14px; |
|
|
margin-top: 12px; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .basic-infos p { |
|
|
color: #bdbebf; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .basic-infos p a { |
|
|
color: #02a5e6; |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .basic-infos p a:hover { |
|
|
text-decoration: underline; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .basic-infos p b { |
|
|
color: #ddd; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .roles { |
|
|
margin-bottom: 14px; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .roles .roles-list { |
|
|
display: flex; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .roles .roles-list .role { |
|
|
background: #292b2f; |
|
|
color: #c4c4c4; |
|
|
border-radius: 3px; |
|
|
font-size: 0.75rem; |
|
|
font-weight: 300; |
|
|
padding: 3px 6px; |
|
|
margin-right: 4px; |
|
|
margin-top: 4px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
flex-direction: row; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .roles .roles-list .role .role-color { |
|
|
position: relative; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
border-radius: 50%; |
|
|
margin-right: 5px; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .roles .roles-list .role .role-color:hover::before { |
|
|
content: "✕"; |
|
|
position: relative; |
|
|
top: -2px; |
|
|
right: 1px; |
|
|
font-size: 0.65rem; |
|
|
color: #f5f5f5; |
|
|
background: rgba(41, 43, 47, 0); |
|
|
border-radius: 50%; |
|
|
width: 15px; |
|
|
height: 15px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .roles .roles-list .role-add { |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .note textarea { |
|
|
border: none; |
|
|
outline: none; |
|
|
background: transparent; |
|
|
width: 100%; |
|
|
min-height: 30px; |
|
|
color: #e0e0e0; |
|
|
resize: none; |
|
|
font-size: 0.8rem; |
|
|
border-radius: 3px; |
|
|
padding: 5px; |
|
|
box-sizing: border-box; |
|
|
scrollbar-width: none; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .note textarea::-webkit-scrollbar { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .note textarea::placeholder { |
|
|
font-size: 0.8rem; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .note textarea:focus::placeholder { |
|
|
opacity: 0; |
|
|
} |
|
|
|
|
|
.card-body .profile-body .message input { |
|
|
background: transparent; |
|
|
outline: none; |
|
|
border: 1.2px solid #272727; |
|
|
padding: 13px; |
|
|
width: 100%; |
|
|
border-radius: 4px; |
|
|
color: #eeeeee; |
|
|
margin-top: 14px; |
|
|
} |
|
|
.nitro-card { |
|
|
position: relative; |
|
|
background-image: linear-gradient(0, #000000, #a77311); |
|
|
background-blend-mode: multiply; |
|
|
background-color: #0000006c; |
|
|
} |
|
|
|
|
|
.nitro-card:before { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
left: 0; |
|
|
z-index: -1; |
|
|
margin: -5px; |
|
|
border-radius: 12px; |
|
|
background: linear-gradient(0, #000000, #e09200); |
|
|
} |
|
|
|
|
|
.nitro-card .card-body .profile-body { |
|
|
background: linear-gradient(0, #000000, #18191c91); |
|
|
} |
|
|
|
|
|
.nitro-card .card-body .profile-header .profil-logo { |
|
|
position: relative; |
|
|
border-color: transparent; |
|
|
z-index: 0; |
|
|
} |
|
|
|
|
|
.nitro-card .card-body .profile-header .profil-logo:after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
left: 0; |
|
|
z-index: -1; |
|
|
margin: -6px; |
|
|
border-radius: 50%; |
|
|
background-color: rgb(74, 50, 7); |
|
|
} |
|
|
|
|
|
.nitro-card .card-body .profile-header .badges-container { |
|
|
background: #18191c77; |
|
|
} |
|
|
|
|
|
.nitro-card .card-body .profile-body .roles .roles-list .role { |
|
|
background: #18191c4d; |
|
|
border: 1px solid #3f4149; |
|
|
} |
|
|
|
|
|
/* Media Queries */ |
|
|
@media screen and (max-width: 1470px) { |
|
|
.card-container { |
|
|
width: 1100px; |
|
|
} |
|
|
.card:last-child { |
|
|
display: none; |
|
|
} |
|
|
} |
|
|
|
|
|
@media screen and (max-width: 1150px) { |
|
|
.card-container { |
|
|
width: 720px; |
|
|
} |
|
|
.card:nth-child(3) { |
|
|
display: none; |
|
|
} |
|
|
} |
|
|
|
|
|
@media screen and (max-width: 770px) { |
|
|
.card-container { |
|
|
width: max-content; |
|
|
} |
|
|
.card:nth-child(2) { |
|
|
display: none; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<!-- partial:index.partial.html --> |
|
|
<div class="card-container"> |
|
|
|
|
|
<!-- nitro card --> |
|
|
|
|
|
<div class="card nitro-card"> |
|
|
<div class="card-header"> |
|
|
<div |
|
|
style="background: url('https://i.ibb.co/wyZxFzw/banner.gif')" |
|
|
class="banner-img"> |
|
|
</div> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<div class="profile-header"> |
|
|
<div class="profil-logo"> |
|
|
<img src=${logo} /> |
|
|
</div> |
|
|
<div class="badges-container"> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xLC.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xN3.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text1}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xMp.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text2}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xNq.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text3}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xNK.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text4}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xLD.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text5}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xNF.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up">${text6}</div> |
|
|
</div> |
|
|
<div class="badge-item"> |
|
|
<img src="https://svgur.com/i/xNe.svg" alt="" /> |
|
|
<div class="tooltip tooltip-up"> |
|
|
${text7} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="profile-body"> |
|
|
<div class="username"> |
|
|
<a> ${text8} </a> |
|
|
</div> |
|
|
<p style="color: white;">d11b</p> |
|
|
<hr/> |
|
|
<div class="basic-infos"> |
|
|
<div class="category-title">${text9}</div> |
|
|
<p> |
|
|
<b>Discord</b> Verified Account User <br>Joined since → 2016<br> |
|
|
<b>Discord</b> Verified Active Developer<br> since → Oct 1, 2022 |
|
|
</p> |
|
|
</div> |
|
|
<div class="basic-infos"> |
|
|
<div class="category-title">Member Since</div> |
|
|
<div style="display: flex; align-items: center;"> |
|
|
<img src="https://i.ibb.co/HpbSK8B/icons8-discord-16.png" style="margin-right: 10px;"> |
|
|
<p style="margin: 0;">Jan 8, 2016</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="roles"> |
|
|
<div class="category-title">Roles</div> |
|
|
<div class="roles-list"> |
|
|
<div class="role"> |
|
|
<div class="role-color" style="background: lightyellow"></div> |
|
|
<p>JavaScript</p> |
|
|
</div> |
|
|
<div class="role"> |
|
|
<div class="role-color" style="background: darkred"></div> |
|
|
<p>HTML</p> |
|
|
</div> |
|
|
<div class="role"> |
|
|
<div class="role-color" style="background: darkgreen"></div> |
|
|
<p>C++</p> |
|
|
</div> |
|
|
<div class="role"> |
|
|
<div class="role-color" style="background: yellow"></div> |
|
|
<p>Python</p> |
|
|
</div> |
|
|
<div class="role"> |
|
|
<div class="role-color" style="background: darkblue"></div> |
|
|
<p>C#</p> |
|
|
</div> |
|
|
<div class="role"> |
|
|
<div class="role-color" style="background: orange"></div> |
|
|
<p>IT</p> |
|
|
</div> |
|
|
<div class="role role-add"> |
|
|
<div class="role-add-text">+</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="note"> |
|
|
<div class="category-title">Note</div> |
|
|
<textarea placeholder="Click to add a note"></textarea> |
|
|
</div> |
|
|
<div class="message"> |
|
|
<input id="content" type="text" placeholder=${text9}/> |
|
|
</div> |
|
|
<div id="message-status" style="display: none;"><b>Message Sent!</b></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
<!-- partial --> |
|
|
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js'></script><script src="./script.js"></script> |
|
|
|
|
|
</body> |
|
|
</html> |
|
|
`; |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
viewport: config.viewport, |
|
|
userAgent: config.userAgent |
|
|
}); |
|
|
|
|
|
await context.route('**/*', (route) => { |
|
|
const url = route.request().url(); |
|
|
if (url.endsWith('.png') || url.endsWith('.jpg') || url.includes('google-analytics')) { |
|
|
return route.abort(); |
|
|
} |
|
|
route.continue(); |
|
|
}); |
|
|
|
|
|
const page = await context.newPage(); |
|
|
await page.setContent(html); |
|
|
const buffer = await page.screenshot({ type: 'png' }); |
|
|
await browser.close(); |
|
|
|
|
|
res.set('Content-Type', 'image/png'); |
|
|
return res.send(buffer); |
|
|
} catch (error) { |
|
|
console.error('Error generating PNG:', error); |
|
|
res.status(500).json({ error: 'Failed to convert HTML to PNG' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/darkcard', async (req, res) => { |
|
|
const { avatar, name, job, button, desc } = req.query; |
|
|
|
|
|
if (!(avatar || name || job || button || desc)) { |
|
|
return res.status(400).json({ error: 'Missing avatar, name, job, button, desc parameters' }); |
|
|
} |
|
|
const html = `<!DOCTYPE html> |
|
|
<html lang="en" > |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>Glassmorph dark card</title> |
|
|
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css'><style> |
|
|
body { |
|
|
background-color: #001320; |
|
|
background: -webkit-linear-gradient(90deg, 0, #1b121d 50%, #1b121d 100%); |
|
|
min-height: 100vh; |
|
|
font-size: 1.2rem; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.container { |
|
|
min-height: 100vh; |
|
|
} |
|
|
|
|
|
.card-container { |
|
|
position: relative; |
|
|
} |
|
|
.card-container::before, .card-container::after { |
|
|
width: 200px; |
|
|
height: 200px; |
|
|
border-radius: 100%; |
|
|
position: absolute; |
|
|
filter: blur(20px); |
|
|
z-index: 0; |
|
|
} |
|
|
.card-container::before { |
|
|
content: ""; |
|
|
left: -80px; |
|
|
top: -80px; |
|
|
background: linear-gradient(#1845ad, #23a2f6); |
|
|
} |
|
|
.card-container::after { |
|
|
content: ""; |
|
|
right: -30px; |
|
|
bottom: -80px; |
|
|
background: linear-gradient(to right, #ff512f, #f09819); |
|
|
} |
|
|
|
|
|
.card { |
|
|
backdrop-filter: blur(16px) saturate(180%); |
|
|
-webkit-backdrop-filter: blur(16px) saturate(180%); |
|
|
background-color: rgba(17, 25, 40, 0.75); |
|
|
border-radius: 12px; |
|
|
border: 1px solid rgba(255, 255, 255, 0.125); |
|
|
text-align: center; |
|
|
z-index: 2; |
|
|
} |
|
|
.card .card-body { |
|
|
padding: 1.5rem; |
|
|
} |
|
|
.card .card-title { |
|
|
color: white; |
|
|
} |
|
|
.card .card-subtitle { |
|
|
color: #c0f; |
|
|
font-size: 1rem; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 2px; |
|
|
font-weight: 700; |
|
|
opacity: 0.7; |
|
|
} |
|
|
.card .card-description { |
|
|
color: #9ca3af; |
|
|
} |
|
|
.card .card-img img { |
|
|
height: 9rem; |
|
|
width: 9rem; |
|
|
border: 1px solid rgba(209, 213, 219, 0.3); |
|
|
padding: 1rem; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<!-- partial:index.partial.html --> |
|
|
<body> |
|
|
<div class="container d-flex flex-column justify-content-center align-items center h-100"> |
|
|
<div class="row"> |
|
|
<div class="col-md-4 mx-auto"> |
|
|
<div class="card-container"> |
|
|
<div class="card"> |
|
|
<div class="card-body"> |
|
|
<div class="card-img mb-4"><img class="rounded-circle" src=${avatar}/></div> |
|
|
<h2 class="card-title">${name}</h2> |
|
|
<h3 class="card-subtitle">${job}</h3> |
|
|
<div class="btn btn-primary mt-4">${button}</div> |
|
|
<p class="card-description mb-0 mt-4">${desc}</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</body> |
|
|
<!-- partial --> |
|
|
|
|
|
</body> |
|
|
</html> |
|
|
`; |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
viewport: config.viewport, |
|
|
userAgent: config.userAgent |
|
|
}); |
|
|
|
|
|
await context.route('**/*', (route) => { |
|
|
const url = route.request().url(); |
|
|
if (url.endsWith('.png') || url.endsWith('.jpg') || url.includes('google-analytics')) { |
|
|
return route.abort(); |
|
|
} |
|
|
route.continue(); |
|
|
}); |
|
|
|
|
|
const page = await context.newPage(); |
|
|
await page.setContent(html); |
|
|
const buffer = await page.screenshot({ type: 'png' }); |
|
|
await browser.close(); |
|
|
|
|
|
res.set('Content-Type', 'image/png'); |
|
|
return res.send(buffer); |
|
|
} catch (error) { |
|
|
console.error('Error generating PNG:', error); |
|
|
res.status(500).json({ error: 'Failed to convert HTML to PNG' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/welcome', async (req, res) => { |
|
|
const { name, info, desc } = req.query; |
|
|
|
|
|
|
|
|
if (!name || !info) { |
|
|
return res.status(400).json({ error: 'Missing required parameters' }); |
|
|
} |
|
|
|
|
|
|
|
|
const html = ` |
|
|
<!DOCTYPE html> |
|
|
<html lang="en" > |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>Course Card UI Design - #094 of #100Days100Projects</title> |
|
|
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css'> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css?family=Muli&display=swap'); |
|
|
|
|
|
* { |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
|
|
|
body { |
|
|
background-image: linear-gradient(45deg, #7175da, #9790F2); |
|
|
font-family: 'Muli', sans-serif; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
flex-direction: column; |
|
|
min-height: 100vh; |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.courses-container { |
|
|
|
|
|
} |
|
|
|
|
|
.course { |
|
|
background-color: #fff; |
|
|
border-radius: 10px; |
|
|
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2); |
|
|
display: flex; |
|
|
max-width: 100%; |
|
|
margin: 20px; |
|
|
overflow: hidden; |
|
|
width: 700px; |
|
|
} |
|
|
|
|
|
.course h6 { |
|
|
opacity: 0.6; |
|
|
margin: 0; |
|
|
letter-spacing: 1px; |
|
|
text-transform: uppercase; |
|
|
} |
|
|
|
|
|
.course h2 { |
|
|
letter-spacing: 1px; |
|
|
margin: 10px 0; |
|
|
} |
|
|
|
|
|
.course-preview { |
|
|
background-color: #2A265F; |
|
|
color: #fff; |
|
|
padding: 30px; |
|
|
max-width: 250px; |
|
|
} |
|
|
|
|
|
.course-preview a { |
|
|
color: #fff; |
|
|
display: inline-block; |
|
|
font-size: 12px; |
|
|
opacity: 0.6; |
|
|
margin-top: 30px; |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.course-info { |
|
|
padding: 30px; |
|
|
position: relative; |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
.progress-container { |
|
|
position: absolute; |
|
|
top: 30px; |
|
|
right: 30px; |
|
|
text-align: right; |
|
|
width: 150px; |
|
|
} |
|
|
|
|
|
.progress { |
|
|
background-color: #ddd; |
|
|
border-radius: 3px; |
|
|
height: 5px; |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
.progress::after { |
|
|
border-radius: 3px; |
|
|
background-color: #2A265F; |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
height: 5px; |
|
|
width: 66%; |
|
|
} |
|
|
|
|
|
.progress-text { |
|
|
font-size: 10px; |
|
|
opacity: 0.6; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
.btn { |
|
|
background-color: #2A265F; |
|
|
border: 0; |
|
|
border-radius: 50px; |
|
|
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2); |
|
|
color: #fff; |
|
|
font-size: 16px; |
|
|
padding: 12px 25px; |
|
|
position: absolute; |
|
|
bottom: 30px; |
|
|
right: 30px; |
|
|
letter-spacing: 1px; |
|
|
} |
|
|
|
|
|
/* SOCIAL PANEL CSS */ |
|
|
.social-panel-container { |
|
|
position: fixed; |
|
|
right: 0; |
|
|
bottom: 80px; |
|
|
transform: translateX(100%); |
|
|
transition: transform 0.4s ease-in-out; |
|
|
} |
|
|
|
|
|
.social-panel-container.visible { |
|
|
transform: translateX(-10px); |
|
|
} |
|
|
|
|
|
.social-panel { |
|
|
background-color: #fff; |
|
|
border-radius: 16px; |
|
|
box-shadow: 0 16px 31px -17px rgba(0,31,97,0.6); |
|
|
border: 5px solid #001F61; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
font-family: 'Muli'; |
|
|
position: relative; |
|
|
height: 169px; |
|
|
width: 370px; |
|
|
max-width: calc(100% - 10px); |
|
|
} |
|
|
|
|
|
.social-panel button.close-btn { |
|
|
border: 0; |
|
|
color: #97A5CE; |
|
|
cursor: pointer; |
|
|
font-size: 20px; |
|
|
position: absolute; |
|
|
top: 5px; |
|
|
right: 5px; |
|
|
} |
|
|
|
|
|
.social-panel button.close-btn:focus { |
|
|
outline: none; |
|
|
} |
|
|
|
|
|
.social-panel p { |
|
|
background-color: #001F61; |
|
|
border-radius: 0 0 10px 10px; |
|
|
color: #fff; |
|
|
font-size: 14px; |
|
|
line-height: 18px; |
|
|
padding: 2px 17px 6px; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 50%; |
|
|
margin: 0; |
|
|
transform: translateX(-50%); |
|
|
text-align: center; |
|
|
width: 235px; |
|
|
} |
|
|
|
|
|
.social-panel p i { |
|
|
margin: 0 5px; |
|
|
} |
|
|
|
|
|
.social-panel p a { |
|
|
color: #FF7500; |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.social-panel h4 { |
|
|
margin: 20px 0; |
|
|
color: #97A5CE; |
|
|
font-family: 'Muli'; |
|
|
font-size: 14px; |
|
|
line-height: 18px; |
|
|
text-transform: uppercase; |
|
|
} |
|
|
|
|
|
.social-panel ul { |
|
|
display: flex; |
|
|
list-style-type: none; |
|
|
padding: 0; |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.social-panel ul li { |
|
|
margin: 0 10px; |
|
|
} |
|
|
|
|
|
.social-panel ul li a { |
|
|
border: 1px solid #DCE1F2; |
|
|
border-radius: 50%; |
|
|
color: #001F61; |
|
|
font-size: 20px; |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
height: 50px; |
|
|
width: 50px; |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.social-panel ul li a:hover { |
|
|
border-color: #FF6A00; |
|
|
box-shadow: 0 9px 12px -9px #FF6A00; |
|
|
} |
|
|
|
|
|
.floating-btn { |
|
|
border-radius: 26.5px; |
|
|
background-color: #001F61; |
|
|
border: 1px solid #001F61; |
|
|
box-shadow: 0 16px 22px -17px #03153B; |
|
|
color: #fff; |
|
|
cursor: pointer; |
|
|
font-size: 16px; |
|
|
line-height: 20px; |
|
|
padding: 12px 20px; |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
z-index: 999; |
|
|
} |
|
|
|
|
|
.floating-btn:hover { |
|
|
background-color: #ffffff; |
|
|
color: #001F61; |
|
|
} |
|
|
|
|
|
.floating-btn:focus { |
|
|
outline: none; |
|
|
} |
|
|
|
|
|
.floating-text { |
|
|
background-color: #001F61; |
|
|
border-radius: 10px 10px 0 0; |
|
|
color: #fff; |
|
|
font-family: 'Muli'; |
|
|
padding: 7px 15px; |
|
|
position: fixed; |
|
|
bottom: 0; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
text-align: center; |
|
|
z-index: 998; |
|
|
} |
|
|
|
|
|
.floating-text a { |
|
|
color: #FF7500; |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
@media screen and (max-width: 480px) { |
|
|
|
|
|
.social-panel-container.visible { |
|
|
transform: translateX(0px); |
|
|
} |
|
|
|
|
|
.floating-btn { |
|
|
right: 10px; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
|
|
|
</head> |
|
|
<body> |
|
|
<!-- partial:index.partial.html --> |
|
|
<div class="courses-container"> |
|
|
<div class="course"> |
|
|
<div class="course-preview"> |
|
|
<h6>Course</h6> |
|
|
<h2>${name}</h2> |
|
|
<a href="#">View all chapters <i class="fas fa-chevron-right"></i></a> |
|
|
</div> |
|
|
<div class="course-info"> |
|
|
<div class="progress-container"> |
|
|
<div class="progress"></div> |
|
|
<span class="progress-text"> |
|
|
6/9 Challenges |
|
|
</span> |
|
|
</div> |
|
|
<h6>${info}</h6> |
|
|
<h2>${desc}</h2> |
|
|
<button class="btn">Continue</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<!-- SOCIAL PANEL HTML --> |
|
|
<div class="social-panel-container"> |
|
|
<div class="social-panel"> |
|
|
<p>Created with <i class="fa fa-heart"></i> by |
|
|
<a target="_blank" href="https://florin-pop.com">Florin Pop</a></p> |
|
|
<button class="close-btn"><i class="fas fa-times"></i></button> |
|
|
<h4>Get in touch on</h4> |
|
|
<ul> |
|
|
<li> |
|
|
<a href="https://www.patreon.com/florinpop17" target="_blank"> |
|
|
<i class="fab fa-discord"></i> |
|
|
</a> |
|
|
</li> |
|
|
<li> |
|
|
<a href="https://twitter.com/florinpop1705" target="_blank"> |
|
|
<i class="fab fa-twitter"></i> |
|
|
</a> |
|
|
</li> |
|
|
<li> |
|
|
<a href="https://linkedin.com/in/florinpop17" target="_blank"> |
|
|
<i class="fab fa-linkedin"></i> |
|
|
</a> |
|
|
</li> |
|
|
<li> |
|
|
<a href="https://facebook.com/florinpop17" target="_blank"> |
|
|
<i class="fab fa-facebook"></i> |
|
|
</a> |
|
|
</li> |
|
|
<li> |
|
|
<a href="https://instagram.com/florinpop17" target="_blank"> |
|
|
<i class="fab fa-instagram"></i> |
|
|
</a> |
|
|
</li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
<button class="floating-btn"> |
|
|
Get in Touch |
|
|
</button> |
|
|
|
|
|
<div class="floating-text"> |
|
|
Part of <a href="https://florin-pop.com/blog/2019/09/100-days-100-projects" target="_blank">#100Days100Projects</a> |
|
|
</div> |
|
|
<!-- partial --> |
|
|
<script> |
|
|
// INSERT JS HERE |
|
|
|
|
|
|
|
|
// SOCIAL PANEL JS |
|
|
const floating_btn = document.querySelector('.floating-btn'); |
|
|
const close_btn = document.querySelector('.close-btn'); |
|
|
const social_panel_container = document.querySelector('.social-panel-container'); |
|
|
|
|
|
floating_btn.addEventListener('click', () => { |
|
|
social_panel_container.classList.toggle('visible') |
|
|
}); |
|
|
|
|
|
close_btn.addEventListener('click', () => { |
|
|
social_panel_container.classList.remove('visible') |
|
|
}); |
|
|
</script> |
|
|
|
|
|
</body> |
|
|
</html>`; |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
viewport: config.viewport, |
|
|
userAgent: config.userAgent |
|
|
}); |
|
|
|
|
|
await context.route('**/*', (route) => { |
|
|
const url = route.request().url(); |
|
|
if (url.endsWith('.png') || url.endsWith('.jpg') || url.includes('google-analytics')) { |
|
|
return route.abort(); |
|
|
} |
|
|
route.continue(); |
|
|
}); |
|
|
|
|
|
const page = await context.newPage(); |
|
|
await page.setContent(html); |
|
|
const buffer = await page.screenshot({ type: 'png' }); |
|
|
await browser.close(); |
|
|
|
|
|
res.set('Content-Type', 'image/png'); |
|
|
return res.send(buffer); |
|
|
} catch (error) { |
|
|
console.error('Error generating PNG:', error); |
|
|
res.status(500).json({ error: 'Failed to convert HTML to PNG' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const PORT = process.env.PORT || 7860; |
|
|
|
|
|
app.listen(PORT, async () => { |
|
|
console.log(`Server running on port ${PORT}`); |
|
|
}); |
|
|
|
|
|
process.on('SIGINT', async () => { |
|
|
process.exit(0); |
|
|
}); |