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.util.RegistrationHelper Maven / Gradle / Ivy
Go to download
Standalone fully-featured Whatsapp Web API for Java and Kotlin
package it.auties.whatsapp.util;
import it.auties.whatsapp.controller.Keys;
import it.auties.whatsapp.controller.Store;
import it.auties.whatsapp.exception.RegistrationException;
import it.auties.whatsapp.model.mobile.VerificationCodeMethod;
import it.auties.whatsapp.model.mobile.VerificationCodeResponse;
import it.auties.whatsapp.model.request.Attributes;
import it.auties.whatsapp.model.signal.auth.UserAgent.UserAgentPlatform;
import it.auties.whatsapp.util.Spec.Whatsapp;
import lombok.experimental.UtilityClass;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.security.Security;
import java.util.Base64;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@UtilityClass
public class RegistrationHelper {
static {
Security.addProvider(new BouncyCastleProvider());
}
public CompletableFuture registerPhoneNumber(Store store, Keys keys, Supplier> handler, VerificationCodeMethod method) {
if (method == VerificationCodeMethod.NONE) {
return sendVerificationCode(store, keys, handler);
}
return requestVerificationCode(store, keys, method)
.thenComposeAsync(ignored -> sendVerificationCode(store, keys, handler));
}
public CompletableFuture requestVerificationCode(Store store, Keys keys, VerificationCodeMethod method) {
if(method == VerificationCodeMethod.NONE){
return CompletableFuture.completedFuture(null);
}
return requestVerificationCodeOptions(store, keys, method)
.thenComposeAsync(attrs -> sendRegistrationRequest(store,"/code", attrs))
.thenAcceptAsync(RegistrationHelper::checkResponse)
.thenRunAsync(() -> saveRegistrationStatus(store, keys, false));
}
private static CompletableFuture> requestVerificationCodeOptions(Store store, Keys keys, VerificationCodeMethod method) {
return getRegistrationOptions(store, keys,
Map.entry("mcc", store.phoneNumber().countryCode().mcc()),
Map.entry("mnc", store.phoneNumber().countryCode().mnc()),
Map.entry("sim_mcc", "000"),
Map.entry("sim_mnc", "000"),
Map.entry("method", method.type()),
Map.entry("reason", ""),
Map.entry("hasav", "1"));
}
public CompletableFuture sendVerificationCode(Store store, Keys keys, Supplier> handler) {
return handler.get()
.thenComposeAsync(result -> sendVerificationCode(store, keys, result))
.thenRunAsync(() -> saveRegistrationStatus(store, keys, true));
}
private void saveRegistrationStatus(Store store, Keys keys, boolean registered) {
keys.registered(registered);
if(registered){
store.jid(store.phoneNumber().toJid());
store.addLinkedDevice(store.jid(), 0);
}
keys.serialize(true);
store.serialize(true);
}
private CompletableFuture sendVerificationCode(Store store, Keys keys, String code) {
return getRegistrationOptions(store, keys, Map.entry("code", code.replaceAll("-", "")))
.thenComposeAsync(attrs -> sendRegistrationRequest(store, "/register", attrs))
.thenAcceptAsync(RegistrationHelper::checkResponse);
}
private void checkResponse(HttpResponse result) {
Validate.isTrue(result.statusCode() == HttpURLConnection.HTTP_OK,
"Invalid status code: %s", RegistrationException.class, result.statusCode(), result.body());
var response = Json.readValue(result.body(), VerificationCodeResponse.class);
if(response.status().isSuccessful()){
return;
}
throw new RegistrationException("Invalid response: %s".formatted(result.body()));
}
private CompletableFuture> sendRegistrationRequest(Store store, String path, Map params) {
return getUserAgent(store).thenComposeAsync(userAgent -> {
var client = createClient(store);
var request = HttpRequest.newBuilder()
.uri(URI.create("%s%s?%s".formatted(Whatsapp.MOBILE_REGISTRATION_ENDPOINT, path, toFormParams(params))))
.GET()
.header("Content-Type", "application/x-www-form-urlencoded")
.header("User-Agent", userAgent)
.build();
return client.sendAsync(request, BodyHandlers.ofString());
});
}
private CompletableFuture getUserAgent(Store store) {
return store.version()
.thenApplyAsync(version -> "WhatsApp/%s %s/%s Device/%s-%s".formatted(version, getMobileOsName(store.osType()), store.osVersion(), store.manufacturer(), store.model()));
}
private Object getMobileOsName(UserAgentPlatform platform) {
return switch (platform) {
case ANDROID -> "Android";
case IOS -> "iOS";
default -> throw new IllegalStateException("Unsupported mobile os: " + platform);
};
}
private HttpClient createClient(Store store) {
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();
}
@SafeVarargs
private CompletableFuture> getRegistrationOptions(Store store, Keys keys, Entry... attributes) {
return MetadataHelper.getToken(store.phoneNumber().numberWithoutPrefix(), store.osType())
.thenApplyAsync(token -> getRegistrationOptions(store, keys, token, attributes));
}
private Map getRegistrationOptions(Store store, Keys keys, String token, Entry[] attributes) {
return Attributes.of(attributes)
.put("cc", store.phoneNumber().countryCode().prefix())
.put("in", store.phoneNumber().numberWithoutPrefix())
.put("rc", store.releaseChannel().index())
.put("lg", "en")
.put("lc", "GB")
.put("mistyped", "6")
.put("authkey", Base64.getUrlEncoder().encodeToString(keys.noiseKeyPair().publicKey()))
.put("e_regid", Base64.getUrlEncoder().encodeToString(keys.encodedRegistrationId()))
.put("e_keytype", "BQ")
.put("e_ident", Base64.getUrlEncoder().encodeToString(keys.identityKeyPair().publicKey()))
.put("e_skey_id", "AAAA")
.put("e_skey_val", Base64.getUrlEncoder().encodeToString(keys.signedKeyPair().publicKey()))
.put("e_skey_sig", Base64.getUrlEncoder().encodeToString(keys.signedKeyPair().signature()))
.put("fdid", keys.phoneId())
.put("network_ratio_type", "1")
.put("expid", keys.deviceId())
.put("simnum", "1")
.put("hasinrc", "1")
.put("pid", ProcessHandle.current().pid())
.put("id", keys.identityId())
.put("token", token)
.toMap();
}
private String toFormParams(Map values) {
return values.entrySet()
.stream()
.map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue()))
.collect(Collectors.joining("&"));
}
}