Compare commits

...

13 Commits

Author SHA1 Message Date
AsamK
14297986f2 Use SequencedCollection instead of List 2026-01-24 17:24:27 +01:00
AsamK
0bd4d554d8 Use virtual threads 2026-01-24 17:24:27 +01:00
AsamK
32c8d4f801 Update to java 25 2026-01-24 15:38:02 +01:00
AsamK
82abc20871 Remove deprecated functionality 2026-01-24 15:37:00 +01:00
AsamK
e8ab01f665 Fix use of deprecated API in plugin 2026-01-24 15:20:55 +01:00
AsamK
dee557a9ad Prepare next release 2026-01-24 15:09:23 +01:00
AsamK
f1fa2eba1d Bump version to 0.13.23 2026-01-24 14:21:40 +01:00
AsamK
ccd58bbf23 Use graalvm-community 2026-01-24 14:21:40 +01:00
AsamK
54700d9cd0 Update libsignal-service 2026-01-24 12:36:29 +01:00
AsamK
984ea47f9d Update gradle 2026-01-24 12:36:29 +01:00
AsamK
9458972d15 Update dependencies 2026-01-24 12:36:29 +01:00
AsamK
8eb9662694 Implement new updateDevice command to update device names
Fixes #1906
2026-01-23 20:00:25 +01:00
Benjamin Loison
a9be1aa608 Remove an unnecessary space in README.md 2026-01-23 19:37:26 +01:00
53 changed files with 9763 additions and 405 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '21', '25' ]
java: [ '25' ]
steps:
- uses: actions/checkout@v4
@ -58,8 +58,8 @@ jobs:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
version: 'latest'
java-version: '21'
distribution: 'graalvm'
java-version: '25'
cache: 'gradle'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Gradle

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 21
java-version: 25
- name: Checkout repository
uses: actions/checkout@v4

View File

@ -2,6 +2,31 @@
## [Unreleased]
**Attention**: Now requires Java 25
### Breaking changes
- Remove isRegistered method without parameters from Signal dbus interface, which always returned `true`
- Remove `sandbox` value for --service-environment parameter, use `staging` instead
- The `daemon` command now requires at least one channel parameter (`--socket`, `--dbus`, ...) and no longer defaults to dbus
## [0.13.23] - 2026-01-24
Requires libsignal-client version 0.86.12.
### Added
- Add sendPollCreate, sendPollVote, sendPollTerminate commands for polls
- Add updateDevice command to set device name of linked devices
### Changed
- Allow updating contact names from linked devices
### Fixed
- Start multi account mode even if some accounts have authorization failures
## [0.13.22] - 2025-11-14
Requires libsignal-client version 0.86.1.

View File

@ -1,4 +1,4 @@
FROM docker.io/azul/zulu-openjdk:21-jre-headless
FROM docker.io/azul/zulu-openjdk:25-jre-headless
LABEL org.opencontainers.image.source=https://github.com/AsamK/signal-cli
LABEL org.opencontainers.image.description="signal-cli provides an unofficial commandline, dbus and JSON-RPC interface for the Signal messenger."

View File

@ -8,7 +8,7 @@ For registering you need a phone number where you can receive SMS or incoming ca
signal-cli is primarily intended to be used on servers to notify admins of important events.
For this use-case, it has a daemon mode with JSON-RPC interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc))
and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)) .
and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)).
For the JSON-RPC interface there's also a simple [example client](https://github.com/AsamK/signal-cli/tree/master/client), written in Rust.
signal-cli needs to be kept up-to-date to keep up with Signal-Server changes.
@ -23,7 +23,7 @@ Windows. There's also a [docker image and some Linux packages](https://github.co
System requirements:
- at least Java Runtime Environment (JRE) 21
- at least Java Runtime Environment (JRE) 25
- native library: libsignal-client
The native libs are bundled for x86_64 Linux (with recent enough glibc), Windows and MacOS. For other

View File

@ -3,17 +3,17 @@ plugins {
application
eclipse
`check-lib-versions`
id("org.graalvm.buildtools.native") version "0.11.3"
id("org.graalvm.buildtools.native") version "0.11.4"
}
allprojects {
group = "org.asamk"
version = "0.13.23-SNAPSHOT"
version = "0.14.0-SNAPSHOT"
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {
@ -39,7 +39,7 @@ graalvmNative {
if (System.getenv("GRAALVM_HOME") == null) {
toolchainDetection.set(true)
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(25))
})
} else {
toolchainDetection.set(false)

View File

@ -7,11 +7,11 @@ plugins {
}
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
compilerOptions.jvmTarget.set(JvmTarget.JVM_24)
}
java {
targetCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_24
}
repositories {

View File

@ -1,5 +1,3 @@
@file:Suppress("DEPRECATION")
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
@ -8,7 +6,7 @@ import javax.xml.parsers.DocumentBuilderFactory
class CheckLibVersionsPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.task("checkLibVersions") {
project.tasks.register("checkLibVersions") {
description =
"Find any 3rd party libraries which have released new versions to the central Maven repo since we last upgraded."
doLast {
@ -28,7 +26,7 @@ class CheckLibVersionsPlugin : Plugin<Project> {
try {
val dbf = DocumentBuilderFactory.newInstance()
val db = dbf.newDocumentBuilder()
val doc = db.parse(metaDataUrl);
val doc = db.parse(metaDataUrl)
val newest = doc.getElementsByTagName("latest").item(0).textContent
if (version != newest.toString()) {
println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}")

View File

@ -45,6 +45,9 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="0.13.23" date="2026-01-24">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.23</url>
</release>
<release version="0.13.22" date="2025-11-14">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.22</url>
</release>

View File

@ -121,7 +121,7 @@
},
{
"name":"org.signal.libsignal.net.ChatConnection$ListenerBridge",
"methods":[{"name":"onConnectionInterrupted","parameterTypes":["java.lang.Throwable"] }, {"name":"onIncomingMessage","parameterTypes":["byte[]","long","long"] }, {"name":"onQueueEmpty","parameterTypes":[] }, {"name":"onReceivedAlerts","parameterTypes":["java.lang.String[]"] }]
"methods":[{"name":"connectionInterrupted","parameterTypes":["java.lang.Throwable"] }, {"name":"onConnectionInterrupted","parameterTypes":["java.lang.Throwable"] }, {"name":"onIncomingMessage","parameterTypes":["byte[]","long","long"] }, {"name":"onQueueEmpty","parameterTypes":[] }, {"name":"onReceivedAlerts","parameterTypes":["java.lang.String[]"] }, {"name":"receivedAlerts","parameterTypes":["java.lang.String[]"] }, {"name":"receivedIncomingMessage","parameterTypes":["byte[]","long","long"] }, {"name":"receivedQueueEmpty","parameterTypes":[] }]
},
{
"name":"org.signal.libsignal.net.ChatConnection$Response",

View File

@ -1,26 +1 @@
[
{
"interfaces":["java.sql.Connection"]
},
{
"interfaces":["org.asamk.Signal"]
},
{
"interfaces":["org.asamk.Signal$Configuration"]
},
{
"interfaces":["org.asamk.Signal$Device"]
},
{
"interfaces":["org.asamk.Signal$Group"]
},
{
"interfaces":["org.asamk.Signal$Identity"]
},
{
"interfaces":["org.asamk.SignalControl"]
},
{
"interfaces":["org.freedesktop.dbus.interfaces.DBus"]
}
]
[{"interfaces":["java.sql.Connection"]},{"interfaces":["org.asamk.Signal"]},{"interfaces":["org.asamk.Signal$Configuration"]},{"interfaces":["org.asamk.Signal$Device"]},{"interfaces":["org.asamk.Signal$Group"]},{"interfaces":["org.asamk.Signal$Identity"]},{"interfaces":["org.asamk.SignalControl"]},{"interfaces":["org.freedesktop.dbus.interfaces.DBus"]}]

File diff suppressed because it is too large Load Diff

View File

@ -1832,6 +1832,15 @@
"name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.kdf.HKDF$Mappings"
},
{
"name":"org.bouncycastle.jcajce.provider.kdf.PBEPBKDF2$Mappings"
},
{
"name":"org.bouncycastle.jcajce.provider.kdf.SCRYPT$Mappings"
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
@ -1919,6 +1928,10 @@
"name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HKDF$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
@ -2172,199 +2185,199 @@
"queryAllDeclaredConstructors":true
},
{
"name":"org.signal.storageservice.protos.groups.AccessControl",
"name":"org.signal.storageservice.storage.protos.groups.AccessControl",
"fields":[{"name":"addFromInviteLink_"}, {"name":"attributes_"}, {"name":"members_"}]
},
{
"name":"org.signal.storageservice.protos.groups.AvatarUploadAttributes",
"name":"org.signal.storageservice.storage.protos.groups.AvatarUploadAttributes",
"fields":[{"name":"acl_"}, {"name":"algorithm_"}, {"name":"credential_"}, {"name":"date_"}, {"name":"key_"}, {"name":"policy_"}, {"name":"signature_"}]
},
{
"name":"org.signal.storageservice.protos.groups.BannedMember",
"name":"org.signal.storageservice.storage.protos.groups.BannedMember",
"fields":[{"name":"timestamp_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.Group",
"name":"org.signal.storageservice.storage.protos.groups.Group",
"fields":[{"name":"accessControl_"}, {"name":"announcementsOnly_"}, {"name":"avatar_"}, {"name":"bannedMembers_"}, {"name":"description_"}, {"name":"disappearingMessagesTimer_"}, {"name":"inviteLinkPassword_"}, {"name":"members_"}, {"name":"pendingMembers_"}, {"name":"publicKey_"}, {"name":"requestingMembers_"}, {"name":"revision_"}, {"name":"title_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupAttributeBlob",
"name":"org.signal.storageservice.storage.protos.groups.GroupAttributeBlob",
"fields":[{"name":"contentCase_"}, {"name":"content_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange",
"fields":[{"name":"actions_"}, {"name":"changeEpoch_"}, {"name":"serverSignature_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions",
"fields":[{"name":"addBannedMembers_"}, {"name":"addMembers_"}, {"name":"addPendingMembers_"}, {"name":"addRequestingMembers_"}, {"name":"deleteBannedMembers_"}, {"name":"deleteMembers_"}, {"name":"deletePendingMembers_"}, {"name":"deleteRequestingMembers_"}, {"name":"modifyAddFromInviteLinkAccess_"}, {"name":"modifyAnnouncementsOnly_"}, {"name":"modifyAttributesAccess_"}, {"name":"modifyAvatar_"}, {"name":"modifyDescription_"}, {"name":"modifyDisappearingMessagesTimer_"}, {"name":"modifyInviteLinkPassword_"}, {"name":"modifyMemberAccess_"}, {"name":"modifyMemberProfileKeys_"}, {"name":"modifyMemberRoles_"}, {"name":"modifyTitle_"}, {"name":"promotePendingMembers_"}, {"name":"promotePendingPniAciMembers_"}, {"name":"promoteRequestingMembers_"}, {"name":"revision_"}, {"name":"sourceServiceId_"}, {"name":"sourceUuid_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddBannedMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$AddBannedMemberAction",
"fields":[{"name":"added_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$AddMemberAction",
"fields":[{"name":"added_"}, {"name":"joinFromInviteLink_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddPendingMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$AddPendingMemberAction",
"fields":[{"name":"added_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddRequestingMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$AddRequestingMemberAction",
"fields":[{"name":"added_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteBannedMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$DeleteBannedMemberAction",
"fields":[{"name":"deletedUserId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$DeleteMemberAction",
"fields":[{"name":"deletedUserId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeletePendingMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$DeletePendingMemberAction",
"fields":[{"name":"deletedUserId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteRequestingMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$DeleteRequestingMemberAction",
"fields":[{"name":"deletedUserId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAddFromInviteLinkAccessControlAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyAddFromInviteLinkAccessControlAction",
"fields":[{"name":"addFromInviteLinkAccess_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAnnouncementsOnlyAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyAnnouncementsOnlyAction",
"fields":[{"name":"announcementsOnly_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAttributesAccessControlAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyAttributesAccessControlAction",
"fields":[{"name":"attributesAccess_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAvatarAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyAvatarAction",
"fields":[{"name":"avatar_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDescriptionAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyDescriptionAction",
"fields":[{"name":"description_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDisappearingMessagesTimerAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyDisappearingMessagesTimerAction",
"fields":[{"name":"timer_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyInviteLinkPasswordAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyInviteLinkPasswordAction",
"fields":[{"name":"inviteLinkPassword_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberProfileKeyAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyMemberProfileKeyAction",
"fields":[{"name":"presentation_"}, {"name":"profileKey_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberRoleAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyMemberRoleAction",
"fields":[{"name":"role_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMembersAccessControlAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyMembersAccessControlAction",
"fields":[{"name":"membersAccess_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyTitleAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$ModifyTitleAction",
"fields":[{"name":"title_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromotePendingMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$PromotePendingMemberAction",
"fields":[{"name":"presentation_"}, {"name":"profileKey_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromotePendingPniAciMemberProfileKeyAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$PromotePendingPniAciMemberProfileKeyAction",
"fields":[{"name":"pni_"}, {"name":"presentation_"}, {"name":"profileKey_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromoteRequestingMemberAction",
"name":"org.signal.storageservice.storage.protos.groups.GroupChange$Actions$PromoteRequestingMemberAction",
"fields":[{"name":"role_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChanges",
"name":"org.signal.storageservice.storage.protos.groups.GroupChanges",
"fields":[{"name":"groupChanges_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupChanges$GroupChangeState",
"name":"org.signal.storageservice.storage.protos.groups.GroupChanges$GroupChangeState",
"fields":[{"name":"groupChange_"}, {"name":"groupState_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupInviteLink",
"name":"org.signal.storageservice.storage.protos.groups.GroupInviteLink",
"fields":[{"name":"contentsCase_"}, {"name":"contents_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupInviteLink$GroupInviteLinkContentsV1",
"name":"org.signal.storageservice.storage.protos.groups.GroupInviteLink$GroupInviteLinkContentsV1",
"fields":[{"name":"groupMasterKey_"}, {"name":"inviteLinkPassword_"}]
},
{
"name":"org.signal.storageservice.protos.groups.GroupJoinInfo",
"name":"org.signal.storageservice.storage.protos.groups.GroupJoinInfo",
"fields":[{"name":"addFromInviteLink_"}, {"name":"avatar_"}, {"name":"description_"}, {"name":"memberCount_"}, {"name":"pendingAdminApproval_"}, {"name":"publicKey_"}, {"name":"revision_"}, {"name":"title_"}]
},
{
"name":"org.signal.storageservice.protos.groups.Member",
"name":"org.signal.storageservice.storage.protos.groups.Member",
"fields":[{"name":"joinedAtRevision_"}, {"name":"presentation_"}, {"name":"profileKey_"}, {"name":"role_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.PendingMember",
"name":"org.signal.storageservice.storage.protos.groups.PendingMember",
"fields":[{"name":"addedByUserId_"}, {"name":"member_"}, {"name":"timestamp_"}]
},
{
"name":"org.signal.storageservice.protos.groups.RequestingMember",
"name":"org.signal.storageservice.storage.protos.groups.RequestingMember",
"fields":[{"name":"presentation_"}, {"name":"profileKey_"}, {"name":"timestamp_"}, {"name":"userId_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedApproveMember",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedApproveMember",
"fields":[{"name":"role_"}, {"name":"uuid_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedBannedMember",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedBannedMember",
"fields":[{"name":"serviceIdBinary_"}, {"name":"serviceIdBytes_"}, {"name":"timestamp_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedGroup",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedGroup",
"fields":[{"name":"accessControl_"}, {"name":"avatar_"}, {"name":"bannedMembers_"}, {"name":"description_"}, {"name":"disappearingMessagesTimer_"}, {"name":"inviteLinkPassword_"}, {"name":"isAnnouncementGroup_"}, {"name":"members_"}, {"name":"pendingMembers_"}, {"name":"requestingMembers_"}, {"name":"revision_"}, {"name":"title_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedGroupChange",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange",
"fields":[{"name":"deleteBannedMembers_"}, {"name":"deleteMembers_"}, {"name":"deletePendingMembers_"}, {"name":"deleteRequestingMembers_"}, {"name":"editorServiceIdBytes_"}, {"name":"editor_"}, {"name":"modifiedProfileKeys_"}, {"name":"modifyMemberRoles_"}, {"name":"newAttributeAccess_"}, {"name":"newAvatar_"}, {"name":"newBannedMembers_"}, {"name":"newDescription_"}, {"name":"newInviteLinkAccess_"}, {"name":"newInviteLinkPassword_"}, {"name":"newIsAnnouncementGroup_"}, {"name":"newMemberAccess_"}, {"name":"newMembers_"}, {"name":"newPendingMembers_"}, {"name":"newRequestingMembers_"}, {"name":"newTimer_"}, {"name":"newTitle_"}, {"name":"promotePendingMembers_"}, {"name":"promotePendingPniAciMembers_"}, {"name":"promoteRequestingMembers_"}, {"name":"revision_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo",
"fields":[{"name":"addFromInviteLink_"}, {"name":"avatar_"}, {"name":"description_"}, {"name":"isAnnouncementGroup_"}, {"name":"memberCount_"}, {"name":"pendingAdminApproval_"}, {"name":"revision_"}, {"name":"title_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedMember",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedMember",
"fields":[{"name":"aciBytes_"}, {"name":"joinedAtRevision_"}, {"name":"pniBytes_"}, {"name":"pni_"}, {"name":"profileKey_"}, {"name":"role_"}, {"name":"uuid_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedModifyMemberRole",
"fields":[{"name":"role_"}, {"name":"uuid_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMember",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember",
"fields":[{"name":"addedByAci_"}, {"name":"addedByUuid_"}, {"name":"role_"}, {"name":"serviceIdBinary_"}, {"name":"serviceIdBytes_"}, {"name":"serviceIdCipherText_"}, {"name":"timestamp_"}, {"name":"uuidCipherText_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMemberRemoval",
"fields":[{"name":"uuidCipherText_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedRequestingMember",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember",
"fields":[{"name":"aciBytes_"}, {"name":"profileKey_"}, {"name":"timestamp_"}, {"name":"uuid_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedString",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedString",
"fields":[{"name":"value_"}]
},
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedTimer",
"name":"org.signal.storageservice.storage.protos.groups.local.DecryptedTimer",
"fields":[{"name":"duration_"}]
},
{
@ -2426,6 +2439,13 @@
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"<init>","parameterTypes":["java.lang.String","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }]
},
{
"name":"org.whispersystems.signalservice.api.link.SetDeviceNameRequest",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"getDeviceName","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage",
"allDeclaredFields":true,
@ -3068,6 +3088,7 @@
{
"name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord",
"allDeclaredFields":true,
"fields":[{"name":"avatarColor"}, {"name":"avatarUrlPath"}, {"name":"backupSubscriberData"}, {"name":"backupTier"}, {"name":"backupTierHistory"}, {"name":"displayBadgesOnProfile"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hasBackup"}, {"name":"hasCompletedUsernameOnboarding"}, {"name":"hasSeenGroupStoryEducationSheet"}, {"name":"hasSetMyStoriesPrivacy"}, {"name":"hasViewedOnboardingStory"}, {"name":"keepMutedChatsArchived"}, {"name":"linkPreviews"}, {"name":"noteToSelfArchived"}, {"name":"noteToSelfMarkedUnread"}, {"name":"notificationProfileManualOverride"}, {"name":"payments"}, {"name":"phoneNumberSharingMode"}, {"name":"pinnedConversations"}, {"name":"preferContactAvatars"}, {"name":"preferredReactionEmoji"}, {"name":"primarySendsSms"}, {"name":"profileKey"}, {"name":"readReceipts"}, {"name":"sealedSenderIndicators"}, {"name":"storiesDisabled"}, {"name":"storyViewReceiptsEnabled"}, {"name":"subscriberCurrencyCode"}, {"name":"subscriberId"}, {"name":"subscriptionManuallyCancelled"}, {"name":"typingIndicators"}, {"name":"universalExpireTimer"}, {"name":"unlistedPhoneNumber"}, {"name":"username"}, {"name":"usernameLink"}],
"methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
},
{
@ -3106,6 +3127,7 @@
{
"name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord",
"allDeclaredFields":true,
"fields":[{"name":"aci"}, {"name":"aciBinary"}, {"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hidden"}, {"name":"hideStory"}, {"name":"identityKey"}, {"name":"identityState"}, {"name":"markedUnread"}, {"name":"mutedUntilTimestamp"}, {"name":"nickname"}, {"name":"note"}, {"name":"pni"}, {"name":"pniBinary"}, {"name":"pniSignatureVerified"}, {"name":"profileKey"}, {"name":"systemFamilyName"}, {"name":"systemGivenName"}, {"name":"systemNickname"}, {"name":"unregisteredAtTimestamp"}, {"name":"username"}, {"name":"whitelisted"}],
"methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }]
},
{

File diff suppressed because one or more lines are too long

View File

@ -1,18 +1,18 @@
[versions]
slf4j = "2.0.17"
junit = "6.0.1"
junit = "6.0.2"
[libraries]
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.82"
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83"
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.20.1"
argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0"
dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0"
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.21"
logback = "ch.qos.logback:logback-classic:1.5.25"
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_135"
sqlite = "org.xerial:sqlite-jdbc:3.51.0.0"
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_136"
sqlite = "org.xerial:sqlite-jdbc:3.51.1.0"
hikari = "com.zaxxer:HikariCP:7.0.2"
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -4,8 +4,8 @@ plugins {
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {

View File

@ -154,6 +154,8 @@ public interface Manager extends Closeable {
List<Device> getLinkedDevices() throws IOException;
void updateLinkedDevice(int deviceId, String name) throws IOException, NotPrimaryDeviceException;
void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException;
void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException;

View File

@ -0,0 +1,20 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.helper.Context;
public class RetrieveDeviceNameAction implements HandleAction {
private static final RetrieveDeviceNameAction INSTANCE = new RetrieveDeviceNameAction();
public static RetrieveDeviceNameAction create() {
return INSTANCE;
}
private RetrieveDeviceNameAction() {
}
@Override
public void execute(Context context) throws Throwable {
context.getAccountHelper().refreshDeviceName();
}
}

View File

@ -4,8 +4,8 @@ import org.asamk.signal.manager.groups.GroupLinkPassword;
import org.signal.core.util.Base64;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.GroupInviteLink;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.GroupInviteLink;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import java.io.IOException;
import java.net.URI;
@ -52,8 +52,8 @@ public final class GroupInviteLinkUrl {
var bytes = Base64.decode(encoding);
GroupInviteLink groupInviteLink = GroupInviteLink.ADAPTER.decode(bytes);
if (groupInviteLink.v1Contents != null) {
var groupInviteLinkContentsV1 = groupInviteLink.v1Contents;
if (groupInviteLink.contentsV1 != null) {
var groupInviteLinkContentsV1 = groupInviteLink.contentsV1;
var groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.groupMasterKey.toByteArray());
var password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.inviteLinkPassword.toByteArray());
@ -90,7 +90,7 @@ public final class GroupInviteLinkUrl {
}
private static String createUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) {
var groupInviteLink = new GroupInviteLink.Builder().v1Contents(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey(
var groupInviteLink = new GroupInviteLink.Builder().contentsV1(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey(
ByteString.of(groupMasterKey.serialize()))
.inviteLinkPassword(ByteString.of(password.serialize()))
.build()).build();

View File

@ -522,6 +522,22 @@ public class AccountHelper {
account.setEncryptedDeviceName(encryptedDeviceName);
}
public void setDeviceName(int deviceId, String deviceName) throws IOException {
final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
handleResponseException(dependencies.getLinkDeviceApi().setDeviceName(encryptedDeviceName, deviceId));
context.getSyncHelper().sendDeviceNameChange(deviceId);
}
public void refreshDeviceName() throws IOException {
final var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
final var deviceId = account.getDeviceId();
final var device = devices.stream().filter(d -> d.id == deviceId).findFirst();
if (device.isPresent()) {
account.setEncryptedDeviceName(device.get().name);
}
}
public void updateAccountAttributes() throws IOException {
handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null)));
}

View File

@ -34,10 +34,10 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.storageservice.storage.protos.groups.GroupChangeResponse;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog;
@ -192,9 +192,9 @@ public class GroupHelper {
final GroupInfoV2 groupInfoV2,
final GroupChangeResponse groupChangeResponse
) {
if (groupChangeResponse.groupSendEndorsementsResponse.size() > 0) {
if (groupChangeResponse.group_send_endorsements_response.size() > 0) {
try {
final var groupSendEndorsementsResponse = new GroupSendEndorsementsResponse(groupChangeResponse.groupSendEndorsementsResponse.toByteArray());
final var groupSendEndorsementsResponse = new GroupSendEndorsementsResponse(groupChangeResponse.group_send_endorsements_response.toByteArray());
updateGroupEndorsements(groupInfoV2.getGroupId(),
groupInfoV2.getMasterKey(),
@ -391,7 +391,7 @@ public class GroupHelper {
.joinGroup(inviteLinkUrl.getGroupMasterKey(), inviteLinkUrl.getPassword(), groupJoinInfo);
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
groupJoinInfo.revision + 1,
changeResponse.groupChange == null ? null : changeResponse.groupChange.encode());
changeResponse.group_change == null ? null : changeResponse.group_change.encode());
if (group.getGroup() == null) {
// Only requested member, can't send update to group members
@ -873,10 +873,10 @@ public class GroupHelper {
final var groupChangeResponse = groupGroupChangePair.second();
handleGroupChangeResponse(groupInfoV2, groupChangeResponse);
if (groupChangeResponse.groupChange == null) {
if (groupChangeResponse.group_change == null) {
throw new AssertionError("groupChange is null");
}
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupChangeResponse.groupChange.encode());
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupChangeResponse.group_change.encode());
return sendGroupMessage(messageBuilder,
groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()),
groupInfoV2);
@ -924,10 +924,10 @@ public class GroupHelper {
members.addAll(group.getMembersIncludingPendingWithout(selfRecipientId));
account.getGroupStore().updateGroup(group);
if (groupChangeResponse.groupChange == null) {
if (groupChangeResponse.group_change == null) {
throw new AssertionError("groupChange is null");
}
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChangeResponse.groupChange.encode());
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChangeResponse.group_change.encode());
return sendGroupMessage(messageBuilder, members, group);
}

View File

@ -21,15 +21,15 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.storage.protos.groups.AccessControl;
import org.signal.storageservice.storage.protos.groups.GroupChange;
import org.signal.storageservice.storage.protos.groups.GroupChangeResponse;
import org.signal.storageservice.storage.protos.groups.Member;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.groupsv2.DecryptChangeVerificationMode;
@ -225,7 +225,7 @@ class GroupV2Helper {
change.modifyAvatar(new GroupChange.Actions.ModifyAvatarAction.Builder().avatar(avatarCdnKey).build());
}
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -252,7 +252,7 @@ class GroupV2Helper {
final var aci = getSelfAci();
final var change = groupOperations.createModifyGroupMembershipChange(candidates, bannedUuids, aci);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -343,7 +343,7 @@ class GroupV2Helper {
false,
groupInfoV2.getGroup().bannedMembers);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -360,7 +360,7 @@ class GroupV2Helper {
final var change = groupOperations.createUnbanServiceIdsChange(serviceIds);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -436,7 +436,7 @@ class GroupV2Helper {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
final var change = groupOperations.createUpdateProfileKeyCredentialChange(profileKeyCredential);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -459,7 +459,7 @@ class GroupV2Helper {
? groupOperations.createGroupJoinRequest(profileKeyCredential)
: groupOperations.createGroupJoinDirect(profileKeyCredential);
change.sourceServiceId(context.getRecipientHelper()
change.sourceUserId(context.getRecipientHelper()
.resolveSignalServiceAddress(selfRecipientId)
.getServiceId()
.toByteString());
@ -479,7 +479,7 @@ class GroupV2Helper {
final var change = groupOperations.createAcceptInviteChange(profileKeyCredential);
final var aci = context.getRecipientHelper().resolveSignalServiceAddress(selfRecipientId).getServiceId();
change.sourceServiceId(aci.toByteString());
change.sourceUserId(aci.toByteString());
return commitChange(groupInfoV2, change);
}
@ -585,7 +585,7 @@ class GroupV2Helper {
final var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams);
final var previousGroupState = groupInfoV2.getGroup();
final var nextRevision = previousGroupState.revision + 1;
final var changeActions = change.revision(nextRevision).build();
final var changeActions = change.version(nextRevision).build();
final DecryptedGroupChange decryptedChange;
final DecryptedGroup decryptedGroupState;
@ -611,7 +611,7 @@ class GroupV2Helper {
GroupLinkPassword password
) throws IOException {
final var nextRevision = currentRevision + 1;
final var changeActions = change.revision(nextRevision).build();
final var changeActions = change.version(nextRevision).build();
return dependencies.getGroupsV2Api()
.patchGroup(changeActions,
@ -621,6 +621,9 @@ class GroupV2Helper {
Pair<ServiceId, ProfileKey> getAuthoritativeProfileKeyFromChange(final DecryptedGroupChange change) {
UUID editor = UuidUtil.fromByteStringOrNull(change.editorServiceIdBytes);
if (editor == null) {
return null;
}
final var editorProfileKeyBytes = Stream.concat(Stream.of(change.newMembers.stream(),
change.promotePendingMembers.stream(),
change.modifiedProfileKeys.stream())

View File

@ -5,6 +5,7 @@ import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.actions.RefreshPreKeysAction;
import org.asamk.signal.manager.actions.RenewSessionAction;
import org.asamk.signal.manager.actions.ResendMessageAction;
import org.asamk.signal.manager.actions.RetrieveDeviceNameAction;
import org.asamk.signal.manager.actions.RetrieveProfileAction;
import org.asamk.signal.manager.actions.SendGroupInfoAction;
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
@ -633,6 +634,12 @@ public final class IncomingMessageHandler {
context.getAccountHelper().handlePniChangeNumberMessage(pniChangeNumber, updatedPni);
}
}
if (syncMessage.getDeviceNameChange().isPresent()) {
final var deviceNameChange = syncMessage.getDeviceNameChange().get();
if (deviceNameChange.deviceId != null && deviceNameChange.deviceId == account.getDeviceId()) {
actions.add(RetrieveDeviceNameAction.create());
}
}
return actions;
}

View File

@ -52,6 +52,8 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import okio.ByteString;
public class SyncHelper {
private static final Logger logger = LoggerFactory.getLogger(SyncHelper.class);
@ -406,6 +408,11 @@ public class SyncHelper {
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
}
public SendMessageResult sendDeviceNameChange(final int deviceId) {
final var deviceNameChange = new SyncMessage.DeviceNameChange(deviceId, ByteString.EMPTY);
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forDeviceNameChange(deviceNameChange));
}
private SendMessageResult requestSyncData(final SyncMessage.Request.Type type) {
var r = new SyncMessage.Request.Builder().type(type).build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));

View File

@ -20,7 +20,7 @@ public class JobExecutor implements AutoCloseable {
public JobExecutor(final Context context) {
this.context = context;
this.executorService = Executors.newCachedThreadPool();
this.executorService = Executors.newVirtualThreadPerTaskExecutor();
}
public void enqueueJob(Job job) {

View File

@ -157,7 +157,7 @@ public class ManagerImpl implements Manager {
private final SignalDependencies dependencies;
private final Context context;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
private Thread receiveThread;
private boolean isReceivingSynchronous;
@ -487,6 +487,21 @@ public class ManagerImpl implements Manager {
}).toList();
}
@Override
public void updateLinkedDevice(
final int deviceId,
final String name
) throws IOException, NotPrimaryDeviceException {
if (deviceId == account.getDeviceId()) {
context.getAccountHelper().setDeviceName(name);
} else {
if (!account.isPrimaryDevice()) {
throw new NotPrimaryDeviceException();
}
context.getAccountHelper().setDeviceName(deviceId, name);
}
}
private Long getPlaintextCreatedAt(DeviceInfo d) {
final var DECRYPTION_INFO = "deviceCreatedAt";
var identityKey = account.getAciIdentityKeyPair().getPrivateKey();

View File

@ -8,10 +8,10 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.EnabledState;
import org.signal.storageservice.storage.protos.groups.AccessControl;
import org.signal.storageservice.storage.protos.groups.Member;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
import org.whispersystems.signalservice.api.push.DistributionId;
import java.util.Set;

View File

@ -16,7 +16,7 @@ import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;

View File

@ -16,7 +16,7 @@ import org.signal.core.models.ServiceId;
import org.signal.core.util.Hex;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;

View File

@ -32,7 +32,7 @@ fi
run() {
# To update graalvm config, set GRAALVM_HOME, e.g:
# export GRAALVM_HOME=/usr/lib/jvm/java-21-graalvm
# export GRAALVM_HOME=/usr/lib/jvm/java-25-graalvm
if [ ! -z "$GRAALVM_HOME" ]; then
export JAVA_HOME=$GRAALVM_HOME
export SIGNAL_CLI_OPTS="-agentlib:native-image-agent=config-merge-dir=graalvm-config-dir-${SIGNAL_CLI_AGENT_ID}/"

View File

@ -1,7 +1,14 @@
dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
mavenLocal()
maven {
name = "SignalBuildArtifacts"
url = uri("https://build-artifacts.signal.org/libraries/maven/")
content {
includeGroupByRegex("org\\.signal.*")
}
}
}
}

View File

@ -143,9 +143,6 @@ public interface Signal extends DBusInterface {
String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId;
@Deprecated
boolean isRegistered() throws Error.Failure, Error.InvalidNumber;
boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber;
List<Boolean> isRegistered(List<String> numbers) throws Error.Failure, Error.InvalidNumber;

View File

@ -13,10 +13,4 @@ public enum ServiceEnvironmentCli {
return "staging";
}
},
@Deprecated SANDBOX {
@Override
public String toString() {
return "sandbox";
}
},
}

View File

@ -3,12 +3,13 @@ package org.asamk.signal.commands;
import org.asamk.signal.OutputType;
import java.util.List;
import java.util.SequencedCollection;
public interface Command {
String getName();
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT);
}
}

View File

@ -58,6 +58,7 @@ public class Commands {
addCommand(new UpdateAccountCommand());
addCommand(new UpdateConfigurationCommand());
addCommand(new UpdateContactCommand());
addCommand(new UpdateDeviceCommand());
addCommand(new UpdateGroupCommand());
addCommand(new UpdateProfileCommand());
addCommand(new UploadStickerPackCommand());

View File

@ -10,6 +10,7 @@ import org.asamk.signal.ReceiveMessageHandler;
import org.asamk.signal.Shutdown;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.dbus.DbusHandler;
import org.asamk.signal.http.HttpServerHandler;
import org.asamk.signal.json.JsonReceiveMessageHandler;
@ -31,6 +32,7 @@ import java.nio.channels.Channel;
import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.SequencedCollection;
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
@ -86,7 +88,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
@ -201,9 +203,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
&& tcpAddress == null
&& httpAddress == null
&& inheritedChannel == null) {
logger.warn(
"Running daemon command without explicit mode is deprecated. Use 'daemon --dbus' to use the dbus interface.");
daemonHandler.runDbus(false, DbusConfig.getBusname());
throw new UserErrorException("At least one channel parameter is required, e.g. --socket or --dbus.");
}
}

View File

@ -16,6 +16,7 @@ import org.asamk.signal.output.JsonWriter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonRpcRegistrationCommand<Map<String, Object>> {
@ -54,7 +55,7 @@ public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonR
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.asamk.signal.OutputType;
import java.util.List;
import java.util.SequencedCollection;
public interface JsonRpcCommand<T> extends Command {
@ -12,7 +13,7 @@ public interface JsonRpcCommand<T> extends Command {
return null;
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.JSON);
}
}

View File

@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;
import java.util.List;
import java.util.SequencedCollection;
import java.util.function.Supplier;
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
@ -53,7 +54,7 @@ public class JsonRpcDispatcherCommand implements LocalCommand, MultiLocalCommand
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.JSON);
}

View File

@ -11,6 +11,7 @@ import org.asamk.signal.output.JsonWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public interface JsonRpcLocalCommand extends JsonRpcSingleCommand<Map<String, Object>>, LocalCommand {
@ -23,7 +24,7 @@ public interface JsonRpcLocalCommand extends JsonRpcSingleCommand<Map<String, Ob
handleCommand(commandNamespace, m, jsonWriter);
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
}

View File

@ -11,6 +11,7 @@ import org.asamk.signal.output.JsonWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String, Object>>, MultiLocalCommand {
@ -27,7 +28,7 @@ public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String
handleCommand(commandNamespace, c, jsonWriter);
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
}

View File

@ -27,6 +27,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.SequencedCollection;
public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<ReceiveCommand.ReceiveParams> {
@ -60,7 +61,7 @@ public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<Receiv
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -21,6 +21,7 @@ import org.asamk.signal.util.CommandUtil;
import java.io.IOException;
import java.util.List;
import java.util.SequencedCollection;
public class RegisterCommand implements RegistrationCommand, JsonRpcRegistrationCommand<RegisterCommand.RegistrationParams> {
@ -57,7 +58,7 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -0,0 +1,50 @@
package org.asamk.signal.commands;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.output.OutputWriter;
import java.io.IOException;
public class UpdateDeviceCommand implements JsonRpcLocalCommand {
@Override
public String getName() {
return "updateDevice";
}
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.help("Update a linked device.");
subparser.addArgument("-d", "--device-id", "--deviceId")
.type(int.class)
.required(true)
.help("Specify the device you want to update. Use listDevices to see the deviceIds.");
subparser.addArgument("-n", "--device-name")
.required(true)
.help("Specify a name to describe the given device.");
}
@Override
public void handleCommand(
final Namespace ns,
final Manager m,
final OutputWriter outputWriter
) throws CommandException {
try {
final var deviceId = ns.getInt("device-id");
final var deviceName = ns.getString("device-name");
m.updateLinkedDevice(deviceId, deviceName);
} catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new IOErrorException("Error while updating device: " + e.getMessage(), e);
}
}
}

View File

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.SequencedCollection;
public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCommand<VerifyCommand.VerifyParams> {
@ -50,7 +51,7 @@ public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCo
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -16,6 +16,7 @@ import org.asamk.signal.output.PlainTextWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public class VersionCommand implements JsonRpcLocalCommand, JsonRpcMultiLocalCommand {
@ -25,7 +26,7 @@ public class VersionCommand implements JsonRpcLocalCommand, JsonRpcMultiLocalCom
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -246,6 +246,14 @@ public class DbusManagerImpl implements Manager {
}).toList();
}
@Override
public void updateLinkedDevice(
final int deviceId,
final String name
) throws IOException, NotPrimaryDeviceException {
throw new UnsupportedOperationException();
}
@Override
public void removeLinkedDevices(final int deviceId) throws IOException {
final var devicePath = signal.getDevice(deviceId);

View File

@ -675,12 +675,6 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
}
}
@Override
@Deprecated
public boolean isRegistered() {
return true;
}
@Override
public boolean isRegistered(String number) {
var result = isRegistered(List.of(number));

View File

@ -59,7 +59,7 @@ public class HttpServerHandler implements AutoCloseable {
logger.debug("Starting HTTP server on {}", address);
server = HttpServer.create(address, 0);
server.setExecutor(Executors.newCachedThreadPool());
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.createContext("/api/v1/rpc", this::handleRpcEndpoint);
server.createContext("/api/v1/events", this::handleEventsEndpoint);

View File

@ -55,7 +55,7 @@ public class JsonRpcReader {
return;
}
try (final var executor = Executors.newCachedThreadPool()) {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
while (!Thread.interrupted()) {
final var input = lineSupplier.get();
if (input == null) {
@ -91,7 +91,7 @@ public class JsonRpcReader {
case JsonRpcBatchMessage jsonRpcBatchMessage -> {
final var messages = jsonRpcBatchMessage.getMessages();
final var responseList = new ArrayList<JsonRpcResponse>(messages.size());
try (final var executor = Executors.newCachedThreadPool()) {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
final var lock = new ReentrantLock();
messages.forEach(jsonNode -> {
final JsonRpcRequest request;

View File

@ -63,7 +63,7 @@ public class SocketHandler implements AutoCloseable {
logger.debug("Starting JSON-RPC server on {}", address);
listenerThread = Thread.ofPlatform().name("daemon-listener").start(() -> {
try (final var executor = Executors.newCachedThreadPool()) {
try (final var executor = Executors.newVirtualThreadPerTaskExecutor()) {
logger.info("Started JSON-RPC server on {}", address);
while (true) {
final var connectionId = threadNumber.getAndIncrement();