/* * Copyright 2016 Sam Sun <me@samczsun.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.samczsun.skype4j.internal.chat; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; import com.samczsun.skype4j.chat.GroupChat; import com.samczsun.skype4j.exceptions.ChatNotFoundException; import com.samczsun.skype4j.exceptions.ConnectionException; import com.samczsun.skype4j.internal.Endpoints; import com.samczsun.skype4j.internal.Factory; import com.samczsun.skype4j.internal.SkypeImpl; import com.samczsun.skype4j.internal.participants.BotImpl; import com.samczsun.skype4j.internal.participants.ParticipantImpl; import com.samczsun.skype4j.internal.participants.UserImpl; import com.samczsun.skype4j.internal.Utils; import com.samczsun.skype4j.internal.participants.info.ContactImpl; import com.samczsun.skype4j.participants.Participant; import com.samczsun.skype4j.participants.info.Contact; import javax.imageio.ImageIO; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.*; import java.util.stream.Collectors; public class ChatGroup extends ChatImpl implements GroupChat { private String topic; private String pictureUrl; private boolean pictureUpdated; private BufferedImage picture; private Set<Option> enabledOptions = new HashSet<>(); private List<String> bannedIds = new ArrayList<>(); private List<String> whitelistedIds = new ArrayList<>(); public ChatGroup(SkypeImpl skype, String identity) throws ConnectionException, ChatNotFoundException { super(skype, identity); } public void load() throws ConnectionException, ChatNotFoundException { JsonObject object = Endpoints.CHAT_INFO_URL .open(getClient(), getIdentity()) .as(JsonObject.class) .on(404, (connection) -> { throw new ChatNotFoundException(); }) .expect(200, "While loading users") .get(); JsonObject props = object.get("properties").asObject(); for (Option option : GroupChat.Option.values()) { if (props.get(option.getId()) != null && props.get(option.getId()).asString().equals("true")) { this.enabledOptions.add(option); } } if (props.get("topic") != null) { this.topic = props.get("topic").asString(); } else { this.topic = ""; } if (props.get("picture") != null) { this.pictureUrl = props.get("picture").asString().substring(4); } JsonArray members = object.get("members").asArray(); for (JsonValue element : members) { String id = element.asObject().get("id").asString(); String role = element.asObject().get("role").asString(); ParticipantImpl user = Factory.createParticipant(getClient(), this, id); users.put(id.toLowerCase(), user); if (role.equalsIgnoreCase("admin")) { user.updateRole(Participant.Role.ADMIN); } else { user.updateRole(Participant.Role.USER); } } Map<String, UserImpl> toLoad = new HashMap<>(); for (ParticipantImpl participant : this.users.values()) { if (participant instanceof UserImpl) { if (getClient().getContact(participant.getId()) != null) { ((UserImpl) participant).setInfo(getClient().getContact(participant.getId())); } else { toLoad.put(((UserImpl) participant).getUsername(), (UserImpl) participant); } } else if (participant instanceof BotImpl) { ((BotImpl) participant).setInfo(getClient().getOrLoadBotInfo(participant.getId())); } } while (!toLoad.isEmpty()) { Map<String, UserImpl> localToLoad = new HashMap<>(); Iterator<Map.Entry<String, UserImpl>> it = toLoad.entrySet().iterator(); while (localToLoad.size() < 100 && !toLoad.isEmpty()) { Map.Entry<String, UserImpl> ent = it.next(); localToLoad.put(ent.getKey(), ent.getValue()); it.remove(); } JsonArray usernames = new JsonArray(); localToLoad.keySet().forEach(usernames::add); JsonArray info = Endpoints.PROFILE_INFO .open(getClient()) .expect(200, "While getting contact info") .as(JsonArray.class) .post(new JsonObject() .add("usernames", usernames) ); for (JsonValue jsonValue : info) { JsonObject data = jsonValue.asObject(); UserImpl matching = localToLoad.get(data.get("username").asString()); if (matching != null) { matching.setInfo(ContactImpl.createContact(getClient(), matching.getUsername(), data)); } } } } public void addUser(String username) throws ConnectionException { username = "8:" + username; if (!users.containsKey(username.toLowerCase())) { ParticipantImpl user = Factory.createParticipant(getClient(), this, username); users.put(username.toLowerCase(), user); } else if (!username.equalsIgnoreCase(getClient().getUsername())) { //Skype... throw new IllegalArgumentException(username + " joined the chat even though he was already in it?"); } } public void removeUser(String username) { username = "8:" + username; users.remove(username.toLowerCase()); } public void kick(String username) throws ConnectionException { Endpoints.MODIFY_MEMBER_URL .open(getClient(), getIdentity(), username) .expect(200, "While kicking user") .delete(); } @Override public void ban(String id) throws ConnectionException { this.bannedIds.add(id); updateBanlist(); } @Override public void unban(String id) throws ConnectionException { this.bannedIds.remove(id); updateBanlist(); } @Override public List<String> getBannedIds() { return Collections.unmodifiableList(this.bannedIds); } @Override public void whitelist(String id) throws ConnectionException { this.whitelistedIds.add(id); updateWhitelist(); } @Override public void unwhitelist(String id) throws ConnectionException { this.whitelistedIds.remove(id); updateWhitelist(); } @Override public List<String> getWhitelistedIds() { return Collections.unmodifiableList(this.whitelistedIds); } public void leave() throws ConnectionException { kick(getClient().getUsername()); } @Override public String getJoinUrl() throws ConnectionException { if (isOptionEnabled(GroupChat.Option.JOINING_ENABLED)) { JsonObject data = new JsonObject(); data.add("baseDomain", "https://join.skype.com/launch/"); data.add("threadId", this.getIdentity()); JsonObject object = Endpoints.GET_JOIN_URL .open(getClient()) .as(JsonObject.class) .expect(200, "While getting join URL") .post(data); return object.get("JoinUrl").asString(); } else { throw new IllegalStateException("Joining is not enabled"); } } @Override public String getTopic() { return this.topic; } public void setTopic(String topic) throws ConnectionException { putOption("topic", JsonValue.valueOf(topic), true); } @Override public BufferedImage getPicture() throws ConnectionException { if (pictureUrl != null) { if (pictureUpdated) { picture = null; pictureUpdated = false; } if (picture == null) { this.picture = Endpoints .custom(pictureUrl, getClient()) .as(BufferedImage.class) .expect(200, "While fetching image") .header("Authorization", Endpoints.AUTHORIZATION.provide(getClient())) .get(); } BufferedImage clone = new BufferedImage(picture.getWidth(), picture.getHeight(), picture.getType()); Graphics2D g2d = clone.createGraphics(); g2d.drawImage(picture, 0, 0, null); g2d.dispose(); return clone; } return null; } @Override public void setImage(BufferedImage image, String imageType) throws ConnectionException, IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, imageType, baos); String id = Utils.uploadImage(baos.toByteArray(), Utils.ImageType.AVATAR, this); putOption("picture", JsonValue.valueOf( String.format("URL@https://api.asm.skype.com/v1/objects/%s/views/avatar_fullsize", id)), true); } @Override public void setImage(File image) throws ConnectionException, IOException { byte[] data = Files.readAllBytes(image.toPath()); String id = Utils.uploadImage(data, Utils.ImageType.AVATAR, this); putOption("picture", JsonValue.valueOf( String.format("URL@https://api.asm.skype.com/v1/objects/%s/views/avatar_fullsize", id)), true); } @Override public boolean isOptionEnabled(Option option) { return this.enabledOptions.contains(option); } @Override public void setOptionEnabled(Option option, boolean enabled) throws ConnectionException { putOption(option.getId(), JsonValue.valueOf(enabled), true); updateOption(option, enabled); } @Override public void add(Contact contact) throws ConnectionException { Endpoints.ADD_MEMBER_URL .open(getClient(), getIdentity(), contact.getUsername()) .expect(200, "While adding user to group") .put(new JsonObject().add("role", "User")); } public void updateTopic(String topic) { this.topic = topic; } public void updatePicture(String picture) { this.pictureUrl = picture; pictureUpdated = true; } public void updateOption(Option option, boolean enabled) { if (enabled) enabledOptions.add(option); else enabledOptions.remove(option); } private void updateBanlist() throws ConnectionException { JsonValue bannedUserList = JsonValue.valueOf(bannedIds.stream().collect(Collectors.joining(","))); putOption("banneduserlist", bannedUserList, true); } private void updateWhitelist() throws ConnectionException { JsonValue whitelistedUserList = JsonValue.valueOf(whitelistedIds.stream().collect(Collectors.joining(","))); putOption("alloweduserlist", whitelistedUserList, true); } }