1 /* 2 * Copyright (C) 2021 Finn Herzfeld 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package io.finn.signald.clientprotocol.v1; 19 20 import static io.finn.signald.annotations.ExactlyOneOfRequired.RECIPIENT; 21 22 import io.finn.signald.JsonAttachment; 23 import io.finn.signald.Manager; 24 import io.finn.signald.annotations.*; 25 import io.finn.signald.clientprotocol.Request; 26 import io.finn.signald.clientprotocol.RequestType; 27 import io.finn.signald.clientprotocol.v1.exceptions.*; 28 import io.finn.signald.clientprotocol.v1.exceptions.InternalError; 29 import io.finn.signald.db.Recipient; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.nio.file.Files; 35 import java.sql.SQLException; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.stream.Collectors; 39 import org.whispersystems.libsignal.util.guava.Optional; 40 import org.whispersystems.signalservice.api.messages.SendMessageResult; 41 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; 42 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; 43 import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; 44 45 @ProtocolType("send") 46 public class SendRequest implements RequestType<SendResponse> { 47 @ExampleValue(ExampleValue.LOCAL_PHONE_NUMBER) @Required public String username; 48 @ExactlyOneOfRequired(RECIPIENT) public JsonAddress recipientAddress; 49 @ExampleValue(ExampleValue.GROUP_ID) @ExactlyOneOfRequired(RECIPIENT) public String recipientGroupId; 50 @ExampleValue(ExampleValue.MESSAGE_BODY) @AtLeastOneOfRequired({"attachments"}) public String messageBody; 51 @AtLeastOneOfRequired({"messageBody"}) public List<JsonAttachment> attachments; 52 public JsonQuote quote; 53 public Long timestamp; 54 public List<JsonMention> mentions; 55 public List<JsonPreview> previews; 56 57 @Override run(Request request)58 public SendResponse run(Request request) 59 throws NoSuchAccountError, ServerNotFoundError, InvalidProxyError, NoSendPermissionError, InvalidAttachmentError, InternalError, UnknownGroupError, InvalidRecipientError { 60 Manager manager = Common.getManager(username); 61 62 SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder(); 63 64 Recipient recipient = null; 65 if (recipientAddress != null) { 66 try { 67 recipient = manager.getRecipientsTable().get(recipientAddress); 68 } catch (IOException | SQLException e) { 69 throw new InternalError("error looking up recipient", e); 70 } 71 } 72 73 if (messageBody != null) { 74 messageBuilder = messageBuilder.withBody(messageBody); 75 } 76 77 if (attachments != null) { 78 List<SignalServiceAttachment> signalServiceAttachments = new ArrayList<>(attachments.size()); 79 for (JsonAttachment attachment : attachments) { 80 try { 81 File attachmentFile = new File(attachment.filename); 82 InputStream attachmentStream = new FileInputStream(attachmentFile); 83 final long attachmentSize = attachmentFile.length(); 84 if (attachment.contentType == null) { 85 attachment.contentType = Files.probeContentType(attachmentFile.toPath()); 86 if (attachment.contentType == null) { 87 attachment.contentType = "application/octet-stream"; 88 } 89 } 90 String customFilename = attachmentFile.getName(); 91 if (attachment.customFilename != null) { 92 customFilename = attachment.customFilename; 93 } 94 signalServiceAttachments.add(new SignalServiceAttachmentStream( 95 attachmentStream, attachment.contentType, attachmentSize, Optional.of(customFilename), attachment.voiceNote, false, false, attachment.getPreview(), attachment.width, 96 attachment.height, System.currentTimeMillis(), Optional.fromNullable(attachment.caption), Optional.fromNullable(attachment.blurhash), null, null, Optional.absent())); 97 } catch (IOException e) { 98 throw new InvalidAttachmentError(attachment.filename, e); 99 } 100 } 101 messageBuilder.withAttachments(signalServiceAttachments); 102 } 103 104 if (quote != null) { 105 messageBuilder.withQuote(quote.getQuote()); 106 } 107 108 if (timestamp == null) { 109 timestamp = System.currentTimeMillis(); 110 } 111 messageBuilder.withTimestamp(timestamp); 112 113 if (mentions != null && mentions.size() > 0) { 114 messageBuilder.withMentions(mentions.stream().map(JsonMention::asMention).collect(Collectors.toList())); 115 } 116 117 if (previews != null) { 118 List<SignalServiceDataMessage.Preview> signalPreviews = new ArrayList<>(); 119 for (JsonPreview preview : previews) { 120 signalPreviews.add(preview.asSignalPreview()); 121 } 122 messageBuilder.withPreviews(signalPreviews); 123 } 124 125 List<SendMessageResult> results; 126 127 results = Common.send(manager, messageBuilder, recipient, recipientGroupId); 128 return new SendResponse(results, timestamp); 129 } 130 } 131