Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
it.auties.whatsapp.registration.WhatsappRegistration Maven / Gradle / Ivy
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.RegistrationResponse;
import it.auties.whatsapp.model.signal.keypair.SignalKeyPair;
import it.auties.whatsapp.net.HttpClient;
import it.auties.whatsapp.util.*;
import java.net.URI;
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.stream.Collectors;
public final class WhatsappRegistration {
public static final String MOBILE_REGISTRATION_ENDPOINT = "https://v.whatsapp.net/v2";
private static final byte[] REGISTRATION_PUBLIC_KEY = HexFormat.of().parseHex("8e8c0f74c3ebc5d7a6865c6c3c843856b06121cce8ea774d22fb6f122512302d");
private final HttpClient httpClient;
private final Store store;
private final Keys keys;
private final AsyncVerificationCodeSupplier codeHandler;
private final VerificationCodeMethod method;
public WhatsappRegistration(Store store, Keys keys, AsyncVerificationCodeSupplier codeHandler, VerificationCodeMethod method) {
this.store = store;
this.keys = keys;
this.codeHandler = codeHandler;
this.method = method;
this.httpClient = new HttpClient(store.device().platform().isIOS() ? HttpClient.Platform.IOS : HttpClient.Platform.ANDROID);
}
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));
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();
var headers = Map.of(
"User-Agent", store.device().toUserAgent(store.version()),
"Content-Type","application/x-www-form-urlencoded"
);
return httpClient.getString(URI.create(MOBILE_REGISTRATION_ENDPOINT + "/reg_onboard_abprop?" + toFormParams(attributes)), headers)
.thenApply(response -> Json.readValue(response, AbPropsResponse.class));
}
private CompletableFuture exists(VerificationCodeError lastError) {
var ios = store.device().platform().isIOS();
var options = getRegistrationOptions(
store,
keys,
false,
ios ? Map.entry("recovery_token_error", "-25300") : null
);
return options.thenComposeAsync(attrs -> sendRequest("/exist", attrs)).thenComposeAsync(result -> {
var response = Json.readValue(result, RegistrationResponse.class);
if (response.errorReason() == VerificationCodeError.INCORRECT) {
return CompletableFuture.completedFuture(response);
}
if (lastError == null) {
return exists(response.errorReason());
}
throw new RegistrationException(response, result);
});
}
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,
attributes
);
return options.thenCompose(attrs -> sendRequest("/client_log", attrs))
.thenApply(result -> data);
}
private CompletableFuture requestVerificationCode(RegistrationResponse existsResponse, VerificationCodeError lastError) {
var options = getRegistrationOptions(
store,
keys,
true,
getRequestVerificationCodeParameters(existsResponse)
);
return options.thenComposeAsync(attrs -> sendRequest("/code", attrs))
.thenComposeAsync(result -> onCodeRequestSent(existsResponse, lastError, result))
.thenApplyAsync(response -> {
saveRegistrationStatus(store, keys, false);
return response;
});
}
private Entry[] getRequestVerificationCodeParameters(RegistrationResponse existsResponse) {
var countryCode = store.phoneNumber()
.orElseThrow()
.countryCode();
return switch(store.device().platform()) {
case UNKNOWN -> new Entry[]{};
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)
};
default -> throw new IllegalStateException("Unsupported mobile os");
};
}
private CompletionStage onCodeRequestSent(RegistrationResponse existsResponse, VerificationCodeError lastError, String result) {
var response = Json.readValue(result, RegistrationResponse.class);
if (response.status() == VerificationCodeStatus.SUCCESS) {
return CompletableFuture.completedFuture(response);
}
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));
yield requestVerificationCode(existsResponse, newErrorReason);
}
};
}
public CompletableFuture sendVerificationCode() {
return codeHandler.get()
.thenComposeAsync(code -> getRegistrationOptions(store, keys, true, Map.entry("code", normalizeCodeResult(code))))
.thenComposeAsync(attrs -> sendRequest("/register", attrs))
.thenComposeAsync(result -> {
var response = Json.readValue(result, RegistrationResponse.class);
if (response.status() == VerificationCodeStatus.SUCCESS) {
saveRegistrationStatus(store, keys, true);
return CompletableFuture.completedFuture(response);
}
throw new RegistrationException(response, result);
});
}
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 encodedParams = toFormParams(params);
var userAgent = store.device().toUserAgent(store.version());
var keypair = SignalKeyPair.random();
var key = Curve25519.sharedKey(REGISTRATION_PUBLIC_KEY, keypair.privateKey());
var buffer = AesGcm.encrypt(new byte[12], encodedParams.getBytes(StandardCharsets.UTF_8), key);
var cipheredParameters = Base64.getUrlEncoder().encodeToString(Bytes.concat(keypair.publicKey(), buffer));
var android = store.device().platform().isAndroid();
var headers = Attributes.of()
.put("User-Agent", userAgent)
.put("Accept", "text/json", android)
.put("WaMsysRequest", "1", android)
.put("request_token", UUID.randomUUID().toString(), android)
.put("Content-Type", "application/x-www-form-urlencoded", android)
.toMap();
return httpClient.getString(URI.create("%s%s?ENC=%s".formatted(MOBILE_REGISTRATION_ENDPOINT, path, cipheredParameters)), headers);
}
@SafeVarargs
private CompletableFuture> getRegistrationOptions(Store store, Keys keys, boolean useToken, Entry... attributes) {
var phoneNumber = store.phoneNumber()
.orElseThrow(() -> new NoSuchElementException("Missing phone number"));
var tokenFuture = !useToken ? CompletableFuture.completedFuture(null) : WhatsappMetadata.getToken(phoneNumber.numberWithoutPrefix(), store.device().platform(), store.version());
return tokenFuture.thenApplyAsync(token -> {
var certificate = store.device().platform().isBusiness() ? WhatsappMetadata.generateBusinessCertificate(keys) : null;
var requiredAttributes = Arrays.stream(attributes)
.filter(Objects::nonNull)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (first, second) -> first, LinkedHashMap::new));
var result = Attributes.of()
.put("cc", phoneNumber.countryCode().prefix())
.put("in", phoneNumber.numberWithoutPrefix())
.put("rc", store.releaseChannel().index())
.put("lg", phoneNumber.countryCode().lg())
.put("lc", phoneNumber.countryCode().lc())
.put("authkey", Base64.getUrlEncoder().encodeToString(keys.noiseKeyPair().publicKey()))
.put("vname", certificate, certificate != null)
.put("e_regid", Base64.getUrlEncoder().encodeToString(keys.encodedRegistrationId()))
.put("e_keytype", Base64.getUrlEncoder().encodeToString(SignalConstants.KEY_BUNDLE_TYPE))
.put("e_ident", Base64.getUrlEncoder().encodeToString(keys.identityKeyPair().publicKey()))
.put("e_skey_id", Base64.getUrlEncoder().encodeToString(keys.signedKeyPair().encodedId()))
.put("e_skey_val", Base64.getUrlEncoder().encodeToString(keys.signedKeyPair().publicKey()))
.put("e_skey_sig", Base64.getUrlEncoder().encodeToString(keys.signedKeyPair().signature()))
.put("fdid", keys.fdid().toLowerCase(Locale.ROOT), store.device().platform().isAndroid())
.put("fdid", keys.fdid().toUpperCase(Locale.ROOT), store.device().platform().isIOS())
.put("expid", Base64.getUrlEncoder().encodeToString(keys.deviceId()))
.put("id", convertBufferToUrlHex(keys.identityId()))
.put("token", token, useToken)
.putAll(requiredAttributes)
.toMap();
System.out.println(Json.writeValueAsString(result, true));
return result;
});
}
private String toFormParams(Map values) {
return values.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
private void dispose() {
httpClient.close();
}
}