
it.auties.whatsapp.registration.HttpRegistration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cobalt Show documentation
Show all versions of cobalt Show documentation
Standalone fully-featured Whatsapp Web API for Java and Kotlin
package it.auties.whatsapp.registration;
import it.auties.curve25519.Curve25519;
import it.auties.whatsapp.api.AsyncVerificationCodeSupplier;
import it.auties.whatsapp.controller.Keys;
import it.auties.whatsapp.controller.Store;
import it.auties.whatsapp.crypto.AesGcm;
import it.auties.whatsapp.exception.RegistrationException;
import it.auties.whatsapp.model.mobile.VerificationCodeError;
import it.auties.whatsapp.model.mobile.VerificationCodeMethod;
import it.auties.whatsapp.model.mobile.VerificationCodeStatus;
import it.auties.whatsapp.model.node.Attributes;
import it.auties.whatsapp.model.response.AbPropsResponse;
import it.auties.whatsapp.model.response.VerificationCodeResponse;
import it.auties.whatsapp.model.signal.keypair.SignalKeyPair;
import it.auties.whatsapp.util.*;
import it.auties.whatsapp.util.Specification.Whatsapp;
import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
public final class HttpRegistration {
static {
Authenticator.setDefault(new ProxyAuthenticator());
}
private final HttpClient httpClient;
private final Store store;
private final Keys keys;
private final AsyncVerificationCodeSupplier codeHandler;
private final VerificationCodeMethod method;
public HttpRegistration(Store store, Keys keys, AsyncVerificationCodeSupplier codeHandler, VerificationCodeMethod method) {
this.store = store;
this.keys = keys;
this.codeHandler = codeHandler;
this.method = method;
this.httpClient = createClient();
}
public CompletableFuture registerPhoneNumber() {
return requestVerificationCode(false)
.thenCompose(ignored -> sendVerificationCode())
.whenComplete((result, exception) -> {
dispose();
if(exception != null) {
Exceptions.rethrow(exception);
}
});
}
public CompletableFuture requestVerificationCode() {
return requestVerificationCode(true);
}
private CompletableFuture requestVerificationCode(boolean closeResources) {
if(method == VerificationCodeMethod.NONE) {
return CompletableFuture.completedFuture(null);
}
return switch (store.device().platform()) {
case IOS, IOS_BUSINESS -> onboard("1", 2155550000L, null)
.thenComposeAsync(response -> onboard(null, null, response.abHash()))
.thenComposeAsync(ignored -> exists(null))
.thenComposeAsync(result -> clientLog(result, Map.entry("current_screen", "verify_sms"), Map.entry("previous_screen", "enter_number"), Map.entry("action_taken", "continue"))
.thenComposeAsync(response -> requestVerificationCode(response, null)))
.whenComplete((result, exception) -> onRequestVerificationCode(closeResources, exception));
case ANDROID, ANDROID_BUSINESS -> exists(null)
.thenComposeAsync(response -> requestVerificationCode(response, null))
.whenComplete((result, exception) -> onRequestVerificationCode(closeResources, exception));
case KAIOS -> requestVerificationCode(null, null)
.whenComplete((result, exception) -> onRequestVerificationCode(closeResources, exception));
default -> throw new IllegalStateException("Unsupported mobile os");
};
}
private void onRequestVerificationCode(boolean closeResources, Throwable exception) {
if(closeResources) {
dispose();
}
if (exception != null) {
Exceptions.rethrow(exception);
}
}
private CompletableFuture onboard(String cc, Long in, String abHash) {
var phoneNumber = store.phoneNumber()
.orElseThrow();
var attributes = Attributes.of()
.put("cc", Objects.requireNonNullElse(cc, phoneNumber.countryCode().prefix()))
.put("in", Objects.requireNonNullElse(in, phoneNumber.numberWithoutPrefix()))
.put("rc", store.releaseChannel().index())
.put("ab_hash", abHash, abHash != null)
.toMap();
System.out.println(Whatsapp.MOBILE_REGISTRATION_ENDPOINT + "/reg_onboard_abprop?" + toFormParams(attributes));
var request = HttpRequest.newBuilder()
.uri(URI.create(Whatsapp.MOBILE_REGISTRATION_ENDPOINT + "/reg_onboard_abprop?" + toFormParams(attributes)))
.GET()
.header("User-Agent", store.device().toUserAgent(store.version()))
.header("Content-Type","application/x-www-form-urlencoded")
.build();
return httpClient.sendAsync(request, BodyHandlers.ofString())
.thenApply(response -> {
if (response.statusCode() != HttpURLConnection.HTTP_OK) {
throw new RegistrationException(null, response.body());
}
System.out.println(response.body());
return Json.readValue(response.body(), AbPropsResponse.class);
});
}
private CompletableFuture exists(VerificationCodeError lastError) {
var ios = store.device().platform().isIOS();
var options = getRegistrationOptions(
store,
keys,
false,
lastError == VerificationCodeError.OLD_VERSION || lastError == VerificationCodeError.BAD_TOKEN,
ios ? Map.entry("offline_ab", convertBufferToUrlHex(createOfflineAb())) : null,
ios ? Map.entry("recovery_token_error", "-25300") : null
);
return options.thenComposeAsync(attrs -> sendRequest("/exist", attrs)).thenComposeAsync(result -> {
if (result.statusCode() != HttpURLConnection.HTTP_OK) {
throw new RegistrationException(null, result.body());
}
var response = Json.readValue(result.body(), VerificationCodeResponse.class);
if (response.errorReason() == VerificationCodeError.INCORRECT) {
return CompletableFuture.completedFuture(response);
}
if (lastError == null) {
return exists(response.errorReason());
}
throw new RegistrationException(response, result.body());
});
}
private String convertBufferToUrlHex(byte[] buffer) {
var id = new StringBuilder();
for (byte x : buffer) {
id.append(String.format("%%%02x", x));
}
return id.toString().toUpperCase(Locale.ROOT);
}
@SafeVarargs
private CompletableFuture clientLog(T data, Entry... attributes) {
var options = getRegistrationOptions(
store,
keys,
false,
false,
attributes
);
return options.thenCompose(attrs -> sendRequest("/client_log", attrs)).thenApply(result -> {
System.out.println(result.body());
return data;
});
}
private CompletableFuture requestVerificationCode(VerificationCodeResponse existsResponse, VerificationCodeError lastError) {
var options = getRegistrationOptions(
store,
keys,
true,
lastError == VerificationCodeError.OLD_VERSION || lastError == VerificationCodeError.BAD_TOKEN,
getRequestVerificationCodeParameters(existsResponse)
);
return options.thenCompose(attrs -> sendRequest("/code", attrs))
.thenCompose(result -> onCodeRequestSent(existsResponse, lastError, result))
.thenRun(() -> saveRegistrationStatus(store, keys, false));
}
private Entry[] getRequestVerificationCodeParameters(VerificationCodeResponse existsResponse) {
var countryCode = store.phoneNumber()
.orElseThrow()
.countryCode();
return switch(store.device().platform()) {
case UNKNOWN -> new Entry[]{};
case ANDROID, ANDROID_BUSINESS -> {
var gpiaToken = TokenProvider.generateGpiaToken(keys.deviceId(), 430);
yield new Entry[]{
Map.entry("method", method.data()),
Map.entry("sim_mcc", countryCode.mcc()),
Map.entry("sim_mnc", "001"),
Map.entry("reason", ""),
Map.entry("mcc", countryCode.mcc()),
Map.entry("mnc", "001"),
Map.entry("feo2_query_status", "error_security_exception"),
Map.entry("sim_type", 1),
Map.entry("network_radio_type", 1),
Map.entry("prefer_sms_over_flash", true),
Map.entry("simnum", 0),
Map.entry("sim_state", 3),
Map.entry("clicked_education_link", false),
Map.entry("airplane_mode_type", 0),
Map.entry("mistyped", 7),
Map.entry("advertising_id", UUID.randomUUID().toString()),
Map.entry("hasinrc", 1),
Map.entry("roaming_type", 0),
Map.entry("device_ram", 4),
Map.entry("client_metrics", URLEncoder.encode("{\"attempts\":1}", StandardCharsets.UTF_8)),
Map.entry("education_screen_displayed", true),
Map.entry("read_phone_permission_granted", 1),
Map.entry("pid", ProcessHandle.current().pid()),
Map.entry("cellular_strength", ThreadLocalRandom.current().nextInt(3, 6)),
Map.entry("gpia_token", gpiaToken),
Map.entry("gpia", "%7B%22token%22%3A%22" + gpiaToken + "%22%2C%22error_code%22%3A0%7D")
};
}
case IOS, IOS_BUSINESS -> new Entry[]{
Map.entry("method", method.data()),
Map.entry("sim_mcc", existsResponse.flashType() ? countryCode.mcc() : "000"),
Map.entry("sim_mnc", "000"),
Map.entry("reason", ""),
Map.entry("cellular_strength", 1)
};
case KAIOS -> new Entry[]{
Map.entry("mcc", countryCode.mcc()),
Map.entry("mnc", "000"),
Map.entry("method", method.data()),
};
default -> throw new IllegalStateException("Unsupported mobile os");
};
}
private CompletionStage onCodeRequestSent(VerificationCodeResponse existsResponse, VerificationCodeError lastError, HttpResponse result) {
if (result.statusCode() != HttpURLConnection.HTTP_OK) {
throw new RegistrationException(null, result.body());
}
var response = Json.readValue(result.body(), VerificationCodeResponse.class);
if (response.status() == VerificationCodeStatus.SUCCESS) {
return CompletableFuture.completedFuture(null);
}
return switch (response.errorReason()) {
case TOO_RECENT, TOO_MANY, TOO_MANY_GUESSES, TOO_MANY_ALL_METHODS -> throw new RegistrationException(response, "Please wait before trying to register this phone number again");
case NO_ROUTES -> throw new RegistrationException(response, "You can only register numbers that are already on Whatsapp, if you need to register any numbers please contact me on Telegram @Auties00");
default -> {
var newErrorReason = response.errorReason();
Validate.isTrue(newErrorReason != lastError, () -> new RegistrationException(response, result.body()));
yield requestVerificationCode(existsResponse, newErrorReason);
}
};
}
public CompletableFuture sendVerificationCode() {
return codeHandler.get()
.thenComposeAsync(code -> getRegistrationOptions(store, keys, true, false, Map.entry("code", normalizeCodeResult(code))))
.thenComposeAsync(attrs -> sendRequest("/register", attrs))
.thenComposeAsync(result -> {
if (result.statusCode() != HttpURLConnection.HTTP_OK) {
throw new RegistrationException(null, result.body());
}
var response = Json.readValue(result.body(), VerificationCodeResponse.class);
if (response.status() == VerificationCodeStatus.SUCCESS) {
saveRegistrationStatus(store, keys, true);
return CompletableFuture.completedFuture(null);
}
throw new RegistrationException(response, result.body());
});
}
private void saveRegistrationStatus(Store store, Keys keys, boolean registered) {
keys.setRegistered(registered);
if (registered) {
var jid = store.phoneNumber().orElseThrow().toJid();
store.setJid(jid);
store.addLinkedDevice(jid, 0);
}
keys.serialize(true);
store.serialize(true);
}
private String normalizeCodeResult(String captcha) {
return captcha.replaceAll("-", "").trim();
}
private CompletableFuture> sendRequest(String path, Map params) {
var request = createRequest(path, params);
return httpClient.sendAsync(request, BodyHandlers.ofString()).thenApply(result -> {
System.out.println(path + ": " + result.body());
return result;
});
}
private HttpRequest createRequest(String path, Map params) {
var encodedParams = toFormParams(params);
var userAgent = store.device().toUserAgent(store.version());
if(store.device().platform().isKaiOs()) {
return HttpRequest.newBuilder()
.uri(URI.create("%s%s?%s".formatted(Whatsapp.MOBILE_KAIOS_REGISTRATION_ENDPOINT, path, encodedParams)))
.GET()
.header("User-Agent", userAgent)
.build();
}
var keypair = SignalKeyPair.random();
var key = Curve25519.sharedKey(Whatsapp.REGISTRATION_PUBLIC_KEY, keypair.privateKey());
var buffer = AesGcm.encrypt(new byte[12], encodedParams.getBytes(StandardCharsets.UTF_8), key);
var cipheredParameters = Base64.getUrlEncoder().encodeToString(BytesHelper.concat(keypair.publicKey(), buffer));
var request = HttpRequest.newBuilder()
.uri(URI.create("%s%s?ENC=%s".formatted(Whatsapp.MOBILE_REGISTRATION_ENDPOINT, path, cipheredParameters)))
.GET()
.header("User-Agent", userAgent);
if(store.device().platform().isAndroid()) {
request.header("Accept", "text/json");
request.header("WaMsysRequest", "1");
request.header("request_token", UUID.randomUUID().toString());
request.header("Content-Type", "application/x-www-form-urlencoded");
}
return request.build();
}
private HttpClient createClient() {
try {
var clientBuilder = HttpClient.newBuilder();
store.proxy().ifPresent(proxy -> {
clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(proxy.getHost(), proxy.getPort())));
clientBuilder.authenticator(new ProxyAuthenticator());
});
return clientBuilder.build();
}catch (Throwable exception) {
throw new RuntimeException(exception);
}
}
@SafeVarargs
private CompletableFuture
© 2015 - 2025 Weber Informatics LLC | Privacy Policy