From e1b17bf863dda84b820a5a96dc276d33a1194433 Mon Sep 17 00:00:00 2001 From: tonycpsu Date: Wed, 15 Apr 2026 14:17:54 -0400 Subject: [PATCH 01/10] Surface server Retry-After for rate-limit send failures (#2016) * Surface retry-after seconds for plain rate-limit failures libsignal-service's RateLimitException exposes retryAfterMilliseconds for HTTP 413 responses, but signal-cli only forwarded retry-after for ProofRequired (428) failures. Clients had no signal for when it was safe to retry plain rate-limited sends, so every failed retry potentially extended the server-side window. SendMessageResult now carries an optional rateLimitRetryAfterSeconds, populated from the upstream Optional. JsonSendMessageResult exposes it for RATE_LIMIT_FAILURE type. Text output includes the window when known. Aggregate RateLimitErrorException now carries the real nextAttemptTimestamp (was hardcoded to 0). Closes #1996. Co-Authored-By: Claude Opus 4.6 (1M context) * Address review: include proof-required retry-after and ceiling-round millis Codex adversarial review flagged two issues in the phase 1 retry-after plumbing: * Aggregate retry-after ignored proof-required failures. Because isRateLimitFailure is true for proof-required cases but rateLimitRetryAfterSeconds was only populated from plain 413s, an all-proof-required batch (or a mixed batch where the proof-required delay was longer) could flow into outputResult() and produce a RateLimitException(0), telling callers to retry immediately. * Millisecond Retry-After values were truncated by integer division, so 1..999ms became 0 and non-second-aligned values lost up to 999ms. A retry suggested from the floored value can land before the server's real deadline and re-trigger the limit. SendMessageResult.from(...) now populates rateLimitRetryAfterSeconds from either the proof-required seconds or the plain rate-limit ms (converted via ceiling division), giving maxRateLimitRetryAfterSeconds a single source of truth. JsonSendMessageResult.from(...) reads the unified field. New millisToCeilingSeconds helper plus boundary test. Co-Authored-By: Claude Opus 4.6 (1M context) * Preserve source compat and document retry-after field change Add a non-canonical 8-arg SendMessageResult constructor that delegates to the canonical form with null retry-after. This keeps source compatibility for any downstream code that constructs the record directly (tests, mocks) without changing the canonical shape. Records permit additional constructors alongside the canonical one. Document the retryAfterSeconds meaning change in the CHANGELOG. The field was previously populated only for proof-required failures; it is now populated whenever the server sends a Retry-After header. The canonical proof-required discriminator is still token != null. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 4 + .../signal/manager/api/SendMessageResult.java | 57 +++++++-- .../manager/api/SendMessageResults.java | 14 +++ .../manager/api/SendMessageResultTest.java | 38 ++++++ .../signal/json/JsonSendMessageResult.java | 2 +- .../signal/util/SendMessageResultUtils.java | 9 +- .../json/JsonSendMessageResultTest.java | 113 ++++++++++++++++++ 7 files changed, 227 insertions(+), 10 deletions(-) create mode 100644 lib/src/test/java/org/asamk/signal/manager/api/SendMessageResultTest.java create mode 100644 src/test/java/org/asamk/signal/json/JsonSendMessageResultTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index af02c08d..44478806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Send message results now surface server-advised retry time for plain rate-limit (HTTP 413) failures, not only for proof-required challenges. The `retryAfterSeconds` field in JSON-RPC `SendMessageResult` is populated whenever the server sends a `Retry-After` header. The canonical way to distinguish proof-required failures remains `token != null`. Text output includes "retry after N seconds" when known. + ## [0.14.2] - 2026-04-04 ### Added diff --git a/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResult.java b/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResult.java index 4158b60e..82b83aa9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResult.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResult.java @@ -11,11 +11,38 @@ public record SendMessageResult( boolean isIdentityFailure, boolean isRateLimitFailure, ProofRequiredException proofRequiredFailure, - boolean isInvalidPreKeyFailure + boolean isInvalidPreKeyFailure, + Long rateLimitRetryAfterSeconds ) { + /** + * Source-compatible constructor for callers built against the pre-retry-after record shape. + * Delegates to the canonical constructor with a null retry-after, which is the correct value + * for any result not produced by {@link #from}. + */ + public SendMessageResult( + RecipientAddress address, + boolean isSuccess, + boolean isNetworkFailure, + boolean isUnregisteredFailure, + boolean isIdentityFailure, + boolean isRateLimitFailure, + ProofRequiredException proofRequiredFailure, + boolean isInvalidPreKeyFailure + ) { + this(address, + isSuccess, + isNetworkFailure, + isUnregisteredFailure, + isIdentityFailure, + isRateLimitFailure, + proofRequiredFailure, + isInvalidPreKeyFailure, + null); + } + public static SendMessageResult unregisteredFailure(RecipientAddress address) { - return new SendMessageResult(address, false, false, true, false, false, null, false); + return new SendMessageResult(address, false, false, true, false, false, null, false, null); } public static SendMessageResult from( @@ -23,16 +50,32 @@ public record SendMessageResult( RecipientResolver recipientResolver, RecipientAddressResolver addressResolver ) { + final var rateLimitFailure = sendMessageResult.getRateLimitFailure(); + final var proofRequiredFailure = sendMessageResult.getProofRequiredFailure(); + final Long retryAfterSeconds; + if (proofRequiredFailure != null) { + retryAfterSeconds = proofRequiredFailure.getRetryAfterSeconds(); + } else if (rateLimitFailure != null) { + retryAfterSeconds = rateLimitFailure.getRetryAfterMilliseconds() + .map(SendMessageResult::millisToCeilingSeconds) + .orElse(null); + } else { + retryAfterSeconds = null; + } return new SendMessageResult(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient( sendMessageResult.getAddress())).toApiRecipientAddress(), sendMessageResult.isSuccess(), sendMessageResult.isNetworkFailure(), sendMessageResult.isUnregisteredFailure(), sendMessageResult.getIdentityFailure() != null, - sendMessageResult.getRateLimitFailure() != null || sendMessageResult.getProofRequiredFailure() != null, - sendMessageResult.getProofRequiredFailure() == null - ? null - : new ProofRequiredException(sendMessageResult.getProofRequiredFailure()), - sendMessageResult.isInvalidPreKeyFailure()); + rateLimitFailure != null || proofRequiredFailure != null, + proofRequiredFailure == null ? null : new ProofRequiredException(proofRequiredFailure), + sendMessageResult.isInvalidPreKeyFailure(), + retryAfterSeconds); + } + + static long millisToCeilingSeconds(long millis) { + // Round up so we never advise a retry before the server's deadline. + return (millis + 999L) / 1000L; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResults.java b/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResults.java index de250163..83e29928 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResults.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/SendMessageResults.java @@ -26,4 +26,18 @@ public record SendMessageResults(long timestamp, Map res.stream().map(SendMessageResult::isRateLimitFailure)) .allMatch(r -> r) && results.values().stream().mapToInt(List::size).sum() > 0; } + + /** + * Longest rate-limit retry-after window across all rate-limited recipients, in seconds. + * Null when no recipient reported one (server omitted Retry-After, or no rate-limit failures). + */ + public Long maxRateLimitRetryAfterSeconds() { + return results.values() + .stream() + .flatMap(List::stream) + .map(SendMessageResult::rateLimitRetryAfterSeconds) + .filter(r -> r != null) + .max(Long::compareTo) + .orElse(null); + } } diff --git a/lib/src/test/java/org/asamk/signal/manager/api/SendMessageResultTest.java b/lib/src/test/java/org/asamk/signal/manager/api/SendMessageResultTest.java new file mode 100644 index 00000000..b4a4bf4e --- /dev/null +++ b/lib/src/test/java/org/asamk/signal/manager/api/SendMessageResultTest.java @@ -0,0 +1,38 @@ +package org.asamk.signal.manager.api; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class SendMessageResultTest { + + /** + * Ceiling division — we must never advise a retry before the server's deadline, + * so sub-second values round up rather than truncate toward zero. + */ + @Test + void millisToCeilingSecondsRoundsUp() { + assertEquals(0L, SendMessageResult.millisToCeilingSeconds(0L)); + assertEquals(1L, SendMessageResult.millisToCeilingSeconds(1L)); + assertEquals(1L, SendMessageResult.millisToCeilingSeconds(500L)); + assertEquals(1L, SendMessageResult.millisToCeilingSeconds(999L)); + assertEquals(1L, SendMessageResult.millisToCeilingSeconds(1000L)); + assertEquals(2L, SendMessageResult.millisToCeilingSeconds(1001L)); + assertEquals(2L, SendMessageResult.millisToCeilingSeconds(1500L)); + assertEquals(60L, SendMessageResult.millisToCeilingSeconds(60_000L)); + } + + /** + * Source-compat: callers built against the pre-retry-after record shape use the 8-arg + * constructor. It must continue to compile and produce a record with a null retry-after. + */ + @Test + @SuppressWarnings("deprecation") + void legacyEightArgConstructorPreservesSourceCompat() { + var result = new SendMessageResult(new RecipientAddress(null, null, "+15551234567", null), + true, false, false, false, false, null, false); + + assertNull(result.rateLimitRetryAfterSeconds()); + } +} diff --git a/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java b/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java index 1be6cb9e..098d1916 100644 --- a/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java +++ b/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java @@ -32,7 +32,7 @@ public record JsonSendMessageResult( ? Type.INVALID_PRE_KEY_FAILURE : Type.IDENTITY_FAILURE, result.proofRequiredFailure() != null ? result.proofRequiredFailure().getToken() : null, - result.proofRequiredFailure() != null ? result.proofRequiredFailure().getRetryAfterSeconds() : null); + result.rateLimitRetryAfterSeconds()); } public enum Type { diff --git a/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java b/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java index a23e3c54..c438dfc2 100644 --- a/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java +++ b/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java @@ -59,8 +59,10 @@ public class SendMessageResultUtils { if (sendMessageResults.hasOnlyUntrustedIdentity()) { throw new UntrustedKeyErrorException("Failed to send message due to untrusted identities"); } else if (sendMessageResults.hasOnlyRateLimitFailure()) { + final var retryAfter = sendMessageResults.maxRateLimitRetryAfterSeconds(); + final var nextAttempt = retryAfter == null ? 0L : System.currentTimeMillis() + retryAfter * 1000L; throw new RateLimitErrorException("Failed to send message due to rate limiting", - new RateLimitException(0)); + new RateLimitException(nextAttempt)); } else { throw new UserErrorException("Failed to send message"); } @@ -110,7 +112,10 @@ public class SendMessageResultUtils { } else if (result.isNetworkFailure()) { return String.format("Network failure for \"%s\"", identifier); } else if (result.isRateLimitFailure()) { - return String.format("Rate limit failure for \"%s\"", identifier); + final var retryAfter = result.rateLimitRetryAfterSeconds(); + return retryAfter != null + ? String.format("Rate limit failure for \"%s\", retry after %d seconds", identifier, retryAfter) + : String.format("Rate limit failure for \"%s\"", identifier); } else if (result.isUnregisteredFailure()) { return String.format("Unregistered user \"%s\"", identifier); } else if (result.isIdentityFailure()) { diff --git a/src/test/java/org/asamk/signal/json/JsonSendMessageResultTest.java b/src/test/java/org/asamk/signal/json/JsonSendMessageResultTest.java new file mode 100644 index 00000000..1811c6f3 --- /dev/null +++ b/src/test/java/org/asamk/signal/json/JsonSendMessageResultTest.java @@ -0,0 +1,113 @@ +package org.asamk.signal.json; + +import org.asamk.signal.manager.api.RecipientAddress; +import org.asamk.signal.manager.api.RecipientIdentifier; +import org.asamk.signal.manager.api.SendMessageResult; +import org.asamk.signal.manager.api.SendMessageResults; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class JsonSendMessageResultTest { + + private static final RecipientAddress ADDRESS = new RecipientAddress(null, null, "+15551234567", null); + + @Test + void rateLimitFailureSurfacesRetryAfterSeconds() { + var result = new SendMessageResult(ADDRESS, + false, false, false, false, + true, + null, + false, + 3600L); + + var json = JsonSendMessageResult.from(result); + + assertEquals(JsonSendMessageResult.Type.RATE_LIMIT_FAILURE, json.type()); + assertEquals(3600L, json.retryAfterSeconds()); + assertNull(json.token()); + } + + @Test + void rateLimitFailureWithoutRetryAfterLeavesFieldNull() { + var result = new SendMessageResult(ADDRESS, + false, false, false, false, + true, + null, + false, + null); + + var json = JsonSendMessageResult.from(result); + + assertEquals(JsonSendMessageResult.Type.RATE_LIMIT_FAILURE, json.type()); + assertNull(json.retryAfterSeconds()); + } + + @Test + void successLeavesRetryAfterNull() { + var result = new SendMessageResult(ADDRESS, + true, false, false, false, + false, + null, + false, + null); + + var json = JsonSendMessageResult.from(result); + + assertEquals(JsonSendMessageResult.Type.SUCCESS, json.type()); + assertNull(json.retryAfterSeconds()); + } + + @Test + void aggregateReturnsLongestRetryAfter() { + var small = rateLimited("+15551234567", 60L); + var big = rateLimited("+15559876543", 3600L); + var unknown = rateLimited("+15550000000", null); + + var aggregate = new SendMessageResults(1L, + Map.of(new RecipientIdentifier.Uuid(UUID.randomUUID()), List.of(small, big, unknown))); + + assertEquals(3600L, aggregate.maxRateLimitRetryAfterSeconds()); + } + + @Test + void aggregateReturnsNullWhenNoRetryAfter() { + var aggregate = new SendMessageResults(1L, + Map.of(new RecipientIdentifier.Uuid(UUID.randomUUID()), + List.of(rateLimited("+15551234567", null)))); + + assertNull(aggregate.maxRateLimitRetryAfterSeconds()); + } + + /** + * Regression for a bug where the aggregate helper could overlook the longest + * wait if only some recipients reported a value. Ensures the max is picked + * across any mix — which is what downstream captcha/rate-limit clients rely on. + */ + @Test + void aggregatePicksMaxEvenWhenSomeValuesAreNull() { + var withValue = rateLimited("+15551111111", 7200L); + var withoutValue = rateLimited("+15552222222", null); + var alsoWithValue = rateLimited("+15553333333", 120L); + + var aggregate = new SendMessageResults(1L, + Map.of(new RecipientIdentifier.Uuid(UUID.randomUUID()), + List.of(withoutValue, withValue, alsoWithValue))); + + assertEquals(7200L, aggregate.maxRateLimitRetryAfterSeconds()); + } + + private static SendMessageResult rateLimited(String number, Long retryAfterSeconds) { + return new SendMessageResult(new RecipientAddress(null, null, number, null), + false, false, false, false, + true, + null, + false, + retryAfterSeconds); + } +} From 5bfb0442454d912e877ec008e0648bc6bc01a79e Mon Sep 17 00:00:00 2001 From: Gara Dorta Date: Wed, 15 Apr 2026 20:57:36 +0200 Subject: [PATCH 02/10] JSON Schema for JSON-RPC (#1952) * Add OpenAPIDocs * Remove the json prefix from the names * Format file * Rename models to schemas * Add required = true to all the required attributes * Add missing required = true schemas * Deprecated fields are not required * switch to micronout json generation * Fix generator for JsonUnwrapped files * Fix layout of manual schemas * Pretty print the json files * Remove @JsonProperty(required = true) * Make references local * Updated the readme * Removed uneeded import * Remove extra empty lines * Clean readme * Add docs depedency only when needed * Revert uneeded changes * Revert more changes * Better formatting * Simplified name * fix: remove jsonunwrapped workaround by upgrading to micronaut-json-schema version 2.0.0-M6 * Simplified jsonSchemas task definition * Updated readme with the new schemas path * typo fixing * Remove empty space from merge --- README.md | 10 +++++ build.gradle.kts | 40 +++++++++++++++++++ gradle/libs.versions.toml | 6 +++ .../asamk/signal/json/JsonAdminDelete.java | 2 + .../org/asamk/signal/json/JsonAttachment.java | 3 ++ .../asamk/signal/json/JsonAttachmentData.java | 3 ++ .../asamk/signal/json/JsonCallMessage.java | 2 + .../org/asamk/signal/json/JsonContact.java | 4 ++ .../asamk/signal/json/JsonContactAddress.java | 3 ++ .../asamk/signal/json/JsonContactAvatar.java | 3 ++ .../asamk/signal/json/JsonContactEmail.java | 3 ++ .../asamk/signal/json/JsonContactName.java | 3 ++ .../asamk/signal/json/JsonContactPhone.java | 3 ++ .../asamk/signal/json/JsonDataMessage.java | 2 + .../asamk/signal/json/JsonEditMessage.java | 3 ++ .../java/org/asamk/signal/json/JsonError.java | 3 ++ .../org/asamk/signal/json/JsonGroupInfo.java | 3 ++ .../org/asamk/signal/json/JsonMention.java | 3 ++ .../signal/json/JsonMessageEnvelope.java | 2 + .../org/asamk/signal/json/JsonPayment.java | 3 ++ .../org/asamk/signal/json/JsonPinMessage.java | 3 ++ .../org/asamk/signal/json/JsonPollCreate.java | 3 ++ .../asamk/signal/json/JsonPollTerminate.java | 3 ++ .../org/asamk/signal/json/JsonPollVote.java | 3 ++ .../org/asamk/signal/json/JsonPreview.java | 3 ++ .../java/org/asamk/signal/json/JsonQuote.java | 2 + .../signal/json/JsonQuotedAttachment.java | 2 + .../org/asamk/signal/json/JsonReaction.java | 3 ++ .../asamk/signal/json/JsonReceiptMessage.java | 3 ++ .../signal/json/JsonRecipientAddress.java | 3 ++ .../asamk/signal/json/JsonRemoteDelete.java | 3 ++ .../signal/json/JsonSendMessageResult.java | 2 + .../asamk/signal/json/JsonSharedContact.java | 2 + .../org/asamk/signal/json/JsonSticker.java | 3 ++ .../asamk/signal/json/JsonStoryContext.java | 3 ++ .../asamk/signal/json/JsonStoryMessage.java | 2 + .../signal/json/JsonSyncDataMessage.java | 2 + .../asamk/signal/json/JsonSyncMessage.java | 3 ++ .../signal/json/JsonSyncReadMessage.java | 3 ++ .../signal/json/JsonSyncStoryMessage.java | 2 + .../org/asamk/signal/json/JsonTextStyle.java | 3 ++ .../asamk/signal/json/JsonTypingMessage.java | 2 + .../asamk/signal/json/JsonUnpinMessage.java | 3 ++ 43 files changed, 165 insertions(+) diff --git a/README.md b/README.md index 1d192bb3..dcc2d80e 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,16 @@ version installed, you can replace `./gradlew` with `gradle` in the following st ./gradlew run --args="--help" ``` +### JSON Schemas for the JSON-RPC mode + +1. Generate [JSON Schema](https://json-schema.org/) files for all the JSON-RPC data classes (`src/main/java/org/asamk/signal/json`): + + ```sh + ./gradlew jsonSchemas + ``` + +2. The generated files can be found in the `build/generated/META-INF/schemas` folder. + ### Building a native binary with GraalVM (EXPERIMENTAL) It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not diff --git a/build.gradle.kts b/build.gradle.kts index b9579a94..f1e10f4d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import groovy.json.JsonOutput + plugins { java application @@ -72,6 +74,11 @@ val excludePatterns = mapOf( ) ) +val schemaAnnotationProcessor by configurations.creating { + isCanBeConsumed = false + isCanBeResolved = true +} + dependencies { registerTransform(JarFileExcluder::class) { from.attribute(minified, false).attribute(artifactType, "jar") @@ -82,6 +89,8 @@ dependencies { } } + schemaAnnotationProcessor(libs.micronaut.json.schema.processor) + schemaAnnotationProcessor(libs.micronaut.inject.java) implementation(libs.bouncycastle) implementation(libs.jackson.databind) implementation(libs.argparse4j) @@ -90,6 +99,10 @@ dependencies { implementation(libs.slf4j.jul) implementation(libs.logback) implementation(libs.zxing) + implementation(libs.micronaut.json.schema.annotations) + if (gradle.startParameter.taskNames.any { it.contains("jsonSchemas") }) { + implementation(libs.micronaut.json.schema.generator) + } implementation(project(":libsignal-cli")) testImplementation(libs.junit.jupiter) @@ -160,3 +173,30 @@ tasks.register("writeLibsignalVersion") { } } } + +tasks.register("jsonSchemas") { + dependsOn(tasks.compileJava) + val schemaBaseUri = "http://localhost:8080/schemas/" + source = sourceSets.main.get().java + include("org/asamk/signal/json/**/*.java") + classpath = sourceSets.main.get().compileClasspath + files(sourceSets.main.get().java.destinationDirectory) + destinationDirectory.set(layout.buildDirectory.dir("generated")) + options.annotationProcessorPath = schemaAnnotationProcessor + options.compilerArgs.addAll( + listOf( + "-Amicronaut.processing.group=org.asamk", + "-Amicronaut.processing.module=signal-cli", + "-Amicronaut.processing.annotations=org.asamk.signal.json.*", + "-Amicronaut.jsonschema.baseUri=$schemaBaseUri", + ) + ) + doLast { + fileTree(destinationDirectory.get().dir("META-INF/schemas").asFile) { + include("*.schema.json") + }.forEach { schemaFile -> + val normalized = schemaFile.readText().replace("\"$schemaBaseUri/", "\"") + val prettyJson = JsonOutput.prettyPrint(normalized) + schemaFile.writeText("$prettyJson\n") + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f83c5457..697ab32a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,8 @@ [versions] slf4j = "2.0.17" junit = "6.0.2" +micronaut-json-schema = "2.0.0-M6" +micronaut-core = "4.9.3" [libraries] bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83" @@ -8,6 +10,10 @@ jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.20.2" argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" zxing = "com.google.zxing:core:3.5.4" +micronaut-json-schema-annotations = { module = "io.micronaut.jsonschema:micronaut-json-schema-annotations", version.ref = "micronaut-json-schema" } +micronaut-json-schema-processor = { module = "io.micronaut.jsonschema:micronaut-json-schema-processor", version.ref = "micronaut-json-schema" } +micronaut-json-schema-generator = { module = "io.micronaut.jsonschema:micronaut-json-schema-generator", version.ref = "micronaut-json-schema" } +micronaut-inject-java = { module = "io.micronaut:micronaut-inject-java", version.ref = "micronaut-core" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.32" diff --git a/src/main/java/org/asamk/signal/json/JsonAdminDelete.java b/src/main/java/org/asamk/signal/json/JsonAdminDelete.java index 60ab45bd..db7a9915 100644 --- a/src/main/java/org/asamk/signal/json/JsonAdminDelete.java +++ b/src/main/java/org/asamk/signal/json/JsonAdminDelete.java @@ -1,9 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "AdminDelete") public record JsonAdminDelete( @Deprecated String targetAuthor, String targetAuthorNumber, String targetAuthorUuid, long targetSentTimestamp ) { diff --git a/src/main/java/org/asamk/signal/json/JsonAttachment.java b/src/main/java/org/asamk/signal/json/JsonAttachment.java index c75b3987..eb320ba2 100644 --- a/src/main/java/org/asamk/signal/json/JsonAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachment.java @@ -1,7 +1,10 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "Attachment") record JsonAttachment( String contentType, String filename, diff --git a/src/main/java/org/asamk/signal/json/JsonAttachmentData.java b/src/main/java/org/asamk/signal/json/JsonAttachmentData.java index 05a90c21..a9d91078 100644 --- a/src/main/java/org/asamk/signal/json/JsonAttachmentData.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachmentData.java @@ -1,5 +1,8 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + +@JsonSchema(title = "AttachmentData") public record JsonAttachmentData( String data ) {} diff --git a/src/main/java/org/asamk/signal/json/JsonCallMessage.java b/src/main/java/org/asamk/signal/json/JsonCallMessage.java index c539cc46..b568c18a 100644 --- a/src/main/java/org/asamk/signal/json/JsonCallMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonCallMessage.java @@ -1,6 +1,7 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.MessageEnvelope; @@ -10,6 +11,7 @@ import java.util.List; import static org.asamk.signal.manager.util.Utils.callIdUnsigned; +@JsonSchema(title = "CallMessage") record JsonCallMessage( @JsonInclude(JsonInclude.Include.NON_NULL) Offer offerMessage, @JsonInclude(JsonInclude.Include.NON_NULL) Answer answerMessage, diff --git a/src/main/java/org/asamk/signal/json/JsonContact.java b/src/main/java/org/asamk/signal/json/JsonContact.java index 85d5043f..4d49bdbb 100644 --- a/src/main/java/org/asamk/signal/json/JsonContact.java +++ b/src/main/java/org/asamk/signal/json/JsonContact.java @@ -1,9 +1,11 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import java.util.List; +@JsonSchema(title = "Contact") public record JsonContact( String number, String uuid, @@ -26,6 +28,7 @@ public record JsonContact( @JsonInclude(JsonInclude.Include.NON_NULL) JsonInternal internal ) { + @JsonSchema(title = "Profile") public record JsonProfile( long lastUpdateTimestamp, String givenName, @@ -36,6 +39,7 @@ public record JsonContact( String mobileCoinAddress ) {} + @JsonSchema(title = "Internal") public record JsonInternal( List capabilities, String unidentifiedAccessMode, diff --git a/src/main/java/org/asamk/signal/json/JsonContactAddress.java b/src/main/java/org/asamk/signal/json/JsonContactAddress.java index 7184f532..763d6bd7 100644 --- a/src/main/java/org/asamk/signal/json/JsonContactAddress.java +++ b/src/main/java/org/asamk/signal/json/JsonContactAddress.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.util.Util; +@JsonSchema(title = "ContactAddress") public record JsonContactAddress( String type, String label, diff --git a/src/main/java/org/asamk/signal/json/JsonContactAvatar.java b/src/main/java/org/asamk/signal/json/JsonContactAvatar.java index 56b1a4e3..ff686f1a 100644 --- a/src/main/java/org/asamk/signal/json/JsonContactAvatar.java +++ b/src/main/java/org/asamk/signal/json/JsonContactAvatar.java @@ -1,7 +1,10 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "ContactAvatar") public record JsonContactAvatar(JsonAttachment attachment, boolean isProfile) { static JsonContactAvatar from(MessageEnvelope.Data.SharedContact.Avatar avatar) { diff --git a/src/main/java/org/asamk/signal/json/JsonContactEmail.java b/src/main/java/org/asamk/signal/json/JsonContactEmail.java index 7db600db..4967f29f 100644 --- a/src/main/java/org/asamk/signal/json/JsonContactEmail.java +++ b/src/main/java/org/asamk/signal/json/JsonContactEmail.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.util.Util; +@JsonSchema(title = "ContactEmail") public record JsonContactEmail(String value, String type, String label) { static JsonContactEmail from(MessageEnvelope.Data.SharedContact.Email email) { diff --git a/src/main/java/org/asamk/signal/json/JsonContactName.java b/src/main/java/org/asamk/signal/json/JsonContactName.java index 4d8c2327..b9d85a99 100644 --- a/src/main/java/org/asamk/signal/json/JsonContactName.java +++ b/src/main/java/org/asamk/signal/json/JsonContactName.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.util.Util; +@JsonSchema(title = "ContactName") public record JsonContactName( String nickname, String given, String family, String prefix, String suffix, String middle ) { diff --git a/src/main/java/org/asamk/signal/json/JsonContactPhone.java b/src/main/java/org/asamk/signal/json/JsonContactPhone.java index 5da482ff..186e4e51 100644 --- a/src/main/java/org/asamk/signal/json/JsonContactPhone.java +++ b/src/main/java/org/asamk/signal/json/JsonContactPhone.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.util.Util; +@JsonSchema(title = "ContactPhone") public record JsonContactPhone(String value, String type, String label) { static JsonContactPhone from(MessageEnvelope.Data.SharedContact.Phone phone) { diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index 5f667932..c1f7e49e 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -1,12 +1,14 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; +@JsonSchema(title = "DataMessage") record JsonDataMessage( long timestamp, String message, diff --git a/src/main/java/org/asamk/signal/json/JsonEditMessage.java b/src/main/java/org/asamk/signal/json/JsonEditMessage.java index ef61e343..922c4716 100644 --- a/src/main/java/org/asamk/signal/json/JsonEditMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonEditMessage.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "EditMessage") record JsonEditMessage(long targetSentTimestamp, JsonDataMessage dataMessage) { static JsonEditMessage from(MessageEnvelope.Edit editMessage, Manager m) { diff --git a/src/main/java/org/asamk/signal/json/JsonError.java b/src/main/java/org/asamk/signal/json/JsonError.java index 07f8a431..d881c0b7 100644 --- a/src/main/java/org/asamk/signal/json/JsonError.java +++ b/src/main/java/org/asamk/signal/json/JsonError.java @@ -1,5 +1,8 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + +@JsonSchema(title = "Error") public record JsonError(String message, String type) { public static JsonError from(Throwable exception) { diff --git a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java index c370d1e2..69297d0c 100644 --- a/src/main/java/org/asamk/signal/json/JsonGroupInfo.java +++ b/src/main/java/org/asamk/signal/json/JsonGroupInfo.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "GroupInfo") record JsonGroupInfo(String groupId, String groupName, int revision, String type) { static JsonGroupInfo from(MessageEnvelope.Data.GroupContext groupContext, Manager m) { diff --git a/src/main/java/org/asamk/signal/json/JsonMention.java b/src/main/java/org/asamk/signal/json/JsonMention.java index c4c962a0..0d88e736 100644 --- a/src/main/java/org/asamk/signal/json/JsonMention.java +++ b/src/main/java/org/asamk/signal/json/JsonMention.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "Mention") public record JsonMention(@Deprecated String name, String number, String uuid, int start, int length) { static JsonMention from(MessageEnvelope.Data.Mention mention) { diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index 8ab8ea83..f2df7e0a 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -1,6 +1,7 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.MessageEnvelope; @@ -10,6 +11,7 @@ import org.asamk.signal.manager.api.UntrustedIdentityException; import java.util.UUID; +@JsonSchema(title = "MessageEnvelope") public record JsonMessageEnvelope( @Deprecated String source, String sourceNumber, diff --git a/src/main/java/org/asamk/signal/json/JsonPayment.java b/src/main/java/org/asamk/signal/json/JsonPayment.java index d3e5c483..7d7addd4 100644 --- a/src/main/java/org/asamk/signal/json/JsonPayment.java +++ b/src/main/java/org/asamk/signal/json/JsonPayment.java @@ -1,7 +1,10 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "Payment") public record JsonPayment(String note, byte[] receipt) { static JsonPayment from(MessageEnvelope.Data.Payment payment) { diff --git a/src/main/java/org/asamk/signal/json/JsonPinMessage.java b/src/main/java/org/asamk/signal/json/JsonPinMessage.java index 137805af..eebd23d5 100644 --- a/src/main/java/org/asamk/signal/json/JsonPinMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonPinMessage.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "PinMessage") public record JsonPinMessage( @Deprecated String targetAuthor, String targetAuthorNumber, diff --git a/src/main/java/org/asamk/signal/json/JsonPollCreate.java b/src/main/java/org/asamk/signal/json/JsonPollCreate.java index 2c36c4ca..25b8d884 100644 --- a/src/main/java/org/asamk/signal/json/JsonPollCreate.java +++ b/src/main/java/org/asamk/signal/json/JsonPollCreate.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; +@JsonSchema(title = "PollCreate") public record JsonPollCreate( String question, boolean allowMultiple, List options ) { diff --git a/src/main/java/org/asamk/signal/json/JsonPollTerminate.java b/src/main/java/org/asamk/signal/json/JsonPollTerminate.java index 0642559f..8e2c3e8a 100644 --- a/src/main/java/org/asamk/signal/json/JsonPollTerminate.java +++ b/src/main/java/org/asamk/signal/json/JsonPollTerminate.java @@ -1,7 +1,10 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "PollTerminate") public record JsonPollTerminate(long targetSentTimestamp) { static JsonPollTerminate from(MessageEnvelope.Data.PollTerminate pollTerminate) { diff --git a/src/main/java/org/asamk/signal/json/JsonPollVote.java b/src/main/java/org/asamk/signal/json/JsonPollVote.java index 520a838e..8a911f4c 100644 --- a/src/main/java/org/asamk/signal/json/JsonPollVote.java +++ b/src/main/java/org/asamk/signal/json/JsonPollVote.java @@ -1,10 +1,13 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; import java.util.UUID; +@JsonSchema(title = "PollVote") public record JsonPollVote( @Deprecated String author, String authorNumber, diff --git a/src/main/java/org/asamk/signal/json/JsonPreview.java b/src/main/java/org/asamk/signal/json/JsonPreview.java index c5e0d49e..9fd0a37a 100644 --- a/src/main/java/org/asamk/signal/json/JsonPreview.java +++ b/src/main/java/org/asamk/signal/json/JsonPreview.java @@ -1,7 +1,10 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "Preview") public record JsonPreview(String url, String title, String description, JsonAttachment image) { static JsonPreview from(MessageEnvelope.Data.Preview preview) { diff --git a/src/main/java/org/asamk/signal/json/JsonQuote.java b/src/main/java/org/asamk/signal/json/JsonQuote.java index 0f951941..839ab077 100644 --- a/src/main/java/org/asamk/signal/json/JsonQuote.java +++ b/src/main/java/org/asamk/signal/json/JsonQuote.java @@ -1,12 +1,14 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; import java.util.UUID; +@JsonSchema(title = "Quote") public record JsonQuote( long id, @Deprecated String author, diff --git a/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java b/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java index e5022798..9d98bf86 100644 --- a/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonQuotedAttachment.java @@ -1,9 +1,11 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "QuotedAttachment") public record JsonQuotedAttachment( String contentType, String filename, @JsonInclude(JsonInclude.Include.NON_NULL) JsonAttachment thumbnail ) { diff --git a/src/main/java/org/asamk/signal/json/JsonReaction.java b/src/main/java/org/asamk/signal/json/JsonReaction.java index 71da4df5..db7f4086 100644 --- a/src/main/java/org/asamk/signal/json/JsonReaction.java +++ b/src/main/java/org/asamk/signal/json/JsonReaction.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "Reaction") public record JsonReaction( String emoji, @Deprecated String targetAuthor, diff --git a/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java b/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java index ec61b2f1..26a4aca5 100644 --- a/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonReceiptMessage.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; +@JsonSchema(title = "ReceiptMessage") record JsonReceiptMessage(long when, boolean isDelivery, boolean isRead, boolean isViewed, List timestamps) { static JsonReceiptMessage from(MessageEnvelope.Receipt receiptMessage) { diff --git a/src/main/java/org/asamk/signal/json/JsonRecipientAddress.java b/src/main/java/org/asamk/signal/json/JsonRecipientAddress.java index 0d9fc8cd..5236461f 100644 --- a/src/main/java/org/asamk/signal/json/JsonRecipientAddress.java +++ b/src/main/java/org/asamk/signal/json/JsonRecipientAddress.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.RecipientAddress; import java.util.UUID; +@JsonSchema(title = "RecipientAddress") public record JsonRecipientAddress(String uuid, String number, String username) { public static JsonRecipientAddress from(RecipientAddress address) { diff --git a/src/main/java/org/asamk/signal/json/JsonRemoteDelete.java b/src/main/java/org/asamk/signal/json/JsonRemoteDelete.java index 5a5727e5..b1def8e8 100644 --- a/src/main/java/org/asamk/signal/json/JsonRemoteDelete.java +++ b/src/main/java/org/asamk/signal/json/JsonRemoteDelete.java @@ -1,3 +1,6 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + +@JsonSchema(title = "RemoteDelete") record JsonRemoteDelete(long timestamp) {} diff --git a/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java b/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java index 098d1916..260a33bd 100644 --- a/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java +++ b/src/main/java/org/asamk/signal/json/JsonSendMessageResult.java @@ -1,10 +1,12 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.SendMessageResult; +@JsonSchema(title = "SendMessageResult") public record JsonSendMessageResult( JsonRecipientAddress recipientAddress, @JsonInclude(JsonInclude.Include.NON_NULL) String groupId, diff --git a/src/main/java/org/asamk/signal/json/JsonSharedContact.java b/src/main/java/org/asamk/signal/json/JsonSharedContact.java index fa4ca779..37e6bd67 100644 --- a/src/main/java/org/asamk/signal/json/JsonSharedContact.java +++ b/src/main/java/org/asamk/signal/json/JsonSharedContact.java @@ -1,11 +1,13 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; +@JsonSchema(title = "SharedContact") public record JsonSharedContact( JsonContactName name, @JsonInclude(JsonInclude.Include.NON_NULL) JsonContactAvatar avatar, diff --git a/src/main/java/org/asamk/signal/json/JsonSticker.java b/src/main/java/org/asamk/signal/json/JsonSticker.java index 720130ef..5723a1c4 100644 --- a/src/main/java/org/asamk/signal/json/JsonSticker.java +++ b/src/main/java/org/asamk/signal/json/JsonSticker.java @@ -1,8 +1,11 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.util.Hex; +@JsonSchema(title = "Sticker") public record JsonSticker(String packId, int stickerId) { static JsonSticker from(MessageEnvelope.Data.Sticker sticker) { diff --git a/src/main/java/org/asamk/signal/json/JsonStoryContext.java b/src/main/java/org/asamk/signal/json/JsonStoryContext.java index e2cc0c55..31e1dfff 100644 --- a/src/main/java/org/asamk/signal/json/JsonStoryContext.java +++ b/src/main/java/org/asamk/signal/json/JsonStoryContext.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "StoryContext") record JsonStoryContext( String authorNumber, String authorUuid, long sentTimestamp ) { diff --git a/src/main/java/org/asamk/signal/json/JsonStoryMessage.java b/src/main/java/org/asamk/signal/json/JsonStoryMessage.java index c1ca6144..5b7495c3 100644 --- a/src/main/java/org/asamk/signal/json/JsonStoryMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonStoryMessage.java @@ -1,6 +1,7 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.Color; import org.asamk.signal.manager.api.GroupId; @@ -8,6 +9,7 @@ import org.asamk.signal.manager.api.MessageEnvelope; import java.util.List; +@JsonSchema(title = "StoryMessage") record JsonStoryMessage( boolean allowsReplies, @JsonInclude(JsonInclude.Include.NON_NULL) String groupId, diff --git a/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java index 4c2cd6ab..5563056f 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncDataMessage.java @@ -2,6 +2,7 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonUnwrapped; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.MessageEnvelope; @@ -9,6 +10,7 @@ import org.asamk.signal.manager.api.RecipientAddress; import java.util.UUID; +@JsonSchema(title = "SyncDataMessage") record JsonSyncDataMessage( @Deprecated String destination, String destinationNumber, diff --git a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java index 899a48fb..e4b1242c 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java @@ -9,12 +9,15 @@ import org.asamk.signal.manager.api.RecipientAddress; import java.util.List; +import io.micronaut.jsonschema.JsonSchema; + enum JsonSyncMessageType { CONTACTS_SYNC, GROUPS_SYNC, REQUEST_SYNC } +@JsonSchema(title = "SyncMessage") record JsonSyncMessage( @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncDataMessage sentMessage, @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncStoryMessage sentStoryMessage, diff --git a/src/main/java/org/asamk/signal/json/JsonSyncReadMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncReadMessage.java index 50c6ecee..e2c4a6e9 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncReadMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncReadMessage.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "SyncReadMessage") record JsonSyncReadMessage( @Deprecated String sender, String senderNumber, String senderUuid, long timestamp ) { diff --git a/src/main/java/org/asamk/signal/json/JsonSyncStoryMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncStoryMessage.java index 6b435223..b8254a8f 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncStoryMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncStoryMessage.java @@ -1,11 +1,13 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonUnwrapped; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "SyncStoryMessage") record JsonSyncStoryMessage( String destinationNumber, String destinationUuid, @JsonUnwrapped JsonStoryMessage dataMessage ) { diff --git a/src/main/java/org/asamk/signal/json/JsonTextStyle.java b/src/main/java/org/asamk/signal/json/JsonTextStyle.java index 898e7db6..87591c2b 100644 --- a/src/main/java/org/asamk/signal/json/JsonTextStyle.java +++ b/src/main/java/org/asamk/signal/json/JsonTextStyle.java @@ -1,7 +1,10 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.TextStyle; +@JsonSchema(title = "TextStyle") public record JsonTextStyle(String style, int start, int length) { static JsonTextStyle from(TextStyle textStyle) { diff --git a/src/main/java/org/asamk/signal/json/JsonTypingMessage.java b/src/main/java/org/asamk/signal/json/JsonTypingMessage.java index 79a66d34..dae86d74 100644 --- a/src/main/java/org/asamk/signal/json/JsonTypingMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonTypingMessage.java @@ -1,10 +1,12 @@ package org.asamk.signal.json; import com.fasterxml.jackson.annotation.JsonInclude; +import io.micronaut.jsonschema.JsonSchema; import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.MessageEnvelope; +@JsonSchema(title = "TypingMessage") record JsonTypingMessage( String action, long timestamp, @JsonInclude(JsonInclude.Include.NON_NULL) String groupId ) { diff --git a/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java b/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java index dad137c5..6b5ffa5c 100644 --- a/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonUnpinMessage.java @@ -1,9 +1,12 @@ package org.asamk.signal.json; +import io.micronaut.jsonschema.JsonSchema; + import org.asamk.signal.manager.api.MessageEnvelope; import java.util.UUID; +@JsonSchema(title = "UnpinMessage") public record JsonUnpinMessage( @Deprecated String targetAuthor, String targetAuthorNumber, String targetAuthorUuid, long targetSentTimestamp ) { From 561dfc373fa5397633b9963a795d12e0c0d1754e Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 15 Apr 2026 20:55:47 +0200 Subject: [PATCH 03/10] Refactor retry after handling --- .../manager/api/CaptchaRequiredException.java | 10 +-- .../manager/api/ProofRequiredException.java | 21 ++++-- .../manager/api/RateLimitException.java | 14 ++-- .../signal/manager/api/SendMessageResult.java | 65 +++++-------------- .../manager/api/SendMessageResults.java | 9 +-- .../signal/manager/internal/ManagerImpl.java | 2 +- .../manager/util/NumberVerificationUtils.java | 6 +- .../manager/api/SendMessageResultTest.java | 38 ----------- .../org/asamk/signal/dbus/DbusSignalImpl.java | 10 ++- .../signal/json/JsonSendMessageResult.java | 3 +- .../org/asamk/signal/util/CommandUtil.java | 11 ++-- .../signal/util/SendMessageResultUtils.java | 14 ++-- .../json/JsonSendMessageResultTest.java | 47 +++++++------- 13 files changed, 101 insertions(+), 149 deletions(-) delete mode 100644 lib/src/test/java/org/asamk/signal/manager/api/SendMessageResultTest.java diff --git a/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java index 86fcdc93..cbaa22b1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRequiredException.java @@ -2,11 +2,11 @@ package org.asamk.signal.manager.api; public class CaptchaRequiredException extends Exception { - private long nextAttemptTimestamp; + private long nextVerificationAttemptMilliseconds; - public CaptchaRequiredException(final long nextAttemptTimestamp) { + public CaptchaRequiredException(final long nextVerificationAttemptMilliseconds) { super("Captcha required"); - this.nextAttemptTimestamp = nextAttemptTimestamp; + this.nextVerificationAttemptMilliseconds = nextVerificationAttemptMilliseconds; } public CaptchaRequiredException(final String message) { @@ -17,7 +17,7 @@ public class CaptchaRequiredException extends Exception { super(message, cause); } - public long getNextAttemptTimestamp() { - return nextAttemptTimestamp; + public long getNextVerificationAttemptMilliseconds() { + return nextVerificationAttemptMilliseconds; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java b/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java index 21b4034d..7e1a7d21 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java @@ -10,12 +10,19 @@ public class ProofRequiredException extends Exception { private final String token; private final Set