diff --git a/KinanCity-captcha-2captcha/pom.xml b/KinanCity-captcha-2captcha/pom.xml index ae260ef..61e4616 100644 --- a/KinanCity-captcha-2captcha/pom.xml +++ b/KinanCity-captcha-2captcha/pom.xml @@ -37,7 +37,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-captcha-AntiCaptcha/pom.xml b/KinanCity-captcha-AntiCaptcha/pom.xml index f7f2e01..79bfc84 100644 --- a/KinanCity-captcha-AntiCaptcha/pom.xml +++ b/KinanCity-captcha-AntiCaptcha/pom.xml @@ -33,7 +33,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-captcha-ImageTypers/pom.xml b/KinanCity-captcha-ImageTypers/pom.xml index b5df582..adbf406 100644 --- a/KinanCity-captcha-ImageTypers/pom.xml +++ b/KinanCity-captcha-ImageTypers/pom.xml @@ -27,7 +27,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-captcha-api/pom.xml b/KinanCity-captcha-api/pom.xml index 36e2a3b..028c242 100644 --- a/KinanCity-captcha-api/pom.xml +++ b/KinanCity-captcha-api/pom.xml @@ -27,7 +27,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-captcha-client/pom.xml b/KinanCity-captcha-client/pom.xml index c326695..42fd9a3 100644 --- a/KinanCity-captcha-client/pom.xml +++ b/KinanCity-captcha-client/pom.xml @@ -26,7 +26,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-captcha-server/dependency-reduced-pom.xml b/KinanCity-captcha-server/dependency-reduced-pom.xml index 7df8271..1d3cee6 100644 --- a/KinanCity-captcha-server/dependency-reduced-pom.xml +++ b/KinanCity-captcha-server/dependency-reduced-pom.xml @@ -21,7 +21,7 @@ - com.kinancity.captcha.server.Application + com.kinancity.captcha.server.CaptchaServer META-INF/spring.handlers diff --git a/KinanCity-captcha-server/pom.xml b/KinanCity-captcha-server/pom.xml index 223abe9..eeb4d34 100644 --- a/KinanCity-captcha-server/pom.xml +++ b/KinanCity-captcha-server/pom.xml @@ -24,7 +24,7 @@ spring-boot-maven-plugin true - com.kinancity.captcha.server.Application + com.kinancity.captcha.server.CaptchaServer @@ -78,7 +78,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-core/README.md b/KinanCity-core/README.md index 97c1873..06b85b9 100644 --- a/KinanCity-core/README.md +++ b/KinanCity-core/README.md @@ -2,6 +2,7 @@ **TABLE OF CONTENTS** +- [What does Kinan Core do ?](#what-does-kinan-core-do) - [Main usage and tips](#main-usage-and-tips) - Configuration examples - [Create a sequence of accounts](#create-a-sequence-of-accounts) @@ -9,6 +10,24 @@ - [Create a single account](#create-a-single-account) - [Additional parameters](#additional-parameters) +# What does Kinan Core do ? + +Instead of manually going to the PTC website, Kinan Core automates all the requests needed to create the account itself. + +The captcha challenge still needs to be done, but you can use a third party provider (see below) or implement your own (see kinanCity-captcha-server). + +![](../docs/1_KinanCore.png) + +In the end you will still need to take care of the activation link that will be sent by email. + +Instead of doing these steps one by one, Kinan core does multi-thread the process (see -thread option below). + +However PTC has some restrictions that only allows to create 5 accounts per 15 minutes from the same IP. + +![](../docs/2_IPrestrictions.png) + +To overcome that limit, Kinan Core includes an embedded cooldown system, and allows using multiple proxies to call PTC from several different IP addresses. + # Main usage and tips KinanCity-core main class accepts configuration from : diff --git a/KinanCity-core/pom.xml b/KinanCity-core/pom.xml index ea7d46d..b050598 100644 --- a/KinanCity-core/pom.xml +++ b/KinanCity-core/pom.xml @@ -78,7 +78,7 @@ commons-io commons-io - 2.5 + 2.7 commons-lang @@ -102,7 +102,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/KinanCity-mail/README.md b/KinanCity-mail/README.md index 8d87310..8b084de 100644 --- a/KinanCity-mail/README.md +++ b/KinanCity-mail/README.md @@ -1,13 +1,35 @@ # KinanCity-mail : Email Activator +## About activation emails + +Here is how activations emails work in a standard way : + +There are 2 part in an email address : after the @ symbol, is the internet domain where the email should be sent. And before the @ symbol is the username who this email should be delivered to. + +![](../docs/3_email.png) + +PTC sends and email to the address you gave it. +It arrives to the DNS server of that domain, that defines to which server the the emails should be sent. + +On that email server, there is a program setup with mailboxes for each user and the emails are stored. + +Then the user connects to that mailbox and retreives his emails. He will then find the email with the activation link and open it in his browser. + ## What this module does +If you have your own domain, you can run your own email server. +Instead of running a standard email server, you can run Kinan Mail instead. + This module can be run as a standalone service and will : - Start a **Mail server** listening to default port 25 - For each mail received from nintendo, the **activation link** is grabbed - A web request is made to the **activation** link +![](../docs/4_kinanMail.png) + +Using Kinan Mail, you don't need to create a mailbox for each account before hand. And the activation links are taken care automatically. + ## How to setup The machine running the email server must be accessible from **the internet**. diff --git a/KinanCity-mail/accounts.example.csv b/KinanCity-mail/accounts.example.csv new file mode 100644 index 0000000..a0f582f --- /dev/null +++ b/KinanCity-mail/accounts.example.csv @@ -0,0 +1,3 @@ +#username;email;password +username1;username1@myMxDomain.com;testAA123+ +username2;username2@myMxDomain.com;testAA456+ diff --git a/KinanCity-mail/config.example.properties b/KinanCity-mail/config.example.properties index c85afc3..8885bca 100644 --- a/KinanCity-mail/config.example.properties +++ b/KinanCity-mail/config.example.properties @@ -2,4 +2,19 @@ #proxy=http://login:pass@127.0.0.1:3128|http://login:pass@127.0.0.1:3128 # Uncomment the line below if you want to accept emails that does not come from pokemon.com -#disableDomainFilter=true \ No newline at end of file +#disableDomainFilter=true + +# Add your domain and uncomment the line below to only accept emails sent to a specific email address +#allowedDomains=myDomain.xyz +#hostname=myDomain.xyz + + +# Activate the email changer +# emailChanger.active=true + +# Loading from a csv file +# emailChanger.password.csv=accounts.example.csv +# Mapping between email account and password with wildcard +# emailChanger.password.mapping=aaaaa.*@domain.com:pass1||bbbbb.*@.*:pass2 +# Always the same password +# emailChanger.password.static=sameForAll \ No newline at end of file diff --git a/KinanCity-mail/pom.xml b/KinanCity-mail/pom.xml index 99b57ff..c06132b 100644 --- a/KinanCity-mail/pom.xml +++ b/KinanCity-mail/pom.xml @@ -7,7 +7,7 @@ 1.4.2-SNAPSHOT KinanCity-mail - 1.5.4 + 2.0.2 Email validation for KinanCity @@ -22,7 +22,7 @@ commons-io commons-io - 2.5 + 2.7 @@ -48,7 +48,7 @@ junit junit - 4.12 + 4.13.1 test @@ -58,8 +58,14 @@ 3.6.2 test - - + + org.jsoup + jsoup + 1.13.1 + compile + + + diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/Activation.java b/KinanCity-mail/src/main/java/com/kinancity/mail/Activation.java index aae75df..85c68c4 100644 --- a/KinanCity-mail/src/main/java/com/kinancity/mail/Activation.java +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/Activation.java @@ -7,7 +7,7 @@ public class Activation { private String link; private String email; - private String status; + private String status = "UNDEF"; public Activation(String link, String email) { this.link = link; diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/EmailChangeRequest.java b/KinanCity-mail/src/main/java/com/kinancity/mail/EmailChangeRequest.java new file mode 100644 index 0000000..187a5d8 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/EmailChangeRequest.java @@ -0,0 +1,12 @@ +package com.kinancity.mail; + +public class EmailChangeRequest extends Activation { + + public EmailChangeRequest(String link, String email) { + super(link, email); + } + + public EmailChangeRequest(String link, String email, String status) { + super(link, email, status); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/FileLogger.java b/KinanCity-mail/src/main/java/com/kinancity/mail/FileLogger.java index 3d7a270..aa6e5a6 100644 --- a/KinanCity-mail/src/main/java/com/kinancity/mail/FileLogger.java +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/FileLogger.java @@ -1,5 +1,6 @@ package com.kinancity.mail; +import com.kinancity.mail.mailchanger.ToFileEmailChanger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,21 +11,34 @@ public class FileLogger { public static final String BAD = "BAD"; public static final String THROTTLED = "THROTTLED"; public static final String ERROR = "ERROR"; + public static final String EXPIRED = "EXPIRED"; public static final String SKIPPED = "SKIPPED"; + public static final String TYPE_MAILCHANGE = "MAILCHANGE"; + public static final String TYPE_ACTIVATION = "ACTIVATION"; + private static Logger LOGGER = LoggerFactory.getLogger("LINKS"); public static void logStatus(Activation link, String status) { - LOGGER.info("{};{};{}", link.getLink(), link.getEmail(), status); + String type = (link instanceof EmailChangeRequest) ? TYPE_MAILCHANGE : TYPE_ACTIVATION; + LOGGER.info("{};{};{};{}", type, link.getLink(), link.getEmail(), status); } public static Activation fromLog(String line) { String[] parts = line.split(";"); - if (parts.length > 2) { - return new Activation(parts[0], parts[1], parts[2]); + if(parts[0].equals(TYPE_ACTIVATION)) { + if (parts.length > 3) { + return new Activation(parts[1], parts[2], parts[3]); + } else { + return new Activation(parts[1], null, parts[2]); + } } else { - return new Activation(parts[0], null, parts[1]); + if (parts.length > 3) { + return new EmailChangeRequest(parts[1], parts[2], parts[3]); + } else { + return new EmailChangeRequest(parts[1], parts[2]); + } } } } diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandler.java b/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandler.java index 0e362b4..c49cf58 100644 --- a/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandler.java +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandler.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,11 +14,11 @@ import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; +import com.kinancity.mail.mailchanger.EmailChanger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.subethamail.smtp.MessageHandler; import org.subethamail.smtp.RejectException; -import org.subethamail.smtp.TooMuchDataException; import com.kinancity.mail.activator.LinkActivator; @@ -32,6 +34,7 @@ public class KcMessageHandler implements MessageHandler { private static final String POKEMON_DOMAIN = "@pokemon.com"; private static final String ACTIVATION_EXP = "https://club.pokemon.com/us/pokemon-trainer-club/activated/.*"; + private static final String EMAIL_CHANGE_EXP = "https://club.pokemon.com/us/pokemon-trainer-club/email-change-approval/[a-zA-Z0-9]+"; private Logger logger = LoggerFactory.getLogger(getClass()); @@ -40,16 +43,29 @@ public class KcMessageHandler implements MessageHandler { private LinkActivator activator; + private EmailChanger emailChanger; + @Setter private boolean acceptAllFrom = false; + private boolean handleActivation = true; + + private boolean handleEmailChange = true; + + @Setter + private boolean logOtherMessages = true; + /** * Construct with a given Link Activator * * @param activator */ - public KcMessageHandler(LinkActivator activator) { + public KcMessageHandler(LinkActivator activator, EmailChanger emailChanger) { this.activator = activator; + this.emailChanger = emailChanger; + + handleActivation = activator != null; + handleEmailChange = emailChanger != null; } @Override @@ -63,10 +79,10 @@ public void recipient(String recipient) throws RejectException { } /** - * Parse email, exctact activation link and call Link Activator + * Parse email, extract activation link and call Link Activator */ @Override - public void data(InputStream data) throws RejectException, TooMuchDataException, IOException { + public void data(InputStream data) throws RejectException, IOException { // Only accept pokemon mails if (from.endsWith(POKEMON_DOMAIN) || acceptAllFrom) { @@ -78,21 +94,27 @@ public void data(InputStream data) throws RejectException, TooMuchDataException, MimeMessage message = new MimeMessage(session, data); MimeMultipart mpart = (MimeMultipart) message.getContent(); + List parts = getAllMsgParts(mpart); + + String textMsg = getTextPart(parts); + String htmlMsg = getHtmlPart(parts); - BodyPart part = mpart.getBodyPart(0); - String content = (String) part.getContent(); + boolean done = false; - Pattern p = Pattern.compile(ACTIVATION_EXP); - Matcher m = p.matcher(content); - if (m.find()) { - String activationLink = m.group(0); - logger.info("Activation link found for email {} : [{}]", this.recipient, activationLink); + if(handleActivation) { + if(searchForActivationLink(textMsg)) { + done = true; + } + } - // Link activation, may be sync or async - activator.activateLink(new Activation(activationLink, this.recipient)); + if(handleEmailChange && !done) { + if(searchForEmailChangeRequestLink(htmlMsg)) { + done = true; + } + } - } else { - logger.error("No activation link found"); + if(logOtherMessages && !done) { + logger.info("Other unknown mail received with content : {}", textMsg); } } catch (MessagingException e) { @@ -104,6 +126,77 @@ public void data(InputStream data) throws RejectException, TooMuchDataException, } + private List getAllMsgParts(MimeMultipart mpart) throws MessagingException { + List parts = new ArrayList<>(); + int nbParts = mpart.getCount(); + for (int i = 0 ; i < nbParts; i++) { + parts.add(mpart.getBodyPart(i)); + } + return parts; + } + + private boolean searchForActivationLink(String content) { + Pattern p = Pattern.compile(ACTIVATION_EXP); + Matcher m = p.matcher(content); + if (m.find()) { + String activationLink = m.group(0); + logger.info("Activation link found for email {} : [{}]", this.recipient, activationLink); + + // Link activation, may be sync or async + activator.activateLink(new Activation(activationLink, this.recipient)); + return true; + } else { + logger.error("No activation link found"); + return false; + } + } + + private boolean searchForEmailChangeRequestLink(String content) { + Pattern p = Pattern.compile(EMAIL_CHANGE_EXP); + Matcher m = p.matcher(content); + if (m.find()) { + String emailChangeLink = m.group(0); + logger.info("Email Change Request link found for email {} : [{}]", this.recipient, emailChangeLink); + + // Email Change acceptation, may be sync or async + emailChanger.acceptChange(new EmailChangeRequest(emailChangeLink, this.recipient)); + return true; + } else { + logger.error("No email change link found"); + return false; + } + } + + + private String getTextPart(List parts) throws IOException, MessagingException { + BodyPart part = parts.stream().filter(this::isTextPart).findFirst().orElse(null); + return (String) part.getContent(); + } + private String getHtmlPart(List parts) throws IOException, MessagingException { + BodyPart part = parts.stream().filter(this::isHtmlPart).findFirst().orElse(null); + return (String) part.getContent(); + } + + + private boolean isTextPart(BodyPart bodyPart) { + try { + return bodyPart.isMimeType("text/plain"); + } catch ( MessagingException e ) { + logger.error("Error extracting text part form Email"); + return false; + } + } + + private boolean isHtmlPart(BodyPart bodyPart) { + try { + return bodyPart.isMimeType("text/html"); + } catch ( MessagingException e ) { + logger.error("Error extracting html part form Email"); + return false; + } + } + + @Override public void done() { diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandlerFactory.java b/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandlerFactory.java index bab444e..c50e554 100644 --- a/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandlerFactory.java +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/KcMessageHandlerFactory.java @@ -1,5 +1,6 @@ package com.kinancity.mail; +import com.kinancity.mail.mailchanger.EmailChanger; import org.subethamail.smtp.MessageContext; import org.subethamail.smtp.MessageHandler; import org.subethamail.smtp.MessageHandlerFactory; @@ -18,16 +19,19 @@ public class KcMessageHandlerFactory implements MessageHandlerFactory { private LinkActivator activator; + private EmailChanger emailChanger; + @Setter private boolean acceptAllFrom = false; - public KcMessageHandlerFactory(LinkActivator activator) { + public KcMessageHandlerFactory(LinkActivator activator, EmailChanger emailChanger) { this.activator = activator; + this.emailChanger = emailChanger; } @Override public MessageHandler create(MessageContext ctx) { - KcMessageHandler handler = new KcMessageHandler(activator); + KcMessageHandler handler = new KcMessageHandler(activator, emailChanger); handler.setAcceptAllFrom(acceptAllFrom); return handler; } diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/MailServerApplication.java b/KinanCity-mail/src/main/java/com/kinancity/mail/MailServerApplication.java index c1b7e5c..41f67ba 100644 --- a/KinanCity-mail/src/main/java/com/kinancity/mail/MailServerApplication.java +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/MailServerApplication.java @@ -10,7 +10,11 @@ import java.util.Properties; import java.util.regex.Pattern; -import org.subethamail.wiser.Wiser; +import com.kinancity.mail.mailchanger.*; +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import com.kinancity.mail.config.PasswordProviderFactory; +import com.kinancity.mail.wiser.KinanWiser; +import lombok.extern.slf4j.Slf4j; import com.kinancity.mail.activator.LinkActivator; import com.kinancity.mail.activator.MultiThreadQueueLinkActivator; @@ -20,6 +24,7 @@ import com.kinancity.mail.proxy.HttpProxy; import com.kinancity.mail.tester.ThrottleTester; +@Slf4j public class MailServerApplication { private static final String CONFIG_FILE = "config.properties"; @@ -37,7 +42,7 @@ public static void main(String[] args) { } if (mode.equals("test")) { - System.out.println("Start in Tester Mode"); + log.info("Start in Tester Mode"); ThrottleTester tester = new ThrottleTester(); @@ -66,31 +71,62 @@ public static void main(String[] args) { } LinkActivator activator = getQueueLinkActivator(); - activator.start(); + EmailChanger mailChanger = getEmailChanger(); + + HybridActivator hybrid = new HybridActivator(activator, mailChanger); + hybrid.start(); - System.out.println("Started in file Mode"); - new SkippedFileProcessor(activator, filePath).process(); + log.info("Started in file Mode"); + SkippedFileProcessor processor = new SkippedFileProcessor(hybrid, filePath); + processor.process(); } else { LinkActivator activator = getQueueLinkActivator(); + EmailChanger emailChanger = null; + if (mode.equals("log")) { - System.out.println("Started in Log Mode"); + log.info("Started in Log Mode"); activator = new ToFileLinkActivator(); + emailChanger = new ToFileEmailChanger(); } else { - System.out.println("Started in Direct Mode"); + log.info("Started in Direct Mode"); + emailChanger = getEmailChanger(); } activator.start(); // Start Wiser server - Wiser wiser = new Wiser(); - wiser.setPort(25); - wiser.setHostname("localhost"); + KinanWiser wiser = new KinanWiser(); + int port = Integer.parseInt(config.getProperty("port", "25")); + wiser.setPort(port); + wiser.setHostname(config.getProperty("hostname", "")); + + // Additional Setup + wiser.getServer().setSoftwareName("Kinan Mail Server"); + - KcMessageHandlerFactory handlerFactory = new KcMessageHandlerFactory(activator); + log.info("SMTP server started on port {}", port); + + String allowedDomains = config.getProperty("allowedDomains"); + if(allowedDomains != null) { + log.info("Only accept emails for " + allowedDomains); + wiser.setAllowedDomain(allowedDomains); + } + + KcMessageHandlerFactory handlerFactory = new KcMessageHandlerFactory(activator, emailChanger); boolean disableDomainFilter = config.getProperty("disableDomainFilter", "false").equals("true"); if (disableDomainFilter) { handlerFactory.setAcceptAllFrom(true); } + + if(activator != null ) { + log.info("Email Activation is Enabled"); + } + + if(emailChanger != null) { + log.info("Email Change is Enabled"); + } + + wiser.getServer().setMessageHandlerFactory(handlerFactory); wiser.start(); } @@ -102,14 +138,24 @@ public static void loadConfig() { try { File configFile = new File(CONFIG_FILE); if (!configFile.exists()) { - System.out.println("Missing configuration file " + CONFIG_FILE); + log.info("Missing configuration file " + CONFIG_FILE); return; } InputStream in = new FileInputStream(configFile); config.load(in); in.close(); } catch (IOException e) { - System.out.println("Error loading configuration file " + CONFIG_FILE); + log.info("Error loading configuration file " + CONFIG_FILE); + } + } + + private static EmailChanger getEmailChanger() { + String isActive = config.getProperty("emailChanger.active"); + if(isActive == null || isActive.equals("true")) { + PasswordProvider passwordProvider = PasswordProviderFactory.getPasswordProvider(config); + return new DirectEmailChanger(passwordProvider); + } else { + return null; } } @@ -136,19 +182,19 @@ public static LinkActivator getQueueLinkActivator() { List proxies = new LinkedList(Arrays.asList(proxy.split(Pattern.quote("|")))); String initialProxy = proxies.get(0); HttpProxy httpProxy = HttpProxy.fromURI(initialProxy); - System.out.println("Using proxy " + httpProxy); + log.info("Using proxy " + httpProxy); activator.setHttpProxy(httpProxy); proxies.remove(0); for (String backupProxyStr : proxies) { HttpProxy backupProxy = HttpProxy.fromURI(backupProxyStr); httpProxy.getOtherProxies().add(backupProxy); - System.out.println("with backup proxy " + backupProxy); + log.info("with backup proxy " + backupProxy); } } else { HttpProxy httpProxy = HttpProxy.fromURI(proxy); - System.out.println("Using proxy " + httpProxy); + log.info("Using proxy " + httpProxy); activator.setHttpProxy(httpProxy); } } @@ -170,7 +216,7 @@ public static LinkActivator getQueueLinkActivator() { limiter.setLimiterPause(Integer.parseInt(pause)); } - System.out.println("Using limiter " + limiter); + log.info("Using limiter " + limiter); activator.setLimiter(limiter); } diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/SaveAllCookieJar.java b/KinanCity-mail/src/main/java/com/kinancity/mail/SaveAllCookieJar.java new file mode 100644 index 0000000..2f7799f --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/SaveAllCookieJar.java @@ -0,0 +1,34 @@ +package com.kinancity.mail; + +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; + +import java.util.ArrayList; +import java.util.List; + +/** + * Basic cookie jar that mix all the cookies together + * + * @author drallieiv + * + */ +public class SaveAllCookieJar implements CookieJar { + + private List cookies = new ArrayList<>(); + + @Override + public void saveFromResponse(HttpUrl url, List cookies) { + this.cookies.addAll(cookies); + } + + @Override + public List loadForRequest(HttpUrl url) { + return cookies != null ? cookies : new ArrayList<>(); + } + + public List getCookies() { + return cookies; + } + +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/SkippedFileProcessor.java b/KinanCity-mail/src/main/java/com/kinancity/mail/SkippedFileProcessor.java index a15501f..7744ef1 100644 --- a/KinanCity-mail/src/main/java/com/kinancity/mail/SkippedFileProcessor.java +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/SkippedFileProcessor.java @@ -11,6 +11,7 @@ import com.kinancity.mail.activator.LinkActivator; import com.kinancity.mail.activator.QueueLinkActivator; +import com.kinancity.mail.mailchanger.EmailChanger; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -36,14 +37,16 @@ public void process() { String line; while ((line = reader.readLine()) != null) { - // CSV or not ? - if (line.contains(";")) { - Activation activation = FileLogger.fromLog(line); - if (processStatus.contains(activation.getStatus().toUpperCase())) { - activator.activateLink(activation); + if(!line.isEmpty()) { + // CSV or not ? + if (line.contains(";")) { + Activation activation = FileLogger.fromLog(line); + if (processStatus.contains(activation.getStatus().toUpperCase())) { + activator.activateLink(activation); + } + } else { + activator.activateLink(new Activation(line, "")); } - } else { - activator.activateLink(new Activation(line, "")); } } diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/config/CsvPasswordReader.java b/KinanCity-mail/src/main/java/com/kinancity/mail/config/CsvPasswordReader.java new file mode 100644 index 0000000..2dbc3d0 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/config/CsvPasswordReader.java @@ -0,0 +1,100 @@ +package com.kinancity.mail.config; + +import com.kinancity.mail.mailchanger.password.EmailMatcher; +import com.kinancity.mail.mailchanger.password.matcher.ExactEmailMatcher; +import com.kinancity.mail.mailchanger.password.matcher.MatchingPair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; + +public class CsvPasswordReader { + private Logger logger = LoggerFactory.getLogger(getClass()); + + private static final String CSV_COMMENT_PREFIX = "#"; + + private static final String CSV_SPLITTER = ";"; + + public static final String EMAIL = "email"; + public static final String PASSWORD = "password"; + + public List load(String accountFileName) { + logger.info("Will load a list of accout to create from a csv file"); + + File accountFile = new File(accountFileName); + if (!accountFile.exists() || !accountFile.canRead()) { + logger.error("Cannot open file {}. Abort", accountFileName); + } + + try { + return loadFile(accountFile); + } catch (FileNotFoundException e) { + logger.error("Cannot open file {}. Abort", accountFileName); + } + return new ArrayList<>(); + } + + + + /** + * Load CSV file and build a list of Exact matchers + * @param accountFile + * @return + * @throws FileNotFoundException + */ + private List loadFile(File accountFile) throws FileNotFoundException { + List rules = new ArrayList<>(); + try (Scanner scanner = new Scanner(accountFile)) { + // Read first line that must be header + String firstline = scanner.nextLine(); + List headers = null; + if (firstline != null && firstline.startsWith(CSV_COMMENT_PREFIX)) { + headers = Arrays.asList(firstline.replace(CSV_COMMENT_PREFIX, "").split(CSV_SPLITTER)); + if (!headers.containsAll(Arrays.asList(PASSWORD, EMAIL))) { + logger.error("CSV file header is missing either password or email fields."); + } + } else { + logger.error("CSV file is missing header line."); + return rules; + } + + // Read all other lines + while (scanner.hasNext()) { + String line = scanner.nextLine(); + rules.add(buildMatcherFromCsv(line, headers)); + } + } + return rules; + } + + /** + * Create a matching pair from csv + * @param line + * @param headers + * @return MatchingPair + */ + public MatchingPair buildMatcherFromCsv(String line, List headers) { + // Parse csv into data map + Map fieldMap = new HashMap<>(); + List fields = Arrays.asList(line.split(CSV_SPLITTER)); + for (int i = 0; i < Math.min(fields.size(), headers.size()); i++) { + fieldMap.put(headers.get(i), fields.get(i)); + } + return buildMatcherFromDataFromMap(fieldMap); + } + + /** + * Create an MatchingPair given a set of fields + * + * @param fieldMap + * @return MatchingPair + */ + public MatchingPair buildMatcherFromDataFromMap(Map fieldMap) { + String email = fieldMap.get(EMAIL); + String password = fieldMap.get(PASSWORD); + EmailMatcher matcher = new ExactEmailMatcher(email); + return new MatchingPair(matcher, password); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/config/PasswordProviderFactory.java b/KinanCity-mail/src/main/java/com/kinancity/mail/config/PasswordProviderFactory.java new file mode 100644 index 0000000..60b364d --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/config/PasswordProviderFactory.java @@ -0,0 +1,59 @@ +package com.kinancity.mail.config; + +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import com.kinancity.mail.mailchanger.password.matcher.AnyEmailMatcher; +import com.kinancity.mail.mailchanger.password.matcher.MatchingPair; +import com.kinancity.mail.mailchanger.password.matcher.RegexEmailMatcher; +import com.kinancity.mail.mailchanger.password.provider.MatcherPasswordProvider; +import com.kinancity.mail.mailchanger.password.provider.StaticPasswordProvider; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +@Slf4j +public class PasswordProviderFactory { + private static Logger LOGGER = LoggerFactory.getLogger(PasswordProviderFactory.class); + + private static final String STATIC_CONFIG = "emailChanger.password.static"; + private static final String MAPPING_CONFIG = "emailChanger.password.mapping"; + private static final String CSV_CONFIG = "emailChanger.password.csv"; + + public static PasswordProvider getPasswordProvider(Properties config) { + List mapping = new ArrayList<>(); + + // CSV Mapping + String csvConfig = config.getProperty(CSV_CONFIG); + if(StringUtils.isNotEmpty(csvConfig)) { + CsvPasswordReader csvReader = new CsvPasswordReader(); + mapping.addAll(csvReader.load(csvConfig)); + } + + // Regexp Mapping + String mappingConfig = config.getProperty(MAPPING_CONFIG); + if(StringUtils.isNotEmpty(mappingConfig)) { + String[] configs = mappingConfig.split("\\|\\|"); + for (String sConfig : configs) { + if(!sConfig.contains(":")){ + log.error("Invalid mapping config [{}] => skip", sConfig); + } else { + String[] data = sConfig.split(":"); + mapping.add(new MatchingPair(new RegexEmailMatcher(data[0]), data[1])); + } + } + + } + + // Default static Mapping + String staticConfig = config.getProperty(STATIC_CONFIG); + if(StringUtils.isNotEmpty(staticConfig)) { + mapping.add(new MatchingPair(new AnyEmailMatcher(), staticConfig)); + } + + MatcherPasswordProvider provider = new MatcherPasswordProvider(); + provider.setMatchingPairs(mapping); + return provider; + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/DirectEmailChanger.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/DirectEmailChanger.java new file mode 100644 index 0000000..aaedf82 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/DirectEmailChanger.java @@ -0,0 +1,152 @@ +package com.kinancity.mail.mailchanger; + +import com.kinancity.mail.EmailChangeRequest; +import com.kinancity.mail.FileLogger; +import com.kinancity.mail.MailConstants; +import com.kinancity.mail.SaveAllCookieJar; +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import okhttp3.*; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class DirectEmailChanger implements EmailChanger{ + private Logger logger = LoggerFactory.getLogger(getClass()); + + private final String EXPIRED_TXT = "Your attempt to change your email address is invalid or has timed out"; + private final String REQUEST_TXT = "A request has been made to change your email address"; + private final String EMAIL_UPDATED = "You have updated your email address."; + + /** + * + */ + + private okhttp3.OkHttpClient client; + + private PasswordProvider passwordProvider; + + private SaveAllCookieJar cookieJar; + + public DirectEmailChanger(PasswordProvider passwordProvider) { + cookieJar = new SaveAllCookieJar(); + this.client = new OkHttpClient.Builder().cookieJar(cookieJar).build(); + this.passwordProvider = passwordProvider; + } + + @Override + public boolean acceptChange(EmailChangeRequest emailChangeRequest) { + + try { + Request request = new Request.Builder() + .header(MailConstants.HEADER_USER_AGENT, MailConstants.CHROME_USER_AGENT) + .url(emailChangeRequest.getLink()) + .build(); + + Response response = client.newCall(request).execute(); + String strResponse = response.body().string(); + response.body().close(); + + if (response.isSuccessful()) { + // Now check the page itself + if (strResponse.contains(EXPIRED_TXT)) { + logger.info("Email Change Link Expired"); + FileLogger.logStatus(emailChangeRequest, FileLogger.EXPIRED); + return false; + } + + if (strResponse.contains(REQUEST_TXT)) { + logger.info("Email Change Link Valid"); + // Parse the response + Document doc = Jsoup.parse(strResponse); + + // Grab all data + String crsfToken = this.getCrsfToken(doc); + String currentEmail = this.getField(doc, "current_email"); + String newEmail = this.getField(doc, "new_email"); + // Add the password + String password = passwordProvider.getPassword(currentEmail); + if(password == null) { + logger.error("Mail Change failed : missing password for email {}", currentEmail); + FileLogger.logStatus(emailChangeRequest, FileLogger.ERROR); + return false; + } + + // Send everything + FormBody body = new FormBody.Builder() + .add("secure-change-approve", "Confirm") + .add("csrfmiddlewaretoken", crsfToken) + .add("current_email", currentEmail) + .add("new_email", newEmail) + .add("current_password", password) + .build(); + + Request acceptRequest = new Request.Builder() + .header(MailConstants.HEADER_USER_AGENT, MailConstants.CHROME_USER_AGENT) + .header("Origin", "https://club.pokemon.com") + .header("referer", emailChangeRequest.getLink()) + .url(emailChangeRequest.getLink()) + .post(body) + .build(); + + Response acceptResponse = client.newCall(acceptRequest).execute(); + cookieJar.getCookies().clear(); + + if (response.isSuccessful()) { + String strAcceptResponse = acceptResponse.body().string(); + + if (strAcceptResponse.contains(EMAIL_UPDATED)) { + logger.info("Email Change Successful"); + FileLogger.logStatus(emailChangeRequest, FileLogger.OK); + return true; + } else { + logger.info("Email Change FAILED"); + FileLogger.logStatus(emailChangeRequest, FileLogger.ERROR); + return false; + } + + } else { + logger.error("Mail Change failed : Failed to call PTC to accept"); + FileLogger.logStatus(emailChangeRequest, FileLogger.ERROR); + return false; + } + } + + } else { + logger.error("Mail Change failed : Failed to call PTC"); + FileLogger.logStatus(emailChangeRequest, FileLogger.ERROR); + return false; + } + } catch (MailChangerException e) { + logger.error("Mail Change failed : {}", e.getMessage()); + FileLogger.logStatus(emailChangeRequest, FileLogger.ERROR); + return false; + } catch (IOException e) { + return false; + } + + return false; + } + + private String getCrsfToken(Document doc) throws MailChangerException { + Elements tokenField = doc.select("[name=csrfmiddlewaretoken]"); + if (tokenField.isEmpty()) { + throw new MailChangerException("CSRF Token not found"); + } else { + return tokenField.get(0).val(); + } + } + + private String getField(Document doc, String fieldName) throws MailChangerException { + Elements tokenField = doc.select("[name="+fieldName+"]"); + if (tokenField.isEmpty()) { + throw new MailChangerException(fieldName + " Field not found"); + } else { + return tokenField.get(0).val(); + } + } + +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/EmailChanger.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/EmailChanger.java new file mode 100644 index 0000000..34d8400 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/EmailChanger.java @@ -0,0 +1,7 @@ +package com.kinancity.mail.mailchanger; + +import com.kinancity.mail.EmailChangeRequest; + +public interface EmailChanger { + boolean acceptChange(EmailChangeRequest emailChangeRequest); +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/HybridActivator.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/HybridActivator.java new file mode 100644 index 0000000..5ac2c42 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/HybridActivator.java @@ -0,0 +1,31 @@ +package com.kinancity.mail.mailchanger; + +import com.kinancity.mail.Activation; +import com.kinancity.mail.EmailChangeRequest; +import com.kinancity.mail.activator.LinkActivator; + +public class HybridActivator implements LinkActivator { + + private LinkActivator activator; + + private EmailChanger emailChanger; + + public HybridActivator(LinkActivator activator, EmailChanger emailChanger) { + this.activator = activator; + this.emailChanger = emailChanger; + } + + @Override + public boolean activateLink(Activation link) { + if(link instanceof EmailChangeRequest){ + return emailChanger.acceptChange((EmailChangeRequest) link); + } else { + return activator.activateLink(link); + } + } + + @Override + public void start() { + this.activator.start(); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/MailChangerException.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/MailChangerException.java new file mode 100644 index 0000000..a10ea4c --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/MailChangerException.java @@ -0,0 +1,11 @@ +package com.kinancity.mail.mailchanger; + +public class MailChangerException extends Exception{ + public MailChangerException(String message) { + super(message); + } + + public MailChangerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/ToFileEmailChanger.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/ToFileEmailChanger.java new file mode 100644 index 0000000..ed21013 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/ToFileEmailChanger.java @@ -0,0 +1,12 @@ +package com.kinancity.mail.mailchanger; + +import com.kinancity.mail.EmailChangeRequest; +import com.kinancity.mail.FileLogger; + +public class ToFileEmailChanger implements EmailChanger { + @Override + public boolean acceptChange(EmailChangeRequest emailChangeRequest) { + FileLogger.logStatus(emailChangeRequest, FileLogger.SKIPPED); + return true; + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/EmailMatcher.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/EmailMatcher.java new file mode 100644 index 0000000..841a117 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/EmailMatcher.java @@ -0,0 +1,5 @@ +package com.kinancity.mail.mailchanger.password; + +public interface EmailMatcher { + boolean matches(String email); +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/PasswordProvider.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/PasswordProvider.java new file mode 100644 index 0000000..2a796ca --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/PasswordProvider.java @@ -0,0 +1,11 @@ +package com.kinancity.mail.mailchanger.password; + +public interface PasswordProvider { + /** + * Return the password for that email. + * + * @param email email address + * @return the password or null if not handled + */ + String getPassword(String email); +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/AnyEmailMatcher.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/AnyEmailMatcher.java new file mode 100644 index 0000000..12bbe2f --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/AnyEmailMatcher.java @@ -0,0 +1,11 @@ +package com.kinancity.mail.mailchanger.password.matcher; + +import com.kinancity.mail.mailchanger.password.EmailMatcher; + +public class AnyEmailMatcher implements EmailMatcher { + + @Override + public boolean matches(String email) { + return true; + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/ExactEmailMatcher.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/ExactEmailMatcher.java new file mode 100644 index 0000000..f8ed89e --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/ExactEmailMatcher.java @@ -0,0 +1,20 @@ +package com.kinancity.mail.mailchanger.password.matcher; + +import com.kinancity.mail.mailchanger.password.EmailMatcher; + +public class ExactEmailMatcher implements EmailMatcher { + + private String email; + + public ExactEmailMatcher(String email) { + this.email = email; + } + + @Override + public boolean matches(String otherEmail) { + if(otherEmail == null || email == null){ + return false; + } + return otherEmail.equals(email); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/MatchingPair.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/MatchingPair.java new file mode 100644 index 0000000..8c3adb8 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/MatchingPair.java @@ -0,0 +1,12 @@ +package com.kinancity.mail.mailchanger.password.matcher; + +import com.kinancity.mail.mailchanger.password.EmailMatcher; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class MatchingPair { + private EmailMatcher matcher; + private String password; +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/RegexEmailMatcher.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/RegexEmailMatcher.java new file mode 100644 index 0000000..4ae45f7 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/matcher/RegexEmailMatcher.java @@ -0,0 +1,34 @@ +package com.kinancity.mail.mailchanger.password.matcher; + +import com.kinancity.mail.mailchanger.password.EmailMatcher; +import lombok.Getter; + +import java.util.regex.Pattern; + +/** + * Matcher based on regular expression + */ +@Getter +public class RegexEmailMatcher implements EmailMatcher { + + private String regex; + + private Pattern exp; + + public RegexEmailMatcher(String regex) { + this.setRegex(regex); + } + + @Override + public boolean matches(String accountName) { + if(accountName == null){ + return false; + } + return exp.matcher(accountName).matches(); + } + + public void setRegex(String regex) { + this.regex = regex; + exp = Pattern.compile(regex); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/ChainedPasswordProvider.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/ChainedPasswordProvider.java new file mode 100644 index 0000000..a94bb58 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/ChainedPasswordProvider.java @@ -0,0 +1,28 @@ +package com.kinancity.mail.mailchanger.password.provider; + +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import org.apache.commons.lang.StringUtils; + +import java.util.List; + +public class ChainedPasswordProvider implements PasswordProvider { + + private List providerChain; + + public ChainedPasswordProvider(List providerChain) { + this.providerChain = providerChain; + } + + @Override + public String getPassword(String currentEmail) { + + for (PasswordProvider passwordProvider : providerChain) { + String password = passwordProvider.getPassword(currentEmail); + if(StringUtils.isNotEmpty(password)) { + return password; + } + } + + return null; + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/MappedPasswordProvider.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/MappedPasswordProvider.java new file mode 100644 index 0000000..75291f9 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/MappedPasswordProvider.java @@ -0,0 +1,23 @@ +package com.kinancity.mail.mailchanger.password.provider; + +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +/** + * Fixed 1 to 1 mapping + */ +public class MappedPasswordProvider implements PasswordProvider { + + @Setter + @Getter + private Map mapping; + + + @Override + public String getPassword(String currentEmail) { + return mapping.get(currentEmail); + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/MatcherPasswordProvider.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/MatcherPasswordProvider.java new file mode 100644 index 0000000..4f6bb61 --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/MatcherPasswordProvider.java @@ -0,0 +1,26 @@ +package com.kinancity.mail.mailchanger.password.provider; + +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import com.kinancity.mail.mailchanger.password.matcher.MatchingPair; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +public class MatcherPasswordProvider implements PasswordProvider { + + @Setter + @Getter + private List matchingPairs; + + + @Override + public String getPassword(String email) { + for (MatchingPair pair : matchingPairs) { + if(pair.getMatcher().matches(email)) { + return pair.getPassword(); + } + } + return null; + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/StaticPasswordProvider.java b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/StaticPasswordProvider.java new file mode 100644 index 0000000..98fe58f --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/mailchanger/password/provider/StaticPasswordProvider.java @@ -0,0 +1,17 @@ +package com.kinancity.mail.mailchanger.password.provider; + +import com.kinancity.mail.mailchanger.password.PasswordProvider; + +public class StaticPasswordProvider implements PasswordProvider { + + private String fixedPwd; + + public StaticPasswordProvider(String fixedPwd) { + this.fixedPwd = fixedPwd; + } + + @Override + public String getPassword(String currentEmail) { + return fixedPwd; + } +} diff --git a/KinanCity-mail/src/main/java/com/kinancity/mail/wiser/KinanWiser.java b/KinanCity-mail/src/main/java/com/kinancity/mail/wiser/KinanWiser.java new file mode 100644 index 0000000..4b86c9f --- /dev/null +++ b/KinanCity-mail/src/main/java/com/kinancity/mail/wiser/KinanWiser.java @@ -0,0 +1,36 @@ +package com.kinancity.mail.wiser; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.subethamail.wiser.Wiser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// Only Accept emails to a given domain +@Slf4j +public class KinanWiser extends Wiser { + + @Setter + private List allowedDomains = new ArrayList<>(); + + @Override + public boolean accept(String from, String recipient) { + boolean okay = super.accept(from, recipient); + + if(okay && !allowedDomains.isEmpty()) { + okay = allowedDomains.stream().anyMatch((allowedDomain) -> recipient.endsWith(allowedDomain) ); + } + + if(!okay) { + log.warn("Rejected email, from {} to {}", from, recipient); + } + + return okay; + } + + public void setAllowedDomain(String domains){ + this.allowedDomains = Arrays.asList(domains.split(",")); + } +} diff --git a/KinanCity-mail/src/test/java/com/kinancity/mail/config/PasswordProviderFactoryTest.java b/KinanCity-mail/src/test/java/com/kinancity/mail/config/PasswordProviderFactoryTest.java new file mode 100644 index 0000000..17159af --- /dev/null +++ b/KinanCity-mail/src/test/java/com/kinancity/mail/config/PasswordProviderFactoryTest.java @@ -0,0 +1,66 @@ +package com.kinancity.mail.config; + +import com.kinancity.mail.mailchanger.password.PasswordProvider; +import org.junit.Test; + +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class PasswordProviderFactoryTest { + + @Test + public void getStaticPasswordProvider() { + Properties config = new Properties(); + config.setProperty("emailChanger.password.static", "static"); + PasswordProvider factory = PasswordProviderFactory.getPasswordProvider(config); + + assertThat(factory.getPassword("anything")).isEqualTo("static"); + } + + @Test + public void getCsvPasswordProvider() { + Properties config = new Properties(); + config.setProperty("emailChanger.password.csv", "accounts.example.csv"); + PasswordProvider factory = PasswordProviderFactory.getPasswordProvider(config); + + assertThat(factory.getPassword("username1@myMxDomain.com")).isEqualTo("testAA123+"); + assertThat(factory.getPassword("username2@myMxDomain.com")).isEqualTo("testAA456+"); + assertThat(factory.getPassword("anything")).isNull(); + } + + @Test + public void getRegexPasswordProvider() { + Properties config = new Properties(); + config.setProperty("emailChanger.password.mapping", "aaaaa.*@domain.com:pass1||bbbbb[0-9]+@.*:pass2"); + PasswordProvider factory = PasswordProviderFactory.getPasswordProvider(config); + + assertThat(factory.getPassword("aaaaa123@domain.com")).isEqualTo("pass1"); + assertThat(factory.getPassword("aaaaaXYZ@domain.com")).isEqualTo("pass1"); + assertThat(factory.getPassword("bbbbb123@other.com")).isEqualTo("pass2"); + assertThat(factory.getPassword("anything")).isNull(); + assertThat(factory.getPassword("aaaaa123@other.com")).isNull(); + assertThat(factory.getPassword("bbbbbXYZ@other.com")).isNull(); + } + + @Test + public void getFullPasswordProvider() { + Properties config = new Properties(); + config.setProperty("emailChanger.password.csv", "accounts.example.csv"); + config.setProperty("emailChanger.password.mapping", "aaaaa.*@domain.com:pass1||bbbbb[0-9]+@.*:pass2"); + config.setProperty("emailChanger.password.static", "static"); + PasswordProvider factory = PasswordProviderFactory.getPasswordProvider(config); + + assertThat(factory.getPassword("username1@myMxDomain.com")).isEqualTo("testAA123+"); + assertThat(factory.getPassword("username2@myMxDomain.com")).isEqualTo("testAA456+"); + + assertThat(factory.getPassword("aaaaa123@domain.com")).isEqualTo("pass1"); + assertThat(factory.getPassword("aaaaaXYZ@domain.com")).isEqualTo("pass1"); + assertThat(factory.getPassword("bbbbb123@other.com")).isEqualTo("pass2"); + + assertThat(factory.getPassword("anything")).isEqualTo("static"); + assertThat(factory.getPassword("aaaaa123@other.com")).isEqualTo("static"); + assertThat(factory.getPassword("bbbbbXYZ@other.com")).isEqualTo("static"); + } +} \ No newline at end of file diff --git a/KinanCity-utils/pom.xml b/KinanCity-utils/pom.xml index d4cb4d0..e688885 100644 --- a/KinanCity-utils/pom.xml +++ b/KinanCity-utils/pom.xml @@ -20,7 +20,7 @@ junit junit - 4.12 + 4.13.1 test diff --git a/README.md b/README.md index c4a373a..98b8d4a 100644 --- a/README.md +++ b/README.md @@ -3,29 +3,45 @@ [![Last Version](https://img.shields.io/badge/version-1.0.0--Alpha1-brightgreen.svg)](https://github.com/drallieiv/KinanCity/releases/latest) [![Build Status](https://travis-ci.org/drallieiv/KinanCity.svg?branch=master)](https://travis-ci.org/drallieiv/KinanCity) -Any issues with KinanCity, or you just want to talk about the project ? Go to [our discord server]( http://discord.gg/3jkb3zA) +Any issues with KinanCity, or you just want to talk about the project? Go to [our discord server]( http://discord.gg/3jkb3zA) -## Were is Kinan City ? +## Where is Kinan City? **Kinan City** (キナンシティ) is one of the cities of the Kalos region in Pokemon XY games and anime. It is known as **Kiloude City** is the English version and **Batisques** in French. -Kinan City is known for it **Friend Safari** where many trainer comes to find pokemon. This is a good place if you want to meet a **lot of new trainers**. +Kinan City is known for its **Friend Safari** where many trainer come to find pokemon. This is a good place if you want to meet a **lot of new trainers**. + +## How does one get a Pokemon Trainer Club account ? + +In order to get a PTC account you need to go the pokemon website and request to create a new account, and complete theses steps. + +![](docs/0_PTCsignup.png) + +You then first have to fill some personal informations such as your country and birthdate. Then information about the account you want. + +You also need to complete a Captcha task to prove that you are a human. + +One the subscription is done, the account still needs to be activated in the next 48 hours. An email will be sent to the given address with a link. You need to follow that link to activate the account. + +NOTE : You cannot use the same email twice for multiple PTC account, they each need their own email address. + +Theses steps must be done for each PTC account you want to create. ## What does KinanCity do ? -**KinanCity** is a tool that automates the creation of Pokemon Trainer Accounts and contains multiple modules. +**KinanCity** is a tool that automates the creation of Pokemon Trainer Accounts and contains multiple modules when you need to create a lot of accounts, fast. -- **KinanCity-core** : is the core module that can also be used in command line. [more info here](KinanCity-core/README.md) -- **KinanCity-mail** : is a minimalist Email server that does auto-activation. [more info here](KinanCity-mail/README.md) +- **KinanCity-core** is the core module that can also be used in command line. [more info here](KinanCity-core/README.md) +- **KinanCity-mail** is a minimalist Email server that does auto-activation. [more info here](KinanCity-mail/README.md) -## Why another tool ? +## Why another tool? -There are already many accout creator with each their specific features. +There are already many account creators with their specific features each. KinanCity was born by taking the best features of them to have a complete solution. -The advantages of KinanCity : -* is cross platform compatible (Unix, Windows, Mac) +The advantages of KinanCity: +* cross platform compatible (Unix, Windows, Mac) * can work on headless systems without any need of a web driver * does parallel processing to be faster * can use multiple proxies to go over the limit of 30 accounts per hour (5 accounts per 10 minutes) @@ -34,7 +50,7 @@ The advantages of KinanCity : You can **download** the latest version of each module [here](https://github.com/drallieiv/KinanCity/packages) or -You can **compile** it yourself using a **maven** goal : `mvn package` +You can **compile** it yourself using a **maven** goal: `mvn package` **KinanCity** modules are java applications and all you need is a Java 8 Runtime Environment on your system. diff --git a/docs/0_PTCsignup.png b/docs/0_PTCsignup.png new file mode 100644 index 0000000..4459013 Binary files /dev/null and b/docs/0_PTCsignup.png differ diff --git a/docs/1_KinanCore.png b/docs/1_KinanCore.png new file mode 100644 index 0000000..a098ce8 Binary files /dev/null and b/docs/1_KinanCore.png differ diff --git a/docs/2_IPrestrictions.png b/docs/2_IPrestrictions.png new file mode 100644 index 0000000..59f6f7c Binary files /dev/null and b/docs/2_IPrestrictions.png differ diff --git a/docs/3_email.png b/docs/3_email.png new file mode 100644 index 0000000..ba1c8fd Binary files /dev/null and b/docs/3_email.png differ diff --git a/docs/4_kinanMail.png b/docs/4_kinanMail.png new file mode 100644 index 0000000..53dfb5d Binary files /dev/null and b/docs/4_kinanMail.png differ diff --git a/ptc-api/pom.xml b/ptc-api/pom.xml index f30441a..949eb48 100644 --- a/ptc-api/pom.xml +++ b/ptc-api/pom.xml @@ -42,7 +42,7 @@ commons-io commons-io - 2.5 + 2.7 commons-lang @@ -69,7 +69,7 @@ junit junit - 4.12 + 4.13.1 test