Add user and captcha handling methods

This commit is contained in:
netkas 2024-12-09 19:03:33 -05:00
parent 23ac46eee8
commit 69b68ed1e4
9 changed files with 854 additions and 436 deletions

View file

@ -8,6 +8,8 @@ import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
import net.nosial.socialclient.exceptions.RpcException;
import net.nosial.socialclient.objects.RpcRequest;
import net.nosial.socialclient.objects.standard.ImageCaptcha;
import net.nosial.socialclient.objects.standard.SelfUser;
import java.security.KeyPair;
import java.security.PrivateKey;
@ -17,8 +19,6 @@ import java.util.Map;
public class Client extends RpcClient
{
public Client(String domain) throws ResolutionException, CryptographyException
{
super(domain);
@ -31,7 +31,8 @@ public class Client extends RpcClient
public String createSession(KeyPair keyPair) throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("create_session", Utilities.randomCrc32(), new HashMap<>(){{
RpcResult response = this.sendRequest(new RpcRequest("createSession", Utilities.randomCrc32(), new HashMap<>()
{{
put("public_key", Cryptography.exportPublicKey(keyPair));
}}));
@ -78,4 +79,90 @@ public class Client extends RpcClient
return true;
}
public boolean register(String username) throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("register", Utilities.randomCrc32(), new HashMap<>()
{{
put("username", username);
}}));
if(response == null)
{
throw new RpcException("Response is null");
}
if(!response.isSuccess())
{
throw new RpcException(response);
}
return (boolean)response.getResponse().getResult();
}
@SuppressWarnings("unchecked")
public SelfUser getMe() throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("getMe", Utilities.randomCrc32()));
if(response == null)
{
throw new RpcException("Response is null");
}
if(!response.isSuccess())
{
throw new RpcException(response);
}
if(response.getResponse().getResult() instanceof Map<?,?>)
{
return new SelfUser((Map<String, Object>) response.getResponse().getResult());
}
throw new RpcException("Unexpected result type, expected Map<?,?>");
}
@SuppressWarnings("unchecked")
public ImageCaptcha verificationGetImageCaptcha() throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("verificationGetImageCaptcha", Utilities.randomCrc32()));
if(response == null)
{
throw new RpcException("Response is null");
}
if(!response.isSuccess())
{
throw new RpcException(response);
}
if(response.getResponse().getResult() instanceof Map<?,?>)
{
return new ImageCaptcha((Map<String, Object>) response.getResponse().getResult());
}
throw new RpcException("Unexpected result type, expected Map<?,?>");
}
public boolean verificationAnswerImageCaptcha(String answer) throws RpcException
{
RpcResult response = this.sendRequest(new RpcRequest("verificationAnswerImageCaptcha", Utilities.randomCrc32(), new HashMap<>()
{{
put("answer", answer);
}}));
if(response == null)
{
throw new RpcException("Response is null");
}
if(!response.isSuccess())
{
throw new RpcException(response);
}
return (boolean)response.getResponse().getResult();
}
}

View file

@ -9,8 +9,10 @@ import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Resolver {
public static ResolvedServer resolveDomain(String domain) throws ResolutionException {
public class Resolver
{
public static ResolvedServer resolveDomain(String domain) throws ResolutionException
{
final Pattern fullPattern = Pattern.compile("v=socialbox;sb-rpc=(https?://[^;]+);sb-key=([^;]+)");
Hashtable<String, String> env = new Hashtable<>();
@ -18,18 +20,21 @@ public class Resolver {
env.put("com.sun.jndi.dns.cache.ttl", "1");
env.put("com.sun.jndi.dns.cache.negative.ttl", "0");
try {
try
{
DirContext dirContext = new InitialDirContext(env);
Attributes attributes = dirContext.getAttributes("dns:/" + domain, new String[]{"TXT"});
Attribute attributeTXT = attributes.get("TXT");
if (attributeTXT == null) {
if (attributeTXT == null)
{
throw new ResolutionException("Failed to resolve DNS TXT records for " + domain);
}
StringBuilder fullRecordBuilder = new StringBuilder();
for (int i = 0; i < attributeTXT.size(); i++) {
for (int i = 0; i < attributeTXT.size(); i++)
{
String value = (String) attributeTXT.get(i);
fullRecordBuilder.append(value.replaceAll("^\"|\"$", "").trim());
}
@ -37,23 +42,30 @@ public class Resolver {
String fullRecord = fullRecordBuilder.toString();
Matcher matcher = fullPattern.matcher(fullRecord);
if (matcher.find()) {
if (matcher.find())
{
String endpoint = matcher.group(1);
String publicKey = matcher.group(2).replaceAll("\\s+", "");
if (endpoint == null || endpoint.isEmpty()) {
if (endpoint == null || endpoint.isEmpty())
{
throw new ResolutionException("Failed to resolve RPC endpoint for " + domain);
}
if (publicKey == null || publicKey.isEmpty()) {
if (publicKey == null || publicKey.isEmpty())
{
throw new ResolutionException("Failed to resolve public key for " + domain);
}
return new ResolvedServer(endpoint, publicKey);
} else {
}
else
{
throw new ResolutionException("Failed to find valid SocialBox record for " + domain);
}
} catch (NamingException e) {
}
catch (NamingException e)
{
throw new ResolutionException("Error resolving domain: " + e.getMessage(), e);
}
}

View file

@ -1,423 +1,426 @@
package net.nosial.socialclient.classes;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.squareup.okhttp.*;
import net.nosial.socialclient.abstracts.RpcResult;
import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
import net.nosial.socialclient.objects.ResolvedServer;
import net.nosial.socialclient.objects.RpcRequest;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class RpcClient
{
private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final static MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private final String domain;
private final String endpoint;
private final PublicKey serverPublicKey;
private final OkHttpClient httpClient;
private String clientName = "SocialClient Java";
private String clientVersion = "1.0.0";
protected String sessionUuid;
protected PrivateKey privateKey;
/**
* Initializes a new instance of the RpcClient class using the specified domain.
* This constructor resolves the domain to obtain the RPC endpoint and public key.
*
* @param domain The domain to be resolved for the RPC endpoint and public key.
* @throws ResolutionException If the domain cannot be resolved or if required information is missing.
* @throws CryptographyException If an error occurs while importing the public key.
*/
public RpcClient(String domain) throws ResolutionException, CryptographyException
package net.nosial.socialclient.classes;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.squareup.okhttp.*;
import net.nosial.socialclient.abstracts.RpcResult;
import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
import net.nosial.socialclient.objects.ResolvedServer;
import net.nosial.socialclient.objects.RpcRequest;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class RpcClient
{
this.domain = domain;
this.httpClient = new OkHttpClient();
// Resolve the domain to get the endpoint and public key
ResolvedServer resolvedServer = Resolver.resolveDomain(domain);
this.endpoint = resolvedServer.getEndpoint();
this.serverPublicKey = Cryptography.importPublicKey(resolvedServer.getPublicKey());
this.sessionUuid = null;
this.privateKey = null;
}
/**
* Constructs an RpcClient instance with the specified endpoint and public key.
*
* @param endpoint The endpoint to which RPC requests will be sent.
* @param serverPublicKey The public key used for cryptographic operations with the RPC server.
*/
public RpcClient(String endpoint, PublicKey serverPublicKey)
{
this.domain = null;
this.endpoint = endpoint;
this.serverPublicKey = serverPublicKey;
this.httpClient = new OkHttpClient();
this.sessionUuid = null;
this.privateKey = null;
}
/**
* Retrieves the domain associated with this RpcClient instance.
*
* @return the domain as a string.
*/
public String getDomain()
{
return this.domain;
}
/**
* Returns the RPC endpoint for the client.
*
* @return the endpoint as a String.
*/
public String getEndpoint()
{
return this.endpoint;
}
/**
* Returns the public key associated with the RpcClient instance.
*
* @return the public key as a PublicKey object.
*/
public PublicKey getServerPublicKey()
{
return this.serverPublicKey;
}
/**
* Retrieves the session UUID associated with the RpcClient instance.
*
* @return the session UUID as a string.
*/
public String getSessionUuid()
{
return this.sessionUuid;
}
/**
* Sets the session UUID and imports the provided private key.
*
* @param sessionUuid the unique identifier for this session
* @param privateKey the private key to be imported
* @throws CryptographyException if an error occurs while importing the private key
*/
protected void setSession(String sessionUuid, String privateKey) throws CryptographyException
{
this.sessionUuid = sessionUuid;
this.privateKey = Cryptography.importPrivateKey(privateKey);
}
/**
* Sets the session details for the RPC client.
*
* @param sessionUuid the UUID of the session
* @param privateKey the private key associated with the session
*/
protected void setSession(String sessionUuid, PrivateKey privateKey)
{
this.sessionUuid = sessionUuid;
this.privateKey = privateKey;
}
/**
* Retrieves the client name associated with this RpcClient instance.
*
* @return the client name as a string.
*/
public String getClientName()
{
return clientName;
}
/**
* Sets the name of the client.
*
* @param clientName The name to be set for the client.
* @throws NullPointerException if the provided*/
public void setClientName(String clientName)
{
if(clientName == null)
private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final static MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private final String domain;
private final String endpoint;
private final PublicKey serverPublicKey;
private final OkHttpClient httpClient;
private String clientName = "SocialClient Java";
private String clientVersion = "1.0";
protected String sessionUuid;
protected PrivateKey privateKey;
/**
* Initializes a new instance of the RpcClient class using the specified domain.
* This constructor resolves the domain to obtain the RPC endpoint and public key.
*
* @param domain The domain to be resolved for the RPC endpoint and public key.
* @throws ResolutionException If the domain cannot be resolved or if required information is missing.
* @throws CryptographyException If an error occurs while importing the public key.
*/
public RpcClient(String domain) throws ResolutionException, CryptographyException
{
throw new NullPointerException("Client name cannot be null");
this.domain = domain;
this.httpClient = new OkHttpClient();
// Resolve the domain to get the endpoint and public key
ResolvedServer resolvedServer = Resolver.resolveDomain(domain);
this.endpoint = resolvedServer.getEndpoint();
this.serverPublicKey = Cryptography.importPublicKey(resolvedServer.getPublicKey());
this.sessionUuid = null;
this.privateKey = null;
}
this.clientName = clientName;
}
/**
* Returns the client's version.
*
* @return the client version as a string.
*/
public String getClientVersion()
{
return clientVersion;
}
/**
* Sets the client version for the RpcClient instance.
*
* @param clientVersion The version of the client software to be set.
* @throws NullPointerException if the provided clientVersion*/
public void setClientVersion(String clientVersion)
{
if(clientVersion == null)
/**
* Constructs an RpcClient instance with the specified endpoint and public key.
*
* @param endpoint The endpoint to which RPC requests will be sent.
* @param serverPublicKey The public key used for cryptographic operations with the RPC server.
*/
public RpcClient(String endpoint, PublicKey serverPublicKey)
{
throw new NullPointerException("Client version cannot be null");
this.domain = null;
this.endpoint = endpoint;
this.serverPublicKey = serverPublicKey;
this.httpClient = new OkHttpClient();
this.sessionUuid = null;
this.privateKey = null;
}
this.clientVersion = clientVersion;
}
/**
* Clears the current session by setting the sessionUuid and privateKey fields to null.
* This method effectively logs out the user from the current session.
*/
public void clearSession()
{
this.sessionUuid = null;
this.privateKey = null;
}
/**
* Sends an RPC request with the provided JSON data and returns a list of RpcResult objects.
*
* @param jsonData the JSON-formatted string representing the RPC request data.
* @return a list of RpcResult objects representing the response(s) from the RPC server.
* @throws RuntimeException if there is an error during the request or response processing.
*/
@SuppressWarnings("unchecked")
public List<RpcResult> sendRequest(String jsonData)
{
final Request request = new Request.Builder()
{{
this.url(endpoint);
this.post(RequestBody.create(MEDIA_TYPE_JSON, jsonData));
this.addHeader("Client-Name", clientName);
this.addHeader("Client-Version", clientVersion);
if(sessionUuid != null)
{
// We are seeing the session UUID correctly.
this.addHeader("Session-UUID", sessionUuid);
}
if(privateKey != null)
{
try
{
// NOTE: Signature looks okay, it's base64 encoded.
System.out.println("Hash: " + Cryptography.sha1(jsonData));
System.out.println("Signature: " + Cryptography.signContent(jsonData, privateKey));
this.addHeader("Signature", Cryptography.signContent(jsonData, privateKey));
}
catch(CryptographyException | NoSuchAlgorithmException e)
{
throw new RuntimeException(e);
}
}
}}.build();
try
/**
* Retrieves the domain associated with this RpcClient instance.
*
* @return the domain as a string.
*/
public String getDomain()
{
final Response response = this.httpClient.newCall(request).execute();
final String responseString = response.body().string();
if (!response.isSuccessful())
return this.domain;
}
/**
* Returns the RPC endpoint for the client.
*
* @return the endpoint as a String.
*/
public String getEndpoint()
{
return this.endpoint;
}
/**
* Returns the public key associated with the RpcClient instance.
*
* @return the public key as a PublicKey object.
*/
public PublicKey getServerPublicKey()
{
return this.serverPublicKey;
}
/**
* Retrieves the session UUID associated with the RpcClient instance.
*
* @return the session UUID as a string.
*/
public String getSessionUuid()
{
return this.sessionUuid;
}
/**
* Sets the session UUID and imports the provided private key.
*
* @param sessionUuid the unique identifier for this session
* @param privateKey the private key to be imported
* @throws CryptographyException if an error occurs while importing the private key
*/
protected void setSession(String sessionUuid, String privateKey) throws CryptographyException
{
this.sessionUuid = sessionUuid;
this.privateKey = Cryptography.importPrivateKey(privateKey);
}
/**
* Sets the session details for the RPC client.
*
* @param sessionUuid the UUID of the session
* @param privateKey the private key associated with the session
*/
protected void setSession(String sessionUuid, PrivateKey privateKey)
{
this.sessionUuid = sessionUuid;
this.privateKey = privateKey;
}
/**
* Retrieves the client name associated with this RpcClient instance.
*
* @return the client name as a string.
*/
public String getClientName()
{
return clientName;
}
/**
* Sets the name of the client.
*
* @param clientName The name to be set for the client.
* @throws NullPointerException if the provided*/
public void setClientName(String clientName)
{
if(clientName == null)
{
if(!responseString.isEmpty())
throw new NullPointerException("Client name cannot be null");
}
this.clientName = clientName;
}
/**
* Returns the client's version.
*
* @return the client version as a string.
*/
public String getClientVersion()
{
return clientVersion;
}
/**
* Sets the client version for the RpcClient instance.
*
* @param clientVersion The version of the client software to be set.
* @throws NullPointerException if the provided clientVersion*/
public void setClientVersion(String clientVersion)
{
if(clientVersion == null)
{
throw new NullPointerException("Client version cannot be null");
}
this.clientVersion = clientVersion;
}
/**
* Clears the current session by setting the sessionUuid and privateKey fields to null.
* This method effectively logs out the user from the current session.
*/
public void clearSession()
{
this.sessionUuid = null;
this.privateKey = null;
}
/**
* Sends an RPC request with the provided JSON data and returns a list of RpcResult objects.
*
* @param jsonData the JSON-formatted string representing the RPC request data.
* @return a list of RpcResult objects representing the response(s) from the RPC server.
* @throws RuntimeException if there is an error during the request or response processing.
*/
@SuppressWarnings("unchecked")
public List<RpcResult> sendRequest(String jsonData)
{
final Request request = new Request.Builder()
{{
this.url(endpoint);
this.post(RequestBody.create(MEDIA_TYPE_JSON, jsonData));
this.addHeader("Client-Name", clientName);
this.addHeader("Client-Version", clientVersion);
if(sessionUuid != null)
{
// We are seeing the session UUID correctly.
this.addHeader("Session-UUID", sessionUuid);
}
if(privateKey != null)
{
try
{
// NOTE: Signature looks okay, it's base64 encoded.
this.addHeader("Signature", Cryptography.signContent(jsonData, privateKey));
}
catch(CryptographyException e)
{
throw new RuntimeException(e);
}
}
}}.build();
try
{
final Response response = this.httpClient.newCall(request).execute();
final String responseString = response.body().string();
if (!response.isSuccessful())
{
throw new RuntimeException(responseString);
if(!responseString.isEmpty())
{
throw new RuntimeException(responseString);
}
throw new RuntimeException("Failed to send request: " + response.code());
}
throw new RuntimeException("Failed to send request: " + response.code());
}
if(response.code() == 204)
{
// The response is empty
return new ArrayList<>();
}
Object decoded = decode(responseString);
// Singular object response
if(decoded instanceof List<?>)
{
List<Map<String, Object>> responseList = (List<Map<String, Object>>) decoded;
List<RpcResult> results = new ArrayList<>(responseList.size());
for(Map<String, Object> responseMap : responseList)
if(response.code() == 204)
{
results.add(RpcResult.fromMap(responseMap));
// The response is empty
return new ArrayList<>();
}
return results;
}
if(decoded instanceof Map<?, ?>)
{
if(responseString.isEmpty())
{
throw new RuntimeException("The request was successful but the server did not indicate a empty response");
}
Object decoded = decode(responseString);
// Singular object response
List<RpcResult> results = new ArrayList<>(1);
results.add(RpcResult.fromMap((Map<String, Object>) decoded));
return results;
if(decoded instanceof List<?>)
{
List<Map<String, Object>> responseList = (List<Map<String, Object>>) decoded;
List<RpcResult> results = new ArrayList<>(responseList.size());
for(Map<String, Object> responseMap : responseList)
{
results.add(RpcResult.fromMap(responseMap));
}
return results;
}
if(decoded instanceof Map<?, ?>)
{
// Singular object response
List<RpcResult> results = new ArrayList<>(1);
results.add(RpcResult.fromMap((Map<String, Object>) decoded));
return results;
}
throw new RuntimeException("Failed to decode response");
}
catch (IOException e)
{
throw new RuntimeException(e);
}
throw new RuntimeException("Failed to decode response");
}
catch (IOException e)
/**
* Sends an RPC request to the configured endpoint and returns the first result.
*
* @param request the RPC request to be sent
* @return the first RPC result received from the response
*/
public RpcResult sendRequest(RpcRequest request)
{
throw new RuntimeException(e);
List<RpcResult> results = sendRequest(encode(request.toMap()));
if(results.isEmpty())
{
return null;
}
return results.getFirst();
}
/**
* Sends a list of RPC requests to the server and returns their results as a list of RpcResult objects.
*
* @param requests the list of RPC requests to be sent
* @return a list of RpcResult objects containing the responses from the server
*/
public List<RpcResult> sendRequests(List<RpcRequest> requests)
{
return sendRequest(encode(requests));
}
/**
* Decodes a JSON string into a corresponding Java object. If the JSON string
* represents an object, it decodes it into a Map. If the JSON string represents
* an array, it decodes it into a List.
*
* @param json the JSON string to be decoded
* @return a decoded Java object which can be a Map or a List depending on the JSON structure
* @throws RuntimeException if the JSON string is neither an object nor an array, or if decoding fails
*/
private static Object decode(String json)
{
try
{
JsonNode jsonNode = OBJECT_MAPPER.readTree(json);
if (jsonNode.isObject())
{
return decodeJson(OBJECT_MAPPER.readValue(json, new TypeReference<>() {}));
}
else if (jsonNode.isArray())
{
return decodeList(OBJECT_MAPPER.readValue(json, new TypeReference<>() {}));
}
else
{
throw new RuntimeException("JSON is neither an object nor an array");
}
}
catch (JsonProcessingException e)
{
throw new RuntimeException("Failed to decode input", e);
}
}
/**
* Recursively decodes a JSON structure represented as a Map. If the value
* of an entry is another Map, it recursively decodes that Map. If the value
* is a List, it passes the List to the decodeList method.
*
* @param map the JSON structure to be decoded represented as a Map
* @return the decoded JSON structure as a Map
*/
@SuppressWarnings("unchecked")
private static Map<String, Object> decodeJson(Map<String, Object> map)
{
for (Map.Entry<String, Object> entry : map.entrySet())
{
if (entry.getValue() instanceof Map)
{
entry.setValue(decodeJson((Map<String, Object>) entry.getValue()));
}
else if (entry.getValue() instanceof List)
{
entry.setValue(decodeList((List<Object>) entry.getValue()));
}
}
return map;
}
/**
* Recursively decodes a list of objects, transforming any nested maps or lists within it.
*
* @param list the list of objects to be decoded
* @return the decoded list of objects, with all nested maps and lists transformed
*/
@SuppressWarnings("unchecked")
private static List<Object> decodeList(List<Object> list)
{
for (int i = 0; i < list.size(); i++)
{
final Object item = list.get(i);
if (item instanceof Map)
{
list.set(i, decodeJson((Map<String, Object>) item));
}
else if (item instanceof List)
{
list.set(i, decodeList((List<Object>) item));
}
}
return list;
}
/**
* Encodes the given input object to its JSON string representation.
*
* @param input the object to encode
* @return the JSON string representation of the input object
*/
private static String encode(Object input)
{
try
{
return OBJECT_MAPPER.writeValueAsString(input);
}
catch(JsonProcessingException e)
{
throw new RuntimeException("Failed to encode input to JSON Data", e);
}
}
}
/**
* Sends an RPC request to the configured endpoint and returns the first result.
*
* @param request the RPC request to be sent
* @return the first RPC result received from the response
*/
public RpcResult sendRequest(RpcRequest request)
{
List<RpcResult> results = sendRequest(encode(request.toMap()));
if(results.isEmpty())
{
return null;
}
return results.getFirst();
}
/**
* Sends a list of RPC requests to the server and returns their results as a list of RpcResult objects.
*
* @param requests the list of RPC requests to be sent
* @return a list of RpcResult objects containing the responses from the server
*/
public List<RpcResult> sendRequests(List<RpcRequest> requests)
{
return sendRequest(encode(requests));
}
/**
* Decodes a JSON string into a corresponding Java object. If the JSON string
* represents an object, it decodes it into a Map. If the JSON string represents
* an array, it decodes it into a List.
*
* @param json the JSON string to be decoded
* @return a decoded Java object which can be a Map or a List depending on the JSON structure
* @throws RuntimeException if the JSON string is neither an object nor an array, or if decoding fails
*/
private static Object decode(String json)
{
try
{
JsonNode jsonNode = OBJECT_MAPPER.readTree(json);
if (jsonNode.isObject())
{
return decodeJson(OBJECT_MAPPER.readValue(json, new TypeReference<>() {}));
}
else if (jsonNode.isArray())
{
return decodeList(OBJECT_MAPPER.readValue(json, new TypeReference<>() {}));
}
else
{
throw new RuntimeException("JSON is neither an object nor an array");
}
}
catch (JsonProcessingException e)
{
throw new RuntimeException("Failed to decode input", e);
}
}
/**
* Recursively decodes a JSON structure represented as a Map. If the value
* of an entry is another Map, it recursively decodes that Map. If the value
* is a List, it passes the List to the decodeList method.
*
* @param map the JSON structure to be decoded represented as a Map
* @return the decoded JSON structure as a Map
*/
@SuppressWarnings("unchecked")
private static Map<String, Object> decodeJson(Map<String, Object> map)
{
for (Map.Entry<String, Object> entry : map.entrySet())
{
if (entry.getValue() instanceof Map)
{
entry.setValue(decodeJson((Map<String, Object>) entry.getValue()));
}
else if (entry.getValue() instanceof List)
{
entry.setValue(decodeList((List<Object>) entry.getValue()));
}
}
return map;
}
/**
* Recursively decodes a list of objects, transforming any nested maps or lists within it.
*
* @param list the list of objects to be decoded
* @return the decoded list of objects, with all nested maps and lists transformed
*/
@SuppressWarnings("unchecked")
private static List<Object> decodeList(List<Object> list)
{
for (int i = 0; i < list.size(); i++)
{
final Object item = list.get(i);
if (item instanceof Map)
{
list.set(i, decodeJson((Map<String, Object>) item));
}
else if (item instanceof List)
{
list.set(i, decodeList((List<Object>) item));
}
}
return list;
}
/**
* Encodes the given input object to its JSON string representation.
*
* @param input the object to encode
* @return the JSON string representation of the input object
*/
private static String encode(Object input)
{
try
{
return OBJECT_MAPPER.writeValueAsString(input);
}
catch(JsonProcessingException e)
{
throw new RuntimeException("Failed to encode input to JSON Data", e);
}
}
}

View file

@ -11,8 +11,15 @@ public enum StandardErrorCodes
SERVER_UNAVAILABLE(-2001),
INVALID_PUBLIC_KEY(-3000),
SESSION_NOT_FOUND(-3001),
UNSUPPORTED_AUTHENTICATION_TYPE(-3002);
UNSUPPORTED_AUTHENTICATION_TYPE(-3001),
ALREADY_AUTHENTICATED(-3002),
AUTHENTICATION_REQUIRED(-3003),
SESSION_NOT_FOUND(-3004),
SESSION_REQUIRED(-3005),
PEER_NOT_FOUND(-4000),
INVALID_USERNAME(-4001),
USERNAME_ALREADY_EXISTS(-4002);
private final int code;

View file

@ -0,0 +1,39 @@
package net.nosial.socialclient.enums.flags;
public enum PeerFlags
{
// Administrative Flags
ADMIN(true, "ADMIN"),
MODERATOR(true, "MODERATOR"),
// General Flags
VERIFIED(true, "VERIFIED"),
// Verification Flags
VER_SET_PASSWORD(false, "VER_SET_PASSWORD"),
VER_SET_OTP(false, "VER_SET_OTP"),
VER_SET_DISPLAY_NAME(false, "VER_SET_DISPLAY_NAME"),
VER_EMAIL(false, "VER_EMAIL"),
VER_SMS(false, "VER_SMS"),
VER_PHONE_CALL(false, "VER_PHONE_CALL"),
VER_SOLVE_IMAGE_CAPTCHA(false, "VER_SOLVE_IMAGE_CAPTCHA");
private final boolean isPublic;
private final String flag;
PeerFlags(boolean isPublic, String flag)
{
this.isPublic = isPublic;
this.flag = flag;
}
public String getFlag()
{
return this.flag;
}
public boolean isPublic()
{
return this.isPublic;
}
}

View file

@ -0,0 +1,46 @@
package net.nosial.socialclient.objects.standard;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Map;
public class ImageCaptcha
{
private final int expires;
private final String image;
public ImageCaptcha(Map<String, Object> data)
{
this.expires = (int) data.get("expires");
this.image = (String) data.get("image");
}
public int getExpires()
{
return expires;
}
public String getImage()
{
return image;
}
public BufferedImage getImageBuffer() throws IOException
{
String base64Image = this.image;
if(this.image.contains(","))
{
base64Image = this.image.split(",")[1];
}
byte[] imageBytes = Base64.getDecoder().decode(base64Image);
ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
BufferedImage image = javax.imageio.ImageIO.read(bis);
bis.close();
return image;
}
}

View file

@ -0,0 +1,106 @@
package net.nosial.socialclient.objects.standard;
import net.nosial.socialclient.enums.flags.PeerFlags;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class SelfUser
{
private final String uuid;
private final boolean enabled;
private final String address;
private final String username;
private final String displayName;
private final List<PeerFlags> flags;
private final int created;
/**
* Constructs a SelfUser instance by populating fields from a provided data map.
*
* @param data a map containing user details such as uuid, username, display name, flags,
* enabled status, and creation time.
*/
@SuppressWarnings("unchecked")
public SelfUser(Map<String, Object> data)
{
this.uuid = (String) data.get("uuid");
this.enabled = (boolean) data.get("enabled");
this.username = (String) data.get("username");
this.address = (String) data.get("address");
this.displayName = (String) data.get("display_name");
this.flags = ((List<String>) data.get("flags")).stream().map(PeerFlags::valueOf).collect(Collectors.toList());
this.created = (int) data.get("created");
}
/**
* Retrieves the universally unique identifier (UUID) associated with the user.
*
* @return the UUID as a String
*/
public String getUuid()
{
return uuid;
}
/**
* Checks if the user account is enabled.
*
* @return true if the user account is enabled; false otherwise.
*/
public boolean isEnabled()
{
return enabled;
}
/**
* Retrieves the username associated with this SelfUser instance.
*
* @return the username of the user
*/
public String getUsername()
{
return username;
}
/**
* Retrieves the address associated with this SelfUser instance.
*
* @return the address of the user
*/
public String getAddress()
{
return address;
}
/**
* Retrieves the display name of the user.
*
* @return the display name of the user
*/
public String getDisplayName()
{
return displayName;
}
/**
* Retrieves the list of flags associated with the user.
*
* @return a list of PeerFlags indicating various user permissions and statuses.
*/
public List<PeerFlags> getFlags()
{
return flags;
}
/**
* Returns the creation timestamp of this user.
*
* @return the creation timestamp as an integer
*/
public int getCreated()
{
return created;
}
}

View file

@ -1,5 +1,6 @@
package net.nosial.socialclient;
package net.nosial.socialclient.methods;
import net.nosial.socialclient.Client;
import net.nosial.socialclient.classes.Cryptography;
import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
@ -10,11 +11,10 @@ import java.security.KeyPair;
import static org.junit.jupiter.api.Assertions.*;
class SessionTest
public class CreateSessionTest
{
@Test
void createSessionTest()
void testSessionCreation()
{
Client socialClient;
KeyPair keyPair;
@ -23,18 +23,9 @@ class SessionTest
try
{
socialClient = new Client("n64.cc");
}
catch (ResolutionException | CryptographyException e)
{
fail(e.getMessage(), e);
return;
}
try
{
keyPair = Cryptography.generateKeyPair();
}
catch (CryptographyException e)
catch (ResolutionException | CryptographyException e)
{
fail(e.getMessage(), e);
return;
@ -86,4 +77,4 @@ class SessionTest
fail(e.getMessage(), e);
}
}
}
}

View file

@ -0,0 +1,127 @@
package net.nosial.socialclient.methods;
import net.nosial.socialclient.Client;
import net.nosial.socialclient.classes.Cryptography;
import net.nosial.socialclient.exceptions.CryptographyException;
import net.nosial.socialclient.exceptions.ResolutionException;
import net.nosial.socialclient.exceptions.RpcException;
import net.nosial.socialclient.objects.standard.ImageCaptcha;
import net.nosial.socialclient.objects.standard.SelfUser;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.security.KeyPair;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
public class RegisterTest
{
@Test
public void registerTest()
{
// Step 1. Connect and establish a session to the server
final Client socialClient;
final KeyPair keyPair;
final String sessionUuid;
try
{
socialClient = new Client("n64.cc");
keyPair = Cryptography.generateKeyPair();
sessionUuid = socialClient.createAndSetSession(keyPair);
}
catch (ResolutionException | CryptographyException | RpcException e)
{
fail(e.getMessage(), e);
return;
}
System.out.println("Session UUID: " + sessionUuid);
assertNotNull(sessionUuid);
final SelfUser selfUser;
Random random = new Random();
String username = "user" + random.nextInt(10000);
// Step 2. Register the username and getSelf
try
{
assertTrue(socialClient.register(username));
selfUser = socialClient.getMe();
}
catch (RpcException e)
{
fail(e.getMessage(), e);
return;
}
assertNotNull(selfUser);
// Step 3. Get the captcha
ImageCaptcha captcha;
try
{
captcha = socialClient.verificationGetImageCaptcha();
}
catch(RpcException e)
{
fail(e.getMessage(), e);
return;
}
SwingUtilities.invokeLater(() -> {
try
{
ImageFrame frame = new ImageFrame(captcha.getImageBuffer());
frame.setVisible(true);
}
catch(IOException e)
{
e.printStackTrace();
}
});
assertNotNull(captcha);
// Step 4. Verify the captcha, get input from the user
String captchaInput = JOptionPane.showInputDialog("Enter the captcha: ");
assertNotNull(captchaInput);
// Step 5. Verify the captcha
try
{
assertTrue(socialClient.verificationAnswerImageCaptcha(captchaInput));
}
catch(RpcException e)
{
fail(e.getMessage(), e);
return;
}
}
private static class ImageFrame extends JFrame {
public ImageFrame(BufferedImage image) {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
}
};
panel.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
add(panel);
pack();
setLocationRelativeTo(null);
}
}
}