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