From 5059925b22169c0beb45d2080d6ec9cee836592d Mon Sep 17 00:00:00 2001 From: John Freed Date: Thu, 12 Aug 2021 14:38:47 +0200 Subject: [PATCH] Revert "track attachment data (#671 and #316)" This reverts commit 8aed35799458829ba9ac07c3bc25ff07a2369802. --- .gitignore | 3 - README.md | 2 +- build.gradle.kts | 2 +- .../org/asamk/signal/manager/Manager.java | 2 +- .../signal/manager/util/AttachmentUtils.java | 34 +-- .../org/asamk/signal/manager/util/Utils.java | 8 - signal-cli.1.gz | Bin 5682 -> 0 bytes src/main/java/org/asamk/Signal.java | 57 ++--- src/main/java/org/asamk/SignalControl.java | 2 + src/main/java/org/asamk/signal/App.java | 3 +- .../signal/JsonDbusReceiveMessageHandler.java | 38 +-- .../asamk/signal/ReceiveMessageHandler.java | 3 - .../asamk/signal/commands/DaemonCommand.java | 7 +- .../asamk/signal/commands/SendCommand.java | 27 +- .../org/asamk/signal/dbus/DbusAttachment.java | 238 ------------------ .../signal/dbus/DbusSignalControlImpl.java | 1 + .../org/asamk/signal/dbus/DbusSignalImpl.java | 93 +------ .../org/asamk/signal/json/JsonAttachment.java | 47 ---- .../asamk/signal/json/JsonDataMessage.java | 2 - src/main/java/org/asamk/signal/util/Util.java | 2 - 20 files changed, 52 insertions(+), 519 deletions(-) delete mode 100644 signal-cli.1.gz delete mode 100644 src/main/java/org/asamk/signal/dbus/DbusAttachment.java diff --git a/.gitignore b/.gitignore index 536bdd41..8fa9c8bd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,3 @@ local.properties .settings/ out/ .DS_Store -.git/ -signal-cli -bin/ diff --git a/README.md b/README.md index 67f2c397..f8eee725 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # signal-cli signal-cli is a commandline interface for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering, verifying, sending and receiving messages. -To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a secondary device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). +To be able to link to an existing Signal-Android/signal-cli instance, signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because libsignal-service-java does not yet support [provisioning as a slave device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). For registering you need a phone number where you can receive SMS or incoming calls. signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a dbus interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)), that can be used to send messages from any programming language that has dbus bindings. diff --git a/build.gradle.kts b/build.gradle.kts index da2a54bf..c7de229c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { `check-lib-versions` } -version = "0.8.6" +version = "0.8.5" java { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index f7290cd7..98b02c7f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -1274,7 +1274,7 @@ public class Manager implements Closeable { ) throws IOException, AttachmentInvalidException, InvalidNumberException { final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); if (attachments != null) { - List attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); + var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); // Upload attachments here, so we only upload once even for multiple recipients var messageSender = createMessageSender(); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java index e7caadab..aadadf95 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java @@ -11,7 +11,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.net.URL; public class AttachmentUtils { @@ -22,13 +21,8 @@ public class AttachmentUtils { for (var attachment : attachments) { try { signalServiceAttachments.add(createAttachment(new File(attachment))); - } catch (IOException f) { - // no such file, send it as URL - try { - signalServiceAttachments.add(createAttachment(new URL(attachment))); - } catch (IOException e) { - throw new AttachmentInvalidException(attachment, e); - } + } catch (IOException e) { + throw new AttachmentInvalidException(attachment, e); } } } @@ -40,39 +34,25 @@ public class AttachmentUtils { return createAttachment(streamDetails, Optional.of(attachmentFile.getName())); } - public static SignalServiceAttachmentStream createAttachment(URL aURL) throws IOException { - final var streamDetails = Utils.createStreamDetailsFromURL(aURL); - String path = aURL.getPath(); - String name = path.substring(path.lastIndexOf('/') + 1); - return createAttachment(streamDetails, Optional.of(name)); - } - public static SignalServiceAttachmentStream createAttachment( StreamDetails streamDetails, Optional name ) { - // TODO maybe add a parameter to set the voiceNote, borderless, preview, width, height, caption, blurHash options + // TODO mabybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option final var uploadTimestamp = System.currentTimeMillis(); - boolean voicenote = false; - boolean borderless = false; Optional preview = Optional.absent(); - int width = 0; - int height = 0; Optional caption = Optional.absent(); Optional blurHash = Optional.absent(); final Optional resumableUploadSpec = Optional.absent(); - //ProgressListener listener = null; //Android OS - //CancellationSignal cancellationSignal = null; //Android OS; Signal developers misspelled class name - return new SignalServiceAttachmentStream(streamDetails.getStream(), streamDetails.getContentType(), streamDetails.getLength(), name, - voicenote, - borderless, + false, + false, false, preview, - width, - height, + 0, + 0, uploadTimestamp, caption, blurHash, diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index 1001a600..b0a9bbb1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -11,7 +11,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; @@ -37,13 +36,6 @@ public class Utils { return new StreamDetails(stream, mime, size); } - public static StreamDetails createStreamDetailsFromURL(URL aURL) throws IOException { - InputStream stream = aURL.openStream(); - final var mime = aURL.openConnection().getContentType(); - final var size = aURL.openConnection().getContentLengthLong(); - return new StreamDetails(stream, mime, size); - } - public static String computeSafetyNumber( boolean isUuidCapable, SignalServiceAddress ownAddress, diff --git a/signal-cli.1.gz b/signal-cli.1.gz deleted file mode 100644 index 48c52d9f4c15f134ceade13dd974718bdb7af92d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5682 zcmV-27R~7&iwFp|^bBDD19NF-ZeeUKV{BLWNSS2oYH?UXzgk>~AFdX|(Rex>oloE>y&gRKQNXjw<>Arj?5ux&>}GjS zy7d*j)$w3*bTOnM*lj%C`ef9_jh2ALVW?*oU7+uco~!OVc_U%Ro)~7rR;$!OBZl+X0$AD2nFiZnrTueWKl6DS~(aMrAt2hq>JRtBc4`m8ORd;Hh zXDR@~M7S)FY8^+oY8?lvQ|>N-kMlUnbd<-rft3!49?P(mY?j~NAt>Og1OI~$6((Y< zcQFyqwkE6iR?q_rmm~~MoB@=JfhZT>@M2^v(95kKgS2IILN`!ZUoNMWv)CxN@>7iYtwhDr_S-X zmy^K-8dielr~$f>Mslrc6C5oPHAf93diaI_4g@O&=m#7fR6hW1)C;ZBo|x9>u5*(W z%t1(`rIBRK0dj!)pw%D&)jidtN&aG!$utwU8W9#E_+h0d*(m8E_p5O8iE`qp@v~OFBU&?dGMN)m0#vflae4CLzY%6%ZQT4S`3i z*As2)uDWIeM_%8%-VPIlDu5V;Y;-vtUrxJcqvNU|#(9?HS#yTn=4ch8`Y%ukGGYNQ zAk9fasiUiIrtUI4I}F0ZcZNB+6L>mSooTg%!h^!Amo`rZ!LdSb%bJpEQj&{diPb4c z7cicg8t`1wbzmI-?1@nX+`Em_Yhd+)hcxk5tR>1$G0|5x*oDk9V5_1+RlAz(g z1;@rSBXwIq_#VvAtp+J0-;JYWj}Vvw{fiC5dY{J`3kiaw)w~fFJ~Z$*Axw%U!ENfE+|^^e%g5G?|=F0kF}Vo2$?8aA|f6Vu+l!# zx#tkh03VlWoF_)?L~-f5=!E3;*kkQlOE6!7^Cq2%k>so_v%Clc%qE(z(m2vTaX8>} z%^*2*gLVkR76=rMEEX7Kl_E@jh8gvch5J5iYV1F&H3&1%b+qrte^A5-?qBFyuhCYBU8VZ~^|O z<|B~O#IX)pw$eD2ECQGiuVEZ%Af`zq*eS)Z&%z6&f8 zkSFx5XTu<}&9Vz2hhZG8C7Tf`;49=O5QCC^oHdNwRTq3xoT{$OKyFq@M`HmV$I;*a z^0!O`ptfNQP`oJ!v55bQh`i3rrOGg~ntUd{g%cYq-sA4%ZV713SpRdi`k2Zp-n?LpWH!#|e8VGcx%c z`~b};yZdG`I%h-fB-Dx`rNa)Kv$K>S!AE#2H-_9?> z)slqLNs?T-P_V`+bSW(CsxbMbAgUp`&a8T}%Xx@D=;Rm81cIp{>GA`PW_(-QI6kJj zCpJnrgn$8s*oznbL{DuXQ5!3=)hJ-ggRn#~(4)|2ydKjaL@L1%V3S;+-9{2+9OD%@GpbCp(VJFY&3Y(~RWmeJ0= zUqmbtNL$s)@Q7(pC}~s;18};F4>&w6(>uRn@Bzg~S)*~*dteswF!B|ga;)j_Yyf#y zMba|}{^GngY^iY0S^%nU6d)B)N#j=PtU(5d)zuPM4HUeQA!LPsy^%ABzbt$R*KO4U zQGf0L-V;t;eUW&|`Tin0cE9WOPV@k_ym=F2Y(eS0p0i0SQ%jfw=ejh?RR-G10POZz zCSBF50M@G%%ZjoWZfcR7)>e3FL4Y`eB3mr6Sd<}7&El6#GRTvN$D=oc^OC~|-Hm4P zHAeMCO+ve9$<@EubGU+xTju6(3zeoZmV#o8TtxwJ86ycA(Vty!dU3kk^EcXCsz_nZ z&|78e&=pzc6x1U9TnQQLQE|vBiDEq$dDgQg)eA56~_sLEbMraLNLLVTBA7HtAQ6gHQ@zK zaB(H(EV~_)V)O@ICP3D*Ls>G4%nYXAm+d}~*S2J^zyO_4#P+a3_rusc9dDnrM_`X07i8eSoSw4?r;h0Oq*TMyMnt%>0ZJ0j_9~B!m7a`c9)> zJlxQDztktHLS5DiZL!oIph%uM;4IQb%8P8gUCRJ%fwFpWM^#F9J^5gvp^oqcmAkiI+wGw2aSTNCC zn1*UkHwlKKpQ_kk_%eb}s{v%GUM?xW;bDOd4RcJnon9|kydJOE9CSad*^sun?OctH zFR^g(L6HNx%THOJLrkWlv40qC*eoq{YO;Tx$uQ*>CX16sHKrZ=7AegzWFx|#3@6iz zKC-oYIy!pO9S^8oR({CZMMg@O7H~k#6TIbr7u^0!_<=4MRov&uhqtijm3zViWwqbh7&vx zrCC|z-I>xJqe#3WNDw8H^i4;vE_W%WLPQWSUjv#>Q8~tk)J8B<1!VBUIaZ!M0g|4H z!&&eo46nLWhJ+8bapecb1id_$D5>ZdTmMbhOMHV+l;^4{ewl3uBcI$I3**D-cY6^pX7#i7uI(-7)B;``x` zxTpHCS-Z5r9=;#G_i|XsrPR^C(iO3#{>NPs=TW^Bc5JJ4Np4oIv(T2b{d+WS(a zTU(QJ)l+P&qx7LHj}9LQaXX@PD+akiz^V;+tuiK%>nDZonB%nW^$9a_5oH>sI zUPZ;Iv^6vDkla%@PnrGW`mz{ai^jc<7^)+5C2v}-N85*f8JZw+0l6oRWJC^95tflE zGNLdx#%tyv_2V8%>@2h8;quCbIxUYN&f`?)CF`=SAW?gk!^G;d<5i*Vj-se4|4v;$ z|5gzapRi6&F6kkMn05pb!&HMdVY(Q#l+5YK;0f-q?EqwKZ3Cp1mel62nGaq#=g}s&2nR+_?M5mOI*h8y~1GZ5D0p71(?=pE&ucPJ3`CZ)IzV z>)ZacUk!ZZjf8x>3PEDdF4MdX0$!)~(uWcz#G?e>Dqe_5$=n3u@TZ(wN^C?6kfxzSeOT4zUesU>5f2e<2Lk*k~ z+Jj34(U)8ZMT;DeT6sUaXlQ^~3AO;r&<$Gy9yB^y}Gy~o#&(-^IEOGug65kE}hicwZ{0+;^Q*tZVcA_L+dN-E@=K z7L2xlbV~PWGzquBQh_Tlks~IqV1LCj_Tp39zsFgq_v#6yT)f0t$Wr1cd2M*+54mjY zDcPZ=yKRp8s=JVDjbi1Nzv`b2PrvUrq430ZAaZ6KKl+EG%V~Ey_-`2QER|5Oz z+kxC)HE#j)3l-0sVM6!S@YFm07+>YF{uV9FhvEe1ODx>xg(^R9k!oy5nPeK$?{qA= z1Y%z=DrB5HRtOKnGr{1)RGE3RJ1Ad9Q_YA=Y7ocj1(v9ZfUe8VL7<~JH9%-~xp~au zA$@YK3_FiN^BmP|uxcAm2-UKQgNp<`+scbg9IN@Bin_Vk!v>6bsLbBe7yJ89559i- z;`zaouBDLqmBrv~s@{`Dv_!h<3T$=ObB7{zERN4l&?WzmnvLCiAX~jzinIF>oB`F` zpWnv?JkiZNlXrTZubB)6vNKn`wv=$@9A=43Nmtz2{y=!8Z!&y^I*Umyx-Q;LEL_w}jZvbQY@Zmty<3lj-p2&EVqgxPSEK z?Q}Rj9sGW~gH!lEr#Yv8Iek64u#@K43j}k17JGZb_1Ow~+VQKZSy7oSWPxv>@t&8a zZ{cgY+ejDMlB^89i0M?PBZ}rd-ki3NGBBajO~R|(t5(GXQvBKwjQp99cRYmgA87i| zTl}{0BhmN>MD9lgkfHxWvti5CAUP(hltmB}hl&=a(Uy^VDT)9D*wl;#m6fPiQVF+EiSDC=8393m@ zLCNn1leF%Z-s;FaGl*|e4ZiE2jZX*mH7c*O&fPu~O?7@V+pfhD??lQbczpR|CwTwW z!IOh0&!0cX5By!+Y!pnnZ<}V*-luIOxr!BTt{ghR>eA_|4bnFgzuxyF{Yu(U3I8-fsFrfRVRRY47;^F2>nGa2*5VQb2^0>) z8Kj$xAaBKs>@Iu6n8qO14@e-@9&>`T7vRO2)b6c!I85Kj^&9b92_M&gSg!ASf%-kx z3u3Yv1~Xiq$bL0WfH5Y{(>Xg{x{Zue`{x%Z{HWafv0{{3VBaS|&)%VSYcI=P0$cJK Y9ALdiSb1cm`~R)~1$0x}FF8p70LSR$kpKVy diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 8eff0510..f44b615c 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -4,7 +4,6 @@ import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.interfaces.DBusInterface; import org.freedesktop.dbus.messages.DBusSignal; -import org.asamk.signal.dbus.DbusAttachment; import java.util.ArrayList; import java.util.List; @@ -16,21 +15,12 @@ import java.util.Map; */ public interface Signal extends DBusInterface { - - long sendMessageWithDBusAttachments( - String message, List dBusAttachments, String recipient - ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; - - long sendMessageWithDBusAttachments( - String message, List dBusAttachments, List recipients + long sendMessage( + String message, List attachments, String recipient ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendMessage( - String message, List attachmentNames, String recipient - ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; - - long sendMessage( - String message, List attachmentNames, List recipients + String message, List attachments, List recipients ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendRemoteDeleteMessage( @@ -54,21 +44,13 @@ public interface Signal extends DBusInterface { ) throws Error.InvalidNumber, Error.Failure; long sendNoteToSelfMessage( - String message, List attachmentNames - ) throws Error.AttachmentInvalid, Error.Failure; - - long sendNoteToSelfMessageWithDBusAttachments( - String message, List dBusAttachments + String message, List attachments ) throws Error.AttachmentInvalid, Error.Failure; void sendEndSessionMessage(List recipients) throws Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendGroupMessage( - String message, List attachmentNames, byte[] groupId - ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid; - - long sendGroupMessageWithDBusAttachments( - String message, List dBusAttachments, byte[] groupId + String message, List attachments, byte[] groupId ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid; long sendGroupMessageReaction( @@ -79,8 +61,6 @@ public interface Signal extends DBusInterface { void setContactName(String number, String name) throws Error.InvalidNumber; - void setExpirationTimer(final String number, final int expiration) throws Error.InvalidNumber; - void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber; void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound; @@ -133,7 +113,7 @@ public interface Signal extends DBusInterface { private final String sender; private final byte[] groupId; private final String message; - private final List dBusAttachments; + private final List attachments; public MessageReceived( String objectpath, @@ -141,14 +121,14 @@ public interface Signal extends DBusInterface { String sender, byte[] groupId, String message, - List dBusAttachments + List attachments ) throws DBusException { - super(objectpath, timestamp, sender, groupId, message, dBusAttachments); + super(objectpath, timestamp, sender, groupId, message, attachments); this.timestamp = timestamp; this.sender = sender; this.groupId = groupId; this.message = message; - this.dBusAttachments = dBusAttachments; + this.attachments = attachments; } public long getTimestamp() { @@ -167,10 +147,9 @@ public interface Signal extends DBusInterface { return message; } - public List getAttachments() { - return dBusAttachments; - } - + public List getAttachments() { + return attachments; + } } class ReceiptReceived extends DBusSignal { @@ -200,7 +179,7 @@ public interface Signal extends DBusInterface { private final String destination; private final byte[] groupId; private final String message; - private final List dBusAttachments; + private final List attachments; public SyncMessageReceived( String objectpath, @@ -209,15 +188,15 @@ public interface Signal extends DBusInterface { String destination, byte[] groupId, String message, - List dBusAttachments + List attachments ) throws DBusException { - super(objectpath, timestamp, source, destination, groupId, message, dBusAttachments); + super(objectpath, timestamp, source, destination, groupId, message, attachments); this.timestamp = timestamp; this.source = source; this.destination = destination; this.groupId = groupId; this.message = message; - this.dBusAttachments = dBusAttachments; + this.attachments = attachments; } public long getTimestamp() { @@ -240,8 +219,8 @@ public interface Signal extends DBusInterface { return message; } - public List getAttachments() { - return dBusAttachments; + public List getAttachments() { + return attachments; } } diff --git a/src/main/java/org/asamk/SignalControl.java b/src/main/java/org/asamk/SignalControl.java index e4636171..e6c86536 100644 --- a/src/main/java/org/asamk/SignalControl.java +++ b/src/main/java/org/asamk/SignalControl.java @@ -28,6 +28,8 @@ public interface SignalControl extends DBusInterface { String version(); + List listAccounts(); + String getObjectPath(); interface Error { diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index e7afe13b..49172f80 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -163,7 +163,7 @@ public class App { username = usernames.get(0); } else if (!PhoneNumberFormatter.isValidNumber(username, null)) { - throw new UserErrorException("Invalid username (phone number), make sure you include a plus sign (+) followed by the country code."); + throw new UserErrorException("Invalid username (phone number), make sure you include the country code."); } if (command instanceof RegistrationCommand) { @@ -264,7 +264,6 @@ public class App { try { manager = Manager.init(username, dataPath, serviceEnvironment, BaseConfig.USER_AGENT); } catch (NotRegisteredException e) { - logger.debug("dataPath=" + dataPath + " serviceEnvironment=" + serviceEnvironment, e); throw new UserErrorException("User " + username + " is not registered."); } catch (Throwable e) { logger.debug("Loading state file failed", e); diff --git a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java index 5f7eb940..5ed6af00 100644 --- a/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/JsonDbusReceiveMessageHandler.java @@ -1,7 +1,6 @@ package org.asamk.signal; import org.asamk.Signal; -import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupUtils; import org.freedesktop.dbus.connections.impl.DBusConnection; @@ -10,7 +9,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import java.util.ArrayList; import java.util.List; @@ -73,15 +71,12 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { || message.getGroupContext().get().getGroupV1Type() == SignalServiceGroup.Type.DELIVER )) { try { - List attachments = JsonDbusReceiveMessageHandler.getAttachments(message); - List dBusAttachments = JsonDbusReceiveMessageHandler.convertSignalAttachmentsToDbus(attachments); conn.sendMessage(new Signal.MessageReceived(objectPath, message.getTimestamp(), getLegacyIdentifier(sender), groupId != null ? groupId : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", - dBusAttachments - )); + JsonDbusReceiveMessageHandler.getAttachments(message, m))); } catch (DBusException e) { e.printStackTrace(); } @@ -98,8 +93,6 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { var groupId = getGroupId(message); try { - List attachments = JsonDbusReceiveMessageHandler.getAttachments(message); - List dBusAttachments = JsonDbusReceiveMessageHandler.convertSignalAttachmentsToDbus(attachments); conn.sendMessage(new Signal.SyncMessageReceived(objectPath, transcript.getTimestamp(), getLegacyIdentifier(sender), @@ -108,8 +101,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { : "", groupId != null ? groupId : new byte[0], message.getBody().isPresent() ? message.getBody().get() : "", - dBusAttachments - )); + JsonDbusReceiveMessageHandler.getAttachments(message, m))); } catch (DBusException e) { e.printStackTrace(); } @@ -124,7 +116,7 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { .serialize() : null; } - static private List getAttachmentNames(SignalServiceDataMessage message, Manager m) { + static private List getAttachments(SignalServiceDataMessage message, Manager m) { var attachments = new ArrayList(); if (message.getAttachments().isPresent()) { for (var attachment : message.getAttachments().get()) { @@ -136,34 +128,10 @@ public class JsonDbusReceiveMessageHandler extends JsonReceiveMessageHandler { return attachments; } - - static private List getAttachments(SignalServiceDataMessage message) { - var attachments = new ArrayList(); - if (message.getAttachments().isPresent()) { - for (var attachment : message.getAttachments().get()) { - if (attachment.isPointer()) { - attachments.add(attachment); - } - } - } - return attachments; - } - @Override public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) { super.handleMessage(envelope, content, exception); sendReceivedMessageToDbus(envelope, content, conn, objectPath, m); } - - static private List convertSignalAttachmentsToDbus(List attachments) { - ArrayList dBusAttachments = new ArrayList<>(); - if (!attachments.isEmpty()) { - for (SignalServiceAttachment attachment : attachments) { - DbusAttachment dBusAttachment = new DbusAttachment(attachment); - dBusAttachments.add(dBusAttachment); - } - } - return dBusAttachments; - } } diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index 56496973..323b6edf 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -646,9 +646,6 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (pointer.getCaption().isPresent()) { writer.println("Caption: {}", pointer.getCaption().get()); } - if (pointer.getBlurHash().isPresent()) { - writer.println("Blur Hash: {}", pointer.getBlurHash().get()); - } if (pointer.getFileName().isPresent()) { writer.println("Filename: {}", pointer.getFileName().get()); } diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index ffd20d9e..7b6f243d 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -65,12 +65,7 @@ public class DaemonCommand implements MultiLocalCommand { var objectPath = DbusConfig.getObjectPath(); var t = run(conn, objectPath, m, ignoreAttachments); - try { - conn.requestBusName(DbusConfig.getBusname()); - } catch (DBusException e) { - logger.error("Dbus request command failed", e); - throw new UnexpectedErrorException("Dbus request command failed"); - } + conn.requestBusName(DbusConfig.getBusname()); try { t.join(); diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index b6ab0058..8459118c 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -12,7 +12,6 @@ import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; -import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.dbus.DbusSignalImpl; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.groups.GroupIdFormatException; @@ -22,11 +21,9 @@ import org.freedesktop.dbus.errors.UnknownObject; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -94,17 +91,9 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { } } - List attachmentNames = ns.getList("attachment"); - if (attachmentNames == null) { - attachmentNames = List.of(); - } - - ArrayList dBusAttachments = new ArrayList<>(); - if (!attachmentNames.isEmpty()) { - for (var attachmentName : attachmentNames) { - DbusAttachment dBusAttachment = new DbusAttachment(attachmentName); - dBusAttachments.add(dBusAttachment); - } + List attachments = ns.getList("attachment"); + if (attachments == null) { + attachments = List.of(); } if (groupIdString != null) { @@ -116,7 +105,7 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { } try { - var timestamp = signal.sendGroupMessage(messageText, attachmentNames, groupId); + var timestamp = signal.sendGroupMessage(messageText, attachments, groupId); outputResult(timestamp); return; } catch (DBusExecutionException e) { @@ -126,25 +115,25 @@ public class SendCommand implements DbusCommand, JsonRpcLocalCommand { if (isNoteToSelf) { try { - var timestamp = signal.sendNoteToSelfMessage(messageText, attachmentNames); + var timestamp = signal.sendNoteToSelfMessage(messageText, attachments); outputResult(timestamp); return; } catch (Signal.Error.UntrustedIdentity e) { - throw new UntrustedKeyErrorException("Failed to send note to self message: " + e.getMessage()); + throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage()); } catch (DBusExecutionException e) { throw new UnexpectedErrorException("Failed to send note to self message: " + e.getMessage()); } } try { - var timestamp = signal.sendMessageWithDBusAttachments(messageText, dBusAttachments, recipients); + var timestamp = signal.sendMessage(messageText, attachments, recipients); outputResult(timestamp); } catch (UnknownObject e) { throw new UserErrorException("Failed to find dbus object, maybe missing the -u flag: " + e.getMessage()); } catch (Signal.Error.UntrustedIdentity e) { throw new UntrustedKeyErrorException("Failed to send message: " + e.getMessage()); } catch (DBusExecutionException e) { - throw new UnexpectedErrorException("Failed to send message, did not find attachment: " + e.getMessage()); + throw new UnexpectedErrorException("Failed to send message: " + e.getMessage()); } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusAttachment.java b/src/main/java/org/asamk/signal/dbus/DbusAttachment.java deleted file mode 100644 index b5060339..00000000 --- a/src/main/java/org/asamk/signal/dbus/DbusAttachment.java +++ /dev/null @@ -1,238 +0,0 @@ -package org.asamk.signal.dbus; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; -import org.whispersystems.signalservice.api.util.StreamDetails; -import org.freedesktop.dbus.exceptions.DBusException; -import org.freedesktop.dbus.annotations.Position; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; - -import org.asamk.signal.manager.util.Utils; -import org.freedesktop.dbus.Struct; - -public final class DbusAttachment extends Struct -{ - @Position(0) - private String contentType; - @Position(1) - private String fileName; - @Position(2) - private String id; - @Position(3) - private Long size; - @Position(4) - private Integer keyLength; - @Position(5) - private boolean voiceNote; - @Position(6) - private Integer width; - @Position(7) - private Integer height; - @Position(8) - private String caption; - @Position(9) - private String blurHash; - -/* - * API = 2.15.3 from https://github.com/Turasa/libsignal-service-java (nonstandard) - public SignalServiceAttachmentStream(InputStream inputStream, - String contentType, - long length, - Optional fileName, - boolean voiceNote, - boolean borderless, - boolean gif, //nonstandard - Optional preview, - int width, - int height, - long uploadTimestamp, - Optional caption, - Optional blurHash, - ProgressListener listener, //Android OS - CancellationSignal cancellationSignal, //Android OS, Signal developers misspelled class name - Optional resumableUploadSpec) - - - public SignalServiceAttachmentPointer(int cdnNumber, - SignalServiceAttachmentRemoteId remoteId, - String contentType, - byte[] key, - Optional size, - Optional preview, - int width, - int height, - Optional digest, - Optional fileName, - boolean voiceNote, - boolean borderless, - Optional caption, - Optional blurHash, - long uploadTimestamp) - -other stuff : - private long id; // used by v2 attachments, see note - private int keyLength; //TODO: if you're going to do that, probably should have previewLength and digestLength - -notes : -"size" appears to be the same as "length" but is int rather than long -"length" represents file size (or stream/attachment size) -"preview" is also known as "thumbnail" - -from SignalServiceAttachmentRemoteId.java : - * Represents a signal service attachment identifier. This can be either a CDN key or a long, but - * not both at once. Attachments V2 used a long as an attachment identifier. This lacks sufficient - * entropy to reduce the likelihood of any two uploads going to the same location within a 30-day - * window. Attachments V3 uses an opaque string as an attachment identifier which provides more - * flexibility in the amount of entropy present. - - */ - - public DbusAttachment(SignalServiceAttachment attachment) { - this.contentType = attachment.getContentType(); - - if (attachment.isPointer()) { - final var pointer = attachment.asPointer(); - this.id = pointer.getRemoteId().toString(); - this.fileName = pointer.getFileName().orNull(); - if (this.fileName == null) { - this.fileName = ""; - } - this.size = pointer.getSize().transform(Integer::longValue).orNull(); - if (this.size == null) { - this.size = 0L; - } - this.setKeyLength(pointer.getKey().length); - this.setWidth(pointer.getWidth()); - this.setHeight(pointer.getHeight()); - this.setVoiceNote(pointer.getVoiceNote()); - if (pointer.getCaption().isPresent()) { - this.setCaption(pointer.getCaption().get()); - } else { - this.setCaption(""); - } - this.setBlurHash(""); - } else { - final var stream = attachment.asStream(); - this.fileName = stream.getFileName().orNull(); - if (this.fileName == null) { - this.fileName = ""; - } - this.id = ""; - this.size = stream.getLength(); - this.setKeyLength(0); - this.setWidth(0); - this.setHeight(0); - this.setVoiceNote(false); - this.setCaption(""); - this.setBlurHash(""); - } - } - - public DbusAttachment(String fileName) { - this.contentType = "application/octet-stream"; - try { - final File file = new File(fileName); - this.contentType = Utils.getFileMimeType(file, "application/octet-stream"); - this.size = file.length(); - } catch (IOException e) { - //no such file, try URL - try { - final URL aURL = new URL(fileName); - this.contentType = aURL.openConnection().getContentType(); - this.size = aURL.openConnection().getContentLengthLong(); - } catch (IOException f) { - f.printStackTrace(); - } - } - this.fileName = fileName; - this.id = ""; - this.setKeyLength(0); - this.setWidth(0); - this.setHeight(0); - this.setVoiceNote(false); - this.setCaption(""); - this.setBlurHash(""); - } - - public String getContentType() { - return contentType; - } - public void setContentType(String contentType) { - this.contentType = contentType; - } - - public String getId() { - return id; - } - public void setId(String id) { - this.id = id; - } - - public String getFileName() { - return fileName; - } - public void setFileName(String fileName) { - this.fileName = fileName; - } - - public Long getFileSize() { - return size; - } - public void setFileSize(Long size) { - this.size = size; - } - - public Integer getKeyLength() { - return keyLength; - } - public void setKeyLength(Integer keyLength) { - this.keyLength = keyLength; - } - - public Integer getWidth() { - return width; - } - public void setWidth(Integer width) { - this.width = width; - } - - public Integer getHeight() { - return height; - } - public void setHeight(Integer height) { - this.height = height; - } - - public boolean isVoiceNote() { - return voiceNote; - } - public boolean getVoiceNote() { - return voiceNote; - } - public void setVoiceNote(boolean voiceNote) { - this.voiceNote = voiceNote; - } - - public String getCaption() { - return caption; - } - public void setCaption(String caption) { - this.caption = caption; - } - - public String getBlurHash() { - return blurHash; - } - public void setBlurHash(String blurHash) { - this.blurHash = blurHash; - } - -} diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index ff6bfc09..35f530b0 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -150,6 +150,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { return BaseConfig.PROJECT_VERSION; } + @Override public List listAccounts() { synchronized (receiveThreads) { return receiveThreads.stream() diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 180610e9..0f994b10 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -7,7 +7,6 @@ import org.asamk.signal.PlainTextWriter; import org.asamk.signal.PlainTextWriterImpl; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; -import org.asamk.signal.dbus.DbusAttachment; import org.asamk.signal.manager.AttachmentInvalidException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.NotMasterDeviceException; @@ -112,36 +111,10 @@ public class DbusSignalImpl implements Signal { return results; } @Override - public long sendMessageWithDBusAttachments(final String message, final List dBusAttachments, final String recipient) { + public long sendMessage(final String message, final List attachments, final String recipient) { var recipients = new ArrayList(1); recipients.add(recipient); - return sendMessageWithDBusAttachments(message, dBusAttachments, recipients); - } - - @Override - public long sendMessageWithDBusAttachments(final String message, final List dBusAttachments, final List recipients) { - try { - ArrayList attachmentNames = new ArrayList<>(); - for (var dBusAttachment : dBusAttachments) { - attachmentNames.add(dBusAttachment.getFileName()); - } - final var results = m.sendMessage(message, attachmentNames, recipients); - checkSendMessageResults(results.first(), results.second()); - return results.first(); - } catch (InvalidNumberException e) { - throw new Error.InvalidNumber(e.getMessage()); - } catch (AttachmentInvalidException e) { - throw new Error.AttachmentInvalid(e.getMessage()); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); - } - } - - @Override - public long sendMessage(final String message, final List attachmentNames, final String recipient) { - var recipients = new ArrayList(1); - recipients.add(recipient); - return sendMessage(message, attachmentNames, recipients); + return sendMessage(message, attachments, recipients); } private static void checkSendMessageResult(long timestamp, SendMessageResult result) throws DBusExecutionException { @@ -184,9 +157,9 @@ public class DbusSignalImpl implements Signal { } @Override - public long sendMessage(final String message, final List attachmentNames, final List recipients) { + public long sendMessage(final String message, final List attachments, final List recipients) { try { - final var results = m.sendMessage(message, attachmentNames, recipients); + final var results = m.sendMessage(message, attachments, recipients); checkSendMessageResults(results.first(), results.second()); return results.first(); } catch (InvalidNumberException e) { @@ -271,10 +244,10 @@ public class DbusSignalImpl implements Signal { @Override public long sendNoteToSelfMessage( - final String message, final List attachmentNames + final String message, final List attachments ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity { try { - final var results = m.sendSelfMessage(message, attachmentNames); + final var results = m.sendSelfMessage(message, attachments); checkSendMessageResult(results.first(), results.second()); return results.first(); } catch (AttachmentInvalidException e) { @@ -284,25 +257,6 @@ public class DbusSignalImpl implements Signal { } } - @Override - public long sendNoteToSelfMessageWithDBusAttachments( - final String message, final List dBusAttachments - ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity { - try { - ArrayList attachmentNames = new ArrayList<>(); - for (var dBusAttachment : dBusAttachments) { - attachmentNames.add(dBusAttachment.getFileName()); - } - final var results = m.sendSelfMessage(message, attachmentNames); - checkSendMessageResult(results.first(), results.second()); - return results.first(); - } catch (AttachmentInvalidException e) { - throw new Error.AttachmentInvalid(e.getMessage()); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); - } - } - @Override public void sendEndSessionMessage(final List recipients) { try { @@ -316,9 +270,9 @@ public class DbusSignalImpl implements Signal { } @Override - public long sendGroupMessage(final String message, final List attachmentNames, final byte[] groupId) { + public long sendGroupMessage(final String message, final List attachments, final byte[] groupId) { try { - var results = m.sendGroupMessage(message, attachmentNames, GroupId.unknownVersion(groupId)); + var results = m.sendGroupMessage(message, attachments, GroupId.unknownVersion(groupId)); checkSendMessageResults(results.first(), results.second()); return results.first(); } catch (IOException e) { @@ -330,26 +284,6 @@ public class DbusSignalImpl implements Signal { } } - @Override - public long sendGroupMessageWithDBusAttachments(final String message, final List dBusAttachments, final byte[] groupId) { - try { - ArrayList attachmentNames = new ArrayList<>(); - for (var dBusAttachment : dBusAttachments) { - attachmentNames.add(dBusAttachment.getFileName()); - } - var results = m.sendGroupMessage(message, attachmentNames, GroupId.unknownVersion(groupId)); - checkSendMessageResults(results.first(), results.second()); - return results.first(); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); - } catch (GroupNotFoundException | NotAGroupMemberException e) { - throw new Error.GroupNotFound(e.getMessage()); - } catch (AttachmentInvalidException e) { - throw new Error.AttachmentInvalid(e.getMessage()); - } - } - - @Override public long sendGroupMessageReaction( final String emoji, @@ -397,17 +331,6 @@ public class DbusSignalImpl implements Signal { } } - @Override - public void setExpirationTimer(final String number, final int expiration) { - try { - m.setExpirationTimer(number, expiration); - } catch (IOException e) { - throw new Error.Failure(e.getMessage()); - } catch (InvalidNumberException e) { - throw new Error.InvalidNumber(e.getMessage()); - } - } - @Override public void setContactBlocked(final String number, final boolean blocked) { try { diff --git a/src/main/java/org/asamk/signal/json/JsonAttachment.java b/src/main/java/org/asamk/signal/json/JsonAttachment.java index 3fd2505d..a96fc534 100644 --- a/src/main/java/org/asamk/signal/json/JsonAttachment.java +++ b/src/main/java/org/asamk/signal/json/JsonAttachment.java @@ -4,8 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; -import org.asamk.signal.dbus.DbusAttachment; - class JsonAttachment { @JsonProperty @@ -20,33 +18,6 @@ class JsonAttachment { @JsonProperty final Long size; - @JsonProperty - Integer keyLength; - - @JsonProperty - Integer width; - - @JsonProperty - Integer height; - - @JsonProperty - boolean voiceNote; - - @JsonProperty - String caption; - - @JsonProperty - String relay; - - @JsonProperty - byte[] preview; - - @JsonProperty - String digest; - - @JsonProperty - String blurHash; - JsonAttachment(SignalServiceAttachment attachment) { this.contentType = attachment.getContentType(); @@ -55,11 +26,6 @@ class JsonAttachment { this.id = pointer.getRemoteId().toString(); this.filename = pointer.getFileName().orNull(); this.size = pointer.getSize().transform(Integer::longValue).orNull(); - this.keyLength = pointer.getKey().length; - this.width = pointer.getWidth(); - this.height = pointer.getHeight(); - this.voiceNote = pointer.getVoiceNote(); - if (pointer.getCaption().isPresent()) {this.caption = pointer.getCaption().get();} } else { final var stream = attachment.asStream(); this.id = null; @@ -74,17 +40,4 @@ class JsonAttachment { this.id = null; this.size = null; } - - JsonAttachment(DbusAttachment attachment) { - this.contentType = attachment.getContentType(); - this.id = attachment.getId(); - this.filename = attachment.getFileName(); - this.size = attachment.getFileSize(); - this.keyLength = attachment.getKeyLength(); - this.width = attachment.getWidth(); - this.height = attachment.getHeight(); - this.voiceNote = attachment.getVoiceNote(); - this.caption = attachment.getCaption(); - } - } diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index f2c702f8..6dbda978 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.asamk.Signal; import org.asamk.signal.manager.Manager; -import org.asamk.signal.dbus.DbusAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import java.util.List; @@ -127,7 +126,6 @@ class JsonDataMessage { sticker = null; contacts = null; attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList()); - } public JsonDataMessage(Signal.SyncMessageReceived messageReceived) { diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index ced6f9f2..31c6b68e 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -7,9 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupIdFormatException; -import org.asamk.signal.dbus.DbusAttachment; import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Arrays;