Skip to content

Commit

Permalink
[socky] init
Browse files Browse the repository at this point in the history
  • Loading branch information
f3l1x committed Mar 26, 2021
1 parent 3c9b197 commit 11d6390
Show file tree
Hide file tree
Showing 14 changed files with 1,930 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -48,6 +48,7 @@ and givin something back to the community. Enjoy it!
- [**pdfx**](services/pdfx) - generates PDF from many given sources (URL, raw, POST) [[👀🕶](https://pdfx.vercel.app)]
- [**qrx**](services/qrx) - generates QR-codes [[👀🕶](https://qrx.vercel.app)]
- [**showcase**](services/showcase) - show all possible UI components for your integratins [[👀🕶](https://vercel.com/integrations/showcase)]
- [**socky**](services/socky) - generate screenshots from social networks [[👀🕶](https://socky.vercel.app)]

## Runtimes

Expand Down
8 changes: 8 additions & 0 deletions services/socky/.gitignore
@@ -0,0 +1,8 @@
# NodeJS
/node_modules

# App
/dist

# Vercel
.vercel
3 changes: 3 additions & 0 deletions services/socky/.vercelignore
@@ -0,0 +1,3 @@
node_modules
dist
tests
14 changes: 14 additions & 0 deletions services/socky/README.md
@@ -0,0 +1,14 @@
# Juicy(fx) | Socky

Generates image for social networks (Facebook, Twitter).
> https://socky.vercel.app
## Usage

> https://socky.vercel.app/facebook/page/nettefw
![](https://fcbk.vercel.app/facebook/page/nettefw)

> https://socky.vercel.app/twitter/profile/nettefw
![](https://fcbk.vercel.app/twitter/profile/nettefw)
35 changes: 35 additions & 0 deletions services/socky/api/_lib/chrome.ts
@@ -0,0 +1,35 @@
import { createBrowser } from "./chromium";

export async function getImage(chromeOptions: ChromeOptions, browserOptions: ChromeLaunchOptions = {}): Promise<Buffer> {
let content = null;
let browser = null;
let page = null;

try {
browser = await createBrowser(browserOptions);
page = await browser.newPage();
await page.setContent(chromeOptions.content);

if (chromeOptions.wait) {
await page.waitForTimeout(chromeOptions.wait);
}

content = await page.screenshot();
} catch (error) {
throw error;
} finally {
if (page !== null) {
await page.close();
}
if (browser !== null) {
await browser.close();
}
}

if (Buffer.isBuffer(content)) {
return content;
} else {
return Buffer.from(content as string);
}
}

59 changes: 59 additions & 0 deletions services/socky/api/_lib/chromium.ts
@@ -0,0 +1,59 @@
import { launch, Browser } from "puppeteer-core";
import chromeAws from "chrome-aws-lambda";

export async function createBrowser(args: ChromeLaunchOptions = {}): Promise<Browser> {
const defaults: ChromeLaunchOptions = {
defaultViewport: {
deviceScaleFactor: 1,
width: 1280,
height: 640,
},
ignoreHTTPSErrors: true,
};
let options: ChromeLaunchOptions = {};

if (isDev()) {
options = {
...defaults,
...args,
...{
args: [],
executablePath: lookupChrome(),
headless: true,
}
};
} else {
options = {
...defaults,
...args,
...{
args: chromeAws.args,
executablePath: await chromeAws.executablePath,
headless: chromeAws.headless,
}
};
}

// Extra fonts
await chromeAws.font('https://rawcdn.githack.com/rsms/inter/378ab05866aab4cb0d71a5f502961d6a54da0770/docs/font-files/Inter-Regular.woff2');
await chromeAws.font('https://rawcdn.githack.com/rsms/inter/378ab05866aab4cb0d71a5f502961d6a54da0770/docs/font-files/Inter-SemiBold.woff2');
await chromeAws.font('https://rawcdn.githack.com/rsms/inter/378ab05866aab4cb0d71a5f502961d6a54da0770/docs/font-files/Inter-Bold.woff2');

return await launch(options);
}

function lookupChrome(): string {
if (process.platform === 'win32') {
return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe';
}

if (process.platform === 'darwin') {
return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
}

return 'google-chrome';
}

function isDev(): boolean {
return process.env.NOW_REGION === undefined || process.env.NOW_REGION === 'dev1';
}
20 changes: 20 additions & 0 deletions services/socky/api/_lib/types.d.ts
@@ -0,0 +1,20 @@
interface FacebookPageContext {
page: string,
tabs: string,
theme: string,
width: number,
height: number,
}
interface TwitterProfileContext {
profile: string,
theme: string,
width: number,
height: number,
}

interface ChromeOptions {
content: string,
wait?: number
}

type ChromeLaunchOptions = import('puppeteer-core').LaunchOptions & import('puppeteer-core').BrowserLaunchArgumentOptions & import('puppeteer-core').BrowserConnectOptions;
5 changes: 5 additions & 0 deletions services/socky/api/_lib/utils.ts
@@ -0,0 +1,5 @@
import { VercelRequest } from "@vercel/node";

export function queryNumber(req: VercelRequest, key: string, defaults: any): number {
return parseInt(<string>req.query[key] || defaults);
}
81 changes: 81 additions & 0 deletions services/socky/api/facebook/page.ts
@@ -0,0 +1,81 @@
import { VercelRequest, VercelResponse } from '@vercel/node';
import { getImage } from "./../_lib/chrome";
import { queryNumber } from './../_lib/utils';

const CACHE_BROWSER = 60 * 60 * 24 * 2;
const CACHE_CDN = 60 * 60 * 24 * 7;

export default async function handler(req: VercelRequest, res: VercelResponse) {
console.log("HTTP", req.url);

if (req.query._p) {
generateImage(req, res);
} else {
res.statusCode = 400;
res.setHeader("Content-Type", "text/html");
res.end(`<h1>Client Error</h1><p>Provide ?_p=Some text</p>`);
}
}

async function generateImage(req: VercelRequest, res: VercelResponse): Promise<void> {
const width = queryNumber(req, 'width', 320);
const height = queryNumber(req, 'height', 800);

try {
const template = createTemplate({
page: <string>req.query._p,
tabs: <string>req.query.tabs || 'timeline',
theme: <string>req.query.theme || 'light',
width,
height,
});
const file = await getImage(
{
content: template,
wait: queryNumber(req, 'wait', 3000)
},
{
defaultViewport: {
deviceScaleFactor: 1.5,
width,
height,
}
}
);

res.statusCode = 200;
res.setHeader("Content-Type", "image/png");
res.setHeader('Cache-Control', `max-age=${CACHE_BROWSER}, s-maxage=${CACHE_CDN}, public`);
res.end(file);
} catch (e) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/html");
res.end(`<h1>Server Error</h1><p>Sorry, there was a problem</p><p>${e.message}</p>`);

console.error(e);
console.error(e.message);
}
}

function createTemplate(ctx: FacebookPageContext): string {
return `
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Socky/Facebook</title>
</head>
<body style="margin: 0; padding: 0">
<iframe
src="https://www.facebook.com/plugins/page.php?href=https://facebook.com/${ctx.page}&tabs=${ctx.tabs}&width=${ctx.width}&height=${ctx.height}&colorscheme=${ctx.theme}&locale=cs_CZ"
width="${ctx.width}"
height="${ctx.height}"
style="border:none;overflow:hidden"
scrolling="no"
frameborder="0"
></iframe>
</body>
</html>
`;
};
73 changes: 73 additions & 0 deletions services/socky/api/twitter/profile.ts
@@ -0,0 +1,73 @@
import { VercelRequest, VercelResponse } from '@vercel/node';
import { getImage } from "./../_lib/chrome";
import { queryNumber } from './../_lib/utils';

const CACHE_BROWSER = 60 * 60 * 24 * 2;
const CACHE_CDN = 60 * 60 * 24 * 7;

export default async function handler(req: VercelRequest, res: VercelResponse) {
console.log("HTTP", req.url);

if (req.query._p) {
generateImage(req, res);
} else {
res.statusCode = 400;
res.setHeader("Content-Type", "text/html");
res.end(`<h1>Client Error</h1><p>Provide ?_p=Some text</p>`);
}
}

async function generateImage(req: VercelRequest, res: VercelResponse): Promise<void> {
const width = queryNumber(req, 'width', 320);
const height = queryNumber(req, 'height', 800);

try {
const template = createTemplate({
profile: <string>req.query._p,
theme: <string>req.query.theme || 'light',
width,
height,
});
const file = await getImage(
{
content: template,
wait: queryNumber(req, 'wait', 3000)
},
{
defaultViewport: {
deviceScaleFactor: 1.5,
width,
height,
}
}
);

res.statusCode = 200;
res.setHeader("Content-Type", "image/png");
res.setHeader('Cache-Control', `max-age=${CACHE_BROWSER}, s-maxage=${CACHE_CDN}, public`);
res.end(file);
} catch (e) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/html");
res.end(`<h1>Server Error</h1><p>Sorry, there was a problem</p><p>${e.message}</p>`);

console.error(e);
console.error(e.message);
}
}

function createTemplate(ctx: TwitterProfileContext): string {
return `
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Socky/Twitter</title>
</head>
<body style="margin: 0; padding: 0; background: ${ctx.theme === 'dark' ? '#293033' : '#fffff'}">
<a data-lang="cs" data-height="${ctx.height}" data-width=${ctx.width} data-theme="${ctx.theme}" class="twitter-timeline" href="https://twitter.com/${ctx.profile}?ref_src=twsrc%5Etfw">Tweets by ${ctx.profile}</a><script src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</body>
</html>
`;
};

0 comments on commit 11d6390

Please sign in to comment.