Files
ma1sd/src/main/java/io/kamax/matrix/client/MatrixHttpRoom.java

441 lines
14 KiB
Java

/*
* matrix-java-sdk - Matrix Client SDK for Java
* Copyright (C) 2017 Kamax Sarl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.matrix.client;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.kamax.matrix.MatrixErrorInfo;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixContent;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix._MatrixUserProfile;
import io.kamax.matrix.hs._MatrixRoom;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.RoomMessageChunkResponseJson;
import io.kamax.matrix.json.RoomMessageFormattedTextPutBody;
import io.kamax.matrix.json.RoomMessageTextPutBody;
import io.kamax.matrix.json.RoomTagSetBody;
import io.kamax.matrix.json.event.MatrixJsonPersistentEvent;
import io.kamax.matrix.room.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import okhttp3.HttpUrl;
import okhttp3.Request;
public class MatrixHttpRoom extends AMatrixHttpClient implements _MatrixRoom {
private Logger log = LoggerFactory.getLogger(MatrixHttpRoom.class);
private String roomId;
public MatrixHttpRoom(MatrixClientContext context, String roomId) {
super(context);
this.roomId = roomId;
}
@Override
public String getAddress() {
return roomId;
}
@Override
public Optional<String> getName() {
return getState("m.room.name").flatMap(obj -> GsonUtil.findString(obj, "name"));
}
@Override
public Optional<String> getTopic() {
return getState("m.room.topic").flatMap(obj -> GsonUtil.findString(obj, "topic"));
}
@Override
public Optional<String> getAvatarUrl() {
return getState("m.room.avatar").flatMap(obj -> GsonUtil.findString(obj, "url"));
}
@Override
public Optional<_MatrixContent> getAvatar() {
return getAvatarUrl().flatMap(url -> {
try {
return Optional.of(new MatrixHttpContent(context, new URI(url)));
} catch (URISyntaxException e) {
log.debug("{} is not a valid URI for avatar, returning empty", url);
return Optional.empty();
}
});
}
@Override
public String getId() {
return roomId;
}
@Override
public Optional<JsonObject> getState(String type) {
URL path = getClientPath("rooms", getAddress(), "state", type);
MatrixHttpRequest request = new MatrixHttpRequest(new Request.Builder().get().url(path));
request.addIgnoredErrorCode(404);
String body = executeAuthenticated(request);
if (StringUtils.isBlank(body)) {
return Optional.empty();
}
return Optional.of(GsonUtil.parseObj(body));
}
@Override
public Optional<JsonObject> getState(String type, String key) {
URL path = getClientPath("rooms", roomId, "state", type, key);
MatrixHttpRequest request = new MatrixHttpRequest(new Request.Builder().get().url(path));
request.addIgnoredErrorCode(404);
String body = executeAuthenticated(request);
if (StringUtils.isBlank(body)) {
return Optional.empty();
}
return Optional.of(GsonUtil.parseObj(body));
}
@Override
public void join() {
join(Collections.emptyList());
}
@Override
public void join(List<String> servers) {
HttpUrl.Builder b = getClientPathBuilder("rooms", roomId, "join");
servers.forEach(server -> b.addQueryParameter("server_name", server));
executeAuthenticated(new Request.Builder().post(getJsonBody(new JsonObject())).url(b.build()));
}
@Override
public Optional<MatrixErrorInfo> tryJoin() {
return tryJoin(Collections.emptyList());
}
@Override
public Optional<MatrixErrorInfo> tryJoin(List<String> servers) {
try {
join(servers);
return Optional.empty();
} catch (MatrixClientRequestException e) {
return e.getError();
}
}
@Override
public void leave() {
URL path = getClientPath("rooms", roomId, "leave");
MatrixHttpRequest request = new MatrixHttpRequest(
new Request.Builder().post(getJsonBody(new JsonObject())).url(path));
// TODO Find a better way to handle room objects for unknown rooms
// Maybe throw exception?
// TODO implement method to check room existence - isValid() ?
// if (res.getStatusLine().getStatusCode() == 404) {
// log.warn("Room {} is not joined, ignoring call", roomId);
// return;
// }
request.addIgnoredErrorCode(404);
executeAuthenticated(request);
}
@Override
public Optional<MatrixErrorInfo> tryLeave() {
try {
leave();
return Optional.empty();
} catch (MatrixClientRequestException e) {
return e.getError();
}
}
@Override
public void kick(_MatrixID user) {
kick(user, null);
}
@Override
public void kick(_MatrixID user, String reason) {
JsonObject body = new JsonObject();
body.addProperty("user_id", user.getId());
body.addProperty("reason", reason);
URL path = getClientPath("rooms", roomId, "kick");
MatrixHttpRequest request = new MatrixHttpRequest(new Request.Builder().post(getJsonBody(body)).url(path));
executeAuthenticated(request);
}
@Override
public Optional<MatrixErrorInfo> tryKick(_MatrixID user) {
return tryKick(user, null);
}
@Override
public Optional<MatrixErrorInfo> tryKick(_MatrixID user, String reason) {
try {
kick(user, reason);
return Optional.empty();
} catch (MatrixClientRequestException e) {
return e.getError();
}
}
@Override
public String sendEvent(String type, JsonObject content) {
// FIXME URL encoding
URL path = getClientPath("rooms", roomId, "send", type, Long.toString(System.currentTimeMillis()));
String body = executeAuthenticated(new Request.Builder().put(getJsonBody(content)).url(path));
return GsonUtil.getStringOrThrow(GsonUtil.parseObj(body), "event_id");
}
private String sendMessage(RoomMessageTextPutBody content) {
return sendEvent("m.room.message", GsonUtil.makeObj(content));
}
@Override
public String sendText(String message) {
return sendMessage(new RoomMessageTextPutBody(message));
}
@Override
public String sendFormattedText(String formatted, String rawFallback) {
// TODO sanitize input
return sendMessage(new RoomMessageFormattedTextPutBody(rawFallback, formatted));
}
@Override
public String sendNotice(String message) {
return sendMessage(new RoomMessageTextPutBody("m.notice", message));
}
@Override
public String sendNotice(String formatted, String plain) {
// TODO sanitize input
return sendMessage(new RoomMessageFormattedTextPutBody("m.notice", plain, formatted));
}
@Override
public void sendReceipt(String type, String eventId) {
URL path = getClientPath("rooms", roomId, "receipt", type, eventId);
executeAuthenticated(new Request.Builder().post(getJsonBody(new JsonObject())).url(path));
}
@Override
public void sendReceipt(ReceiptType type, String eventId) {
sendReceipt(type.getId(), eventId);
}
@Override
public void sendReadReceipt(String eventId) {
sendReceipt(ReceiptType.Read, eventId);
}
@Override
public void invite(_MatrixID mxId) {
URL path = getClientPath("rooms", roomId, "invite");
executeAuthenticated(
new Request.Builder().post(getJsonBody(GsonUtil.makeObj("user_id", mxId.getId()))).url(path));
}
@Override
public List<_MatrixUserProfile> getJoinedUsers() {
URL path = getClientPath("rooms", roomId, "joined_members");
String body = executeAuthenticated(new Request.Builder().get().url(path));
List<_MatrixUserProfile> ids = new ArrayList<>();
if (StringUtils.isNotEmpty(body)) {
JsonObject joinedUsers = jsonParser.parse(body).getAsJsonObject().get("joined").getAsJsonObject();
ids = joinedUsers.entrySet().stream().filter(e -> e.getValue().isJsonObject()).map(entry -> {
JsonObject obj = entry.getValue().getAsJsonObject();
return new MatrixHttpUser(getContext(), MatrixID.asAcceptable(entry.getKey())) {
@Override
public Optional<String> getName() {
return GsonUtil.findString(obj, "display_name");
}
@Override
public Optional<_MatrixContent> getAvatar() {
return GsonUtil.findString(obj, "avatar_url").flatMap(s -> {
try {
return Optional.of(new URI(s));
} catch (URISyntaxException e) {
return Optional.empty();
}
}).map(uri -> new MatrixHttpContent(getContext(), uri));
}
};
}).collect(Collectors.toList());
}
return ids;
}
@Override
public _MatrixRoomMessageChunk getMessages(_MatrixRoomMessageChunkOptions options) {
HttpUrl.Builder builder = getClientPathBuilder("rooms", roomId, "messages");
builder.addQueryParameter("from", options.getFromToken());
builder.addQueryParameter("dir", options.getDirection());
options.getToToken().ifPresent(token -> builder.addQueryParameter("to", token));
options.getLimit().ifPresent(limit -> builder.addQueryParameter("limit", limit.toString()));
String bodyRaw = executeAuthenticated(new Request.Builder().get().url(builder.build().url()));
RoomMessageChunkResponseJson body = GsonUtil.get().fromJson(bodyRaw, RoomMessageChunkResponseJson.class);
return new MatrixRoomMessageChunk(body.getStart(), body.getEnd(),
body.getChunk().stream().map(MatrixJsonPersistentEvent::new).collect(Collectors.toList()));
}
@Override
public List<Tag> getUserTags() {
return getAllTags().stream().filter(tag -> "u".equals(tag.getNamespace()))
.collect(Collectors.toList());
}
@Override
public List<Tag> getAllTags() {
URL path = getClientPath("user", getUserId(), "rooms", getAddress(), "tags");
String body = executeAuthenticated(new Request.Builder().get().url(path));
JsonObject jsonTags = GsonUtil.parseObj(body).getAsJsonObject("tags").getAsJsonObject();
List<Tag> tags = jsonTags.entrySet().stream().map(entry -> {
String completeName = entry.getKey();
String name = "";
String namespace = "";
if (completeName.startsWith("m.")) {
name = completeName.substring(2);
namespace = "m";
} else if (completeName.startsWith("u.")) {
name = completeName.substring(2);
namespace = "u";
} else {
name = completeName;
}
JsonElement jsonOrder = entry.getValue().getAsJsonObject().get("order");
Double order = null;
if (jsonOrder != null) {
order = jsonOrder.getAsDouble();
}
return new Tag(namespace, name, order);
}).collect(Collectors.toList());
return tags;
}
@Override
public void addUserTag(String tag) {
addTag("u." + tag, null);
}
@Override
public void addUserTag(String tag, double order) {
addTag("u." + tag, order);
}
@Override
public void deleteUserTag(String tag) {
deleteTag("u." + tag);
}
@Override
public void addFavouriteTag() {
addTag("m.favourite", null);
}
@Override
public void addFavouriteTag(double order) {
addTag("m.favourite", order);
}
@Override
public Optional<Tag> getFavouriteTag() {
return getAllTags().stream()
.filter(tag -> "m".equals(tag.getNamespace()) && "favourite".equals(tag.getName())).findFirst();
}
@Override
public void deleteFavouriteTag() {
deleteTag("m.favourite");
}
@Override
public void addLowpriorityTag() {
addTag("m.lowpriority", null);
}
@Override
public void addLowpriorityTag(double order) {
addTag("m.lowpriority", order);
}
@Override
public Optional<Tag> getLowpriorityTag() {
return getAllTags().stream()
.filter(tag -> "m".equals(tag.getNamespace()) && "lowpriority".equals(tag.getName())).findFirst();
}
@Override
public void deleteLowpriorityTag() {
deleteTag("m.lowpriority");
}
private void addTag(String tag, Double order) {
// TODO check name size
if (order != null && (order < 0 || order > 1)) {
throw new IllegalArgumentException("Order out of range!");
}
URL path = getClientPath("user", getUserId(), "rooms", getAddress(), "tags", tag);
Request.Builder request = new Request.Builder().url(path);
if (order != null) {
request.put(getJsonBody(new RoomTagSetBody(order)));
} else {
request.put(getJsonBody(new JsonObject()));
}
executeAuthenticated(request);
}
private void deleteTag(String tag) {
URL path = getClientPath("user", getUserId(), "rooms", getAddress(), "tags", tag);
executeAuthenticated(new Request.Builder().url(path).delete());
}
}