Gmail API list messages NodeJSNodeJS and Express with PromisesPromises with NodeJS and BlueBirdRESTful...
Who deserves to be first and second author? PhD student who collected data, research associate who wrote the paper or supervisor?
Built-In Shelves/Bookcases - IKEA vs Built
Reverse string, can I make it faster?
Signed and unsigned numbers
Accepted offer letter, position changed
What are some noteworthy "mic-drop" moments in math?
Is there any way to click on 6th item of this list
GPLv2 - licensing for commercial use
How to share a mine of irresistable resources in the middle of an ocean without conflict?
UART pins to unpowered MCU?
What does C-53 signify?
Is it true that real estate prices mainly go up?
Leftbar without indentation
Does splitting a potentially monolithic application into several smaller ones help prevent bugs?
Are there historical instances of the capital of a colonising country being temporarily or permanently shifted to one of its colonies?
They call me Inspector Morse
Set and print content of environment variable in cmd.exe subshell?
How can The Temple of Elementary Evil reliably protect itself against kinetic bombardment?
infinitive telling the purpose
How to create a hard link to an inode (ext4)?
PTIJ: How can I halachically kill a vampire?
Can someone explain what is being said here in color publishing in the American Mathematical Monthly?
Why is this plane circling around the LKO airport every day?
Detecting subscript in command argument
Gmail API list messages NodeJS
NodeJS and Express with PromisesPromises with NodeJS and BlueBirdRESTful backend API for user accountGet data from external APINodeJS/Express/socket.io backendAPI Implementation and ArchitectureBeginner Node.js app using Heroku w/ Mailchimp APIRouter for quering MySQL results in NodeJSMongoDB database connector on NodeJSNodejs Interview - API calls and exchange rate
$begingroup$
I use the following code to list and trash messages from the Gmail API in NodeJS. I call the getAllMessages method to get all email bodies because the Gmail API list endpoint retrieve just message IDs.
The code is available on github: https://github.com/aektos/emailcleaner/blob/master/services/gmailServices.js
I would like to have a valuable feedback about this code and specifically the way to get all email bodies. Thank you all!
const {google} = require('googleapis');
/**
* Number of messages per page
*
* @type {number}
*/
const NB_MSG_PER_PAGE_GMAIL = 20;
/**
* Total number of messages to list
*
* @type {number}
*/
const TOTAL_LIST_MSG_GMAIL = 500;
/**
* Class to interact with GMAIL API
*/
class GmailServices {
constructor(googleServices) {
this.gmail = google.gmail({
version: 'v1',
auth: googleServices.oAuth2Client
})
}
/**
* Get user profile info
*
* @returns {Promise}
*/
getProfile() {
return new Promise((resolve, reject) => {
this.gmail.users.getProfile({
userId: 'me',
})
.then((result) => {
let user = {'email': result.data.emailAddress ? result.data.emailAddress : ''};
resolve(user);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Lists the messages in the user's account.
*
* @param pageToken
* @param total
* @param socket
* @returns {Promise}
*/
listMessages(pageToken, total = 0, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.list({
userId: 'me',
maxResults: NB_MSG_PER_PAGE_GMAIL,
pageToken: pageToken,
includeSpamTrash: false
}).then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
let messages = typeof res.data.messages !== 'undefined' && typeof res.data.messages !== 'undefined' ? res.data.messages : [];
if (messages.length) {
total += NB_MSG_PER_PAGE_GMAIL;
if (res.data.nextPageToken && total <= TOTAL_LIST_MSG_GMAIL) {
this.listMessages(res.data.nextPageToken, total, socket)
.then((res) => {
messages = messages.concat(res);
resolve(messages);
})
.catch((err) => {
reject(err);
});
} else {
resolve(messages);
}
} else {
reject('No messages found');
}
}).catch((err) => {
reject(err);
});
});
};
/**
* Get all the messages in the user's account.
*
* @param messages
* @param socket
* @returns {Promise}
*/
getAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
let data = [];
messages.forEach((message) => {
allPromises = allPromises.then((msg) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
data.push(msg);
return this.getMessage(message.id, socket);
});
});
allPromises
.then(() => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Get a message by id
*
* @param id
* @param socket
* @returns {Promise}
*/
getMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.get({
id: id,
userId: 'me',
format: 'full'
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(res);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in getAllMessages
})
});
}
/**
* Move a message by id to trash
*
* @param id
* @param socket
* @returns {Promise}
*/
trashMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.trash({
id: id,
userId: 'me',
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in trashAllMessages
})
});
}
/**
* Trash all messages from the list 'messages"
*
* @param messages
* @param socket
* @returns {Promise}
*/
trashAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
messages.forEach((messageId) => {
allPromises = allPromises.then(() => {
return this.trashMessage(messageId, socket);
});
});
allPromises
.then(() => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
reject(err);
});
});
}
}
module.exports = GmailServices;
node.js api express.js
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
add a comment |
$begingroup$
I use the following code to list and trash messages from the Gmail API in NodeJS. I call the getAllMessages method to get all email bodies because the Gmail API list endpoint retrieve just message IDs.
The code is available on github: https://github.com/aektos/emailcleaner/blob/master/services/gmailServices.js
I would like to have a valuable feedback about this code and specifically the way to get all email bodies. Thank you all!
const {google} = require('googleapis');
/**
* Number of messages per page
*
* @type {number}
*/
const NB_MSG_PER_PAGE_GMAIL = 20;
/**
* Total number of messages to list
*
* @type {number}
*/
const TOTAL_LIST_MSG_GMAIL = 500;
/**
* Class to interact with GMAIL API
*/
class GmailServices {
constructor(googleServices) {
this.gmail = google.gmail({
version: 'v1',
auth: googleServices.oAuth2Client
})
}
/**
* Get user profile info
*
* @returns {Promise}
*/
getProfile() {
return new Promise((resolve, reject) => {
this.gmail.users.getProfile({
userId: 'me',
})
.then((result) => {
let user = {'email': result.data.emailAddress ? result.data.emailAddress : ''};
resolve(user);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Lists the messages in the user's account.
*
* @param pageToken
* @param total
* @param socket
* @returns {Promise}
*/
listMessages(pageToken, total = 0, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.list({
userId: 'me',
maxResults: NB_MSG_PER_PAGE_GMAIL,
pageToken: pageToken,
includeSpamTrash: false
}).then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
let messages = typeof res.data.messages !== 'undefined' && typeof res.data.messages !== 'undefined' ? res.data.messages : [];
if (messages.length) {
total += NB_MSG_PER_PAGE_GMAIL;
if (res.data.nextPageToken && total <= TOTAL_LIST_MSG_GMAIL) {
this.listMessages(res.data.nextPageToken, total, socket)
.then((res) => {
messages = messages.concat(res);
resolve(messages);
})
.catch((err) => {
reject(err);
});
} else {
resolve(messages);
}
} else {
reject('No messages found');
}
}).catch((err) => {
reject(err);
});
});
};
/**
* Get all the messages in the user's account.
*
* @param messages
* @param socket
* @returns {Promise}
*/
getAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
let data = [];
messages.forEach((message) => {
allPromises = allPromises.then((msg) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
data.push(msg);
return this.getMessage(message.id, socket);
});
});
allPromises
.then(() => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Get a message by id
*
* @param id
* @param socket
* @returns {Promise}
*/
getMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.get({
id: id,
userId: 'me',
format: 'full'
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(res);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in getAllMessages
})
});
}
/**
* Move a message by id to trash
*
* @param id
* @param socket
* @returns {Promise}
*/
trashMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.trash({
id: id,
userId: 'me',
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in trashAllMessages
})
});
}
/**
* Trash all messages from the list 'messages"
*
* @param messages
* @param socket
* @returns {Promise}
*/
trashAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
messages.forEach((messageId) => {
allPromises = allPromises.then(() => {
return this.trashMessage(messageId, socket);
});
});
allPromises
.then(() => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
reject(err);
});
});
}
}
module.exports = GmailServices;
node.js api express.js
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
add a comment |
$begingroup$
I use the following code to list and trash messages from the Gmail API in NodeJS. I call the getAllMessages method to get all email bodies because the Gmail API list endpoint retrieve just message IDs.
The code is available on github: https://github.com/aektos/emailcleaner/blob/master/services/gmailServices.js
I would like to have a valuable feedback about this code and specifically the way to get all email bodies. Thank you all!
const {google} = require('googleapis');
/**
* Number of messages per page
*
* @type {number}
*/
const NB_MSG_PER_PAGE_GMAIL = 20;
/**
* Total number of messages to list
*
* @type {number}
*/
const TOTAL_LIST_MSG_GMAIL = 500;
/**
* Class to interact with GMAIL API
*/
class GmailServices {
constructor(googleServices) {
this.gmail = google.gmail({
version: 'v1',
auth: googleServices.oAuth2Client
})
}
/**
* Get user profile info
*
* @returns {Promise}
*/
getProfile() {
return new Promise((resolve, reject) => {
this.gmail.users.getProfile({
userId: 'me',
})
.then((result) => {
let user = {'email': result.data.emailAddress ? result.data.emailAddress : ''};
resolve(user);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Lists the messages in the user's account.
*
* @param pageToken
* @param total
* @param socket
* @returns {Promise}
*/
listMessages(pageToken, total = 0, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.list({
userId: 'me',
maxResults: NB_MSG_PER_PAGE_GMAIL,
pageToken: pageToken,
includeSpamTrash: false
}).then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
let messages = typeof res.data.messages !== 'undefined' && typeof res.data.messages !== 'undefined' ? res.data.messages : [];
if (messages.length) {
total += NB_MSG_PER_PAGE_GMAIL;
if (res.data.nextPageToken && total <= TOTAL_LIST_MSG_GMAIL) {
this.listMessages(res.data.nextPageToken, total, socket)
.then((res) => {
messages = messages.concat(res);
resolve(messages);
})
.catch((err) => {
reject(err);
});
} else {
resolve(messages);
}
} else {
reject('No messages found');
}
}).catch((err) => {
reject(err);
});
});
};
/**
* Get all the messages in the user's account.
*
* @param messages
* @param socket
* @returns {Promise}
*/
getAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
let data = [];
messages.forEach((message) => {
allPromises = allPromises.then((msg) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
data.push(msg);
return this.getMessage(message.id, socket);
});
});
allPromises
.then(() => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Get a message by id
*
* @param id
* @param socket
* @returns {Promise}
*/
getMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.get({
id: id,
userId: 'me',
format: 'full'
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(res);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in getAllMessages
})
});
}
/**
* Move a message by id to trash
*
* @param id
* @param socket
* @returns {Promise}
*/
trashMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.trash({
id: id,
userId: 'me',
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in trashAllMessages
})
});
}
/**
* Trash all messages from the list 'messages"
*
* @param messages
* @param socket
* @returns {Promise}
*/
trashAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
messages.forEach((messageId) => {
allPromises = allPromises.then(() => {
return this.trashMessage(messageId, socket);
});
});
allPromises
.then(() => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
reject(err);
});
});
}
}
module.exports = GmailServices;
node.js api express.js
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
$endgroup$
I use the following code to list and trash messages from the Gmail API in NodeJS. I call the getAllMessages method to get all email bodies because the Gmail API list endpoint retrieve just message IDs.
The code is available on github: https://github.com/aektos/emailcleaner/blob/master/services/gmailServices.js
I would like to have a valuable feedback about this code and specifically the way to get all email bodies. Thank you all!
const {google} = require('googleapis');
/**
* Number of messages per page
*
* @type {number}
*/
const NB_MSG_PER_PAGE_GMAIL = 20;
/**
* Total number of messages to list
*
* @type {number}
*/
const TOTAL_LIST_MSG_GMAIL = 500;
/**
* Class to interact with GMAIL API
*/
class GmailServices {
constructor(googleServices) {
this.gmail = google.gmail({
version: 'v1',
auth: googleServices.oAuth2Client
})
}
/**
* Get user profile info
*
* @returns {Promise}
*/
getProfile() {
return new Promise((resolve, reject) => {
this.gmail.users.getProfile({
userId: 'me',
})
.then((result) => {
let user = {'email': result.data.emailAddress ? result.data.emailAddress : ''};
resolve(user);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Lists the messages in the user's account.
*
* @param pageToken
* @param total
* @param socket
* @returns {Promise}
*/
listMessages(pageToken, total = 0, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.list({
userId: 'me',
maxResults: NB_MSG_PER_PAGE_GMAIL,
pageToken: pageToken,
includeSpamTrash: false
}).then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
let messages = typeof res.data.messages !== 'undefined' && typeof res.data.messages !== 'undefined' ? res.data.messages : [];
if (messages.length) {
total += NB_MSG_PER_PAGE_GMAIL;
if (res.data.nextPageToken && total <= TOTAL_LIST_MSG_GMAIL) {
this.listMessages(res.data.nextPageToken, total, socket)
.then((res) => {
messages = messages.concat(res);
resolve(messages);
})
.catch((err) => {
reject(err);
});
} else {
resolve(messages);
}
} else {
reject('No messages found');
}
}).catch((err) => {
reject(err);
});
});
};
/**
* Get all the messages in the user's account.
*
* @param messages
* @param socket
* @returns {Promise}
*/
getAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
let data = [];
messages.forEach((message) => {
allPromises = allPromises.then((msg) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
data.push(msg);
return this.getMessage(message.id, socket);
});
});
allPromises
.then(() => {
resolve(data);
})
.catch((err) => {
reject(err);
});
});
}
/**
* Get a message by id
*
* @param id
* @param socket
* @returns {Promise}
*/
getMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.get({
id: id,
userId: 'me',
format: 'full'
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(res);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in getAllMessages
})
});
}
/**
* Move a message by id to trash
*
* @param id
* @param socket
* @returns {Promise}
*/
trashMessage(id, socket) {
return new Promise((resolve, reject) => {
this.gmail.users.messages.trash({
id: id,
userId: 'me',
})
.then((res) => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
console.log(err);
// reject(err); // don't reject to not block the loop in trashAllMessages
})
});
}
/**
* Trash all messages from the list 'messages"
*
* @param messages
* @param socket
* @returns {Promise}
*/
trashAllMessages(messages, socket) {
return new Promise((resolve, reject) => {
if (!messages.length) {
resolve([]);
}
let allPromises = new Promise((resolve, reject) => {
resolve(null);
});
messages.forEach((messageId) => {
allPromises = allPromises.then(() => {
return this.trashMessage(messageId, socket);
});
});
allPromises
.then(() => {
if (!socket.handshake.session.isConnected) {
throw 'user disconnected';
}
resolve(true);
})
.catch((err) => {
reject(err);
});
});
}
}
module.exports = GmailServices;
node.js api express.js
node.js api express.js
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
asked 6 mins ago
AektosAektos
1
1
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
Aektos is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Aektos is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215277%2fgmail-api-list-messages-nodejs%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Aektos is a new contributor. Be nice, and check out our Code of Conduct.
Aektos is a new contributor. Be nice, and check out our Code of Conduct.
Aektos is a new contributor. Be nice, and check out our Code of Conduct.
Aektos is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215277%2fgmail-api-list-messages-nodejs%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown