|
|
import express from 'express'; |
|
|
import { chromium } from 'playwright'; |
|
|
import cors from 'cors'; |
|
|
import dotenv from 'dotenv'; |
|
|
import os from 'os'; |
|
|
import axios from 'axios'; |
|
|
import fetch from 'node-fetch'; |
|
|
import sharp from 'sharp'; |
|
|
import { spawn } from 'child_process'; |
|
|
import path from 'path'; |
|
|
import { NodeVM } from "vm2"; |
|
|
import { CompileFile } from 've-compiler'; |
|
|
import { createRequire } from "module"; |
|
|
const require = createRequire(import.meta.url); |
|
|
|
|
|
import EventEmitter from 'events'; |
|
|
import WebSocket from 'ws'; |
|
|
import crypto from 'crypto'; |
|
|
|
|
|
import pkg from 'axios-cookiejar-support'; |
|
|
const { wrapper } = pkg; |
|
|
|
|
|
import { CookieJar } from 'tough-cookie'; |
|
|
import * as cheerio from 'cheerio'; |
|
|
import TurndownService from 'turndown'; |
|
|
import { Readable } from 'stream'; |
|
|
|
|
|
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' |
|
|
}; |
|
|
|
|
|
let browser, page; |
|
|
|
|
|
const utils = { |
|
|
async initialize() { |
|
|
if (!browser) { |
|
|
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(); |
|
|
}); |
|
|
|
|
|
page = await context.newPage(); |
|
|
await page.goto('https://www.bratgenerator.com/', { waitUntil: 'domcontentloaded', timeout: 10000 }); |
|
|
|
|
|
try { |
|
|
await page.click('#onetrust-accept-btn-handler', { timeout: 2000 }); |
|
|
} catch { } |
|
|
|
|
|
|
|
|
} |
|
|
}, |
|
|
|
|
|
async generateBrat(text) { |
|
|
await page.fill('#textInput', text); |
|
|
const overlay = page.locator('#textOverlay'); |
|
|
return overlay.screenshot({ timeout: 3000 }); |
|
|
}, |
|
|
|
|
|
async close() { |
|
|
if (browser) await browser.close(); |
|
|
} |
|
|
}; |
|
|
|
|
|
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('/brat', async (req, res) => { |
|
|
try { |
|
|
const { text: q } = req.query; |
|
|
if (!q) { |
|
|
return res.json({ |
|
|
name: 'HD Bart Generator API', |
|
|
message: 'Parameter text di perlukan', |
|
|
version: '2.1.0', |
|
|
runtime: { |
|
|
os: os.type(), |
|
|
platform: os.platform(), |
|
|
architecture: os.arch(), |
|
|
cpuCount: os.cpus().length, |
|
|
uptime: `${os.uptime()} seconds`, |
|
|
memoryUsage: `${Math.round((os.totalmem() - os.freemem()) / 1024 / 1024)} MB used of ${Math.round(os.totalmem() / 1024 / 1024)} MB` |
|
|
} |
|
|
}); |
|
|
} |
|
|
const imageBuffer = await utils.generateBrat(q); |
|
|
res.set('Content-Type', 'image/png'); |
|
|
res.send(imageBuffer); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
res.status(500).json({ |
|
|
status: false, |
|
|
message: 'Error generating image', |
|
|
error: process.env.NODE_ENV === 'development' ? error.message : undefined |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/screenshot', async (req, res) => { |
|
|
const { url } = req.query; |
|
|
|
|
|
if (!url) { |
|
|
return res.status(400).send('URL is required'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch(); |
|
|
const page = await browser.newPage(); |
|
|
await page.goto(url); |
|
|
|
|
|
const screenshotBuffer = await page.screenshot(); |
|
|
await browser.close(); |
|
|
|
|
|
res.type('image/png').send(screenshotBuffer); |
|
|
} catch (error) { |
|
|
res.status(500).send('Internal Server Error'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function generateRandomString(length) { |
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
|
|
let result = ''; |
|
|
for (let i = 0; i < length; i++) { |
|
|
result += characters.charAt(Math.floor(Math.random() * characters.length)); |
|
|
} |
|
|
return result; |
|
|
} |
|
|
|
|
|
class MicrosoftBingAutoLogin { |
|
|
constructor(bing_account, bing_password) { |
|
|
console.log('Initializing auto login for Microsoft Bing ...'); |
|
|
this.bing_account = bing_account; |
|
|
this.bing_password = bing_password; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
async init() { |
|
|
this.browser = await chromium.launch(); |
|
|
} |
|
|
|
|
|
|
|
|
async login() { |
|
|
const sig = generateRandomString(32); |
|
|
const CSRFToken = generateRandomString(8) + '-' + generateRandomString(4) + '-' + generateRandomString(4) + '-' + generateRandomString(4) + '-' + generateRandomString(12); |
|
|
const loginUrl = `https://login.live.com/login.srf?wa=wsignin1.0&rpsnv=13&id=264960&wreply=https%3a%2f%2fwww.bing.com%2fsecure%2fPassport.aspx%3fedge_suppress_profile_switch%3d1%26requrl%3dhttps%253a%252f%252fwww.bing.com%252fsearch%253ftoWww%253d1%2526redig%253d9220EACAFFCA40508E4E7BD52023921B%2526q%253dBing%252bAI%2526showconv%253d1%2526wlexpsignin%253d1%26sig=${sig}&wp=MBI_SSL&lc=1028&CSRFToken=${CSRFToken}&aadredir=1`; |
|
|
|
|
|
|
|
|
const page = await this.browser.newPage(); |
|
|
await page.goto(loginUrl); |
|
|
|
|
|
|
|
|
const accountInput = await page.$('input[name="loginfmt"]'); |
|
|
await accountInput.type(this.bing_account); |
|
|
const passwordInput = await page.$('input[name="passwd"]'); |
|
|
await passwordInput.type(this.bing_password); |
|
|
|
|
|
|
|
|
const submitButton = await page.$('input[type="submit"]'); |
|
|
await submitButton.click(); |
|
|
|
|
|
|
|
|
await page.waitForNavigation(); |
|
|
} |
|
|
|
|
|
async getCookies() { |
|
|
const page = await this.browser.newPage(); |
|
|
await page.goto('https://bing.com/chat'); |
|
|
const cookies = await page.cookies(); |
|
|
return cookies; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
app.get('/login', async (req, res) => { |
|
|
const { user, pass } = req.query; |
|
|
|
|
|
if (!user || !pass) { |
|
|
return res.status(400).send('Missing username or password.'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const bingLogin = new MicrosoftBingAutoLogin(user, pass); |
|
|
await bingLogin.login(); |
|
|
const cookies = await bingLogin.getCookies(); |
|
|
res.json(cookies); |
|
|
} catch (error) { |
|
|
res.status(500).send('Error during login: ' + error.message); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/cookie', async (req, res) => { |
|
|
const { url } = req.query; |
|
|
|
|
|
if (!url) { |
|
|
return res.status(400).send('URL is required'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch(); |
|
|
const page = await browser.newPage(); |
|
|
await page.goto(url); |
|
|
const cookies = await page.context().cookies(); |
|
|
await browser.close(); |
|
|
res.json(cookies); |
|
|
} catch (error) { |
|
|
res.status(500).send('Internal Server Error'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.get('/cookie/v2', async (req, res) => { |
|
|
const { url } = req.query; |
|
|
|
|
|
if (!url) { |
|
|
return res.status(400).send('URL is required'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch(); |
|
|
const page = await browser.newPage(); |
|
|
await page.goto(url); |
|
|
|
|
|
|
|
|
const cookies = await page.context().cookies(); |
|
|
|
|
|
|
|
|
const response = await page.waitForResponse(url); |
|
|
const headers = response.headers(); |
|
|
|
|
|
await browser.close(); |
|
|
|
|
|
|
|
|
res.json({ cookies, headers }); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
res.status(500).send('Internal Server Error'); |
|
|
} |
|
|
}); |
|
|
|
|
|
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(); |
|
|
const page = await browser.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('/youtube-videos', async (req, res) => { |
|
|
const channelName = req.query.name || 'YasoobKhalid'; |
|
|
const url = `https://www.youtube.com/@${channelName}/videos`; |
|
|
|
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const page = await browser.newPage(); |
|
|
await page.goto(url); |
|
|
|
|
|
const channelTitle = "Title" || await page.locator('yt-formatted-string[class*="ytd-channel-name"]').textContent(); |
|
|
const handle = await page.locator('yt-formatted-string#channel-handle').textContent(); |
|
|
const subscriberCount = await page.locator('yt-formatted-string#subscriber-count').textContent(); |
|
|
|
|
|
let lastHeight = await page.evaluate(() => { |
|
|
return document.documentElement.scrollHeight; |
|
|
}); |
|
|
|
|
|
const WAIT_IN_SECONDS = 5; |
|
|
while (true) { |
|
|
await page.evaluate('window.scrollTo(0, document.documentElement.scrollHeight)'); |
|
|
await page.waitForTimeout(WAIT_IN_SECONDS * 1000); |
|
|
|
|
|
const newHeight = await page.evaluate(() => { |
|
|
return document.documentElement.scrollHeight; |
|
|
}); |
|
|
|
|
|
if (newHeight === lastHeight) { |
|
|
break; |
|
|
} |
|
|
|
|
|
lastHeight = newHeight; |
|
|
} |
|
|
|
|
|
const videoData = await page.evaluate(() => { |
|
|
const thumbnails = Array.from(document.querySelectorAll('a#thumbnail yt-image img')); |
|
|
const views = Array.from(document.querySelectorAll('div#metadata-line span:nth-child(1)')); |
|
|
const titles = Array.from(document.querySelectorAll('#video-title')); |
|
|
const links = Array.from(document.querySelectorAll('#video-title-link')); |
|
|
|
|
|
return titles.map((title, index) => ({ |
|
|
title: title.textContent.trim(), |
|
|
views: views[index]?.textContent.trim(), |
|
|
thumbnail: thumbnails[index]?.src, |
|
|
link: links[index]?.href, |
|
|
})); |
|
|
}); |
|
|
|
|
|
await browser.close(); |
|
|
|
|
|
res.json({ |
|
|
channelTitle, |
|
|
handle, |
|
|
subscriberCount, |
|
|
videos: videoData, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const Patterns = { |
|
|
channel: { |
|
|
name: /channelMetadataRenderer\":{\"title\":\"(.*?)\"/, |
|
|
id: /channelId\":\"(.*?)\"/, |
|
|
verified: /"label":"Verified"/, |
|
|
check_live: /{"text":"LIVE"}/, |
|
|
live: /thumbnailOverlays\":\[(.*?)]/, |
|
|
video_id: /videoId\":\"(.*?)\"/, |
|
|
uploads: /gridVideoRenderer\":{\"videoId\":\"(.*?)\"/, |
|
|
subscribers: /\"subscriberCountText\":{\"accessibility\":(.*?),/, |
|
|
views: /viewCountText\":{\"simpleText\":\"(.*?)\"}/, |
|
|
creation: /{\"text\":\"Joined \"},{\"text\":\"(.*?)\"}/, |
|
|
country: /country\":{\"simpleText\":\"(.*?)\"}/, |
|
|
custom_url: /canonicalChannelUrl\":\"(.*?)\"/, |
|
|
description: /{\"description\":{\"simpleText\":\"(.*?)\"}/, |
|
|
avatar: /height\":88},{\"url\":\"(.*?)\"/, |
|
|
banner: /width\":1280,\"height\":351},{\"url\":\"(.*?)\"/, |
|
|
playlists: /{\"url\":\"\/playlist\?list=(.*?)\"/, |
|
|
video_count: /videoCountText\":{\"runs\":\[{\"text\":(.*?)}\]/, |
|
|
socials: /q=https%3A%2F%2F(.*?)\"/, |
|
|
upload_ids: /videoId\":\"(.*?)\"/, |
|
|
stream_ids: /videoId\":\"(.*?)\"/, |
|
|
upload_chunk: /gridVideoRenderer\":{(.*?)\"navigationEndpoint/, |
|
|
upload_chunk_fl_1: /simpleText\":\"Streamed/, |
|
|
upload_chunk_fl_2: /default_live./, |
|
|
upcoming_check: /\"title\":\"Upcoming live streams\"/, |
|
|
upcoming: /gridVideoRenderer\":{\"videoId\":\"(.*?)\"/, |
|
|
}, |
|
|
video: { |
|
|
video_id: /videoId\":\"(.*?)\"/, |
|
|
title: /title\":\"(.*?)\"/, |
|
|
duration: /approxDurationMs\":\"(.*?)\"/, |
|
|
upload_date: /uploadDate\":\"(.*?)\"/, |
|
|
author_id: /channelIds\":\[\"(.*?)\"/, |
|
|
description: /shortDescription\":\"(.*)\",\"isCrawlable/, |
|
|
tags: /<meta name=\"keywords\" content=\"(.*?)\">/, |
|
|
is_streamed: /simpleText\":\"Streamed live/, |
|
|
is_premiered: /dateText\":{\"simpleText\":\"Premiered/, |
|
|
views: /videoViewCountRenderer\":{\"viewCount\":{\"simpleText\":\"(.*?)\"/, |
|
|
likes: /toggledText\":{\"accessibility\":{\"accessibilityData\":{\"label\":\"(.*?) /, |
|
|
thumbnail: /playerMicroformatRenderer\":{\"thumbnail\":{\"thumbnails\":\[{\"url\":\"(.*?)\"/, |
|
|
}, |
|
|
playlist: { |
|
|
name: /{\"title\":\"(.*?)\"/, |
|
|
video_count: /stats\":\[{\"runs\":\[{\"text\":\"(.*?)\"/, |
|
|
video_id: /videoId\":\"(.*?)\"/, |
|
|
thumbnail: /og:image\" content=\"(.*?)\?"/, |
|
|
}, |
|
|
extra: { |
|
|
video_id: /videoId\":\"(.*?)\"/, |
|
|
}, |
|
|
query: { |
|
|
channel_id: /channelId\":\"(.*?)\"/, |
|
|
video_id: /videoId\":\"(.*?)\"/, |
|
|
playlist_id: /playlistId\":\"(.*?)\"/, |
|
|
}, |
|
|
}; |
|
|
|
|
|
|
|
|
const matchPattern = (pattern, data) => { |
|
|
const matches = []; |
|
|
let match; |
|
|
while ((match = pattern.exec(data)) !== null) { |
|
|
matches.push(match[1] || match[0]); |
|
|
} |
|
|
return matches; |
|
|
}; |
|
|
|
|
|
|
|
|
const fetchChannelData = async (channelName) => { |
|
|
try { |
|
|
const url = `https://www.youtube.com/@${channelName}/videos`; |
|
|
const response = await axios.get(url); |
|
|
return response.data; |
|
|
} catch (error) { |
|
|
throw new Error('Error fetching YouTube channel data: ' + error.message); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
app.get('/youtube-info', async (req, res) => { |
|
|
const { name } = req.query; |
|
|
|
|
|
if (!name) { |
|
|
return res.status(400).json({ error: 'Channel name is required' }); |
|
|
} |
|
|
|
|
|
try { |
|
|
const data = await fetchChannelData(name); |
|
|
const results = {}; |
|
|
|
|
|
|
|
|
Object.keys(Patterns.channel).forEach((key) => { |
|
|
const pattern = Patterns.channel[key]; |
|
|
results[key] = matchPattern(pattern, data); |
|
|
}); |
|
|
|
|
|
res.json({ channelName: name, data: results }); |
|
|
} catch (error) { |
|
|
res.status(500).json({ error: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
const playwright = { |
|
|
avLang: ['javascript', 'python', 'java', 'csharp'], |
|
|
|
|
|
request: async function(language = 'javascript', code) { |
|
|
if (!this.avLang.includes(language.toLowerCase())) { |
|
|
throw new Error(`Language "${language}" is not supported. Choose from available languages: ${this.avLang.join(', ')}`); |
|
|
} |
|
|
|
|
|
const url = 'https://try.playwright.tech/service/control/run'; |
|
|
const headers = { |
|
|
'authority': 'try.playwright.tech', |
|
|
'accept': '*/*', |
|
|
'content-type': 'application/json', |
|
|
'origin': 'https://try.playwright.tech', |
|
|
'referer': 'https://try.playwright.tech/?l=playwright-test', |
|
|
'user-agent': 'Postify/1.0.0', |
|
|
}; |
|
|
|
|
|
const data = { |
|
|
code: code, |
|
|
language: language |
|
|
}; |
|
|
|
|
|
try { |
|
|
const response = await axios.post(url, data, { headers }); |
|
|
const { success, error, version, duration, output, files } = response.data; |
|
|
return { success, error, version, duration, output, files }; |
|
|
} catch (error) { |
|
|
if (error.response) { |
|
|
const { success, error: errMsg, version, duration, output, files } = error.response.data; |
|
|
return { success, error: errMsg, version, duration, output, files }; |
|
|
} else { |
|
|
throw new Error(error.message); |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
app.get('/view', async (req, res) => { |
|
|
const { url, count } = req.query; |
|
|
|
|
|
if (!url || !count) { |
|
|
return res.status(400).json({ error: 'Missing required parameters: url and count' }); |
|
|
} |
|
|
|
|
|
const language = 'javascript'; |
|
|
const code = ` |
|
|
const { chromium } = require('playwright'); |
|
|
(async () => { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const page = await browser.newPage(); |
|
|
const targetUrl = '${url}'; |
|
|
|
|
|
for (let i = 0; i < ${count}; i++) { |
|
|
await page.goto(targetUrl); |
|
|
console.log(\`View \${i + 1}: \${targetUrl}\`); |
|
|
await page.waitForTimeout(3000); // Delay for 3 seconds before next view |
|
|
} |
|
|
|
|
|
await browser.close(); |
|
|
})(); |
|
|
`; |
|
|
|
|
|
try { |
|
|
const data = await playwright.request(language, code); |
|
|
return res.status(200).json(data); |
|
|
} catch (error) { |
|
|
console.error('Error:', error); |
|
|
return res.status(500).json({ error: error.message }); |
|
|
} |
|
|
}) |
|
|
|
|
|
const font = 'BlueArchive'; |
|
|
|
|
|
const crossBuffer = Buffer.from('<svg width="480" height="200" version="1.1"><polygon points="252,0 159,190 165,190 287,0" style="fill:#fff" /></svg>'); |
|
|
const haloBuffer = Buffer.from('<svg width="480" height="200" version="1.1"><polygon points="252,0 159,190 165,190 287,0" style="fill:#fff" /></svg>'); |
|
|
|
|
|
const splitText = (text) => { |
|
|
if (Array.isArray(text)) return text; |
|
|
if (text.includes(' ')) { |
|
|
return text.split(' ').filter(i => i.trim()); |
|
|
} else if (text.match(/^[A-Z][a-z]*[A-Z][a-z]*$/)) { |
|
|
return text.replace(/[A-Z]/g, t => ' ' + t).trim().split(' '); |
|
|
} else { |
|
|
const h = Math.floor(text.length / 2); |
|
|
return [text.substring(0, h), text.substring(h)]; |
|
|
} |
|
|
}; |
|
|
|
|
|
const baLogo = async (text, left = 0) => { |
|
|
const [head, tail] = splitText(text); |
|
|
if (!head || !tail) throw new Error('Invalid input text'); |
|
|
|
|
|
let width = 32, height = 260; |
|
|
const top = 208; |
|
|
const matrix = [[1, -0.35], [0, 1]]; |
|
|
|
|
|
const comps = []; |
|
|
|
|
|
const headPart = sharp({ |
|
|
text: { |
|
|
font, |
|
|
text: `<span color="#208ef7" size="144pt">${head}</span>`, |
|
|
dpi: 72, |
|
|
rgba: true, |
|
|
} |
|
|
}).affine(matrix, { |
|
|
background: '#fff0', |
|
|
interpolator: sharp.interpolators.nohalo |
|
|
}).png(); |
|
|
const headMeta = await headPart.metadata(); |
|
|
|
|
|
const w = width + headMeta.width - 162; |
|
|
const dl = w < 0 ? 0 : w + left; |
|
|
|
|
|
const tailPart = sharp({ |
|
|
text: { |
|
|
font, |
|
|
text: `<span color="#208ef7" size="144pt">${head}<span color="#2b2b2b" size="144pt">${tail}</span></span>`, |
|
|
dpi: 72, |
|
|
rgba: true, |
|
|
} |
|
|
}).affine(matrix, { |
|
|
background: '#fff0', |
|
|
interpolator: sharp.interpolators.nohalo |
|
|
}).png(); |
|
|
const tailMeta = await tailPart.metadata(); |
|
|
|
|
|
comps.push({ |
|
|
input: await tailPart.toBuffer(), |
|
|
left: width, |
|
|
top, |
|
|
}); |
|
|
|
|
|
comps.push({ |
|
|
input: crossBuffer, |
|
|
left: dl + 4, |
|
|
top: 4, |
|
|
}); |
|
|
|
|
|
width += (tailMeta.width < 256 ? 256 : tailMeta.width ) + 64; |
|
|
height += 144; |
|
|
if (width < 500) width = 500; |
|
|
|
|
|
return sharp({ |
|
|
create: { |
|
|
width, height, |
|
|
channels: 4, |
|
|
background: '#fff', |
|
|
}, |
|
|
}).composite(comps).png(); |
|
|
}; |
|
|
|
|
|
app.get('/balogo', async (req, res) => { |
|
|
try { |
|
|
const { text, text2 } = req.query; |
|
|
if (!text) return res.status(400).send('Text is required'); |
|
|
|
|
|
const logoImage = await baLogo(text); |
|
|
res.set('Content-Type', 'image/png'); |
|
|
res.send(await logoImage.toBuffer()); |
|
|
} catch (error) { |
|
|
res.status(500).send('Error generating logo: ' + error.message); |
|
|
} |
|
|
}); |
|
|
|
|
|
async function baLogov2(textL = "蔚蓝", textR = "档案", x = "-15", y = "0", tp = false) { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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 page = await context.newPage(); |
|
|
await page.goto('https://symbolon.pages.dev/'); |
|
|
await page.waitForSelector('#canvas'); |
|
|
await page.fill('#textL', textL); |
|
|
await page.fill('#textR', textR); |
|
|
await page.fill('#graphX', x); |
|
|
await page.fill('#graphY', y); |
|
|
await page.waitForTimeout(500); |
|
|
const imageBuffer = await page.$eval('#canvas', (canvas) => { |
|
|
return canvas.toDataURL('image/png').split(',')[1]; |
|
|
}); |
|
|
const buffer = Buffer.from(imageBuffer, 'base64'); |
|
|
await browser.close(); |
|
|
return buffer; |
|
|
} |
|
|
|
|
|
app.get('/balogo/v2', async (req, res) => { |
|
|
try { |
|
|
const { textL = "蔚蓝", textR = "档案", x = "-15", y = "0", tp = "false" } = req.query; |
|
|
const tpBool = tp === "true"; |
|
|
const imageBuffer = await baLogov2(textL, textR, x, y, tpBool); |
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
res.send(imageBuffer); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
res.status(500).json({ error: "Error generating logo" }); |
|
|
} |
|
|
}); |
|
|
|
|
|
async function baLogov3(textL = "蔚蓝", textR = "档案") { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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 page = await context.newPage(); |
|
|
await page.goto('https://appleneko2001-bluearchive-logo.vercel.app/'); |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('#canvas', { timeout: 30000 }); |
|
|
} catch (err) { |
|
|
console.error('Canvas tidak ditemukan:', err); |
|
|
} |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('#textL', { state: 'visible', timeout: 10000 }); |
|
|
await page.fill('#textL', textL); |
|
|
} catch (err) { |
|
|
console.error('Elemen #textL gagal diisi:', err); |
|
|
} |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('#textR', { state: 'visible', timeout: 10000 }); |
|
|
await page.fill('#textR', textR); |
|
|
} catch (err) { |
|
|
console.error('Elemen #textR gagal diisi:', err); |
|
|
} |
|
|
|
|
|
await page.waitForTimeout(2000); |
|
|
|
|
|
const imageBuffer = await page.$eval('#canvas', (canvas) => { |
|
|
return canvas.toDataURL('image/png').split(',')[1]; |
|
|
}); |
|
|
|
|
|
const buffer = Buffer.from(imageBuffer, 'base64'); |
|
|
|
|
|
await browser.close(); |
|
|
return buffer; |
|
|
} |
|
|
|
|
|
app.get('/balogo/v3', async (req, res) => { |
|
|
try { |
|
|
const { textL = "蔚蓝", textR = "档案" } = req.query; |
|
|
const imageBuffer = await baLogov3(textL, textR); |
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
res.send(imageBuffer); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
res.status(500).json({ error: "Error generating logo" }); |
|
|
} |
|
|
}); |
|
|
|
|
|
async function baLogov4(textL = "蔚蓝", textR = "档案") { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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 page = await context.newPage(); |
|
|
await page.goto('https://ba.api.hli.icu/'); |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('#canvas', { timeout: 30000 }); |
|
|
} catch (err) { |
|
|
console.error('Canvas tidak ditemukan:', err); |
|
|
} |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('#textL', { state: 'visible', timeout: 10000 }); |
|
|
await page.fill('#textL', textL); |
|
|
} catch (err) { |
|
|
console.error('Elemen #textL gagal diisi:', err); |
|
|
} |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('#textR', { state: 'visible', timeout: 10000 }); |
|
|
await page.fill('#textR', textR); |
|
|
} catch (err) { |
|
|
console.error('Elemen #textR gagal diisi:', err); |
|
|
} |
|
|
|
|
|
await page.waitForTimeout(500); |
|
|
|
|
|
const imageBuffer = await page.$eval('#canvas', (canvas) => { |
|
|
return canvas.toDataURL('image/png').split(',')[1]; |
|
|
}); |
|
|
|
|
|
const buffer = Buffer.from(imageBuffer, 'base64'); |
|
|
|
|
|
await browser.close(); |
|
|
return buffer; |
|
|
} |
|
|
|
|
|
app.get('/balogo/v4', async (req, res) => { |
|
|
try { |
|
|
const { textL = "蔚蓝", textR = "档案" } = req.query; |
|
|
const imageBuffer = await baLogov4(textL, textR); |
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
res.send(imageBuffer); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
res.status(500).json({ error: "Error generating logo" }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/rayso', async (req, res) => { |
|
|
const { code, title = 'app.js', theme = 'supabase', language = '', darkMode = 'true', background = 'false' } = req.query; |
|
|
|
|
|
if (!code) |
|
|
return res.status(400).json({ error: 'Parameter "code" (Base64 encoded) diperlukan' }); |
|
|
|
|
|
try { |
|
|
const encodedCode = code.toString('base64'); |
|
|
const url = `https://www.ray.so/#code=${encodedCode}&title=${title}&theme=${theme}&language=${language}&background=${background}&darkMode=${darkMode}`; |
|
|
|
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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 page = await context.newPage(); |
|
|
|
|
|
await page.goto(url); |
|
|
await page.waitForSelector('.Frame_frame__rcr69'); |
|
|
|
|
|
const elementHandle = await page.$('.Frame_frame__rcr69'); |
|
|
const screenshotBuffer = await elementHandle.screenshot({ fullPage: true }); |
|
|
|
|
|
await browser.close(); |
|
|
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
return res.status(200).end(screenshotBuffer); |
|
|
} catch (error) { |
|
|
return res.status(500).json({ error: 'Gagal mengambil screenshot', details: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/carbon', async (req, res) => { |
|
|
const { code, theme } = req.query; |
|
|
|
|
|
if (!code) return res.status(400).json({ error: 'Parameter "code" diperlukan.' }); |
|
|
if (!theme) return res.status(400).json({ error: 'Parameter "theme" diperlukan.' }); |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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', |
|
|
acceptDownloads: true |
|
|
}); |
|
|
const page = await context.newPage(); |
|
|
|
|
|
await page.goto('https://carbon.now.sh/'); |
|
|
await page.click('input[aria-labelledby="theme-dropdown"]'); |
|
|
await page.keyboard.type(theme); |
|
|
await page.keyboard.press('Enter'); |
|
|
await page.click('pre.CodeMirror-line span[role="presentation"]'); |
|
|
await page.keyboard.press('Control+A'); |
|
|
await page.keyboard.press('Backspace'); |
|
|
await page.keyboard.type(code); |
|
|
await page.waitForSelector('button[data-cy="quick-export-button"]'); |
|
|
const [download] = await Promise.all([ |
|
|
page.waitForEvent('download'), |
|
|
page.click('button[data-cy="quick-export-button"]') |
|
|
]); |
|
|
const downloadPath = await download.path(); |
|
|
|
|
|
res.setHeader('Content-Type', 'application/octet-stream'); |
|
|
return res.status(200).sendFile(downloadPath); |
|
|
} catch (error) { |
|
|
return res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
|
} finally { |
|
|
await browser.close(); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/recoded', async (req, res) => { |
|
|
const { code, colors, font, lang, bg } = req.query; |
|
|
|
|
|
if (!code) return res.status(400).json({ error: 'Parameter "code" diperlukan.' }); |
|
|
if (!colors) return res.status(400).json({ error: 'Parameter "colors" diperlukan.' }); |
|
|
if (!font) return res.status(400).json({ error: 'Parameter "font" diperlukan.' }); |
|
|
if (!lang) return res.status(400).json({ error: 'Parameter "lang" diperlukan.' }); |
|
|
if (!bg) return res.status(400).json({ error: 'Parameter "bg" diperlukan.' }); |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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', |
|
|
acceptDownloads: true, |
|
|
}); |
|
|
const page = await context.newPage(); |
|
|
|
|
|
await page.goto('https://recoded.netlify.app/'); |
|
|
await page.fill('textarea.npm__react-simple-code-editor__textarea', code); |
|
|
|
|
|
await page.click('div.input-box[style*="120px"]:has-text("VS Code Dark")'); |
|
|
await page.keyboard.type(colors); |
|
|
await page.keyboard.press('Enter'); |
|
|
|
|
|
await page.click('div[data-testid="font-select"]'); |
|
|
await page.keyboard.type(font); |
|
|
await page.keyboard.press('Enter'); |
|
|
|
|
|
await page.click('div[data-testid="language-select"]'); |
|
|
await page.keyboard.type(lang); |
|
|
await page.keyboard.press('Enter'); |
|
|
|
|
|
await page.click('div[data-testid="background-color-select"]'); |
|
|
await page.keyboard.type(bg); |
|
|
await page.keyboard.press('Enter'); |
|
|
|
|
|
const [download] = await Promise.all([ |
|
|
page.waitForEvent('download'), |
|
|
page.click('button.button.export') |
|
|
]); |
|
|
const downloadPath = await download.path(); |
|
|
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
return res.status(200).send(downloadPath); |
|
|
} catch (error) { |
|
|
return res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
|
} finally { |
|
|
await browser.close(); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/chalkist', async (req, res) => { |
|
|
const { code, title } = req.query; |
|
|
|
|
|
if (!code) return res.status(400).json({ error: 'Parameter "code" diperlukan.' }); |
|
|
if (!title) return res.status(400).json({ error: 'Parameter "title" diperlukan.' }); |
|
|
|
|
|
let browser; |
|
|
try { |
|
|
browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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 page = await context.newPage(); |
|
|
|
|
|
await page.goto('https://chalk.ist/'); |
|
|
|
|
|
|
|
|
const titleSelector = 'div[data-placeholder="Untitled..."]'; |
|
|
await page.focus(titleSelector); |
|
|
await page.keyboard.type(title); |
|
|
|
|
|
|
|
|
const codeSelector = 'textarea.editor.font-config'; |
|
|
await page.fill(codeSelector, code); |
|
|
|
|
|
|
|
|
const screenshotSelector = 'div[data-editor-frame]'; |
|
|
await page.waitForSelector(screenshotSelector); |
|
|
|
|
|
|
|
|
const element = await page.$(screenshotSelector); |
|
|
const screenshotBuffer = await element.screenshot(); |
|
|
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
res.status(200).send(screenshotBuffer); |
|
|
} catch (error) { |
|
|
console.error('Error:', error); |
|
|
res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
|
} finally { |
|
|
if (browser) await browser.close(); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/brat/v2', async (req, res) => { |
|
|
const { text } = req.query; |
|
|
|
|
|
if (!text) return res.status(400).json({ error: 'Parameter "text" diperlukan.' }); |
|
|
|
|
|
try { |
|
|
const browser = await chromium.launch({ headless: true }); |
|
|
const context = await browser.newContext({ |
|
|
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', |
|
|
acceptDownloads: true, |
|
|
}); |
|
|
|
|
|
const page = await context.newPage(); |
|
|
|
|
|
await page.goto('https://brat-generator.net/'); |
|
|
await page.fill('input[class="w-full p-3 text-base border border-gray-200 rounded-lg mb-5 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors"]', text); |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('button[class="px-5 py-3 text-sm font-medium rounded-lg flex items-center justify-between flex-1 transition-all bg-white border-2 border-blue-500 text-blue-600"]', { visible: true }); |
|
|
await page.click('button[class="px-5 py-3 text-sm font-medium rounded-lg flex items-center justify-between flex-1 transition-all bg-white border-2 border-blue-500 text-blue-600"]'); |
|
|
} catch (error) { |
|
|
console.log("Tombol 'White Background' tidak ditemukan atau tidak terlihat, melewati langkah ini."); |
|
|
} |
|
|
|
|
|
try { |
|
|
await page.waitForSelector('button[class="w-full mt-5 px-5 py-3 text-sm font-medium rounded-lg transition-all bg-blue-500 text-white hover:bg-blue-600"]', { visible: true }); |
|
|
await page.click('button[class="w-full mt-5 px-5 py-3 text-sm font-medium rounded-lg transition-all bg-blue-500 text-white hover:bg-blue-600"]'); |
|
|
} catch (error) { |
|
|
console.log("Tombol 'Download Image' tidak ditemukan atau tidak terlihat, melewati langkah ini."); |
|
|
} |
|
|
|
|
|
await page.waitForSelector('canvas[class="absolute top-0 left-0 w-full h-full bg-white shadow-inner"]', { visible: true }); |
|
|
await page.waitForTimeout(800); |
|
|
|
|
|
const canvas = await page.$('canvas[class="absolute top-0 left-0 w-full h-full bg-white shadow-inner"]'); |
|
|
const screenshot = await canvas.screenshot(); |
|
|
|
|
|
res.setHeader('Content-Type', 'image/png'); |
|
|
res.status(200).send(screenshot); |
|
|
|
|
|
} catch (error) { |
|
|
return res.status(500).json({ error: 'Gagal memproses permintaan.', details: error.message }); |
|
|
} finally { |
|
|
await browser.close(); |
|
|
} |
|
|
}); |
|
|
|
|
|
const getRandomName = () => { |
|
|
const names = ['wudy', 'andi', 'budi', 'citra', 'dina']; |
|
|
return names[Math.floor(Math.random() * names.length)]; |
|
|
}; |
|
|
|
|
|
const getRandomClass = () => { |
|
|
const classes = ['A', 'B', 'C', 'D']; |
|
|
return classes[Math.floor(Math.random() * classes.length)]; |
|
|
}; |
|
|
|
|
|
const getRandomText = () => { |
|
|
const texts = ['halo', 'selamat pagi', 'apa kabar?', 'siang', 'malam']; |
|
|
return texts[Math.floor(Math.random() * texts.length)]; |
|
|
}; |
|
|
|
|
|
const getRandomType = () => { |
|
|
return Math.floor(Math.random() * 14) + 1; |
|
|
}; |
|
|
|
|
|
const getRandomDay = () => { |
|
|
const days = ['senin', 'selasa', 'rabu', 'kamis', 'jumat', 'sabtu', 'minggu']; |
|
|
return days[Math.floor(Math.random() * days.length)]; |
|
|
}; |
|
|
|
|
|
const getRandomYear = () => { |
|
|
const currentYear = new Date().getFullYear(); |
|
|
return Math.floor(Math.random() * (currentYear - 2000 + 1)) + 2000; |
|
|
}; |
|
|
app.get('/nulis', async (req, res) => { |
|
|
try { |
|
|
|
|
|
const { |
|
|
waktu = (getRandomYear()).toString(), |
|
|
hari = (getRandomDay()).toString(), |
|
|
nama = (getRandomName()).toString(), |
|
|
kelas = (getRandomClass()).toString(), |
|
|
text = (getRandomText()).toString(), |
|
|
type = (getRandomType()).toString() |
|
|
} = req.query; |
|
|
|
|
|
const diNama3 = nama; |
|
|
const diKelas3 = kelas; |
|
|
const diTulis9 = text; |
|
|
|
|
|
const panjangKalimat9 = diTulis9.replace(/(\S+\s*){1,10}/g, '$&\n'); |
|
|
const panjangNama3 = diNama3.replace(/(\S+\s*){1,10}/g, '$&\n'); |
|
|
const panjangKelas3 = diKelas3.replace(/(\S+\s*){1,10}/g, '$&\n'); |
|
|
|
|
|
const panjangBaris9 = panjangKalimat9.split('\n').slice(0, 30).join('\n'); |
|
|
const panjangBarisNama3 = panjangNama3.split('\n').slice(0, 30).join('\n'); |
|
|
const panjangBarisKelas3 = panjangKelas3.split('\n').slice(0, 30).join('\n'); |
|
|
|
|
|
const months = ['- 1 -', '- 2 -', '- 3 -', '- 4 -', '- 5 -', '- 6 -', '- 7 -', '- 8 -', '- 9 -', '- 10 -', '- 11 -', '- 12 -']; |
|
|
const myDays = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu']; |
|
|
const date = new Date(); |
|
|
const day = date.getDate(); |
|
|
const month = date.getMonth(); |
|
|
const thisDay = myDays[date.getDay()]; |
|
|
const year = date.getFullYear(); |
|
|
|
|
|
const waktu6 = `${day} ${months[month]} ${year}`; |
|
|
const hari6 = thisDay; |
|
|
|
|
|
const inputImagePath = path.join(process.cwd(), 'magernulis1.jpg'); |
|
|
const fontPath = path.join(process.cwd(), 'Zahraaa.ttf'); |
|
|
|
|
|
let theme = null; |
|
|
if (type === '1') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-font', fontPath, |
|
|
'-fill', '#8c1a00', |
|
|
'-size', '1024x784', |
|
|
'-pointsize', '20', |
|
|
'-interline-spacing', '1', |
|
|
'-annotate', '+806+78', hari6, |
|
|
'-annotate', '+806+102', waktu6, |
|
|
'-annotate', '+360+100', panjangBarisNama3, |
|
|
'-annotate', '+360+120', panjangBarisKelas3, |
|
|
'-annotate', '+344+142', panjangBaris9, |
|
|
'png:-' |
|
|
]; |
|
|
} |
|
|
if (type === '2') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-font', fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangBaris9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '3') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '4') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '5') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+78', |
|
|
hari, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+102', |
|
|
waktu, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '6') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+78', |
|
|
hari, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+102', |
|
|
waktu, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '7') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+78', |
|
|
hari, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+102', |
|
|
waktu, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '8') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+78', |
|
|
hari, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+102', |
|
|
waktu, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+360+100', |
|
|
nama, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+360+120', |
|
|
kelas, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
if (type === '9') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+78', |
|
|
hari, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+102', |
|
|
waktu, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+360+100', |
|
|
nama, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+360+120', |
|
|
kelas, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#001675', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
]; |
|
|
} |
|
|
if (type === '10') { |
|
|
theme = [ |
|
|
inputImagePath, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+78', |
|
|
hari, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+806+102', |
|
|
waktu, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+360+100', |
|
|
nama, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'18', |
|
|
'-interline-spacing', |
|
|
'1', |
|
|
'-annotate', |
|
|
'+360+120', |
|
|
kelas, |
|
|
'-font', |
|
|
fontPath, |
|
|
'-fill', |
|
|
'#8c1a00', |
|
|
'-size', |
|
|
'1024x784', |
|
|
'-pointsize', |
|
|
'20', |
|
|
'-interline-spacing', |
|
|
'-7.5', |
|
|
'-annotate', |
|
|
'+344+142', |
|
|
panjangKalimat9, |
|
|
'png:-' |
|
|
] |
|
|
} |
|
|
|
|
|
const splitText = text.replace(/(\S+\s*){1,9}/g, '$&\n') |
|
|
const fixHeight = splitText.split('\n').slice(0, 31).join('\n') |
|
|
const sebelumkiri = path.join(process.cwd(), 'sebelumkiri.jpg'); |
|
|
const sebelumkanan = path.join(process.cwd(), 'sebelumkanan.jpg'); |
|
|
const foliokanan = path.join(process.cwd(), 'foliokanan.jpg'); |
|
|
const foliokiri = path.join(process.cwd(), 'foliokiri.jpg'); |
|
|
const fontPathB = path.join(process.cwd(), 'Indie-Flower.ttf'); |
|
|
|
|
|
if (type === '11') { |
|
|
theme = [ |
|
|
sebelumkiri, |
|
|
'-font', |
|
|
fontPathB, |
|
|
'-size', |
|
|
'960x1280', |
|
|
'-pointsize', |
|
|
'22', |
|
|
'-interline-spacing', |
|
|
'2', |
|
|
'-annotate', |
|
|
'+140+153', |
|
|
fixHeight, |
|
|
'png:-' |
|
|
]; |
|
|
} |
|
|
|
|
|
if (type === '12') { |
|
|
theme = [ |
|
|
sebelumkanan, |
|
|
'-font', |
|
|
fontPathB, |
|
|
'-size', |
|
|
'960x1280', |
|
|
'-pointsize', |
|
|
'23', |
|
|
'-interline-spacing', |
|
|
'2', |
|
|
'-annotate', |
|
|
'+128+129', |
|
|
fixHeight, |
|
|
'png:-' |
|
|
]; |
|
|
} |
|
|
|
|
|
if (type === '13') { |
|
|
theme = [ |
|
|
foliokanan, |
|
|
'-font', |
|
|
fontPathB, |
|
|
'-size', |
|
|
'960x1280', |
|
|
'-pointsize', |
|
|
'23', |
|
|
'-interline-spacing', |
|
|
'3', |
|
|
'-annotate', |
|
|
'+89+190', |
|
|
fixHeight, |
|
|
'png:-' |
|
|
]; |
|
|
} |
|
|
if (type === '14') { |
|
|
theme = [ |
|
|
foliokiri, |
|
|
'-font', |
|
|
fontPathB, |
|
|
'-size', |
|
|
'1720x1280', |
|
|
'-pointsize', |
|
|
'23', |
|
|
'-interline-spacing', |
|
|
'4', |
|
|
'-annotate', |
|
|
'+48+185', |
|
|
fixHeight, |
|
|
'png:-' |
|
|
]; |
|
|
} |
|
|
const convert = spawn('convert', theme); |
|
|
const collectImageData = () => { |
|
|
return new Promise((resolve, reject) => { |
|
|
let imageData = Buffer.alloc(0); |
|
|
|
|
|
convert.stdout.on('data', (data) => { |
|
|
imageData = Buffer.concat([imageData, data]); |
|
|
}); |
|
|
|
|
|
convert.on('close', (code) => { |
|
|
if (code !== 0) { |
|
|
return reject(new Error(`ImageMagick exited with code ${code}`)); |
|
|
} |
|
|
resolve(imageData); |
|
|
}); |
|
|
|
|
|
convert.on('error', (err) => { |
|
|
reject(err); |
|
|
}); |
|
|
}); |
|
|
}; |
|
|
|
|
|
const imageData = await collectImageData(); |
|
|
res.set('Content-Type', 'image/png'); |
|
|
res.send(imageData); |
|
|
convert.kill('SIGTERM'); |
|
|
} catch (err) { |
|
|
console.error(err); |
|
|
res.status(500).json({ error: 'Terjadi kesalahan server.' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
Readable.fromEventEmitter = function (emitter, events) { |
|
|
const readable = new Readable({ |
|
|
read() {} |
|
|
}); |
|
|
|
|
|
const onData = (data) => { |
|
|
readable.push(data); |
|
|
}; |
|
|
const onEnd = () => { |
|
|
readable.push(null); |
|
|
}; |
|
|
const onError = (err) => { |
|
|
readable.emit('error', err); |
|
|
}; |
|
|
|
|
|
emitter.on(events[0], onData); |
|
|
emitter.on(events[1], onEnd); |
|
|
emitter.on(events[2], onError); |
|
|
|
|
|
return readable; |
|
|
}; |
|
|
|
|
|
const iask = { |
|
|
turndownService: new TurndownService(), |
|
|
|
|
|
request(query, mode, options) { |
|
|
if (typeof query === 'object') { |
|
|
return query; |
|
|
} |
|
|
return { q: query, mode, ...options }; |
|
|
}, |
|
|
|
|
|
eventx(query) { |
|
|
const pipe = new EventEmitter(); |
|
|
const getQueryString = () => { |
|
|
const params = new URLSearchParams(query); |
|
|
return params.toString(); |
|
|
}; |
|
|
return { query, pipe, getQueryString }; |
|
|
}, |
|
|
|
|
|
parseChunk(message) { |
|
|
try { |
|
|
const data = JSON.parse(message); |
|
|
const diff = data.pop(); |
|
|
let content = ''; |
|
|
let stop = false; |
|
|
|
|
|
if (diff?.e?.[0]?.[1]?.data) { |
|
|
content = diff.e[0][1].data.replace(/<br\/>/g, '\n'); |
|
|
console.log(content); |
|
|
} |
|
|
|
|
|
if (diff?.response?.rendered?.[2]?.[1]?.[4]?.[4]) { |
|
|
content = this.turndownService.turndown(diff.response.rendered[2][1][4][4]); |
|
|
stop = true; |
|
|
console.log(content); |
|
|
} |
|
|
|
|
|
return { content, stop }; |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
return { content: '', stop: true }; |
|
|
} |
|
|
}, |
|
|
|
|
|
inspect(response) { |
|
|
const $ = cheerio.load(response.data); |
|
|
const phxElement = $('[id^="phx-"]').first(); |
|
|
const joinMessage = JSON.stringify([ |
|
|
null, |
|
|
null, |
|
|
`lv:${phxElement.attr('id')}`, |
|
|
'phx_join', |
|
|
{ |
|
|
url: response.request.res.responseUrl, |
|
|
session: phxElement.attr('data-phx-session'), |
|
|
}, |
|
|
]); |
|
|
const csrfToken = $('meta[name="csrf-token"]').attr('content'); |
|
|
return { joinMessage, csrfToken }; |
|
|
}, |
|
|
|
|
|
async cws(queryString, jar) { |
|
|
const client = wrapper(axios.create({ jar })); |
|
|
try { |
|
|
const response = await client.get(`https://iask.ai?${queryString}`); |
|
|
const { joinMessage, csrfToken } = this.inspect(response); |
|
|
const cookies = await jar.getCookieString('https://iask.ai'); |
|
|
console.log(cookies); |
|
|
|
|
|
const ws = new WebSocket(`wss://iask.ai/live/websocket?_csrf_token=${csrfToken}&vsn=2.0.0`, { |
|
|
headers: { |
|
|
'Cookie': cookies |
|
|
} |
|
|
}); |
|
|
|
|
|
await new Promise((resolve, reject) => { |
|
|
ws.on('open', () => { |
|
|
console.log('Websocket berhasil terhubung...'); |
|
|
ws.send(joinMessage); |
|
|
resolve(); |
|
|
}); |
|
|
ws.on('error', (err) => { |
|
|
console.error('Tidak dapat terhubung ke WebSocket:', err); |
|
|
reject(err); |
|
|
}); |
|
|
}); |
|
|
|
|
|
return ws; |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
throw error; |
|
|
} |
|
|
}, |
|
|
|
|
|
async handleJoin(event) { |
|
|
try { |
|
|
const jar = new CookieJar(); |
|
|
const ws = await this.cws(event.getQueryString(), jar); |
|
|
const pipe = event.pipe; |
|
|
|
|
|
ws.on('message', (message) => { |
|
|
console.log(message.toString()); |
|
|
const { content, stop } = this.parseChunk(message.toString()); |
|
|
if (content !== '') { |
|
|
pipe.emit('data', content); |
|
|
} |
|
|
if (stop) { |
|
|
pipe.emit('end'); |
|
|
ws.close(); |
|
|
} |
|
|
}); |
|
|
|
|
|
ws.on('close', () => { |
|
|
console.log('Websocket terputus...'); |
|
|
pipe.emit('end'); |
|
|
}); |
|
|
|
|
|
ws.on('error', (err) => { |
|
|
console.error(err); |
|
|
pipe.emit('error', err); |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
event.pipe.emit('error', error); |
|
|
} |
|
|
}, |
|
|
|
|
|
dispatcher: new EventEmitter(), |
|
|
|
|
|
setupDispatcher() { |
|
|
this.dispatcher.on('JoinEvent', (event) => this.handleJoin(event)); |
|
|
}, |
|
|
|
|
|
ask: async (query, mode = 'question', options = {}) => { |
|
|
const summon = iask.request(query, mode, options); |
|
|
const event = iask.eventx(summon); |
|
|
iask.dispatcher.emit('JoinEvent', event); |
|
|
return Readable.fromEventEmitter(event.pipe, ['data', 'end', 'error']); |
|
|
}, |
|
|
|
|
|
init() { |
|
|
this.setupDispatcher(); |
|
|
} |
|
|
}; |
|
|
|
|
|
async function Ask(query, mode = 'question', options = { detail_level: 'detailed' }) { |
|
|
try { |
|
|
iask.init(); |
|
|
const stream = await iask.ask(query, mode, options); |
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
let result = ''; |
|
|
|
|
|
stream.on('data', (chunk) => { |
|
|
result += chunk; |
|
|
}); |
|
|
|
|
|
stream.on('end', () => { |
|
|
console.log('Permintaan ke Websocket iASK berhasil..'); |
|
|
console.log('Result:', result); |
|
|
resolve({ result }); |
|
|
}); |
|
|
|
|
|
stream.on('error', (err) => { |
|
|
console.error(err); |
|
|
reject({ err }); |
|
|
}); |
|
|
}); |
|
|
|
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
app.post('/ask', async (req, res) => { |
|
|
const { query, mode = 'question', ...options } = req.body; |
|
|
|
|
|
if (!query) { |
|
|
return res.status(400).json({ error: 'Query parameter is required' }); |
|
|
} |
|
|
|
|
|
try { |
|
|
const result = await Ask(query, mode, options); |
|
|
|
|
|
return res.status(200).json(result); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
return res.status(500).json({ error: 'Failed to handle the query' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/ask', async (req, res) => { |
|
|
const { query, mode = 'question', ...options } = req.query; |
|
|
|
|
|
if (!query) { |
|
|
return res.status(400).json({ error: 'Query parameter is required' }); |
|
|
} |
|
|
|
|
|
try { |
|
|
const result = await Ask(query, mode, options); |
|
|
|
|
|
return res.status(200).json(result); |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
return res.status(500).json({ error: 'Failed to handle the query' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post("/playwright", async (req, res) => { |
|
|
const { code, timeout = 300000 } = req.body; |
|
|
if (!code) return res.status(400).json({ error: "Kode tidak boleh kosong" }); |
|
|
|
|
|
let output = []; |
|
|
const originalLog = console.log; |
|
|
|
|
|
console.log = (...args) => { |
|
|
const message = args.map(arg => (typeof arg === "object" ? JSON.stringify(arg) : arg)).join(" "); |
|
|
output.push(message); |
|
|
originalLog.apply(console, args); |
|
|
}; |
|
|
|
|
|
const vm = new NodeVM({ |
|
|
console: "inherit", |
|
|
sandbox: {}, |
|
|
require: { |
|
|
external: true, |
|
|
builtin: ["fs", "path"], |
|
|
root: "./", |
|
|
mock: { |
|
|
playwright: { chromium }, |
|
|
}, |
|
|
}, |
|
|
}); |
|
|
|
|
|
const script = ` |
|
|
module.exports = async () => { |
|
|
${code} |
|
|
}; |
|
|
`; |
|
|
|
|
|
let runCode; |
|
|
try { |
|
|
runCode = vm.run(script, "sandbox.js"); |
|
|
} catch (error) { |
|
|
console.log = originalLog; |
|
|
return res.status(500).json({ error: "Gagal menjalankan kode", details: error.message }); |
|
|
} |
|
|
|
|
|
try { |
|
|
await runCode(); |
|
|
|
|
|
const startTime = Date.now(); |
|
|
while (output.length === 0 && Date.now() - startTime < timeout) { |
|
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
|
|
} |
|
|
|
|
|
const result = output.length > 0 ? output.pop() : "Tidak ada hasil yang ditemukan."; |
|
|
console.log = originalLog; |
|
|
|
|
|
res.json({ output: result }); |
|
|
} catch (err) { |
|
|
console.log = originalLog; |
|
|
res.status(500).json({ error: "Kesalahan saat menjalankan kode", details: err.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post("/playwright/v2", async (req, res) => { |
|
|
const { code, timeout = 300000 } = req.body; |
|
|
if (!code) return res.status(400).json({ error: "Kode tidak boleh kosong" }); |
|
|
|
|
|
let output = []; |
|
|
const originalLog = console.log; |
|
|
|
|
|
console.log = (...args) => { |
|
|
const message = args.map(arg => (typeof arg === "object" ? JSON.stringify(arg) : arg)).join(" "); |
|
|
output.push(message); |
|
|
originalLog.apply(console, args); |
|
|
}; |
|
|
|
|
|
let runCode; |
|
|
try { |
|
|
|
|
|
runCode = eval(` |
|
|
(async () => { |
|
|
${code} |
|
|
})(); |
|
|
`); |
|
|
} catch (error) { |
|
|
console.log = originalLog; |
|
|
return res.status(500).json({ error: "Gagal menjalankan kode", details: error.message }); |
|
|
} |
|
|
|
|
|
try { |
|
|
await runCode; |
|
|
|
|
|
const startTime = Date.now(); |
|
|
while (output.length === 0 && Date.now() - startTime < timeout) { |
|
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
|
|
} |
|
|
|
|
|
const result = output.length > 0 ? output.pop() : "Tidak ada hasil yang ditemukan."; |
|
|
console.log = originalLog; |
|
|
|
|
|
res.json({ output: result }); |
|
|
} catch (err) { |
|
|
console.log = originalLog; |
|
|
res.status(500).json({ error: "Kesalahan saat menjalankan kode", details: err.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/compile', async (req, res) => { |
|
|
const { code, language } = req.body; |
|
|
|
|
|
try { |
|
|
const output = await CompileFile(language, code); |
|
|
|
|
|
const stdout = output.stdout || ''; |
|
|
res.json({ output: stdout }); |
|
|
} catch (error) { |
|
|
res.status(500).json({ error: error.toString() }); |
|
|
} |
|
|
}); |
|
|
|
|
|
class ColorifyAI { |
|
|
constructor() { |
|
|
this.ws = null; |
|
|
this.sessionHash = this.generateHash(); |
|
|
} |
|
|
|
|
|
generateHash() { |
|
|
return crypto.randomBytes(8).toString('hex'); |
|
|
} |
|
|
|
|
|
async imageToBase64(imageUrl = "https://i.pinimg.com/236x/21/81/c4/2181c4e2d51db79bb2ac000dcac2df90.jpg") { |
|
|
try { |
|
|
const res = await axios.get(imageUrl, { responseType: 'arraybuffer' }); |
|
|
return `data:image/webp;base64,${Buffer.from(res.data).toString('base64')}`; |
|
|
} catch (error) { |
|
|
console.error('Error converting image to base64:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
async start(options) { |
|
|
return new Promise((resolve, reject) => { |
|
|
let wsUrl = ""; |
|
|
if (options.type === "img2color") { |
|
|
wsUrl = "wss://colorifyai.art/demo-auto-coloring/queue/join"; |
|
|
} else if (options.type === "txt2img") { |
|
|
wsUrl = "wss://colorifyai.art/demo-colorify-text2img/queue/join"; |
|
|
} else if (options.type === "img2img") { |
|
|
wsUrl = "wss://colorifyai.art/demo-colorify-img2img/queue/join"; |
|
|
} else { |
|
|
return reject(new Error("Invalid type. Use 'img2color', 'txt2img', or 'img2img'.")); |
|
|
} |
|
|
|
|
|
this.ws = new WebSocket(wsUrl, { |
|
|
headers: { |
|
|
"Upgrade": "websocket", |
|
|
"Origin": "https://colorifyai.art", |
|
|
"Cache-Control": "no-cache", |
|
|
"Accept-Language": "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7", |
|
|
"Pragma": "no-cache", |
|
|
"Connection": "Upgrade", |
|
|
"Sec-WebSocket-Key": crypto.randomBytes(16).toString("base64"), |
|
|
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36", |
|
|
"Sec-WebSocket-Version": "13", |
|
|
"Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits", |
|
|
"Accept": "application/json, text/plain, */*", |
|
|
"Referer": "https://colorifyai.art", |
|
|
} |
|
|
}); |
|
|
|
|
|
this.ws.on("open", () => { |
|
|
console.log("Connected to WebSocket, waiting for send_hash..."); |
|
|
}); |
|
|
|
|
|
this.ws.on("message", async (data) => { |
|
|
const response = JSON.parse(data.toString()); |
|
|
|
|
|
if (response.msg === "send_hash") { |
|
|
console.log("Sending session_hash..."); |
|
|
this.ws.send(JSON.stringify({ session_hash: this.sessionHash })); |
|
|
} |
|
|
|
|
|
if (response.msg === "send_data") { |
|
|
console.log("Sending data..."); |
|
|
try { |
|
|
let requestData = {}; |
|
|
if (options.type === "img2color" || options.type === "img2img") { |
|
|
const base64Image = await this.imageToBase64(options.imageUrl); |
|
|
requestData = { |
|
|
data: { |
|
|
source_image: base64Image, |
|
|
prompt: options.prompt || "(masterpiece), best quality", |
|
|
request_from: 10 |
|
|
} |
|
|
}; |
|
|
} else { |
|
|
requestData = { |
|
|
data: { |
|
|
prompt: options.prompt, |
|
|
style: options.style || "default", |
|
|
aspect_ratio: options.aspectRatio || "9:16", |
|
|
request_from: 10 |
|
|
} |
|
|
}; |
|
|
} |
|
|
this.ws.send(JSON.stringify(requestData)); |
|
|
} catch (err) { |
|
|
reject(err); |
|
|
} |
|
|
} |
|
|
|
|
|
if (response.msg === "process_completed") { |
|
|
console.log("Process completed:", response); |
|
|
resolve({ |
|
|
baseUrl: 'https://temp.colorifyai.art', |
|
|
...(typeof response.output === 'object' && response.output !== null ? response.output : {}) |
|
|
}); |
|
|
this.ws.close(); |
|
|
} |
|
|
}); |
|
|
|
|
|
this.ws.on("error", (error) => { |
|
|
console.error("WebSocket Error:", error); |
|
|
reject(error); |
|
|
}); |
|
|
|
|
|
this.ws.on("close", () => { |
|
|
console.log("WebSocket closed"); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
app.post('/colorifyai', async (req, res) => { |
|
|
const params = req.body; |
|
|
const validTypes = ["img2color", "txt2img", "img2img"]; |
|
|
|
|
|
if (!params.type || !validTypes.includes(params.type.toLowerCase())) { |
|
|
return res.status(400).json({ error: "Invalid type. Allowed types: img2color, txt2img, img2img" }); |
|
|
} |
|
|
|
|
|
const ai = new ColorifyAI(); |
|
|
try { |
|
|
const data = await ai.start(params); |
|
|
return res.status(200).json(data); |
|
|
} catch (error) { |
|
|
return res.status(500).json({ error: "Error during WebSocket request", details: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
app.get('/colorifyai', async (req, res) => { |
|
|
const params = req.query; |
|
|
const validTypes = ["img2color", "txt2img", "img2img"]; |
|
|
|
|
|
if (!params.type || !validTypes.includes(params.type.toLowerCase())) { |
|
|
return res.status(400).json({ error: "Invalid type. Allowed types: img2color, txt2img, img2img" }); |
|
|
} |
|
|
|
|
|
const ai = new ColorifyAI(); |
|
|
try { |
|
|
const data = await ai.start(params); |
|
|
return res.status(200).json(data); |
|
|
} catch (error) { |
|
|
return res.status(500).json({ error: "Error during WebSocket request", details: error.message }); |
|
|
} |
|
|
}); |
|
|
|
|
|
const PORT = process.env.PORT || 7860; |
|
|
|
|
|
app.listen(PORT, async () => { |
|
|
console.log(`Server running on port ${PORT}`); |
|
|
await utils.initialize(); |
|
|
}); |
|
|
|
|
|
process.on('SIGINT', async () => { |
|
|
await utils.close(); |
|
|
process.exit(0); |
|
|
}); |