mirror of https://github.com/sgoudham/carbon.git
remove
parent
bc3d5e85a9
commit
d472434388
@ -1,7 +0,0 @@
|
|||||||
TWITTER_CONSUMER_KEY=
|
|
||||||
TWITTER_CONSUMER_SECRET=
|
|
||||||
TWITTER_ACCESS_TOKEN_KEY=
|
|
||||||
TWITTER_ACCESS_TOKEN_SECRET=
|
|
||||||
UNSPLASH_ACCESS_KEY=
|
|
||||||
UNSPLASH_SECRET_KEY=
|
|
||||||
UNSPLASH_CALLBACK_URL=
|
|
@ -1,38 +0,0 @@
|
|||||||
FROM node:9-alpine
|
|
||||||
|
|
||||||
# Source https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
|
|
||||||
# Installs latest Chromium package.
|
|
||||||
ENV CHROME_BIN=/usr/bin/chromium-browser
|
|
||||||
RUN apk update && apk upgrade && \
|
|
||||||
echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
|
|
||||||
echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
|
|
||||||
apk add --no-cache \
|
|
||||||
chromium@edge \
|
|
||||||
nss@edge \
|
|
||||||
freetype@edge \
|
|
||||||
harfbuzz@edge
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY package.json ./
|
|
||||||
COPY yarn.lock ./
|
|
||||||
|
|
||||||
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
|
|
||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
|
|
||||||
|
|
||||||
RUN yarn
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Add user so we don't need --no-sandbox.
|
|
||||||
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
|
|
||||||
&& mkdir -p /home/pptruser/Downloads \
|
|
||||||
&& chown -R pptruser:pptruser /home/pptruser \
|
|
||||||
&& chown -R pptruser:pptruser /app
|
|
||||||
|
|
||||||
# Run everything after as non-privileged user.
|
|
||||||
USER pptruser
|
|
||||||
|
|
||||||
ENV NODE_ENV production
|
|
||||||
EXPOSE 4000
|
|
||||||
CMD [ "node", "server.js" ]
|
|
@ -1,75 +0,0 @@
|
|||||||
/* global domtoimage */
|
|
||||||
const ARBITRARY_WAIT_TIME = 250
|
|
||||||
|
|
||||||
module.exports = browser => async (req, res) => {
|
|
||||||
const page = await browser.newPage()
|
|
||||||
const { state } = req.body
|
|
||||||
|
|
||||||
if (!state) res.status(400).send('Invalid Request')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const path = require.resolve('dom-to-image')
|
|
||||||
|
|
||||||
await page.goto(`http://carbon.now.sh?state=${state}`)
|
|
||||||
await page.addScriptTag({ path })
|
|
||||||
|
|
||||||
// wait for page to detect language
|
|
||||||
await delay(ARBITRARY_WAIT_TIME)
|
|
||||||
|
|
||||||
const targetElement = await page.$('.export-container')
|
|
||||||
|
|
||||||
const dataUrl = await page.evaluate((target = document) => {
|
|
||||||
const query = new URLSearchParams(document.location.search.slice(1))
|
|
||||||
|
|
||||||
const EXPORT_SIZES_HASH = {
|
|
||||||
'1x': '1',
|
|
||||||
'2x': '2',
|
|
||||||
'4x': '4'
|
|
||||||
}
|
|
||||||
|
|
||||||
const exportSize = EXPORT_SIZES_HASH[query.get('es')] || '2'
|
|
||||||
|
|
||||||
target.querySelectorAll('span[role="presentation"]').forEach(node => {
|
|
||||||
if (node.innerText && node.innerText.match(/%\S\S/)) {
|
|
||||||
node.innerText = encodeURIComponent(node.innerText)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const width = target.offsetWidth * exportSize
|
|
||||||
const height = query.get('si')
|
|
||||||
? target.offsetWidth * exportSize
|
|
||||||
: target.offsetHeight * exportSize
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
style: {
|
|
||||||
transform: `scale(${exportSize})`,
|
|
||||||
'transform-origin': 'center',
|
|
||||||
background: query.get('si') ? query.get('bg') : 'none'
|
|
||||||
},
|
|
||||||
filter: n => {
|
|
||||||
if (n.className) {
|
|
||||||
return String(n.className).indexOf('eliminateOnRender') < 0
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
}
|
|
||||||
|
|
||||||
return domtoimage.toPng(target, config)
|
|
||||||
}, targetElement)
|
|
||||||
|
|
||||||
res.status(200).send(dataUrl)
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.error(e)
|
|
||||||
res.status(500).send()
|
|
||||||
} finally {
|
|
||||||
await page.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise(r => setTimeout(r, ms))
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* See oEmbed standard here: https://oembed.com/
|
|
||||||
*/
|
|
||||||
const url = require('url')
|
|
||||||
|
|
||||||
const toIFrame = (url, width, height) =>
|
|
||||||
`<iframe
|
|
||||||
src="https://carbon.now.sh/embed${url}"
|
|
||||||
style="transform:scale(0.7); width:${width}px; height:${height}px; border:0; overflow:hidden;"
|
|
||||||
sandbox="allow-scripts allow-same-origin">
|
|
||||||
</iframe>
|
|
||||||
`
|
|
||||||
|
|
||||||
module.exports = (req, res) => {
|
|
||||||
let embedUrl = req.query.url
|
|
||||||
|
|
||||||
try {
|
|
||||||
embedUrl = decodeURIComponent(req.query.url)
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log(e)
|
|
||||||
/* URL is already decoded */
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { query } = url.parse(embedUrl)
|
|
||||||
|
|
||||||
const width = Math.min(Number(req.query.maxwidth) || Infinity, 1024)
|
|
||||||
const height = Math.min(Number(req.query.maxheight) || Infinity, 473)
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
version: '1.0',
|
|
||||||
type: 'rich',
|
|
||||||
provider_name: 'Carbon',
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
html: toIFrame(`?${query}`, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json(obj)
|
|
||||||
} catch (e) {
|
|
||||||
return res.status(500).send()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
const Twitter = require('twit')
|
|
||||||
const morph = require('morphmorph')
|
|
||||||
|
|
||||||
const RATE_LIMIT_CODE = 420
|
|
||||||
const MAX_ALT_TEXT_LENGTH = 420
|
|
||||||
|
|
||||||
let client
|
|
||||||
try {
|
|
||||||
client = new Twitter({
|
|
||||||
consumer_key: process.env.TWITTER_CONSUMER_KEY,
|
|
||||||
consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
|
|
||||||
access_token: process.env.TWITTER_ACCESS_TOKEN_KEY,
|
|
||||||
access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.warn(e.message)
|
|
||||||
}
|
|
||||||
const extractMediaId = morph.get('data.media_id_string')
|
|
||||||
const extractImageUrl = morph.get('data.entities.media.0.display_url')
|
|
||||||
const extractErrorCode = morph.get('0.code')
|
|
||||||
|
|
||||||
const uploadImage = data => client.post('media/upload', { media_data: data })
|
|
||||||
const uploadMetadata = (altText, twitterRes = {}) => {
|
|
||||||
if (!altText) return twitterRes
|
|
||||||
|
|
||||||
const formattedAltText =
|
|
||||||
altText.length > MAX_ALT_TEXT_LENGTH
|
|
||||||
? `${altText.slice(0, MAX_ALT_TEXT_LENGTH - 3)}...`
|
|
||||||
: altText
|
|
||||||
|
|
||||||
return client
|
|
||||||
.post('media/metadata/create', {
|
|
||||||
media_id: extractMediaId(twitterRes),
|
|
||||||
alt_text: { text: formattedAltText }
|
|
||||||
})
|
|
||||||
.then(() => twitterRes)
|
|
||||||
}
|
|
||||||
const uploadTweet = (twitterRes = {}) =>
|
|
||||||
client.post('statuses/update', {
|
|
||||||
status: `Carbon Copy #${extractMediaId(twitterRes).slice(0, 8)}`,
|
|
||||||
media_ids: extractMediaId(twitterRes)
|
|
||||||
})
|
|
||||||
|
|
||||||
const respondSuccess = (res, url) => res.json({ url })
|
|
||||||
const respondFail = (res, err) => {
|
|
||||||
const errorCode = extractErrorCode(err)
|
|
||||||
|
|
||||||
// check for rate limit
|
|
||||||
if (errorCode === RATE_LIMIT_CODE) {
|
|
||||||
return res.status(420).send()
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.error(`Error: ${err.message || JSON.stringify(err, null, 2)}`)
|
|
||||||
return res.status(500).send()
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = (req, res) => {
|
|
||||||
if (!req.body.imageData) {
|
|
||||||
return res.status(400).send()
|
|
||||||
}
|
|
||||||
|
|
||||||
return uploadImage(req.body.imageData)
|
|
||||||
.then(uploadMetadata.bind(null, req.body.altText))
|
|
||||||
.then(uploadTweet)
|
|
||||||
.then(extractImageUrl)
|
|
||||||
.then(respondSuccess.bind(null, res))
|
|
||||||
.catch(respondFail.bind(null, res))
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
require('isomorphic-fetch')
|
|
||||||
const { default: Unsplash, toJson } = require('unsplash-js')
|
|
||||||
|
|
||||||
const WALLPAPER_COLLECTION_ID = 136026
|
|
||||||
|
|
||||||
const client = new Unsplash({
|
|
||||||
applicationId: process.env.UNSPLASH_ACCESS_KEY,
|
|
||||||
secret: process.env.UNSPLASH_SECRET_KEY,
|
|
||||||
callbackUrl: process.env.UNSPLASH_CALLBACK_URL
|
|
||||||
})
|
|
||||||
|
|
||||||
const parseImageResult = img => ({
|
|
||||||
id: img.id,
|
|
||||||
photographer: {
|
|
||||||
name: img.user.name,
|
|
||||||
profile_url: img.user.links.html
|
|
||||||
},
|
|
||||||
url: img.urls.small
|
|
||||||
})
|
|
||||||
|
|
||||||
const getRandomImages = () =>
|
|
||||||
client.photos
|
|
||||||
.getRandomPhoto({
|
|
||||||
collections: [WALLPAPER_COLLECTION_ID],
|
|
||||||
count: 20
|
|
||||||
})
|
|
||||||
.then(toJson)
|
|
||||||
.then(imgs => imgs.map(parseImageResult))
|
|
||||||
|
|
||||||
const downloadImage = imageId =>
|
|
||||||
client.photos
|
|
||||||
.getPhoto(imageId)
|
|
||||||
.then(toJson)
|
|
||||||
.then(client.photos.downloadPhoto)
|
|
||||||
.then(toJson)
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
randomImages: (req, res) => getRandomImages().then(imgs => res.json(imgs)),
|
|
||||||
downloadImage: (req, res) => downloadImage(req.params.imageId).then(url => res.json(url))
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "carbon-api",
|
|
||||||
"alias": "carbon-api.now.sh",
|
|
||||||
"type": "docker",
|
|
||||||
"public": true,
|
|
||||||
"version": 1,
|
|
||||||
"env": {
|
|
||||||
"NODE_ENV": "production",
|
|
||||||
"TWITTER_CONSUMER_KEY": "@twitter-consumer-key",
|
|
||||||
"TWITTER_CONSUMER_SECRET": "@twitter-consumer-secret",
|
|
||||||
"TWITTER_ACCESS_TOKEN_KEY": "@twitter-access-token-key",
|
|
||||||
"TWITTER_ACCESS_TOKEN_SECRET": "@twitter-access-token-secret",
|
|
||||||
"LOGS_SECRET_PREFIX": "@logs_secret_prefix",
|
|
||||||
"UNSPLASH_SECRET_KEY": "@unsplash_secret_key",
|
|
||||||
"UNSPLASH_ACCESS_KEY": "@unsplash_access_key",
|
|
||||||
"UNSPLASH_CALLBACK_URL": "@unsplash_callback_url"
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
"cloud": "v1"
|
|
||||||
},
|
|
||||||
"github": {
|
|
||||||
"autoAlias": false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "carbon-api",
|
|
||||||
"version": "0.0.0-semantically-released",
|
|
||||||
"main": "index.js",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "node server.js",
|
|
||||||
"start": "node server.js",
|
|
||||||
"deploy": "now"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"body-parser": "^1.17.2",
|
|
||||||
"compression": "^1.7.3",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"dom-to-image": "^2.6.0",
|
|
||||||
"express": "^4.16.4",
|
|
||||||
"isomorphic-fetch": "^2.2.1",
|
|
||||||
"morgan": "^1.9.1",
|
|
||||||
"morphmorph": "^0.1.2",
|
|
||||||
"now-logs": "^0.0.7",
|
|
||||||
"puppeteer": "^1.10.0",
|
|
||||||
"twit": "^2.2.9",
|
|
||||||
"unsplash-js": "^4.8.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const cors = require('cors')
|
|
||||||
const compression = require('compression')
|
|
||||||
const morgan = require('morgan')
|
|
||||||
const bodyParser = require('body-parser')
|
|
||||||
const puppeteer = require('puppeteer')
|
|
||||||
|
|
||||||
const port = parseInt(process.env.PORT, 10) || 4000
|
|
||||||
const dev = process.env.NODE_ENV !== 'production'
|
|
||||||
|
|
||||||
process.on('SIGINT', process.exit)
|
|
||||||
|
|
||||||
if (!dev) {
|
|
||||||
const LOGS_ID = `${process.env.LOGS_SECRET_PREFIX}:${process.env.NOW_URL}`
|
|
||||||
require('now-logs')(LOGS_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrap(handler) {
|
|
||||||
return (req, res) =>
|
|
||||||
handler(req, res).catch(err => {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log('ERR:', err)
|
|
||||||
res.status(400).end()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const puppeteerParams = dev
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
executablePath: '/usr/bin/chromium-browser',
|
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
||||||
}
|
|
||||||
|
|
||||||
puppeteer.launch(puppeteerParams).then(browser => {
|
|
||||||
// set up
|
|
||||||
const server = express()
|
|
||||||
const imageHandler = require('./handlers/image')(browser)
|
|
||||||
const unsplashHandler = require('./handlers/unsplash')
|
|
||||||
const oembedHandler = require('./handlers/oembed')
|
|
||||||
|
|
||||||
if (dev) {
|
|
||||||
server.use(morgan('tiny'))
|
|
||||||
}
|
|
||||||
|
|
||||||
server.use(
|
|
||||||
cors({
|
|
||||||
origin(origin, callback) {
|
|
||||||
return origin === 'https://carbon.now.sh' || !origin
|
|
||||||
? callback(null, true)
|
|
||||||
: callback(new Error('Not allowed by CORS'))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
server.use(compression())
|
|
||||||
|
|
||||||
// api endpoints
|
|
||||||
server.post('/twitter', bodyParser.json({ limit: '5mb' }), require('./handlers/twitter'))
|
|
||||||
server.post('/image', bodyParser.json({ limit: '5mb' }), wrap(imageHandler))
|
|
||||||
server.get('/unsplash/random', wrap(unsplashHandler.randomImages))
|
|
||||||
server.get('/unsplash/download/:imageId', wrap(unsplashHandler.downloadImage))
|
|
||||||
server.all('/oembed', oembedHandler)
|
|
||||||
|
|
||||||
server.listen(port, '0.0.0.0', err => {
|
|
||||||
if (err) throw err
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log(`> Ready on http://localhost:${port}`)
|
|
||||||
})
|
|
||||||
})
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue