diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..823c495 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history \ No newline at end of file diff --git a/README.md b/README.md index a3afc88..6f21092 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# ptc-acc-gen -PTC Account Gen (formerly by Seb) +# Nintendo PTC Account Generator + +An automation script based on Nightmare.js that can create any number of Nintendo Pokémon Trainer Club accounts with a single e-mail address. This only works because Nintendo doesn't check for "email+1@domain.com" e-mail tricks, where the e-mail host completely ignores any part after (and including) the plus sign and sends it to "email@domain.com". + +This project was started as a proof of concept: even multi-billion dollar companies that just released the single most popular mobile game (Pokémon Go) sometimes miss the details. + +More about plus signs in e-mail addresses [on StackExchange](http://security.stackexchange.com/questions/65244/what-are-the-security-reasons-for-disallowing-the-plus-sign-in-email-addresses). + +If you're using Gmail and want to automatically verify all accounts, use this gist: https://gist.github.com/sebastienvercammen/e7e0e9e57db246d7f941b789d8508186 + +The new version on Nightmare.js now: + +* Checks for usernames/e-mails that are already taken +* Can screenshot the result page +* Handles server issues better +* Doesn't require PhantomJS/CasperJS or fiddling with the PATH environment variable + +## Requirements +### Prerequisites +* [Node.js](https://nodejs.org/en/) + +## Usage + +1. Install requirements with `npm install` +2. Open [index.js](index.js) and edit the settings at the top of the file. +3. Run the script with Node.js: + `node index.js` + +## Configuration +### 1. Generate 10 accounts in the format USERx, where x is 0 to 9. +This example corresponds to the default settings. It will generate 10 accounts in the same format: user0, user1, ... + +In [index.js](index.js): + + var start = 0; // Start from x (NAMEx, EMAIL+x@domain.com) + var end = 10; // Up to x, but not including (exclusive) + + var useNicknamesFile = false; // Use nicknames file, or just append numbers to username? + var outputFile = 'accounts.txt'; // File which will contain the generated "username password" combinations. + var outputFormat = '%NICK% %PASS%\r\n'; // Format used to save the account data in outputFile. Supports %NICK%, %PASS%. + + var useNicknamesFile = false; // Use nicknames file, or just append numbers to username? + var useRandomPassword = true; // Generate a random password? + var screenshotResult = true; // Saves a screenshot per account creation if set to true + var screenshotOnFailure = true; // Saves a screenshot even if registration failed + +### 2. Generate random passwords per account. +* Set `var useRandomPassword = true;` in [index.js](index.js). + +### 3. Save screenshots. + + var screenshotResult = true; // Saves a screenshot per account creation if set to true + var screenshotOnFailure = true; // Saves a screenshot even if registration failed + var screenshotFolder = "output/screenshots/"; + +### 4. Use a list of unique usernames instead of USERx combinations. +The list of unique usernames must be stored in [nicknames.json](nicknames.json). An example is available on the repo. + +To create a number of accounts with custom usernames instead of user + number combinations, change [index.js](index.js): + + var useNicknamesFile = true; diff --git a/index.js b/index.js new file mode 100644 index 0000000..5b200f0 --- /dev/null +++ b/index.js @@ -0,0 +1,257 @@ +// Requires +var Nightmare = require('nightmare'); +var nicknames = require('./nicknames.json'); +var fs = require('fs'); + +// Settings +var debug = false; +var showWindow = false; + +var start = 0; // Start from x (NAMEx, EMAIL+x@domain.com) +var end = 10; + +var useNicknamesFile = false; // Use nicknames file, or just append numbers to username? +var useRandomPassword = true; // Generate a random password? +var screenshotResult = true; // Saves a screenshot per account creation if set to true +var screenshotOnFailure = true; // Saves a screenshot even if registration failed + +var outputFile = "output/accounts.txt"; // File which will contain the generated "username password" combinations. +var outputFormat = "%NICK% %PASS%\r\n"; // Format used to save the account data in outputFile. Supports %NICK%, %PASS%. +var screenshotFolder = "output/screenshots/"; + +var country = "US"; // Country code (e.g. BE, FR, US, CA) +var dob = "1990-01-01"; // Date of birth, yyyy-mm-dd +var username = "CHANGEME"; // User- & display name. Make sure any "(username + number)@domain.com" is 100% unique. +var password = "CHANGEME"; // Static password for all accounts. Ignored if useRandomPassword is true. +var email_user = "CHANGEME"; // If your email is email@domain.com, enter "email" +var email_domain = "CHANGEME.com"; // Domain of e-mail host + +// App data +var url_ptc = "https://club.pokemon.com/us/pokemon-trainer-club/sign-up/"; +var useragent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36"; +var nightmare_opts = { + show: showWindow, + waitTimeout: 10000, + gotoTimeout: 5000, + loadTimeout: 5000 +}; + +// Settings check +if (!useNicknamesFile && (username + end).length > 16) { + console.log("Error: length of username + number can't be longer than 16 characters."); + console.log("Please use a shorter nickname."); + process.exit(); +} + +if ((email_user + '+' + username + end + '@' + email_domain).length > 75) { + console.log("Error: length of e-mail address including the + trick can't be longer than 75 characters."); + console.log("Please use a shorter e-mail address and/or nickname."); + process.exit(); +} + +if (!useRandomPassword && password.length > 15) { + console.log("Error: length of password can't be longer than 15 characters."); + console.log("Please use a shorter password."); + process.exit(); +} + +// LETSAHGO +var nightmare = Nightmare(nightmare_opts); +nightmare.useragent(useragent); + +createAccount(start); + +// Helpers + +function handleError(err) { + if(debug) { + console.log("[DEBUG] Error:" + JSON.stringify(err)); + } + + return err; +} + +function randomPassword() { + return Math.random().toString(36).substr(2, 8); +} + +function prepareNightmare(nightmare) { + nightmare.useragent(useragent); +} + +function randomPassword() { + return Math.random().toString(36).substr(2, 8); +} + +// Pages +function createAccount(ctr) { + console.log("Creating account " + ctr + " of " + end); + + // Launch instance + handleFirstPage(ctr); +} + +// First page +function handleFirstPage(ctr) { + if(debug) { + console.log("[DEBUG] Handle first page #" + ctr); + } + + nightmare.goto(url_ptc) + .evaluate(evaluateDobPage) + .then(function(validated) { + if(!validated) { + // Missing form data, loop over itself + console.log("[" + ctr + "] Servers are acting up... Trying again."); + return function() { nightmare.wait(500).refresh().wait(); handleFirstPage(ctr); }; + } else { + return function() { fillFirstPage(ctr); }; + } + }) + .then(function(next) { + // Handle next step: either a loop to first page in case of error, or form fill on success + return next(); + }) + .catch(handleError) + .then(function(err) { + if (typeof err !== "undefined") { + return handleFirstPage(ctr); + } + }); +} + +function fillFirstPage(ctr) { + if(debug) { + console.log("[DEBUG] Fill first page #" + ctr); + } + + nightmare.evaluate(function(data) { + document.getElementById("id_dob").value = data.dob; + + var els = document.getElementsByName("id_country"); + for(var i = 0; i < els.length; i++) { + els[i].value = data.country; + } + + return document.getElementById("id_dob").value; + }, { dob: dob, country: country }) + .click("form[name='verify-age'] [type=submit]") + .wait("#id_username") + .then(function() { + handleSignupPage(ctr); + }) + .catch(handleError) + .then(function(err) { + if (typeof err !== "undefined") { + return handleFirstPage(ctr); + } + }); +} + +// Signup page +function handleSignupPage(ctr) { + if(debug) { + console.log("[DEBUG] Handle second page #" + ctr); + } + + nightmare.evaluate(evaluateSignupPage) + .then(function(validated) { + if(!validated) { + // Missing form data, loop over itself + console.log("[" + ctr + "] Servers are acting up... Trying again."); + return function() { nightmare.wait(500).refresh().wait(); handleFirstPage(ctr); }; + } else { + return function() { fillSignupPage(ctr); }; + } + }).then(function(next) { + // Handle next step: either a loop to first page in case of error, or form fill on success + return next(); + }) + .catch(handleError) + .then(function(err) { + if (typeof err !== "undefined") { + return handleSignupPage(ctr); + } + }); +} + +function fillSignupPage(ctr) { + if(debug) { + console.log("[DEBUG] Fill signup page #" + ctr); + } + + var _pass = password; + var _nick = username + ctr; + + if(useRandomPassword) { + _pass = randomPassword(); + } + + // Use nicknames list, or (username + number) combo? + if(useNicknamesFile) { + // Make sure we have a nickname left + if(nicknames.length < 1) { + throw Error("We're out of nicknames to use!"); + } + + // Get the first nickname off the list & use it + _nick = nicknames.shift(); + } + + // Fill it all in + nightmare.evaluate(function(data) { + document.getElementById("id_password").value = data.pass; + document.getElementById("id_confirm_password").value = data.pass; + document.getElementById("id_email").value = data.email_user + "+" + data.nick + "@" + data.email_domain; + document.getElementById("id_confirm_email").value = data.email_user + "+" + data.nick + "@" + data.email_domain; + document.getElementById("id_screen_name").value = data.nick; + document.getElementById("id_username").value = data.nick; + }, { "pass": _pass, "nick": _nick, "email_user": email_user, "email_domain": email_domain }) + .check("#id_terms") + .click("form[name='create-account'] [type=submit]") + .wait(function() { + return (document.getElementById("signup-signin") !== null || document.getElementById("btn-reset") !== null || document.body.textContent.indexOf("That username already exists") > -1); + }) + .evaluate(function() { + return (document.body.textContent.indexOf("Hello! Thank you for creating an account!") > -1); + }) + .then(function(success) { + if(success) { + // Log it in the file of used nicknames + var content = outputFormat.replace('%NICK%', _nick).replace('%PASS%', _pass); + fs.appendFile(outputFile, content, function(err) { + // + }); + } + + if((success && screenshotResult) || screenshotOnFailure) { + // Screenshot + nightmare.screenshot(screenshotFolder + _nick + ".png"); + } + + // Next one, or stop + if(ctr < end) { + return function() { createAccount(ctr + 1); }; + } else { + return nightmare.end(); + } + }).then(function(next) { + return next(); + }).catch(handleError) + .then(function(err) { + if (typeof err !== "undefined") { + return handleSignupPage(ctr); + } + }); +} + +// Evaluations +function evaluateDobPage() { + var dob_value = document.getElementById("id_dob"); + return ((document.title === "The Official Pokémon Website | Pokemon.com") && (dob_value !== null)); +} + +function evaluateSignupPage() { + var username_field = document.getElementById("id_username"); + return ((document.title === "The Official Pokémon Website | Pokemon.com") && (username_field !== null)); +} diff --git a/nicknames.json b/nicknames.json new file mode 100644 index 0000000..0f889ad --- /dev/null +++ b/nicknames.json @@ -0,0 +1,4 @@ +[ + "uniqueuser159", + "betteruser933" +] \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7440c40 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "nintendo-ptc-account-generator", + "version": "1.1.0", + "description": "An automation script based on CasperJS and PhantomJS that can create any number of Nintendo Pokémon Trainer Club (PTC) accounts with a single e-mail address.", + "main": "index.js", + "scripts": {}, + "keywords": [ + "nintendo", + "ptc", + "pokemon", + "pokemongo", + "accounts", + "generator" + ], + "author": "Sébastien Vercammen", + "dependencies": { + "nightmare": "^2.6.0" + } +}