diff --git a/pom.xml b/pom.xml index 40a60a5..944580b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 fr.tunaki.stackoverflow chatexchange - 1.1.2-SNAPSHOT + 1.2.0-SNAPSHOT ChatExchange Simple API to interact with the chat system on Stack Overflow, and the Stack Exchange network. 2016 diff --git a/src/main/java/fr/tunaki/stackoverflow/chat/StackExchangeClient.java b/src/main/java/fr/tunaki/stackoverflow/chat/StackExchangeClient.java index eddb7c8..8a79af8 100644 --- a/src/main/java/fr/tunaki/stackoverflow/chat/StackExchangeClient.java +++ b/src/main/java/fr/tunaki/stackoverflow/chat/StackExchangeClient.java @@ -5,6 +5,7 @@ import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.regex.Matcher; @@ -24,14 +25,39 @@ public class StackExchangeClient implements AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(StackExchangeClient.class); + /** + * @deprecated in 1.2.0. See meta: https://meta.stackexchange.com/q/307647/347985 + */ + @Deprecated private static final Pattern OPEN_ID_PROVIDER_PATTERN = Pattern.compile("(https://openid.stackexchange.com/user/.*?)\""); + /** + * @deprecated in 1.2.0. See meta: https://meta.stackexchange.com/q/307647/347985 + */ + @Deprecated private String openIdProvider; private HttpClient httpClient; private Map cookies = new HashMap<>(); + /** + * Rooms the user is currently in + */ private List rooms = new ArrayList<>(); + + /** + * The user's e-mail-address + * This needs to be stored in order to login to a site when joining a room. + * With OpenID, this was not necessary because we only had to login once while initializing `StackExchangeClient` + * */ + private String email = null; + + /** + * The user's password + * This needs to be stored in order to login to a site when joining a room. + * With OpenID, this was not necessary because we only had to login once while initializing `StackExchangeClient` + * */ + private String password = null; /** * Constructs the client with the provided credentials. Those will be the credentials used to send messages. @@ -40,13 +66,36 @@ public class StackExchangeClient implements AutoCloseable { */ public StackExchangeClient(String email, String password) { httpClient = new HttpClient(); - try { - SEOpenIdLogin(email, password); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + this.email = email; + this.password = password; } + + /** + * Logs in to s given site + * @param email The user's e-mail-address + * @param password The password + * @param The host of the main site (NOT the chat.*! Use ChatHost.getName()) + * */ + private void seLogin(String email, String password, String host) throws IOException { + //The login-form has a hidden field called "fkey" which needs to be sent along with the mail and password + Response response = httpClient.get("https://"+host+"/users/login", cookies); + String fkey = response.parse().select("input[name='fkey']").val(); + + response = httpClient.post("https://"+host+"/users/login", cookies, "email", email, "password", password, "fkey", fkey); + + // check if login succeeded + Response checkResponse = httpClient.get("https://"+host+"/users/current", cookies); + if (checkResponse.parse().getElementsByClass("js-inbox-button").first() == null) { + LOGGER.debug(checkResponse.parse().html()); + throw new IllegalStateException("Unable to login to Stack Exchange. (Site: " + host + ")"); + } // if + } // seLogin + /** + * The old login-flow with OpenID + * @deprecated in 1.2.0. See meta: https://meta.stackexchange.com/q/307647/347985 + * */ + @Deprecated private void SEOpenIdLogin(String email, String password) throws IOException { Response response = httpClient.get("https://openid.stackexchange.com/account/login", cookies); String fkey = response.parse().select("input[name='fkey']").val(); @@ -72,21 +121,40 @@ private void SEOpenIdLogin(String email, String password) throws IOException { * @return Room joined. */ public Room joinRoom(ChatHost host, int roomId) { - if (rooms.stream().anyMatch(r -> r.getHost().equals(host) && r.getRoomId() == roomId)) { - throw new ChatOperationException("Cannot join a room you are already in."); - } - if (rooms.stream().allMatch(r -> !r.getHost().equals(host))) { + String mainSiteHost = host.getName(); + + boolean alreadyLoggedIn = false; + + for (Room room : this.rooms) { + if (room.getHost().equals(host)) { + alreadyLoggedIn = true; + break; + } // if + } // for rooms + + if (!alreadyLoggedIn) { + //not logged in on that site yet try { - siteLogin(host.getName()); + this.seLogin(email, password, mainSiteHost); } catch (IOException e) { - throw new UncheckedIOException(e); + LOGGER.error("Unable to login on " + mainSiteHost + " for " + host.getBaseUrl(), e); + throw new ChatOperationException("Login to " + mainSiteHost + " failed!"); } } + + if (rooms.stream().anyMatch(r -> r.getHost().equals(host) && r.getRoomId() == roomId)) { + throw new ChatOperationException("Cannot join a room you are already in."); + } + Room chatRoom = new Room(host, roomId, httpClient, cookies); rooms.add(chatRoom); return chatRoom; } + /** + * @deprecated in 1.2.0: This is not required anymore, but maybe someone can re-implement the account creation in the new login-flow? + * */ + @Deprecated private void siteLogin(String host) throws IOException { Response response = httpClient.get("https://" + host + "/users/login?returnurl=" + URLEncoder.encode("https://" + host + "/", "UTF-8"), cookies); String fkey = response.parse().select("input[name='fkey']").val();