-
-
Save sebastienvercammen/e7e0e9e57db246d7f941b789d8508186 to your computer and use it in GitHub Desktop.
/* | |
Automatically click all "Verify your email" links in the welcome e-mail from | |
Nintendo Pokémon Trainer Club's signup e-mails. | |
Only unread emails in inbox will be processed. | |
All processed e-mails will be marked as read if verification was successful, | |
and optionally moved to trash if it's enabled in your settings. | |
How to use: | |
1. Login to Gmail | |
2. Go to https://script.google.com/ | |
3. Enter the code in Code.gs and index.html (if index.html doesn't exist, create it). | |
4. Edit the settings at the top of index.html. | |
5. In the editor, click on Resources > Advanced Google services and name your project. | |
6. Enable "These services must also be enabled in the Google Developers Console". | |
7. Enable GMail API for your new project in the Google Developers Console: https://console.developers.google.com/. | |
8. Click Publish, then run as web app. | |
9. You'll see the results on the page. Dev console can be used for debugging. | |
10. Enjoy | |
*/ | |
// Serve HTML file. | |
function doGet() { | |
return HtmlService.createHtmlOutputFromFile('index.html'); | |
} | |
function getAllEmailLinks(limit) { | |
// Prepare response. | |
var items = []; | |
// Start. | |
var threads = GmailApp.search('in:inbox -label:RM_EXPIRED -label:RM_PREACTIVATED is:unread subject:"Pokémon Trainer Club Activation"', 0, limit); | |
Logger.log("Found " + threads.length + " threads."); | |
threads.forEach(function (thread) { | |
var messages = thread.getMessages(); | |
Logger.log("Found " + messages.length + " messages."); | |
messages.forEach(function (msg) { | |
// If we have a limit, follow it. | |
if (limit > 0 && items.length >= limit) { | |
return; | |
} | |
var value = msg.getBody() | |
.match(/verify your email/m); | |
if (msg.isInInbox() && value) { | |
var messageId = msg.getId(); | |
var link = msg.getBody().match(/<a href="https:\/\/club.pokemon.com\/us\/pokemon-trainer-club\/activated\/([\w\d]+)"/); | |
if (link) { | |
var url = 'https://club.pokemon.com/us/pokemon-trainer-club/activated/' + link[1]; | |
// Add to list for front-end. | |
items.push({ | |
'url': url, | |
'id': messageId | |
}); | |
} | |
} | |
}); | |
}); | |
Logger.log("Sent " + items.length + " URLs to front-end for verification."); | |
return items; | |
} | |
function labelAsError(messageId, labelName) { | |
try { | |
modifyMessage('me', messageId, [ labelName ]); | |
return { | |
'success': true | |
}; | |
} catch (e) { | |
Logger.log(e); | |
return { | |
'success': false, | |
'error': e | |
}; | |
} | |
} | |
function markAsVerified(messageId, move_to_trash) { | |
var msg = GmailApp.getMessageById(messageId); | |
msg.markRead(); | |
if (move_to_trash) { | |
msg.moveToTrash(); | |
} | |
} | |
/* | |
* Below methods are Google Apps Script Gmail Utilities, by mogsdad: | |
* https://gist.github.com/mogsdad/6515581 | |
*/ | |
/** | |
* Modify the Labels a Message is associated with. | |
* Throws if unsuccessful. | |
* see https://developers.google.com/gmail/api/v1/reference/users/messages/modify | |
* | |
* @param {String} userId User's email address. The special value 'me' | |
* can be used to indicate the authenticated user. | |
* @param {String} messageId ID of Message to modify. | |
* @param {String} labelsToAdd Array of Label names to add. | |
* @param {String} labelsToRemove Array of Label names to remove. | |
* | |
* @returns {Object} Users.messages resource, see reference. | |
*/ | |
function modifyMessage(userId, messageId, labelsToAdd, labelsToRemove) { | |
labelsToAdd = labelsToAdd || []; | |
labelsToRemove = labelsToRemove || []; | |
// see https://developers.google.com/gmail/api/v1/reference/users/messages/modify | |
var url = 'https://www.googleapis.com/gmail/v1/users/${userId}/messages/${id}/modify' | |
.replace("${userId}", "me") | |
.replace("${id}", messageId); | |
var headers = { | |
Authorization: 'Bearer ' + ScriptApp.getOAuthToken() | |
}; | |
var addLabelIds = []; | |
for (var i = 0; i < labelsToAdd.length; i++) { | |
addLabelIds[i] = getLabelId(labelsToAdd[i]); | |
} | |
var removeLabelIds = []; | |
for (var i = 0; i < labelsToRemove.length; i++) { | |
removeLabelIds[i] = getLabelId(labelsToRemove[i], false); | |
} | |
var request = { | |
'addLabelIds': addLabelIds, | |
'removeLabelIds': removeLabelIds | |
}; | |
var params = { | |
method: "post", | |
contentType: "application/json", | |
headers: headers, | |
payload: JSON.stringify(request), | |
muteHttpExceptions: true | |
}; | |
//var check = UrlFetchApp.getRequest(url, params); // for debugging | |
var response = UrlFetchApp.fetch(url, params); | |
var result = response.getResponseCode(); | |
if (result == '200') { // OK | |
return JSON.parse(response.getContentText()); | |
} else { | |
// This is only needed when muteHttpExceptions == true | |
var err = JSON.parse(response.getContentText()); | |
throw new Error('Error (' + result + ") " + err.error.message); | |
} | |
} | |
/** | |
* Get the Label ID for the given LabelName. If Label isn't found, it will be created | |
* depending on the state of ok2Create. | |
* Throws if unsuccessful. | |
* See https://developers.google.com/gmail/api/v1/reference/users/messages/modify. | |
* | |
* @param {String} labelName Immutable Gmail Message ID to modify | |
* @param {Boolean} ok2Create (optional) Set true if a label should be created when not found. | |
* Default is true. | |
* | |
* @returns {String} ID of Label, or null if not found or created. | |
*/ | |
function getLabelId(labelName, ok2Create) { | |
if (typeof ok2Create == 'undefined') ok2Create = true; | |
var id = null; | |
// see https://developers.google.com/gmail/api/v1/reference/users/labels/list | |
var url = 'https://www.googleapis.com/gmail/v1/users/${userId}/labels' | |
.replace("${userId}", "me") // The user's email address. The special value me can be used to indicate the authenticated user. | |
var headers = { | |
Authorization: 'Bearer ' + ScriptApp.getOAuthToken() | |
}; | |
var params = { | |
method: "get", | |
contentType: "application/json", | |
headers: headers, | |
muteHttpExceptions: true | |
}; | |
//var check = UrlFetchApp.getRequest(url, params); // for debugging | |
var response = UrlFetchApp.fetch(url, params); | |
var result = response.getResponseCode(); | |
if (result == '200') { // OK | |
var labels = JSON.parse(response.getContentText()).labels; | |
var found = false; | |
for (var i = 0; i < labels.length & !found; i++) { | |
if (labels[i].name == labelName) { | |
found = true; | |
id = labels[i].id; | |
} | |
} | |
if (!found && ok2Create) { | |
id = createLabel(labelName); | |
} | |
return id; | |
} else { | |
// This is only needed when muteHttpExceptions == true | |
var err = JSON.parse(response.getContentText()); | |
throw new Error('Error (' + result + ") " + err.error.message); | |
} | |
} | |
/** | |
* Create Label given LabelName. | |
* Throws if unsuccessful. | |
* See https://developers.google.com/gmail/api/v1/reference/users/messages/modify. | |
* | |
* @param {String} labelName Immutable Gmail Message ID to modify | |
* | |
* @returns {String} ID of Label. | |
*/ | |
function createLabel(labelName) { | |
var id = null; | |
// see https://developers.google.com/gmail/api/v1/reference/users/labels/create | |
var url = 'https://www.googleapis.com/gmail/v1/users/${userId}/labels' | |
.replace("${userId}", "me") // The user's email address. The special value me can be used to indicate the authenticated user. | |
var headers = { | |
Authorization: 'Bearer ' + ScriptApp.getOAuthToken() | |
}; | |
var request = { | |
'name': labelName | |
}; | |
var params = { | |
method: "post", | |
contentType: "application/json", | |
headers: headers, | |
payload: JSON.stringify(request), | |
muteHttpExceptions: true | |
}; | |
//var check = UrlFetchApp.getRequest(url, params); // for debugging | |
var response = UrlFetchApp.fetch(url, params); | |
var result = response.getResponseCode(); | |
if (result == '200') { // OK | |
var label = JSON.parse(response.getContentText()); | |
id = label.id; | |
return id; | |
} else { | |
// This is only needed when muteHttpExceptions == true | |
var err = JSON.parse(response.getContentText()); | |
throw new Error('Error (' + result + ") " + err.error.message); | |
} | |
} |
<!DOCTYPE html> | |
<html> | |
<head> | |
<base target="_top"> | |
</head> | |
<body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> | |
<script> | |
$(function() { | |
print('Started processing e-mails.'); | |
// Settings. | |
var limit = 10; // Enable limit of emails to process. 0 to disable. | |
var delay_between_reqs = 1000; // Delay between requests (in ms). | |
var move_to_trash = true; // Move email to trash if successfully verified. | |
var cors_proxy = '//cors-anywhere.herokuapp.com/'; // Link w/o "http:" or "https:", requires trailing slash. | |
// Keep track. | |
var emails = []; | |
// Requests. | |
function getRequest(item) { | |
$.ajax({ | |
url: item['url'] | |
}).done(function(data, textStatus, xhr) { | |
onRequestSuccess(item['id'], data, textStatus, xhr); | |
}).fail(function(jqXHR, textStatus, errorThrown) { | |
var status = jqXHR.status; | |
if (status === 503) { | |
err('PTC rate limit triggered, received a 503 response.'); | |
} else { | |
err('Request failed (' + status + '): ' + textStatus + '.'); | |
} | |
}); | |
} | |
// Callback. | |
function onRequestSuccess(messageId, data, textStatus, xhr) { | |
// Request successful. | |
var status = xhr.status; | |
if (status === 200) { | |
// Expired verification link? | |
var resend = data.match(/Resend your activation email/); | |
var already_done = data.match(/account has already been activated/); | |
var success = data.match(/Thank you for signing up! Your account is now active/); | |
if (resend) { | |
err('Expired verification link: ' + xhr.original_url + '.'); | |
google.script.run.withSuccessHandler(labeledAsError).labelAsError(messageId, 'RM_EXPIRED'); | |
} else if(already_done) { | |
err('Account was already activated: ' + xhr.original_url + '.'); | |
google.script.run.withSuccessHandler(labeledAsError).labelAsError(messageId, 'RM_PREACTIVATED'); | |
} else if(success) { | |
print('Account activated: ' + xhr.original_url + '.'); | |
// Mark message as verified & optionally move to trash. | |
google.script.run.markAsVerified(messageId, move_to_trash); | |
} else { | |
err('Unexpected reply on link: ' + xhr.original_url + '.'); | |
} | |
// Get next item. | |
if (emails.length > 0) { | |
print('Processing next email. Items left in queue: ' + emails.length + '.'); | |
item = emails.pop(); | |
// New request w/ min. delay. | |
print('Sleeping ' + delay_between_reqs + 'ms until next request.'); | |
setTimeout(function() { | |
getRequest(item); | |
}, delay_between_reqs); | |
} else { | |
print('Finished processing all emails.'); | |
} | |
} else { | |
err('Unexpected response (' + status + '): ' + textStatus + '.'); | |
} | |
} | |
// Go. | |
function receivedEmailList(list) { | |
print('Received ' + list.length + ' emails for verification.'); | |
// Update global for callback handler. | |
emails = list; | |
// Stop if we have no results. | |
if (emails.length === 0) { | |
print('No unread PTC emails in inbox to verify.'); | |
return; | |
} | |
// We have results. Set up AJAX for CORS proxy. | |
setupCorsProxy(); | |
// Time for requests. | |
print('Starting first request.'); | |
var item = emails.pop(); | |
getRequest(item); | |
} | |
function setupCorsProxy() { | |
var http = (window.location.protocol === 'http:' ? 'http:' : 'https:'); | |
var proxy = http + cors_proxy; | |
$.ajaxPrefilter(function(options) { | |
if (options.crossDomain && jQuery.support.cors) { | |
options.original_url = options.url; | |
options.url = proxy + options.url; | |
} | |
}); | |
$.ajaxSetup({ | |
beforeSend: function(jqXHR, settings) { | |
jqXHR.original_url = settings.original_url; | |
} | |
}); | |
} | |
function print(txt) { | |
document.write('<p>' + txt + '</p>'); | |
} | |
function err(txt) { | |
document.write('<p style="color: red;">' + txt + '</p>'); | |
} | |
function labeledAsError(response) { | |
var success = response.success; | |
if (!success) { | |
err(JSON.stringify(response.error)); | |
} | |
} | |
google.script.run.withSuccessHandler(receivedEmailList).getAllEmailLinks(limit); | |
}); | |
</script> | |
</body> | |
</html> |
This works perfectly, but there's a little problem:
If you have a lot of accounts to verify, you can't verify all of them because of the 5 mins limit of google script.
I've updated the original yet again. The original version used Google Script's UrlFetchApp
, which verified the email addresses with one of Google's servers. The Pokémon Company has pushed an update to block Google's IP addresses, so I've reworked the gist to use a front-end with jQuery to use the client's own browser to send the requests, so it will work again.
To make this work with JS AJAX and CORS, I use CORS Anywhere (http://cors-anywhere.herokuapp.com), an open proxy that enables CORS for any website via their API. This can be replaced with corsproxy (http://cors.corsproxy.io, already added in comment in the code) or a self-hosted CORS proxy server.
I've also added a limiter, set to 10 by default. Don't increase it too high.
everytime i try to run the new script all i get is:
Started processing e-mails. Received 0 emails for verification. Starting first request. Processing next email. Items left in queue: 8. Processing next email. Items left in queue: 7. Processing next email. Items left in queue: 6. Processing next email. Items left in queue: 5. Processing next email. Items left in queue: 4. Processing next email. Items left in queue: 3. Processing next email. Items left in queue: 2. Processing next email. Items left in queue: 1. Processing next email. Items left in queue: 0. Unexpected response (200): success
any idea what im doing wrong?
@EFMEX505 Updated the script, you were doing nothing wrong and the script was working. It just reported these two things wrong:
Received 0 emails for verification.
Unexpected response (200): success.
These are now fixed and will report as expected.
It get stuck there.
PTC rate limit triggered, received a 503 response.
After 5 of processed emails
I have the same problem as masterThorn. It just hangs after "Started processing e-mails." I believe it may have something to do with the need to do something with AJAX or CORS setup, for which I haven't done any additional configuration. I have a good VPN and would be happy to not use CORS, but I tried // out the SetupCorsProxy(); and it still hangs at the same step. Suggestions?
Anyone manage to get it working? Can please share what steps taken? it just show "Started processing e-mails." and nothing else
@MerlionRock i found messing with the adjustable limit cooked it a few times, works a treat set to 10
I've updated the original to mark all verified e-mails as read and move them to trash. Moving to trash can be disabled by setting
var moveToTrash
tofalse
at the top. I haven't includedis:unread
in the search query because a lot of people open the e-mails to double-check, which marks the thread as read.I've also included muteHttpExceptions, so account verification will continue even if some requests land on errors.