Last active
March 9, 2022 09:58
-
-
Save sebastienvercammen/e7e0e9e57db246d7f941b789d8508186 to your computer and use it in GitHub Desktop.
Nintendo PTC Account Verifier for Gmail v2 with CORS proxy (via Google Scripts & jQuery)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
masterThorn
commented
Jun 5, 2017
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment