Skip to content

Instantly share code, notes, and snippets.

@mooyoul
Created September 17, 2018 13:06
Show Gist options
  • Save mooyoul/4e04531a7a614ec0c524822a4d04202e to your computer and use it in GitHub Desktop.
Save mooyoul/4e04531a7a614ec0c524822a4d04202e to your computer and use it in GitHub Desktop.
Youtube Upload example w/express
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<a href="/auth">Click here to sign in</a>
</body>
</html>
{
"name": "youtube-upload",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "MooYeol Prescott Lee <mooyoul@gmail.com>",
"license": "MIT",
"dependencies": {
"express": "^4.16.3",
"express-session": "^1.15.6",
"googleapis": "^33.0.0"
}
}
const crypto = require('crypto');
const http = require('http');
const app = require('express')();
const session = require('express-session');
const { google } = require('googleapis');
const path = require('path');
const qs = require('querystring');
// get your own oauth2 credentails on https://console.developers.google.com
const GOOGLE_OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID';
const GOOGLE_OAUTH2_CLIENT_SECRET = 'YOUR_CLIENT_SECRET';
const GOOGLE_OAUTH2_CALLBACK_URL = 'YOUR_CALLBACK_URL';
const GOOGLE_OAUTH2_REQUIRED_SCOPES = [ // DO NOT MODIFY
'https://www.googleapis.com/auth/youtube.upload',
];
const googleOAuth2Client = new google.auth.OAuth2(
GOOGLE_OAUTH2_CLIENT_ID,
GOOGLE_OAUTH2_CLIENT_SECRET,
GOOGLE_OAUTH2_CALLBACK_URL,
);
app.use(session({
secret: crypto.randomBytes(32).toString('hex'),
saveUninitialized: false,
resave: false,
}));
app.get('/', (req, res) => {
if (req.session.auth) {
return res.redirect(302, '/upload');
}
res.sendFile(path.join(__dirname, 'index.html'));
});
app.get('/upload', (req, res) => {
if (!req.session.auth) {
return res.redirect(302, '/');
}
res.sendFile(path.join(__dirname, 'upload.html'));
});
app.post('/api/video', async (req, res) => {
if (!req.session.auth) {
return res.sendStatus(401);
}
console.log('reading file information');
const fileSize = req.get('content-length');
console.log('got file size: %d bytes', fileSize);
try {
// Do not use global oauth2 client to prevent
// usage of unexpected credentials on race condition.
const auth = new google.auth.OAuth2(
GOOGLE_OAUTH2_CLIENT_ID,
GOOGLE_OAUTH2_CLIENT_SECRET,
GOOGLE_OAUTH2_CALLBACK_URL,
);
auth.setCredentials(req.session.auth);
const youtube = google.youtube({ version: 'v3', auth });
console.log('starting upload...');
const { data } = await youtube.videos.insert({
part: [
'id',
'snippet',
'status'
].join(),
notifySubscribers: false, // i don't want to notify to channel subscribers
requestBody: {
snippet: {
title: 'Video Title',
description: [
'Video Description',
'',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
].join('\n'),
},
status: {
privacyStatus: 'unlisted', // i don't want to show this video on public
},
},
media: { // pass-through entire request body to youtube server
body: req,
},
});
res.send(data);
} catch (e) {
console.error('Upload filed: ', e.stack);
console.error('Received: ', e.response.status, e.response.data);
res.sendStatus(500);
}
});
// Routes for OAuth2 Flow
app.get('/auth', (req, res) => {
const state = req.session.state = crypto.randomBytes(16).toString('hex');
const redirectionUrl = googleOAuth2Client.generateAuthUrl({
access_type: 'online',
prompt: 'select_account',
response_type: 'code',
scope: GOOGLE_OAUTH2_REQUIRED_SCOPES,
state,
});
res.redirect(302, redirectionUrl);
});
app.get('/auth/callback', async (req, res) => {
const { code, state, error } = req.query;
// google returned an error
if (error) {
return res.redirect(302, `/?${qs.stringify({ error })}`);
}
// perform state validation
if (!state || state !== req.session.state) {
return res.redirect(302, `/?${qs.stringify({ error: 'INVALID_STATE' })}`);
}
// get oauth2 access token
try {
const { tokens } = await googleOAuth2Client.getToken(code);
console.log('got token: ', tokens);
req.session.auth = tokens;
res.redirect(302, '/upload');
} catch (e) {
console.error('got error from google: ', e.stack);
return res.redirect(302, `/?${qs.stringify({ error: e.toString() })}`);
}
});
const server = http.createServer(app);
server.listen(process.env.PORT || 3001, '0.0.0.0', () => {
const { address, port } = server.address();
console.log('server listening in %s:%s', address, port);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Upload</title>
</head>
<body>
<form onsubmit="upload(event)">
<input type="file" id="file">
<button type="submit">Upload</button>
</form>
<pre id="code"></pre>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script type="text/javascript">
async function upload(e) {
e.preventDefault();
e.stopPropagation();
const fileEl = document.querySelector('#file');
const codeEl = document.querySelector('#code');
if (!fileEl.files.length) {
alert('Please select file');
return;
}
const res = await axios({
method: 'POST',
url: '/api/video',
data: fileEl.files[0],
});
codeEl.innerText = JSON.stringify(res, null, 2);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment