package games.strategy.engine.random; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import games.strategy.engine.ClientContext; import games.strategy.engine.framework.system.HttpProxy; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; import lombok.Builder; import lombok.Getter; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; /** A pbem dice roller that reads its configuration from a properties file. */ @Builder public final class MartiDiceRoller implements IRemoteDiceServer { private static final int MESSAGE_MAX_LENGTH = 200; private static final String DICE_ROLLER_PATH = "/MARTI.php"; private final Pattern errorPattern = Pattern.compile("fatal error:(.*)!"); /** * Matches a comma separated list of integers like this: {@literal your dice are: 1,2,3 <p>} or * this: {@literal your dice are: 12,34,56 <p>} */ private final Pattern dicePattern = Pattern.compile("your dice are:\\s*((?:\\d+(?:,\\d+)*)?)\\s*<p>"); @Nonnull private final URI diceRollerUri; @Getter(onMethod_ = @Override) @Nonnull private final String toAddress; @Getter(onMethod_ = @Override) @Nonnull private final String ccAddress; @Getter(onMethod_ = @Override) @Nonnull private final String gameId; @Override public String getDisplayName() { return diceRollerUri.toString(); } @Override public String postRequest( final int max, final int numDice, final String subjectMessage, final String gameId) throws IOException { final String normalizedGameId = gameId.isBlank() ? "TripleA" : gameId; String message = normalizedGameId + ":" + subjectMessage; if (message.length() > MESSAGE_MAX_LENGTH) { message = message.substring(0, MESSAGE_MAX_LENGTH - 1); } try (CloseableHttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new AdvancedRedirectStrategy()).build()) { final HttpPost httpPost = new HttpPost(DICE_ROLLER_PATH); final List<NameValuePair> params = ImmutableList.of( new BasicNameValuePair("numdice", String.valueOf(numDice)), new BasicNameValuePair("numsides", String.valueOf(max)), new BasicNameValuePair("subject", message), new BasicNameValuePair("roller", getToAddress()), new BasicNameValuePair("gm", getCcAddress())); httpPost.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8)); httpPost.addHeader("User-Agent", "triplea/" + ClientContext.engineVersion()); final HttpHost hostConfig = new HttpHost(diceRollerUri.getHost(), diceRollerUri.getPort(), diceRollerUri.getScheme()); HttpProxy.addProxy(httpPost); try (CloseableHttpResponse response = httpClient.execute(hostConfig, httpPost)) { return EntityUtils.toString(response.getEntity()); } } } @Override public int[] getDice(final String string, final int count) throws DiceServerException { final Matcher errorMatcher = errorPattern.matcher(string); if (errorMatcher.find()) { throw new DiceServerException(errorMatcher.group(1)); } final Matcher diceMatcher = dicePattern.matcher(string); if (!diceMatcher.find()) { throw new IllegalStateException("String '" + string + "' has an invalid format."); } return Splitter.on(',').omitEmptyStrings().splitToList(diceMatcher.group(1)).stream() .mapToInt(Integer::parseInt) .peek(i -> Preconditions.checkState(i != 0, "Die can't be zero: '" + string + "'")) // -1 since we are 0 based .map(i -> i - 1) .toArray(); } private static class AdvancedRedirectStrategy extends LaxRedirectStrategy { @Override public HttpUriRequest getRedirect( final HttpRequest request, final HttpResponse response, final HttpContext context) throws ProtocolException { final URI uri = getLocationURI(request, response, context); final String method = request.getRequestLine().getMethod(); if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { return new HttpHead(uri); } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) { return new HttpGet(uri); } else { final int status = response.getStatusLine().getStatusCode(); if (status == HttpStatus.SC_TEMPORARY_REDIRECT || status == HttpStatus.SC_MOVED_PERMANENTLY || status == HttpStatus.SC_MOVED_TEMPORARILY) { return RequestBuilder.copy(request).setUri(uri).build(); } return new HttpGet(uri); } } } }