diff options
Diffstat (limited to 'src/main/java/com/juick/server')
35 files changed, 0 insertions, 4481 deletions
diff --git a/src/main/java/com/juick/server/ActivityPubManager.java b/src/main/java/com/juick/server/ActivityPubManager.java deleted file mode 100644 index 50af506b..00000000 --- a/src/main/java/com/juick/server/ActivityPubManager.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2008-2020, Juick - * - * 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 com.juick.server; - -import com.juick.model.Message; -import com.juick.model.Reaction; -import com.juick.model.User; -import com.juick.formatters.PlainTextFormatter; -import com.juick.model.Tag; -import com.juick.www.api.SystemActivity.ActivityType; -import com.juick.www.api.activity.model.Context; -import com.juick.www.api.activity.model.activities.*; -import com.juick.www.api.activity.model.objects.Hashtag; -import com.juick.www.api.activity.model.objects.Image; -import com.juick.www.api.activity.model.objects.Mention; -import com.juick.www.api.activity.model.objects.Note; -import com.juick.www.api.activity.model.objects.Person; -import com.juick.server.util.HttpBadRequestException; -import com.juick.server.util.HttpUtils; -import com.juick.service.MessagesService; -import com.juick.service.SocialService; -import com.juick.service.UserService; -import com.juick.service.activities.*; -import com.juick.service.component.*; -import com.juick.util.MessageUtils; -import com.mitchellbosecke.pebble.PebbleEngine; -import com.mitchellbosecke.pebble.template.PebbleTemplate; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.annotation.Nonnull; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -@Component -public class ActivityPubManager implements ActivityListener, NotificationListener { - private static final Logger logger = LoggerFactory.getLogger("ActivityPub"); - @Inject - private SignatureManager signatureManager; - @Inject - private SocialService socialService; - @Inject - private UserService userService; - @Inject - private MessagesService messagesService; - @Inject - private PebbleEngine pebbleEngine; - @Value("${ap_base_uri:http://localhost:8080/}") - private String baseUri; - @Value("${service_user:juick}") - private String serviceUsername; - - private User serviceUser; - - @PostConstruct - public void init() { - serviceUser = userService.getUserByName(serviceUsername); - } - - @Override - public void processFollowEvent(@Nonnull FollowEvent followEvent) { - String acct = (String)followEvent.getRequest().getObject(); - logger.info("received follower request to {}", acct); - User followedUser = socialService.getUserByAccountUri(acct); - if (!followedUser.isAnonymous()) { - // automatically accept follower requests - Person me = (Person) signatureManager.getContext(URI.create(acct)).get(); - Person follower = (Person) signatureManager.getContext(URI.create(followEvent.getRequest().getActor())).get(); - Accept accept = new Accept(); - accept.setActor(me.getId()); - accept.setObject(followEvent.getRequest()); - try { - signatureManager.post(me, follower, accept); - socialService.addFollower(followedUser, follower.getId()); - logger.info("Follower added for {}", followedUser.getName()); - } catch (IOException e) { - logger.info("activitypub exception", e); - } - } - } - - @Override - public void undoFollowEvent(UndoFollowEvent event) { - String actor = event.getActor(); - String me = event.getObject(); - logger.info("{} stopping to follow {}", actor, me); - User followedUser = socialService.getUserByAccountUri(me); - if (!followedUser.isAnonymous()) { - socialService.removeFollower(followedUser, actor); - } - } - - @Override - public void deleteUserEvent(DeleteUserEvent event) { - String acct = event.getUserUri(); - logger.info("Deleting {} from followers", acct); - socialService.removeAccount(acct); - } - - @Override - public void deleteMessageEvent(DeleteMessageEvent event) { - Message msg = event.getMessage(); - User user = msg.getUser(); - String userUri = personUri(user); - Note note = makeNote(msg); - Person me = (Person) signatureManager.getContext(URI.create(userUri)).get(); - socialService.getFollowers(user).forEach(acct -> { - Person follower = (Person) signatureManager.getContext(URI.create(acct)).get(); - Delete delete = new Delete(); - delete.setId(note.getId()); - delete.setActor(me.getId()); - delete.setPublished(note.getPublished()); - delete.setObject(note); - try { - logger.info("Deletion to follower {}", follower.getId()); - signatureManager.post(me, follower, delete); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - }); - } - - @Override - public void processAnnounceEvent(AnnounceEvent event) { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(event.getMessageUri()).build(); - List<String> segments = uriComponents.getPathSegments(); - if (segments.get(0).equals("n")) { - String[] ids = segments.get(1).split("-", 2); - if (ids.length == 2 && Integer.parseInt(ids[1]) == 0) { - // only messages - logger.info("{} recommends {}", event.getActorUri(), Integer.valueOf(ids[0])); - messagesService.likeMessage(Integer.parseInt(ids[0]), 0, Reaction.LIKE, event.getActorUri()); - } - } - } - - @Override - public void undoAnnounceEvent(UndoAnnounceEvent event) { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(event.getMessageUri()).build(); - List<String> segments = uriComponents.getPathSegments(); - if (segments.get(0).equals("n")) { - String[] ids = segments.get(1).split("-", 2); - if (ids.length == 2 && Integer.parseInt(ids[1]) == 0) { - // only messages - logger.info("{} stop recommending {}", event.getActorUri(), Integer.valueOf(ids[0])); - messagesService.likeMessage(Integer.parseInt(ids[0]), 0, null, event.getActorUri()); - } - } - } - - @Override - public void processUpdateEvent(UpdateEvent event) { - String objectUri = event.getMessageUri(); - User user = event.getUser(); - String userUri = personUri(user); - Person me = (Person) signatureManager.getContext(URI.create(userUri)).get(); - socialService.getFollowers(user).forEach(acct -> { - Person follower = (Person) signatureManager.getContext(URI.create(acct)).get(); - Update update = new Update(); - update.setId(objectUri + "#update"); - update.setActor(me.getId()); - update.setObject(objectUri); - try { - logger.info("Update to follower {}", follower.getId()); - signatureManager.post(me, follower, update); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - }); - } - - @Override - public void processSystemEvent(SystemEvent systemEvent) { - ActivityType type = systemEvent.getActivity().getType(); - if (type.equals(ActivityType.message)) { - processMessage(systemEvent.getActivity().getMessage()); - } else if (type.equals(ActivityType.like)) { - if (systemEvent.getActivity().getFrom().equals(serviceUser)) { - processTop(systemEvent.getActivity().getMessage()); - } - } - } - private void processMessage(Message msg) { - if (MessageUtils.isPM(msg) || msg.isService()) { - return; - } - User user = msg.getUser(); - String userUri = personUri(user); - Note note = makeNote(msg); - Person me = (Person) signatureManager.getContext(URI.create(userUri)).get(); - Set<String> subscribers = new HashSet<>(socialService.getFollowers(user)); - if (MessageUtils.isReply(msg) && msg.getTo().getUri().toASCIIString().length() > 0) { - String replier = msg.getTo().getUri().toASCIIString(); - subscribers.add(replier); - List<String> cc = new ArrayList<>(note.getCc()); - cc.add(replier); - note.setCc(cc); - } - subscribers.addAll(note.getCc()); - subscribers.forEach(acct -> { - Optional<Context> context = signatureManager.getContext(URI.create(acct)); - if (context.isPresent() && context.get() instanceof Person) { - Person follower = (Person)context.get(); - Create create = new Create(); - create.setId(note.getId()); - create.setActor(me.getId()); - create.setPublished(note.getPublished()); - create.setObject(note); - try { - signatureManager.post(me, follower, create); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - } - }); - } - - public String inboxUri() { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath("/api/inbox").toUriString(); - } - - public String outboxUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s/blog/toc", user.getName())).toUriString(); - } - - public String personUri(User user) { - if (user.getUri().toString().length() > 0) { - return user.getUri().toASCIIString(); - } - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s", user.getName())).toUriString(); - } - public String personWebUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/%s/", user.getName())).toUriString(); - } - - public String followersUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s/followers/toc", user.getName())).toUriString(); - } - - public String followingUri(User user) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/u/%s/following/toc", user.getName())).toUriString(); - } - public String messageUri(Message msg) { - return messageUri(msg.getMid(), msg.getRid()); - } - public String messageUri(int mid, int rid) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - uri.replacePath(String.format("/n/%d-%d", mid, rid)); - return uri.toUriString(); - } - public String tagUri(Tag tag) { - UriComponentsBuilder uri = UriComponentsBuilder.fromUriString(baseUri); - return uri.replacePath(String.format("/t/%s", tag.getName())).toUriString(); - } - - public String postId(String messageUri) { - UriComponents uri = UriComponentsBuilder.fromUriString(messageUri).build(); - return uri.getPath().substring(uri.getPath().lastIndexOf('/') + 1).replace("-", "/"); - } - - public Note makeNote(Message msg) { - Note note = new Note(); - note.setId(messageUri(msg)); - note.setUrl(PlainTextFormatter.formatUrl(msg)); - note.setAttributedTo(personUri(msg.getUser())); - if (MessageUtils.isReply(msg)) { - if (msg.getReplyToUri().toASCIIString().length() > 0) { - note.setInReplyTo(msg.getReplyToUri().toASCIIString()); - } else { - note.setInReplyTo(messageUri(msg.getMid(), msg.getReplyto())); - } - } - if (MessageUtils.isPM(msg)) { - note.setTo(Collections.singletonList(personUri(msg.getTo()))); - } else { - note.setTo(Collections.singletonList(Context.ACTIVITYSTREAMS_PUBLIC)); - note.setCc(Collections.singletonList(followersUri(msg.getUser()))); - } - note.setPublished(msg.getCreated()); - if (StringUtils.isNotBlank(msg.getAttachmentType())) { - Image attachment = new Image(); - attachment.setId(msg.getAttachment().getMedium().getUrl()); - attachment.setUrl(msg.getAttachment().getMedium().getUrl()); - attachment.setMediaType(HttpUtils.mediaType(msg.getAttachmentType())); - note.setAttachment(Collections.singletonList(attachment)); - } - note.setTags(msg.getTags().stream().map(t -> new Hashtag(tagUri(t), t.getName())).collect(Collectors.toList())); - if (msg.getReplyToUri() != null && msg.getReplyToUri().toASCIIString().length() > 0) { - Optional<Context> noteContext = signatureManager.getContext(msg.getReplyToUri()); - if (noteContext.isPresent()) { - Note activity = (Note) noteContext.get(); - Optional<Context> personContext = signatureManager.getContext(URI.create(activity.getAttributedTo())); - if (personContext.isPresent()) { - Person person = (Person) personContext.get(); - note.getTags().add(new Mention(person.getUrl(), person.getPreferredUsername())); - msg.getTo().setName(person.getPreferredUsername()); - note.setInReplyTo(activity.getInReplyTo()); - } - } - } else if (MessageUtils.isReply(msg)) { - note.getTags().add(new Mention(personWebUri(msg.getTo()), msg.getTo().getName())); - } - MessageUtils.getGlobalMentions(msg).forEach(m -> { - // @user@server.tld -> user@server.tld - Optional<Context> personContext = signatureManager.discoverPerson(m.substring(1)); - if (personContext.isPresent()) { - Person person = (Person) personContext.get(); - note.getTags().add(new Mention(person.getUrl(), person.getPreferredUsername())); - List<String> cc = new ArrayList<>(note.getCc()); - cc.add(person.getId()); - note.setCc(cc); - } - }); - if (msg.isHtml()) { - note.setContent(msg.getText()); - } else { - PebbleTemplate noteTemplate = pebbleEngine.getTemplate("layouts/note"); - Map<String, Object> context = new HashMap<>(); - context.put("msg", msg); - context.put("baseUri", baseUri); - try { - Writer writer = new StringWriter(); - noteTemplate.evaluate(writer, context); - note.setContent(writer.toString()); - } catch (IOException e) { - logger.warn("template not rendered, falling back"); - note.setContent(MessageUtils.formatMessage(StringUtils.defaultString(msg.getText()))); - } - } - return note; - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - - } - - private void processTop(Message message) { - Note note = makeNote(message); - Announce announce = new Announce(); - announce.setId(note.getId() + "#top"); - announce.setActor(personUri(serviceUser)); - announce.setTo(Collections.singletonList(Context.ACTIVITYSTREAMS_PUBLIC)); - announce.setObject(note); - Person me = (Person) signatureManager.getContext(URI.create(announce.getActor())).get(); - socialService.getFollowers(serviceUser).forEach(acct -> { - var follower = signatureManager.getContext(URI.create(acct)); - follower.ifPresentOrElse((person) -> { - try { - logger.info("Announcing top: {}", message.getMid()); - signatureManager.post(me, (Person)person, announce); - } catch (IOException e) { - logger.warn("activitypub exception", e); - } - }, () -> logger.warn("Follower not found: {}", acct)); - }); - } - public User personToUser(URI uri) throws HttpBadRequestException { - Person person = (Person) signatureManager.getContext(uri).orElseThrow(HttpBadRequestException::new); - User user = new User(); - user.setUri(URI.create(person.getId())); - user.setName(person.getPreferredUsername()); - if (person.getIcon() != null) { - user.setAvatar(person.getIcon().getUrl()); - } - return user; - } -} diff --git a/src/main/java/com/juick/server/CommandsManager.java b/src/main/java/com/juick/server/CommandsManager.java deleted file mode 100644 index bf907855..00000000 --- a/src/main/java/com/juick/server/CommandsManager.java +++ /dev/null @@ -1,594 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.model.Message; -import com.juick.model.Tag; -import com.juick.model.User; -import com.juick.formatters.PlainTextFormatter; -import com.juick.model.CommandResult; -import com.juick.model.TagStats; -import com.juick.www.api.SystemActivity; -import com.juick.server.helpers.annotation.UserCommand; -import com.juick.server.util.HttpUtils; -import com.juick.www.WebApp; -import com.juick.service.*; -import com.juick.service.activities.DeleteMessageEvent; -import com.juick.service.component.*; -import com.juick.util.MessageUtils; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.apache.commons.lang3.reflect.MethodUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.text.StringEscapeUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Component; - -import javax.annotation.Nonnull; -import javax.inject.Inject; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * - * @author ugnich - */ -@Component -public class CommandsManager { - private static final Logger logger = LoggerFactory.getLogger(CommandsManager.class); - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private TagService tagService; - @Inject - private PMQueriesService pmQueriesService; - @Inject - private ShowQueriesService showQueriesService; - @Inject - private PrivacyQueriesService privacyQueriesService; - @Inject - private SubscriptionService subscriptionService; - @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String tmpDir; - @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String imgDir; - @Inject - private ApplicationEventPublisher applicationEventPublisher; - @Inject - private ImagesService imagesService; - @Inject - private WebApp webApp; - @Inject - private ActivityPubManager activityPubManager; - - public CommandResult processCommand(@Nonnull User user, String data, @Nonnull URI attachment) throws Exception { - if (!user.isAnonymous()) { - userService.updateLastSeen(user); - } - String strippedData = StringUtils.stripStart(data, null); - if (strippedData.startsWith("?OTR")) { - return CommandResult.fromString("?OTR Error: we are not using OTR"); - } - String input = StringEscapeUtils.unescapeHtml4(MessageUtils.stripNonSafeUrls(strippedData)); - Optional<Method> cmd = MethodUtils.getMethodsListWithAnnotation(getClass(), UserCommand.class).stream() - .filter(m -> Pattern.compile(m.getAnnotation(UserCommand.class).pattern(), - m.getAnnotation(UserCommand.class).patternFlags()).matcher(input).matches()) - .findFirst(); - if (cmd.isPresent()) { - Matcher matcher = Pattern.compile(cmd.get().getAnnotation(UserCommand.class).pattern(), - cmd.get().getAnnotation(UserCommand.class).patternFlags()).matcher(input); - List<String> groups = new ArrayList<>(); - while (matcher.find()) { - for (int i = 1; i <= matcher.groupCount(); i++) { - groups.add(matcher.group(i)); - } - } - CommandResult commandResult = (CommandResult) getClass().getMethod(cmd.get().getName(), User.class, URI.class, String[].class) - .invoke(this, user, attachment, groups.toArray(new String[groups.size()])); - if (StringUtils.isNotEmpty(commandResult.getText())) { - return commandResult; - } - } - Pair<String, List<Tag>> tags = tagService.fromString(input); - if (tags.getRight().size() > 5) { - return CommandResult.fromString("Sorry, 5 tags maximum."); - } - // new message - String body = tags.getLeft().trim(); - if (body.length() > 4096) { - return CommandResult.fromString("Sorry, 4096 characters maximum."); - } - boolean haveAttachment = StringUtils.isNotEmpty(attachment.toString()); - String attachmentFName = null; - String attachmentType = null; - if (haveAttachment) { - attachmentFName = attachment.getScheme().equals("juick") ? attachment.getHost() - : HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); - attachmentType = attachmentFName.substring(attachmentFName.length() - 3); - } - if (StringUtils.isEmpty(body) && !haveAttachment) { - return CommandResult.fromString("Empty message"); - } - int mid = messagesService.createMessage(user.getUid(), body, attachmentType, tags.getRight()); - if (haveAttachment) { - String fname = String.format("%d.%s", mid, attachmentType); - imagesService.saveImageWithPreviews(attachmentFName, fname); - } - Message msg = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new); - msg.getUser().setAvatar(webApp.getAvatarUrl(msg.getUser())); - subscriptionService.subscribeMessage(msg, user); - - applicationEventPublisher.publishEvent(new SystemEvent(this, SystemActivity.read(user, msg))); - applicationEventPublisher.publishEvent(new SystemEvent(this, - SystemActivity.message(user, msg, subscriptionService.getSubscribedUsers(msg.getUser().getUid(), msg)))); - return CommandResult.build(msg, - "New message posted.\n#" + msg.getMid() + " https://juick.com/m/" + msg.getMid(), - String.format("[New message](%s) posted", PlainTextFormatter.formatUrl(msg))); - } - - @UserCommand(pattern = "^ping$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "PING - returns you a PONG") - public CommandResult commandPing(User user, URI attachment, String[] input) { - applicationEventPublisher.publishEvent(new PingEvent(this, user)); - return CommandResult.fromString("PONG"); - } - - @UserCommand(pattern = "^help$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "HELP - returns this help message") - public CommandResult commandHelp(User user, URI attachment, String[] input) { - return CommandResult.fromString(Arrays.stream(getClass().getDeclaredMethods()) - .filter(m -> m.isAnnotationPresent(UserCommand.class)) - .map(m -> m.getAnnotation(UserCommand.class).help()) - .collect(Collectors.joining("\n"))); - } - - @UserCommand(pattern = "^login$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "LOGIN - log in to Juick website") - public CommandResult commandLogin(User user_from, URI attachment, String[] input) { - return CommandResult.fromString("http://juick.com/login?hash=" + userService.getHashByUID(user_from.getUid())); - } - @UserCommand(pattern = "^\\@(\\S+)\\s+([\\s\\S]+)$", help = "@username message - send PM to username") - public CommandResult commandPM(User user_from, URI attachment, String... arguments) { - String body = arguments[1]; - - User user_to = userService.getUserByName(arguments[0]); - user_to.setAvatar(webApp.getAvatarUrl(user_to)); - if (!user_to.isAnonymous()) { - if (!userService.isInBLAny(user_to.getUid(), user_from.getUid())) { - if (pmQueriesService.createPM(user_from.getUid(), user_to.getUid(), body)) { - Message jmsg = new Message(); - jmsg.setUser(user_from); - jmsg.setTo(user_to); - jmsg.setText(body); - applicationEventPublisher.publishEvent(new SystemEvent(this, - SystemActivity.message(user_from, jmsg, Collections.singletonList(user_to)))); - return CommandResult.fromString("Private message sent"); - } - } - } - return CommandResult.fromString("Error"); - } - @UserCommand(pattern = "^bl$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "BL - Show your blacklist") - public CommandResult commandBLShow(User user_from, URI attachment, String... arguments) { - List<User> blusers = userService.getUserBLUsers(user_from.getUid()); - List<String> bltags = tagService.getUserBLTags(user_from.getUid()); - - String txt = StringUtils.EMPTY; - if (bltags.size() > 0) { - for (String bltag : bltags) { - txt += "*" + bltag + "\n"; - } - - if (blusers.size() > 0) { - txt += "\n"; - } - } - if (blusers.size() > 0) { - for (User bluser : blusers) { - txt += "@" + bluser.getName() + "\n"; - } - } - if (txt.isEmpty()) { - txt = "You don't have any users or tags in your blacklist."; - } - - return CommandResult.fromString(txt); - } - - @UserCommand(pattern = "^#\\+$", help = "#+ - Show last Juick messages") - public CommandResult commandLast(User user_from, URI attachment, String... arguments) { - return CommandResult.fromString("Last messages:\n" - + printMessages(user_from, messagesService.getAll(user_from.getUid(), 0), true)); - } - - @UserCommand(pattern = "@", help = "@ - Show recommendations and popular personal blogs") - public CommandResult commandUsers(User user_from, URI attachment, String... arguments) { - StringBuilder msg = new StringBuilder(); - msg.append("Recommended blogs"); - List<String> recommendedUsers = showQueriesService.getRecommendedUsers(user_from); - if (recommendedUsers.size() > 0) { - for (String user : recommendedUsers) { - msg.append("\n@").append(user); - } - } else { - msg.append("\nNo recommendations now. Subscribe to more blogs. ;)"); - } - msg.append("\n\nTop 10 personal blogs:"); - List<String> topUsers = showQueriesService.getTopUsers(); - if (topUsers.size() > 0) { - for (String user : topUsers) { - msg.append("\n@").append(user); - } - } else { - msg.append("\nNo top users. Empty DB? ;)"); - } - return CommandResult.fromString(msg.toString()); - } - @UserCommand(pattern = "^bl\\s+@([^\\s\\n\\+]+)", patternFlags = Pattern.CASE_INSENSITIVE, - help = "BL @username - add @username to your blacklist") - public CommandResult blacklistUser(User user_from, URI attachment, String... arguments) { - User blUser = userService.getUserByName(arguments[0]); - if (!blUser.isAnonymous()) { - PrivacyQueriesService.PrivacyResult result = privacyQueriesService.blacklistUser(user_from, blUser); - if (result == PrivacyQueriesService.PrivacyResult.Added) { - return CommandResult.fromString("User added to your blacklist"); - } else { - return CommandResult.fromString("User removed from your blacklist"); - } - } - return CommandResult.fromString("User not found"); - } - @UserCommand(pattern = "^bl\\s\\*(\\S+)$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "BL *tag - add *tag to your blacklist") - public CommandResult blacklistTag(User user_from, URI attachment, String... arguments) { - if (!user_from.isAnonymous()) { - Tag tag = tagService.getTag(arguments[0], false); - if (tag != null) { - PrivacyQueriesService.PrivacyResult result = privacyQueriesService.blacklistTag(user_from, tag); - if (result == PrivacyQueriesService.PrivacyResult.Added) { - return CommandResult.fromString("Tag added to your blacklist"); - } else { - return CommandResult.fromString("Tag removed from your blacklist"); - } - } - } - return CommandResult.fromString("Tag not found"); - } - @UserCommand(pattern = "\\*", help = "* - Show your tags") - public CommandResult commandTags(User currentUser, URI attachment, String... args) { - List<TagStats> tags = tagService.getUserTagStats(currentUser.getUid()); - String msg = "Your tags: (tag - messages)\n" + - tags.stream() - .map(t -> String.format("\n*%s - %d", t.getTag().getName(), t.getUsageCount())).collect(Collectors.joining()); - return CommandResult.fromString(msg); - } - @UserCommand(pattern = "S", help = "S - Show your subscriptions", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandSubscriptions(User currentUser, URI attachment, String... args) { - List<User> friends = userService.getUserFriends(currentUser.getUid()); - List<String> tags = subscriptionService.getSubscribedTags(currentUser); - String msg = friends.size() > 0 ? "You are subscribed to users:" + friends.stream().map(u -> "\n@" + u.getName()) - .collect(Collectors.joining()) - : "You are not subscribed to any user."; - msg += tags.size() > 0 ? "\nYou are subscribed to tags:" + tags.stream().map(t -> "\n*" + t) - .collect(Collectors.joining()) - : "\nYou are not subscribed to any tag."; - return CommandResult.fromString(msg); - } - @UserCommand(pattern = "!", help = "! - Show your favorite messages") - public CommandResult commandFavorites(User currentUser, URI attachment, String... args) { - List<Integer> mids = messagesService.getUserRecommendations(currentUser.getUid(), 0); - if (mids.size() > 0) { - return CommandResult.fromString("Favorite messages: \n" + printMessages(currentUser, mids, false)); - } - return CommandResult.fromString("No favorite messages, try to \"like\" something ;)"); - } - @UserCommand(pattern = "^\\!\\s+#(\\d+)", help = "! #12345 - recommend message") - public CommandResult commandRecommend(User user, URI attachment, String... arguments) { - int mid = NumberUtils.toInt(arguments[0], 0); - if (mid > 0) { - Optional<Message> msg = messagesService.getMessage(mid); - if (msg.isPresent()) { - if (msg.get().getUser() == user) { - return CommandResult.fromString("You can't recommend your own messages."); - } - MessagesService.RecommendStatus status = messagesService.recommendMessage(mid, user.getUid(), user.getUri().toASCIIString()); - switch (status) { - case Added: - applicationEventPublisher.publishEvent(new SystemEvent(this, SystemActivity.like(user, msg.get(), - subscriptionService.getUsersSubscribedToUserRecommendations( - user.getUid(), msg.get())))); - return CommandResult.fromString("Message is added to your recommendations"); - case Deleted: - return CommandResult.fromString("Message deleted from your recommendations."); - } - } - return CommandResult.fromString("Message not found"); - } - return CommandResult.fromString("Message not found"); - } - // TODO: target notification - @UserCommand(pattern = "^(s|u)\\s+\\@(\\S+)$", help = "S @username - subscribe to user" + - "\nU @username - unsubscribe from user", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandSubscribeUser(User user, URI attachment, String... args) { - boolean subscribe = args[0].equalsIgnoreCase("s"); - User toUser = userService.getUserByName(args[1]); - if (toUser.isAnonymous()) { - return CommandResult.fromString("User not found"); - } - if (subscribe) { - if (subscriptionService.subscribeUser(user, toUser)) { - // TODO: already subscribed case - applicationEventPublisher.publishEvent(new SystemEvent(this, - SystemActivity.follow(user, Collections.singletonList(toUser)))); - return CommandResult.fromString("Subscribed to @" + toUser.getName()); - } - } else { - if (subscriptionService.unSubscribeUser(user, toUser)) { - return CommandResult.fromString("Unsubscribed from @" + toUser.getName()); - } - return CommandResult.fromString("You were not subscribed to @" + toUser.getName()); - } - return CommandResult.fromString("Error"); - } - @UserCommand(pattern = "^(s|u)\\s+\\*(\\S+)$", help = "S *tag - subscribe to tag" + - "\nU *tag - unsubscribe from tag", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandSubscribeTag(User user, URI attachment, String... args) { - boolean subscribe = args[0].equalsIgnoreCase("s"); - Tag tag = tagService.getTag(args[1], true); - if (subscribe) { - if (subscriptionService.subscribeTag(user, tag)) { - return CommandResult.fromString("Subscribed"); - } - } else { - if (subscriptionService.unSubscribeTag(user, tag)) { - return CommandResult.fromString("Unsubscribed from " + tag.getName()); - } - return CommandResult.fromString("You were not subscribed to " + tag.getName()); - } - return CommandResult.fromString("Error"); - } - @UserCommand(pattern = "^(s|u)\\s+#(\\d+)$", help = "S #1234 - subscribe to comments" + - "\nU #1234 - unsubscribe from comments", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandSubscribeMessage(@Nonnull User user, URI attachment, String... args) { - boolean subscribe = args[0].equalsIgnoreCase("s"); - int mid = NumberUtils.toInt(args[1], 0); - Optional<Message> msg = messagesService.getMessage(mid); - if (msg.isPresent()) { - if (subscribe) { - if (subscriptionService.subscribeMessage(msg.get(), user)) { - applicationEventPublisher.publishEvent( - new SystemEvent(this, SystemActivity.read(user, msg.get()))); - return CommandResult.fromString("Subscribed"); - } - } else { - if (subscriptionService.unSubscribeMessage(mid, user.getUid())) { - return CommandResult.fromString("Unsubscribed from #" + mid); - } - return CommandResult.fromString("You were not subscribed to #" + mid); - } - } - return CommandResult.fromString("Error"); - } - @UserCommand(pattern = "^(on|off)$", patternFlags = Pattern.CASE_INSENSITIVE, - help = "ON/OFF - Enable/disable subscriptions delivery") - public CommandResult commandOnOff(User user, URI attachment, String[] input) { - UserService.ActiveStatus newStatus; - String retValUpdated; - if (input[0].toLowerCase().equals("on")) { - newStatus = UserService.ActiveStatus.Active; - retValUpdated = "XMPP notifications are activated"; - } else { - newStatus = UserService.ActiveStatus.Inactive; - retValUpdated = "XMPP notifications are disabled"; - } - if (userService.getAllJIDs(user).stream().allMatch(jid -> userService.setActiveStatusForJID(jid, newStatus))) { - return CommandResult.fromString(retValUpdated); - } - return CommandResult.fromString("Error"); - } - @UserCommand(pattern = "^\\@([^\\s\\n\\+]+)(\\+?)$", - help = "@username+ - Show user's info and last 20 messages") - public CommandResult commandUser(User user, URI attachment, String... arguments) { - User blogUser = userService.getUserByName(arguments[0]); - int page = arguments[1].length(); - if (!blogUser.isAnonymous() && !blogUser.isBanned()) { - List<Integer> mids = messagesService.getUserBlog(blogUser.getUid(), 0, 0); - return CommandResult.fromString(String.format("Last messages from @%s:\n%s", arguments[0], - printMessages(user, mids, false))); - } - return CommandResult.fromString("User not found"); - } - @UserCommand(pattern = "^#(\\d+)(\\+?)$", help = "#1234 - Show message (#1234+ - message with replies)") - public CommandResult commandShow(@Nonnull User user, URI attachment, String... arguments) { - boolean showReplies = arguments[1].length() > 0; - int mid = NumberUtils.toInt(arguments[0], 0); - if (mid == 0) { - return CommandResult.fromString("Error"); - } - Optional<Message> msg = messagesService.getMessage(mid); - if (msg.isPresent()) { - if (showReplies) { - List<Message> replies = messagesService.getReplies(user, mid); - applicationEventPublisher.publishEvent( - new SystemEvent(this, SystemActivity.read(user, msg.get()))); - replies.add(0, msg.get()); - return CommandResult.fromString(replies.stream() - .map(PlainTextFormatter::formatPostSummary).collect(Collectors.joining("\n"))); - } - return CommandResult.fromString(PlainTextFormatter.formatPost(msg.get())); - } - return CommandResult.fromString("Message not found"); - } - @UserCommand(pattern = "^#(\\d+)\\/(\\d+)$", help = "#1234/5 - Show reply") - public CommandResult commandShowReply(User user, URI attachment, String... arguments) { - int mid = NumberUtils.toInt(arguments[0], 0); - int rid = NumberUtils.toInt(arguments[1], 0); - Message reply = messagesService.getReply(mid, rid); - if (reply != null) { - return CommandResult.fromString(PlainTextFormatter.formatPost(reply)); - } - return CommandResult.fromString("Reply not found"); - } - @UserCommand(pattern = "^\\*(\\S+)(\\+?)$", help = "*tag - Show last messages with tag") - public CommandResult commandShowTag(User user, URI attachment, String... arguments) { - if (StringUtils.isNotEmpty(attachment.toString())) { - // new message with tag - return CommandResult.fromString(StringUtils.EMPTY); - } - Tag tag = tagService.getTag(arguments[0], false); - if (tag != null) { - // TODO: synonyms - List<Integer> mids = messagesService.getTag(tag.TID, user.getUid(), 0, 10); - return CommandResult.fromString("Last messages with *" + tag.getName() + ":\n" + printMessages(user, mids, true)); - } - return CommandResult.fromString("Tag not found"); - } - @UserCommand(pattern = "^D #(\\d+)$", help = "D #1234 - Delete post", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandDeletePost(User user, URI attachment, String... args) { - int mid = Integer.valueOf(args[0]); - Optional<Message> message = messagesService.getMessage(mid); - if (message.isPresent() && messagesService.deleteMessage(user.getUid(), mid)) { - applicationEventPublisher.publishEvent(new DeleteMessageEvent(this, message.get())); - return CommandResult.fromString("Message deleted"); - } - return CommandResult.fromString("This is not your message"); - } - @UserCommand(pattern = "^D #(\\d+)(\\.|\\-|\\/)(\\d+)$", help = "D #1234/5 - Delete comment", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandDeleteReply(User user, URI attachment, String... args) { - int mid = Integer.parseInt(args[0]); - int rid = Integer.parseInt(args[2]); - if (messagesService.deleteReply(user.getUid(), mid, rid)) { - return CommandResult.fromString("Reply deleted"); - } else { - return CommandResult.fromString("This is not your reply"); - } - } - @UserCommand(pattern = "^(D L|DL|D LAST)$", help = "D L - Delete last message", patternFlags = Pattern.CASE_INSENSITIVE) - public CommandResult commandDeleteLast(User user, URI attachment, String... args) { - return CommandResult.fromString("Temporarily unavailable"); - } - @UserCommand(pattern = "^\\?\\s+\\@([a-zA-Z0-9\\-\\.\\@]+)\\s+([\\s\\S]+)$", help = "? @user string - search in user messages") - public CommandResult commandSearch(User user, URI attachment, String... args) { - return CommandResult.fromString("Temporarily unavailable"); - } - @UserCommand(pattern = "^\\?\\s+([\\s\\S]+)$", help = "? string - search in all messages") - public CommandResult commandSearchAll(User user, URI attachment, String... args) { - return CommandResult.fromString("Temporarily unavailable"); - } - @UserCommand(pattern = "^(#+)$", help = "# - Show last messages from your feed (## - second page, ...)") - public CommandResult commandMyFeed(User user, URI attachment, String... arguments) { - // number of # is the page count - int page = arguments[0].length() - 1; - List<Integer> mids = messagesService.getMyFeed(user.getUid(), page, false); - if (mids.size() > 0) { - return CommandResult.fromString("Your feed: \n" + printMessages(user, mids, true)); - } - return CommandResult.fromString("Your feed is empty"); - } - @UserCommand(pattern = "^(#|\\.)(\\d+)((\\.|\\-|\\/)(\\d+))?\\s([\\s\\S]+)?", - help = "#1234 *tag *tag2 - edit tags\n#1234 text - reply to message") - public CommandResult EditOrReply(@Nonnull User user, @Nonnull URI attachment, String... args) throws Exception { - int mid = NumberUtils.toInt(args[1]); - int rid = NumberUtils.toInt(args[4], 0); - String txt = StringUtils.defaultString(args[5]); - Optional<Message> msg = messagesService.getMessage(mid); - if (!msg.isPresent()) { - return CommandResult.fromString("Message not found"); - } - if (rid > 0) { - Message reply = messagesService.getReply(mid, rid); - if (reply == null) { - return CommandResult.fromString("Reply not found"); - } - } - Pair<String, List<Tag>> messageTags = tagService.fromString(txt); - if (user.getUid() == msg.get().getUser().getUid() && rid == 0 && messageTags.getRight().size() > 0) { - if (!CollectionUtils.isEqualCollection(tagService.updateTags(mid, messageTags.getRight()), msg.get().getTags())) { - return CommandResult.fromString("Tags are updated"); - } else { - return CommandResult.fromString("Tags are NOT updated (5 tags maximum?)"); - } - } else { - if (txt.length() > 4096) { - return CommandResult.fromString("Sorry, 4096 characters maximum."); - } - boolean haveAttachment = StringUtils.isNotEmpty(attachment.toString()); - String attachmentFName = null; - String attachmentType = null; - if (haveAttachment) { - if (attachment.getScheme().equals("juick")) { - attachmentFName = attachment.getHost(); - attachmentType = attachmentFName.substring(attachmentFName.length() - 3); - } else { - try { - attachmentFName = HttpUtils.downloadImage(attachment.toURL(), tmpDir).getHost(); - attachmentType = attachmentFName.substring(attachmentFName.length() - 3); - } catch (IOException e) { - logger.warn("Can not download {}", attachment.toURL()); - } - } - } - boolean attachmentProcessed = !haveAttachment || StringUtils.isNotEmpty(attachmentType); - String messageText = attachmentProcessed ? txt : String.format("%s %s", txt, attachment.toASCIIString()); - int newrid = messagesService.createReply(mid, rid, user, messageText, attachmentType); - if (newrid > 0) { - if (haveAttachment && attachmentProcessed) { - String fname = String.format("%d-%d.%s", mid, newrid, attachmentType); - imagesService.saveImageWithPreviews(attachmentFName, fname); - } - applicationEventPublisher.publishEvent( - new SystemEvent(this, SystemActivity.read(user, msg.get()))); - Message original = messagesService.getMessage(mid).orElseThrow(IllegalStateException::new); - subscriptionService.subscribeMessage(original, user); - Message reply = messagesService.getReply(mid, newrid); - if (reply.getUser().isAnonymous()) { - reply.setUser(activityPubManager.personToUser(reply.getUser().getUri())); - } else { - reply.getUser().setAvatar(webApp.getAvatarUrl(reply.getUser())); - } - applicationEventPublisher.publishEvent( - new SystemEvent(this, - SystemActivity.message(reply.getUser(), reply, - subscriptionService.getUsersSubscribedToComments(original, reply)))); - return CommandResult.build(reply, "Reply posted.\n#" + mid + "/" + newrid + " " - + "https://juick.com/m/" + mid + "#" + newrid, - String.format("[Reply](%s) posted", PlainTextFormatter.formatUrl(reply))); - } else { - return CommandResult.fromString("Message is read-only"); - } - } - } - - String printMessages(User visitor, List<Integer> mids, boolean crop) { - return messagesService.getMessages(visitor, mids).stream() - .sorted(Collections.reverseOrder()) - .map(PlainTextFormatter::formatPostSummary).collect(Collectors.joining("\n\n")); - } -} diff --git a/src/main/java/com/juick/server/EmailManager.java b/src/main/java/com/juick/server/EmailManager.java deleted file mode 100644 index 522f1db6..00000000 --- a/src/main/java/com/juick/server/EmailManager.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.juick.model.Message; -import com.juick.model.User; -import com.juick.www.api.SystemActivity; -import com.juick.server.util.HttpBadRequestException; -import com.juick.www.WebApp; -import com.juick.service.EmailService; -import com.juick.service.MessagesService; -import com.juick.service.UserService; -import com.juick.service.component.*; -import com.juick.util.MessageUtils; -import com.mitchellbosecke.pebble.PebbleEngine; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; - -import javax.annotation.Nonnull; -import javax.inject.Inject; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import java.util.*; - -import static com.juick.formatters.PlainTextFormatter.formatPost; -import static com.juick.formatters.PlainTextFormatter.formatUrl; - -public class EmailManager implements NotificationListener { - - public static final String MSGID_PATTERN = "\\.|@|<"; - - private static final Logger logger = LoggerFactory.getLogger(EmailManager.class); - @Inject - private EmailService emailService; - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private PebbleEngine pebbleEngine; - @Inject - private ObjectMapper jsonMapper; - @Inject - private WebApp webApp; - @Value("${service_email:}") - private String serviceEmail; - - @Override - public void processSystemEvent(@Nonnull SystemEvent systemEvent) { - var activity = systemEvent.getActivity(); - var msg = activity.getMessage(); - var subscribers = activity.getTo(); - if (activity.getType().equals(SystemActivity.ActivityType.message)) { - processMessage(msg, subscribers); - } - try { - var eventHeader = Collections.singletonMap("X-Event-Version", "1.0"); - sendEmail("noreply@juick.com", serviceEmail, "New system event", - jsonMapper.writeValueAsString(systemEvent.getActivity()), null, eventHeader); - } catch (JsonProcessingException e) { - logger.warn("JSON exception", e); - } - } - private void processMessage(Message msg, List<User> subscribedUsers) { - if (msg.isService()) { - return; - } - if (MessageUtils.isPM(msg)) { - String subject = String.format("Private message from %s", msg.getUser().getName()); - emailService.getEmails(msg.getTo().getUid(), true).forEach(email -> emailNotify(email, subject, msg)); - } else if (MessageUtils.isReply(msg)) { - Message originalMessage = messagesService.getMessage(msg.getMid()).orElseThrow(IllegalStateException::new); - String subject = String.format("New reply to %s", originalMessage.getUser().getName()); - subscribedUsers.stream() - .flatMap(user -> emailService.getEmails(user.getUid(), true).stream()) - .forEach(email -> emailNotify(email, subject, msg)); - } else { - String subject = String.format("New message from %s", msg.getUser().getName()); - subscribedUsers - .forEach(user -> emailService.getEmails(user.getUid(), true) - .forEach(email -> emailNotify(email, subject, msg))); - } - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - - } - - @Async - @EventListener - public void processMailVerificationEvent(MailVerificationEvent mailVerificationEvent) { - if (!sendEmail("noreply@juick.com", mailVerificationEvent.getEmail(), "Juick authorization link", - String.format("Follow link to attach this email to Juick account:\n" + - "http://juick.com/settings?page=auth-email&code=%s\n\n" + - "If you don't know, what this mean - just ignore this mail.\n", mailVerificationEvent.getCode()), - StringUtils.EMPTY, Collections.emptyMap())) { - throw new HttpBadRequestException(); - } - } - @Async - @EventListener - public void processAccountVerificationEvent(AccountVerificationEvent accountVerificationEvent) { - String signupUrl = String.format("Follow this link to create Juick account: https://juick.com/signup?type=email&hash=%s", accountVerificationEvent.getCode()); - sendEmail("noreply@juick.com", accountVerificationEvent.getEmail(), "Juick registration", signupUrl, StringUtils.EMPTY, Collections.emptyMap()); - } - - private void emailNotify(String email, String subject, Message msg) { - Map<String, String> headers = new HashMap<>(); - if (!MessageUtils.isPM(msg)) { - headers.put("Message-ID", String.format("<%d.%d@juick.com>", msg.getMid(), msg.getRid())); - } - if (MessageUtils.isReply(msg)) { - if (msg.getReplyto() > 0) { - Message replyto = messagesService.getReply(msg.getMid(), msg.getReplyto()); - headers.put("In-Reply-To", String.format("<%d.%d@juick.com>", replyto.getMid(), replyto.getRid())); - } else { - Message original = messagesService.getMessage(msg.getMid()).orElseThrow(IllegalStateException::new); - headers.put("In-Reply-To", String.format("<%d.%d@juick.com>", original.getMid(), original.getRid())); - } - } - String plainText = webApp.renderPlaintext(formatPost(msg), formatUrl(msg)).orElseThrow(IllegalStateException::new); - String hash = userService.getHashByUID(userService.getUserByEmail(email).getUid()); - String htmlText = webApp.renderHtml(MessageUtils.formatHtml(msg), formatUrl(msg), msg, hash).orElseThrow(IllegalStateException::new); - sendEmail(StringUtils.EMPTY, email, subject, plainText, htmlText, headers); - } - public boolean sendEmail(String from, String to, String subject, String textPart, String htmlPart, Map<String, String> messageHeaders) { - Properties prop = System.getProperties(); - prop.put("mail.smtp.starttls.enable", "true"); - Session session = Session.getDefaultInstance(prop); - try { - Transport transport = session.getTransport("smtp"); - MimeMessage message = new MimeMessage(session) { - protected void updateMessageID() throws MessagingException { - for (Map.Entry<String, String> entry: messageHeaders.entrySet()) { - setHeader(entry.getKey(), entry.getValue()); - } - } - }; - String fromAddress = StringUtils.isNotEmpty(from) ? from : "juick@juick.com"; - message.setFrom(fromAddress); - message.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to)); - message.setSubject(subject); - MimeBodyPart textBodyPart = new MimeBodyPart(); - textBodyPart.setContent(textPart, "text/plain; charset=UTF-8"); - - Multipart multipart = new MimeMultipart("alternative"); - multipart.addBodyPart(textBodyPart); - if (StringUtils.isNotBlank(htmlPart)) { - MimeBodyPart htmlBodyPart = new MimeBodyPart(); - htmlBodyPart.setContent(htmlPart, "text/html; charset=UTF-8"); - multipart.addBodyPart(htmlBodyPart); - } - message.setContent(multipart); - User emailUser = userService.getUserByEmail(to); - if (!emailUser.isAnonymous()) { - message.setHeader("List-Id", "Juick notifications <mail-notifications.juick.com>"); - message.setHeader("List-Post", "<mailto:juick@juick.com>"); - message.setHeader("List-Owner", "<mailto:support@juick.com>"); - message.setHeader("List-Archive", "<https://juick.com/>"); - message.setHeader("List-Unsubscribe", - String.format("<mailto:unsubscribe@juick.com>,<https://juick.com/settings/unsubscribe?hash=%s>", - userService.getHashByUID(emailUser.getUid()))); - message.setHeader("List-Unsubscribe-Post", "List-Unsubscribe=One-Click"); - } - message.saveChanges(); - transport.connect(); - transport.sendMessage(message, message.getAllRecipients()); - return true; - } catch (MessagingException ex) { - logger.error("mail exception", ex); - return false; - } - } -} diff --git a/src/main/java/com/juick/server/KeystoreManager.java b/src/main/java/com/juick/server/KeystoreManager.java deleted file mode 100644 index 0a66c2c8..00000000 --- a/src/main/java/com/juick/server/KeystoreManager.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.www.api.activity.model.objects.Person; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.Resource; -import org.springframework.util.Base64Utils; - -import javax.net.ssl.KeyManagerFactory; -import java.io.IOException; -import java.io.InputStream; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Arrays; - -public class KeystoreManager { - private static final Logger logger = LoggerFactory.getLogger("ActivityPub"); - - private String keystorePassword; - - private KeyStore ks; - - public KeystoreManager(Resource keystore, String keystorePassword) { - this.keystorePassword = keystorePassword; - try (InputStream ksIs = keystore.getInputStream()) { - ks = KeyStore.getInstance("PKCS12"); - ks.load(ksIs, keystorePassword.toCharArray()); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory - .getDefaultAlgorithm()); - kmf.init(ks, keystorePassword.toCharArray()); - } catch (IOException | KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException e) { - logger.error("Keystore error", e); - } - } - - private KeyPair getKeyPair() { - Key privateKey; - try { - privateKey = ks.getKey("1", keystorePassword.toCharArray()); - Certificate certificate = ks.getCertificate("1"); - return new KeyPair(certificate.getPublicKey(), (PrivateKey) privateKey); - } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) { - e.printStackTrace(); - } - return null; - } - public PrivateKey getPrivateKey() { - return getKeyPair().getPrivate(); - } - public PublicKey getPublicKey() { - return getKeyPair().getPublic(); - } - public String getPublicKeyPem() { - String[] key = Base64Utils.encodeToString(getKeyPair().getPublic().getEncoded()).split("(?<=\\G.{64})"); - return String.format("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", - String.join("\n", key)); - } - public static PublicKey publicKeyOf(Person person) { - String pubkeyPem = person.getPublicKey().getPublicKeyPem(); - String[] rawKey = pubkeyPem.split("\\n"); - String pubkeyData = String.join("", Arrays.asList(rawKey).subList(1, rawKey.length - 1)); - try{ - byte[] byteKey = Base64Utils.decodeFromString(pubkeyData); - X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey); - KeyFactory kf = KeyFactory.getInstance("RSA"); - return kf.generatePublic(X509publicKey); - } - catch(Exception e){ - e.printStackTrace(); - } - return null; - } -} diff --git a/src/main/java/com/juick/server/ServerManager.java b/src/main/java/com/juick/server/ServerManager.java deleted file mode 100644 index 1f11a9fb..00000000 --- a/src/main/java/com/juick/server/ServerManager.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.model.Message; -import com.juick.model.User; -import com.juick.model.AnonymousUser; -import com.juick.www.api.SystemActivity; -import com.juick.service.MessagesService; -import com.juick.service.SubscriptionService; -import com.juick.service.UserService; -import com.juick.service.component.*; -import com.juick.util.MessageUtils; -import org.apache.commons.collections4.ListUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import javax.annotation.Nonnull; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - -/** - * @author Ugnich Anton - */ -@Component -public class ServerManager implements NotificationListener { - private static final Logger logger = LoggerFactory.getLogger("Session"); - - @Inject - private MessagesService messagesService; - @Inject - private SubscriptionService subscriptionService; - @Inject - private UserService userService; - private final CopyOnWriteArrayList<EventSession> sessions = new CopyOnWriteArrayList<>(); - - @Value("${service_user:juick}") - private String serviceUsername; - - private User serviceUser; - - @PostConstruct - public void init() { - serviceUser = userService.getUserByName(serviceUsername); - } - - - private void onJuickPM(final User to, final Message jmsg) { - messageEvent(jmsg, Arrays.asList(to, jmsg.getUser())); - } - - private void onJuickMessagePost(final Message jmsg, List<User> subscribedUsers) { - messageEvent(jmsg, subscribedUsers); - messageEvent(jmsg, Collections.singletonList(AnonymousUser.INSTANCE)); - } - - private void onJuickMessageReply(final Message jmsg, final List<User> subscribedUsers) { - messageEvent(jmsg, subscribedUsers); - messageEvent(jmsg, Collections.singletonList(AnonymousUser.INSTANCE)); - } - - @Override - public void processSystemEvent(@Nonnull SystemEvent systemEvent) { - var activity = systemEvent.getActivity(); - var from = activity.getFrom(); - var message = activity.getMessage(); - var subscribers = activity.getTo(); - if (activity.getType().equals(SystemActivity.ActivityType.message)) { - processMessage(from, message, subscribers); - } else if (activity.getType().equals(SystemActivity.ActivityType.like)) { - if (from.equals(serviceUser)) { - processTop(message); - } - } - } - private void processMessage(User from, Message jmsg, List<User> subscribers) { - List<User> subscribedUsers = ListUtils.union(subscribers, Collections.singletonList(jmsg.getUser())); - if (jmsg.isService()) { - logger.info("Message read event from {} for {}", from.getUid(), jmsg.getMid()); - readEvent(jmsg, Collections.singletonList(serviceUser)); - return; - } - if (MessageUtils.isPM(jmsg)) { - onJuickPM(jmsg.getTo(), jmsg); - } else if (!MessageUtils.isReply(jmsg)) { - onJuickMessagePost(jmsg, subscribedUsers); - } else { - // to get quote and attachment - Message op = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); - subscriptionService.getUsersSubscribedToComments(op, jmsg, true).stream() - .filter(u -> userService.isReplyToBL(u, jmsg)) - .forEach(b -> messagesService.setLastReadComment(b, jmsg.getMid(), jmsg.getRid())); - onJuickMessageReply(jmsg, subscribedUsers); - } - messageEvent(jmsg, Collections.singletonList(serviceUser)); - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - - } - - private void processTop(Message msg) { - User topUser = msg.getUser(); - topEvent(msg, Arrays.asList(topUser, serviceUser)); - } - private void topEvent(Message msg, List<User> subscribers){ - sendSseEvent(msg, "top", subscribers); - } - - private void readEvent(Message msg, List<User> subscribers){ - sendSseEvent(msg, "read", subscribers); - } - - private void messageEvent(Message msg, List<User> subscribers) { - sendSseEvent(msg, "msg", subscribers); - } - - private void sendSseEvent(Message msg, String name, List<User> subscribers) { - List<EventSession> deadEmitters = new ArrayList<>(); - this.sessions.stream().filter(s -> subscribers.contains(s.user)).forEach(session -> { - try { - SseEmitter.SseEventBuilder builder = SseEmitter.event() - .name(name) - .data(msg); - session.getEmitter().send(builder); - } catch (Exception e) { - deadEmitters.add(session); - } - }); - this.sessions.removeAll(deadEmitters); - } - - public static class EventSession { - private final User user; - private final SseEmitter emitter; - - public EventSession(User user, SseEmitter sseEmitter) { - this.user = user; - this.emitter = sseEmitter; - } - - public User getUser() { - return user; - } - - public SseEmitter getEmitter() { - return emitter; - } - } - - public CopyOnWriteArrayList<EventSession> getSessions() { - return sessions; - } - @Scheduled(fixedRate = 30000) - public void ping() { - Message ping = new Message(); - ping.setService(true); - sendSseEvent(ping, "ping", getSessions().stream().map(s -> s.user) - .distinct().collect(Collectors.toList())); - } -} diff --git a/src/main/java/com/juick/server/SignatureManager.java b/src/main/java/com/juick/server/SignatureManager.java deleted file mode 100644 index 602b4285..00000000 --- a/src/main/java/com/juick/server/SignatureManager.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.juick.model.User; -import com.juick.model.AnonymousUser; -import com.juick.www.api.activity.model.Context; -import com.juick.www.api.activity.model.objects.Person; -import com.juick.www.api.webfinger.model.Account; -import com.juick.www.api.webfinger.model.Link; -import com.juick.service.UserService; -import com.juick.util.DateFormattersHolder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; -import org.tomitribe.auth.signatures.Signature; -import org.tomitribe.auth.signatures.Signer; -import org.tomitribe.auth.signatures.Verifier; -import rocks.xmpp.addr.Jid; - -import javax.inject.Inject; -import java.io.IOException; -import java.net.URI; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import static com.juick.www.api.activity.model.Context.ACTIVITY_MEDIA_TYPE; - -@Component -public class SignatureManager { - private static final Logger logger = LoggerFactory.getLogger("ActivityPub"); - @Inject - private KeystoreManager keystoreManager; - @Inject - private ObjectMapper jsonMapper; - @Inject - private UserService userService; - @Inject - private RestTemplate apClient; - - public void post(Person from, Person to, Context data) throws IOException { - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(to.getInbox()); - URI inbox = uriComponentsBuilder.build().toUri(); - Instant now = Instant.now(); - String requestDate = DateFormattersHolder.getHttpDateFormatter().format(now); - String host = inbox.getPort() > 0 ? String.format("%s:%d", inbox.getHost(), inbox.getPort()) : inbox.getHost(); - String signatureString = addSignature(from, host, "POST", inbox.getPath(), requestDate); - - HttpHeaders requestHeaders = new HttpHeaders(); - requestHeaders.add("Content-Type", Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE); - requestHeaders.add("Date", requestDate); - requestHeaders.add("Host", host); - requestHeaders.add("Signature", signatureString); - HttpEntity<Context> request = new HttpEntity<>(Context.build(data), requestHeaders); - logger.info("Sending context to {}: {}", to.getId(), jsonMapper.writeValueAsString(data)); - ResponseEntity<Void> response = apClient.postForEntity(inbox, request, Void.class); - logger.info("Remote response: {}", response.getStatusCodeValue()); - } - - public String addSignature(Person from, String host, String method, String path, String dateString) throws IOException { - return addSignature(from, host, method, path, dateString, keystoreManager); - } - - public String addSignature(Person from, String host, String method, String path, String dateString, KeystoreManager keystoreManager) throws IOException { - Signature templateSignature = new Signature(from.getPublicKey().getId(), "rsa-sha256", null, - "(request-target)", "host", "date"); - Map<String, String> headers = new HashMap<>(); - headers.put("host", host); - headers.put("date", dateString); - Signer signer = new Signer(keystoreManager.getPrivateKey(), templateSignature); - Signature signature = signer.sign(method, path, headers); - // remove "Signature: " from result - return signature.toString().substring(10); - } - - public User verifySignature(String method, String path, Map<String, String> headers) { - String signatureString = headers.get("signature"); - logger.info("Signature: {}", signatureString); - Signature signature = Signature.fromString(signatureString); - Optional<Context> context = getContext(UriComponentsBuilder.fromUriString(signature.getKeyId()) - .fragment(null).build().toUri()); - if (context.isPresent() && context.get() instanceof Person) { - Person person = (Person) context.get(); - Key key = KeystoreManager.publicKeyOf(person); - - Verifier verifier = new Verifier(key, signature); - try { - boolean result = verifier.verify(method, path, headers); - logger.info("signature of {} is valid: {}", signature.getKeyId(), result); - if (result) { - User user = new User(); - user.setUri(URI.create(person.getId())); - if (key.equals(keystoreManager.getPublicKey())) { - return userService.getUserByName(person.getName()); - } - return user; - } else { - return AnonymousUser.INSTANCE; - } - } catch (NoSuchAlgorithmException | SignatureException | IOException e) { - logger.warn("Invalid signature {}", signatureString); - } - } else { - logger.warn("Unknown keyId"); - } - return AnonymousUser.INSTANCE; - } - public Optional<Context> getContext(URI contextUri) { - try { - Context context = apClient.getForEntity(contextUri, Context.class).getBody(); - if (context == null) { - logger.warn("Cannot identify {}", contextUri); - return Optional.empty(); - } - return Optional.of(context); - } catch (Exception e) { - logger.warn("REST Exception on {}: {}", contextUri, e.getMessage()); - } - return Optional.empty(); - } - public Optional<Context> discoverPerson(String acct) { - Jid acctId = Jid.of(acct); - URI resourceUri = UriComponentsBuilder.fromPath("/.well-known/webfinger") - .host(acctId.getDomain()) - .scheme("https") - .queryParam("resource", String.format("%s", acctId.toEscapedString())).build().toUri(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Accept", "application/jrd+json"); - HttpEntity<Void> webfingerRequest = new HttpEntity<>(headers); - ResponseEntity<Account> response = apClient.exchange( - resourceUri, HttpMethod.GET, webfingerRequest, Account.class); - if (response.getStatusCode().is2xxSuccessful()) { - Account acctData = response.getBody(); - if (acctData != null) { - for (Link l : acctData.getLinks()) { - if (l.getRel().equals("self") && l.getType().equals(ACTIVITY_MEDIA_TYPE)) { - return getContext(URI.create(l.getHref())); - } - } - } - } - return Optional.empty(); - } -} diff --git a/src/main/java/com/juick/server/TelegramBotManager.java b/src/main/java/com/juick/server/TelegramBotManager.java deleted file mode 100644 index 3c38e5de..00000000 --- a/src/main/java/com/juick/server/TelegramBotManager.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.model.User; -import com.juick.model.AnonymousUser; -import com.juick.model.CommandResult; -import com.juick.www.api.SystemActivity; -import com.juick.server.util.HttpUtils; -import com.juick.service.MessagesService; -import com.juick.service.TelegramService; -import com.juick.service.UserService; -import com.juick.service.component.SystemEvent; -import com.juick.service.component.NotificationListener; -import com.juick.service.component.PingEvent; -import com.juick.util.MessageUtils; -import com.pengrad.telegrambot.Callback; -import com.pengrad.telegrambot.TelegramBot; -import com.pengrad.telegrambot.UpdatesListener; -import com.pengrad.telegrambot.model.Message; -import com.pengrad.telegrambot.model.MessageEntity; -import com.pengrad.telegrambot.model.PhotoSize; -import com.pengrad.telegrambot.model.Update; -import com.pengrad.telegrambot.model.request.ParseMode; -import com.pengrad.telegrambot.request.GetFile; -import com.pengrad.telegrambot.request.SendMessage; -import com.pengrad.telegrambot.request.SendPhoto; -import com.pengrad.telegrambot.request.SetWebhook; -import com.pengrad.telegrambot.response.GetFileResponse; -import com.pengrad.telegrambot.response.SendResponse; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import javax.annotation.Nonnull; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.util.*; - -import static com.juick.formatters.PlainTextFormatter.formatPost; -import static com.juick.formatters.PlainTextFormatter.formatUrl; - -/** - * Created by vt on 12/05/16. - */ -public class TelegramBotManager implements NotificationListener { - private static final Logger logger = LoggerFactory.getLogger("Telegram"); - - private TelegramBot bot; - - @Value("${telegram_api_url:}") - private String apiUrl; - @Value("${telegram_file_api_url:}") - private String fileApiUrl; - @Value("${telegram_webhook_url:}") - private String webhookUrl; - @Value("${telegram_token:12345678}") - private String telegramToken; - @Value("${telegram_debug:false}") - private boolean telegramDebug; - @Inject - private TelegramService telegramService; - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private CommandsManager commandsManager; - @Inject - private ApplicationEventPublisher applicationEventPublisher; - @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String tmpDir; - @Value("${service_user:juick}") - private String serviceUser; - - private static final String MSG_LINK = "🔗"; - - @PostConstruct - public void init() { - TelegramBot.Builder tgBuilder = new TelegramBot.Builder(telegramToken); - if (StringUtils.isNotEmpty(apiUrl)) { - tgBuilder.apiUrl(apiUrl).fileApiUrl(fileApiUrl); - } - bot = tgBuilder.build(); - if (!telegramDebug) { - try { - SetWebhook webhook = new SetWebhook().url(webhookUrl); - if (!bot.execute(webhook).isOk()) { - logger.error("error setting webhook"); - } - } catch (Exception e) { - logger.warn("couldn't initialize telegram bot", e); - } - } else { - bot.setUpdatesListener(updates -> { - logger.info("got updates: {}", updates); - updates.forEach(this::processUpdate); - return UpdatesListener.CONFIRMED_UPDATES_ALL; - }); - } - } - - public void processUpdate(Update update) { - Message message = update.message(); - if (update.message() == null) { - message = update.editedMessage(); - if (message == null) { - logger.error("error parsing telegram update: {}", update); - return; - } - User user_from = userService.getUserByUID(telegramService.getUser(message.chat().id())).orElse(AnonymousUser.INSTANCE); - logger.info("Found juick user {}", user_from.getUid()); - Optional<Pair<Integer, Integer>> originalMessageData = messagesService.findMessageByProperty("durovId", - String.valueOf(message.messageId())); - if (originalMessageData.isPresent()) { - int mid = originalMessageData.get().getLeft(); - int rid = originalMessageData.get().getRight(); - // TODO: this is copypaste from api, need switch to api - com.juick.model.Message originalMessage = rid == 0 ? messagesService.getMessage(mid).orElseThrow(IllegalStateException::new) - : messagesService.getReply(mid, rid); - User author = originalMessage.getUser(); - String newMessageText = StringUtils.defaultString(message.text()); - if (user_from.equals(author) && canUpdateMessage(originalMessage, newMessageText)) { - if (messagesService.updateMessage(mid, rid, newMessageText)) { - telegramNotify(message.chat().id(), "Message updated", new com.juick.model.Message()); - return; - } - } - telegramNotify(message.chat().id(), "Error updating message", new com.juick.model.Message()); - } - } else { - User user_from = userService.getUserByUID(telegramService.getUser(message.chat().id())).orElse(AnonymousUser.INSTANCE); - logger.info("Found juick user {}", user_from.getUid()); - - String username = message.from().username(); - if (username == null) { - username = message.from().firstName(); - } - if (!user_from.isAnonymous()) { - URI attachment = URI.create(StringUtils.EMPTY); - if (message.photo() != null) { - String fileId = Arrays.stream(message.photo()).max(Comparator.comparingInt(PhotoSize::fileSize)) - .orElse(new PhotoSize()).fileId(); - if (StringUtils.isNotEmpty(fileId)) { - GetFile request = new GetFile(fileId); - GetFileResponse response = bot.execute(request); - logger.info("got file {}", response.file()); - try { - URL fileURL = new URL(bot.getFullFilePath(response.file())); - attachment = HttpUtils.downloadImage(fileURL, tmpDir); - } catch (Exception e) { - logger.warn("attachment exception", e); - } - logger.info("received {}", attachment); - } - } - String text = message.text(); - if (StringUtils.isBlank(text)) { - text = message.caption(); - } - if (StringUtils.isBlank(text)) { - text = StringUtils.EMPTY; - } - if (StringUtils.isNotEmpty(text) || StringUtils.isNotEmpty(attachment.toString())) { - if (text.equalsIgnoreCase("LOGIN") - || text.equalsIgnoreCase("PING") - || text.equalsIgnoreCase("HELP") - || text.equalsIgnoreCase("/login") - || text.equalsIgnoreCase("/logout") - || text.equalsIgnoreCase("/start") - || text.equalsIgnoreCase("/help")) { - String msgUrl = "http://juick.com/login?hash=" + userService.getHashByUID(user_from.getUid()); - String msg = String.format("Hi, %s!\nYou can post messages and images to Juick there.\n" + - "Tap to [log into website](%s) to get more info", user_from.getName(), msgUrl); - telegramNotify(message.from().id().longValue(), msg, new com.juick.model.Message()); - } else { - Message replyMessage = message.replyToMessage(); - if (replyMessage != null) { - MessageEntity[] entities = replyMessage.entities(); - if (entities == null) { - entities = replyMessage.captionEntities(); - } - if (entities != null) { - Optional<MessageEntity> juickLink = Arrays.stream(entities) - .filter(this::isJuickLink) - .findFirst(); - if (juickLink.isPresent()) { - if (StringUtils.isNotEmpty(juickLink.get().url())) { - UriComponents uriComponents = UriComponentsBuilder.fromUriString( - juickLink.get().url()).build(); - String path = uriComponents.getPath(); - if (StringUtils.isNotEmpty(path) && path.length() > 1) { - int mid; - try { - mid = Integer.parseInt(path.substring(3)); - } catch (NumberFormatException e) { - logger.warn("wrong mid received"); - return; - } - String prefix = String.format("#%d ", mid); - if (StringUtils.isNotEmpty(uriComponents.getFragment())) { - int rid = Integer.parseInt(uriComponents.getFragment()); - prefix = String.format("#%d/%d ", mid, rid); - } - executeCommand(message.messageId(), message.from().id().longValue(), - user_from, prefix + text, attachment); - } else { - logger.warn("invalid path: {}", path); - } - } else { - logger.warn("invalid entity: {}", juickLink); - } - } else { - telegramNotify(message.from().id().longValue(), - "Can not reply to this message", replyMessage.messageId(), new com.juick.model.Message()); - } - } else { - telegramNotify(message.from().id().longValue(), - "Can not reply to this message", replyMessage.messageId(), new com.juick.model.Message()); - } - } else { - executeCommand(message.messageId(), message.from().id().longValue(), - user_from, text, attachment); - } - } - } - messagesService.getUnread(user_from).forEach(mid -> messagesService.setRead(user_from, mid)); - } else { - List<Long> chats = telegramService.getAnonymous(); - if (!chats.contains(message.chat().id())) { - logger.info("added chat with {}", username); - telegramService.createTelegramUser(message.from().id(), username); - } - telegramSignupNotify(message.from().id().longValue(), userService.getSignUpHashByTelegramID(message.from().id().longValue(), username)); - } - } - } - - /* - validate user input - */ - private boolean canUpdateMessage(com.juick.model.Message message, String newData) { - if (StringUtils.isEmpty(newData)) { - // allow empty text only when image is present - return StringUtils.isNotEmpty(message.getAttachmentType()); - } - return true; - } - - private void executeCommand(Integer messageId, Long userId, User user_from, String text, URI attachment) { - try { - CommandResult result = commandsManager.processCommand(user_from, text, attachment); - if (result.getNewMessage().isPresent()) { - com.juick.model.Message newMessage = result.getNewMessage().get(); - messagesService.setMessageProperty(newMessage.getMid(), newMessage.getRid(), "durovId", - String.valueOf(messageId)); - } - String messageTxt = StringUtils.isNotEmpty(result.getMarkdown()) ? result.getMarkdown() - : "Unknown error or unsupported command"; - telegramNotify(userId, messageTxt, new com.juick.model.Message()); - } catch (Exception e) { - logger.warn("telegram exception", e); - } - } - - private boolean isJuickLink(MessageEntity e) { - return e.offset() == 0 && e.type().equals(MessageEntity.Type.text_link) && e.length() == 2; - } - - public void telegramNotify(Long chatId, String msg, @Nonnull com.juick.model.Message source) { - telegramNotify(chatId, msg, 0, source); - } - - public void telegramNotify(Long chatId, String msg, Integer replyTo, @Nonnull com.juick.model.Message source) { - String attachment = MessageUtils.attachmentUrl(source); - boolean isSendTxt = true; - if (!StringUtils.isEmpty(attachment)) { - SendPhoto telegramPhoto = new SendPhoto(chatId, attachment); - if (replyTo > 0) { - telegramPhoto.replyToMessageId(replyTo); - } - telegramPhoto.parseMode(ParseMode.Markdown); - if(msg.length() < 1024) { - telegramPhoto.caption(msg); - isSendTxt = false; - } - bot.execute(telegramPhoto, new Callback<>() { - @Override - public void onResponse(SendPhoto request, SendResponse response) { - processTelegramResponse(chatId, response, source); - } - - @Override - public void onFailure(SendPhoto request, IOException e) { - logger.warn("telegram failure", e); - } - }); - } - if (isSendTxt){ - SendMessage telegramMessage = new SendMessage(chatId, msg); - if (replyTo > 0) { - telegramMessage.replyToMessageId(replyTo); - } - telegramMessage.parseMode(ParseMode.Markdown).disableWebPagePreview(true); - bot.execute(telegramMessage, new Callback<>() { - @Override - public void onResponse(SendMessage request, SendResponse response) { - processTelegramResponse(chatId, response, source); - } - - @Override - public void onFailure(SendMessage request, IOException e) { - logger.warn("telegram failure", e); - } - }); - } - } - - private void processTelegramResponse(Long chatId, SendResponse response, com.juick.model.Message source) { - int userId = telegramService.getUser(chatId); - if (!response.isOk()) { - if (response.errorCode() == 403) { - // remove from anonymous users - telegramService.getAnonymous().stream().filter(c -> c.equals(chatId)).findFirst().ifPresent( - d -> { - telegramService.deleteAnonymous(d); - logger.info("deleted {} chat", d); - } - ); - if (userId > 0) { - User userToDelete = userService.getUserByUID(userId) - .orElseThrow(IllegalStateException::new); - boolean status = telegramService.deleteTelegramUser(userToDelete.getUid()); - logger.info("deleting telegram id of @{} : {}", userToDelete.getName(), status); - } - } else { - logger.warn("error response, isOk: {}, errorCode: {}, description: {}", - response.isOk(), response.errorCode(), response.description()); - } - } else { - if (MessageUtils.isReply(source)) { - messagesService.setLastReadComment(userService.getUserByUID(userId) - .orElseThrow(IllegalStateException::new), source.getMid(), source.getRid()); - User user = userService.getUserByUID(userId).orElseThrow(IllegalStateException::new); - userService.updateLastSeen(user); - applicationEventPublisher.publishEvent( - new SystemEvent(this, SystemActivity.read(user, source))); - } - } - } - - public void telegramSignupNotify(Long telegramId, String hash) { - bot.execute(new SendMessage(telegramId, - String.format("You are subscribed to all Juick messages. " + - "[Create or link](http://juick.com/signup?type=durov&hash=%s) " + - "an existing Juick account to get your subscriptions and ability to post messages", hash)) - .parseMode(ParseMode.Markdown), new Callback<>() { - @Override - public void onResponse(SendMessage request, SendResponse response) { - logger.info("got response: {}", response.message()); - } - - @Override - public void onFailure(SendMessage request, IOException e) { - logger.warn("telegram failure", e); - } - }); - } - - @Override - public void processSystemEvent(SystemEvent systemEvent) { - var activity = systemEvent.getActivity(); - var type = activity.getType(); - if (type.equals(SystemActivity.ActivityType.message)) { - processMessage(activity.getMessage(), activity.getTo()); - } else if (type.equals(SystemActivity.ActivityType.like)) { - if (systemEvent.getActivity().getFrom().getName().equals(serviceUser)) { - processTop(systemEvent.getActivity().getMessage()); - } else { - processLike(activity.getFrom(), activity.getMessage(), activity.getTo()); - } - } else if (type.equals(SystemActivity.ActivityType.follow)) { - processFollow(activity.getFrom(), activity.getTo()); - } - } - private void processMessage(com.juick.model.Message jmsg, List<User> subscribedUsers) { - if (jmsg.isService()) { - return; - } - String msgUrl = formatUrl(jmsg); - if (MessageUtils.isPM(jmsg)) { - telegramService.getTelegramIdentifiers(Collections.singletonList(jmsg.getTo())) - .forEach(c -> telegramNotify(c, formatPost(jmsg, true), jmsg)); - } else if (MessageUtils.isReply(jmsg)) { - String fmsg = String.format("[%s](%s) %s", MSG_LINK, msgUrl, formatPost(jmsg, true)); - telegramService.getTelegramIdentifiers( - subscribedUsers - ).forEach(c -> telegramNotify(c, fmsg, jmsg)); - } else { - String msg = String.format("[%s](%s) %s", MSG_LINK, msgUrl, formatPost(jmsg, true)); - - List<Long> users = telegramService.getTelegramIdentifiers(subscribedUsers); - List<Long> chats = telegramService.getAnonymous(); - // registered subscribed users - - users.forEach(c -> telegramNotify(c, msg, jmsg)); - // anonymous - chats.stream().filter(u -> telegramService.getUser(u) == 0).forEach(c -> telegramNotify(c, msg, jmsg)); - } - } - - private void processLike(User liker, com.juick.model.Message message, List<User> subscribers) { - if (!liker.getName().equals(serviceUser)) { - logger.info("Like received in tg listener"); - if (!userService.isInBLAny(message.getUser().getUid(), liker.getUid())) { - telegramService.getTelegramIdentifiers(Collections.singletonList(message.getUser())) - .forEach(c -> telegramNotify(c, String.format("%s recommends your [post](%s)", - MessageUtils.getMarkdownUser(liker), formatUrl(message)), new com.juick.model.Message())); - } - telegramService.getTelegramIdentifiers(subscribers) - .forEach(c -> telegramNotify(c, String.format("%s recommends you someone's [post](%s)", - MessageUtils.getMarkdownUser(liker), formatUrl(message)), new com.juick.model.Message())); - } - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - - } - - private void processTop(com.juick.model.Message message) { - telegramService.getTelegramIdentifiers(Collections.singletonList(message.getUser())) - .forEach(c -> telegramNotify(c, String.format("Your [post](%s) became popular!", - formatUrl(message)), new com.juick.model.Message())); - } - - private void processFollow(User subscriber, List<User> target) { - telegramService.getTelegramIdentifiers(target) - .forEach(c -> telegramNotify(c, String.format("%s subscribed to your blog", - MessageUtils.getMarkdownUser(subscriber)), new com.juick.model.Message())); - } -} diff --git a/src/main/java/com/juick/server/TopManager.java b/src/main/java/com/juick/server/TopManager.java deleted file mode 100644 index 15abb6cc..00000000 --- a/src/main/java/com/juick/server/TopManager.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.model.Message; -import com.juick.model.Tag; -import com.juick.model.User; -import com.juick.www.api.SystemActivity; -import com.juick.service.MessagesService; -import com.juick.service.UserService; -import com.juick.service.component.SystemEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -@Component -public class TopManager { - private static Logger logger = LoggerFactory.getLogger(TopManager.class); - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private ApplicationEventPublisher applicationEventPublisher; - - @Value("${service_user:juick}") - private String serviceUsername; - - private User serviceUser; - - @PostConstruct - public void init() { - serviceUser = userService.getUserByName(serviceUsername); - } - - @Scheduled(fixedRate = 3600000) - public void updateTop() { - messagesService.getPopularCandidates().forEach(m -> { - Message jmsg = messagesService.getMessage(m).orElseThrow(IllegalStateException::new); - logger.info("added {} to popular", m); - messagesService.setMessagePopular(m, 1); - List<String> tags = jmsg.getTags().stream().map(Tag::getName).map(String::toLowerCase).collect(Collectors.toList()); - if (!tags.contains("juick")) { - applicationEventPublisher.publishEvent(new SystemEvent(this, - SystemActivity.like(serviceUser, jmsg, Collections.emptyList()))); - } - }); - } -} diff --git a/src/main/java/com/juick/server/TwitterManager.java b/src/main/java/com/juick/server/TwitterManager.java deleted file mode 100644 index e424784c..00000000 --- a/src/main/java/com/juick/server/TwitterManager.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.model.Message; -import com.juick.model.User; -import com.juick.www.api.SystemActivity; -import com.juick.service.UserService; -import com.juick.service.component.*; -import com.juick.service.CrosspostService; -import com.juick.util.MessageUtils; -import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import twitter4j.TwitterFactory; -import twitter4j.conf.ConfigurationBuilder; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - -/** - * @author Ugnich Anton - */ -@Component -public class TwitterManager implements NotificationListener { - - private static Logger logger = LoggerFactory.getLogger(TwitterManager.class); - - @Inject - private CrosspostService crosspostService; - - @Value("${twitter_consumer_key:12345678}") - private String twitter_consumer_key; - @Value("${twitter_consumer_secret:secret}") - private String twitter_consumer_secret; - @Inject - private UserService userService; - - @Value("${service_user:juick}") - private String serviceUsername; - - private User serviceUser; - - @PostConstruct - public void init() { - serviceUser = userService.getUserByName(serviceUsername); - } - - void twitterPost(final Message jmsg) { - crosspostService.getTwitterToken(jmsg.getUser().getUid()).ifPresent(t -> { - String status = MessageUtils.getMessageHashTags(jmsg) + StringUtils.defaultString(jmsg.getText()); - if (status.length() > 253) { - status = status.substring(0, 252) + "…"; - } - status += " http://juick.com/" + jmsg.getMid(); - ConfigurationBuilder configurationBuilder = new ConfigurationBuilder() - .setDebugEnabled(true) - .setOAuthConsumerKey(twitter_consumer_key) - .setOAuthConsumerSecret(twitter_consumer_secret); - TwitterFactory tf = new TwitterFactory(configurationBuilder - .setOAuthAccessToken(t.getToken()) - .setOAuthAccessTokenSecret(t.getSecret()).build()); - try { - tf.getInstance().updateStatus(status); - } catch (Exception e) { - logger.info("Twitter exception", e); - } - }); - } - - @Override - public void processSystemEvent(SystemEvent systemEvent) { - var activity = systemEvent.getActivity(); - var msg = activity.getMessage(); - if (activity.getType().equals(SystemActivity.ActivityType.message)) { - if (MessageUtils.isPM(msg) || MessageUtils.isReply(msg) || msg.isService()) { - return; - } - if (StringUtils.isNotEmpty(crosspostService.getTwitterName(msg.getUser().getUid()))) { - if (msg.getTags().stream().noneMatch(t -> t.getName().equals("notwitter"))) { - twitterPost(msg); - } - } - } else if (activity.getType().equals(SystemActivity.ActivityType.like)) { - if (activity.getFrom().getName().equals(serviceUsername)) { - processTop(msg); - } - } - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - - } - - private void processTop(Message message) { - Message jmsg = SerializationUtils.clone(message); - jmsg.setUser(serviceUser); - twitterPost(jmsg); - } -} diff --git a/src/main/java/com/juick/server/Utils.java b/src/main/java/com/juick/server/Utils.java deleted file mode 100644 index 58662c71..00000000 --- a/src/main/java/com/juick/server/Utils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import javax.servlet.http.HttpServletRequest; -import java.util.Optional; - -/** - * - * @author Ugnich Anton - */ -public class Utils { - - - public static String encodeSphinx(String str) { - return str.replaceAll("@", "\\\\@") - .replaceAll("\\'", "\\\\'") - .replaceAll("=", "\\\\\\\\="); - } - /** - * Returns the viewName to return for coming back to the sender url - * - * @param request Instance of {@link HttpServletRequest} or use an injected instance - * @return Optional with the view name. Recomended to use an alternativa url with - * {@link Optional#orElse(java.lang.Object)} - */ - public static Optional<String> getPreviousPageByRequest(HttpServletRequest request) - { - return Optional.ofNullable(request.getHeader("Referer")); - } -} diff --git a/src/main/java/com/juick/server/XMPPManager.java b/src/main/java/com/juick/server/XMPPManager.java deleted file mode 100644 index 32f1b94e..00000000 --- a/src/main/java/com/juick/server/XMPPManager.java +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server; - -import com.juick.model.User; -import com.juick.formatters.PlainTextFormatter; -import com.juick.model.CommandResult; -import com.juick.www.api.SystemActivity; -import com.juick.www.WebApp; -import com.juick.server.xmpp.iq.MessageQuery; -import com.juick.service.MessagesService; -import com.juick.service.PMQueriesService; -import com.juick.service.UserService; -import com.juick.service.component.*; -import com.juick.util.MessageUtils; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import rocks.xmpp.addr.Jid; -import rocks.xmpp.core.XmppException; -import rocks.xmpp.core.session.Extension; -import rocks.xmpp.core.session.XmppSession; -import rocks.xmpp.core.session.XmppSessionConfiguration; -import rocks.xmpp.core.session.debug.LogbackDebugger; -import rocks.xmpp.core.stanza.AbstractIQHandler; -import rocks.xmpp.core.stanza.model.IQ; -import rocks.xmpp.core.stanza.model.Message; -import rocks.xmpp.core.stanza.model.Presence; -import rocks.xmpp.core.stanza.model.StanzaError; -import rocks.xmpp.core.stanza.model.client.ClientMessage; -import rocks.xmpp.core.stanza.model.client.ClientPresence; -import rocks.xmpp.core.stanza.model.errors.Condition; -import rocks.xmpp.extensions.caps.EntityCapabilitiesManager; -import rocks.xmpp.extensions.component.accept.ExternalComponent; -import rocks.xmpp.extensions.disco.ServiceDiscoveryManager; -import rocks.xmpp.extensions.disco.model.info.Identity; -import rocks.xmpp.extensions.filetransfer.FileTransfer; -import rocks.xmpp.extensions.filetransfer.FileTransferManager; -import rocks.xmpp.extensions.nick.model.Nickname; -import rocks.xmpp.extensions.oob.model.x.OobX; -import rocks.xmpp.extensions.ping.PingManager; -import rocks.xmpp.extensions.receipts.MessageDeliveryReceiptsManager; -import rocks.xmpp.extensions.vcard.temp.model.VCard; -import rocks.xmpp.extensions.version.SoftwareVersionManager; -import rocks.xmpp.extensions.version.model.SoftwareVersion; - -import javax.annotation.Nonnull; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; - -/** - * @author ugnich - */ -public class XMPPManager implements NotificationListener { - - private static final Logger logger = LoggerFactory.getLogger("XMPP"); - - private ExternalComponent xmpp; - @Inject - private CommandsManager commandsManager; - @Value("${xmppbot_jid:juick@localhost}") - private Jid jid; - @Value("${hostname:localhost}") - private String componentName; - @Value("${component_port:5347}") - private int componentPort; - @Value("${component_host:localhost}") - private String componentHost; - @Value("${xmpp_password:secret}") - private String password; - @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String tmpDir; - @Value("classpath:juick.png") - private Resource vCardImage; - - @Inject - private MessagesService messagesService; - @Inject - private UserService userService; - @Inject - private PMQueriesService pmQueriesService; - @Inject - private Executor applicationTaskExecutor; - @Value("${service_user:juick}") - private String serviceUsername; - @Inject - private WebApp webApp; - - private User serviceUser; - - @PostConstruct - public void init() { - logger.info("xmpp component start connecting to {}", componentPort); - XmppSessionConfiguration configuration = XmppSessionConfiguration.builder() - .extensions(Extension.of(com.juick.model.Message.class), Extension.of(MessageQuery.class)) - .debugger(LogbackDebugger.class) - .defaultResponseTimeout(Duration.ofMillis(120000)) - .build(); - xmpp = ExternalComponent.create(componentName, password, configuration, componentHost, componentPort); - ServiceDiscoveryManager serviceDiscoveryManager = xmpp.getManager(ServiceDiscoveryManager.class); - serviceDiscoveryManager.addIdentity(Identity.clientBot().withName("Juick")); - EntityCapabilitiesManager entityCapabilitiesManager = xmpp.getManager(EntityCapabilitiesManager.class); - entityCapabilitiesManager.setNode("https://juick.com/caps"); - MessageDeliveryReceiptsManager messageDeliveryReceiptsManager = xmpp.getManager(MessageDeliveryReceiptsManager.class); - messageDeliveryReceiptsManager.setEnabled(true); - PingManager pingManager = xmpp.getManager(PingManager.class); - pingManager.setEnabled(true); - SoftwareVersionManager softwareVersionManager = xmpp.getManager(SoftwareVersionManager.class); - softwareVersionManager.setSoftwareVersion(new SoftwareVersion("Juick", "2.x", - System.getProperty("os.name", "generic"))); - VCard vCard = new VCard(); - vCard.setFormattedName("Juick"); - vCard.setBirthday(LocalDate.of(2008, 10, 22)); - try { - vCard.setUrl(new URL("http://juick.com/")); - vCard.setPhoto(new VCard.Image("image/png", IOUtils.toByteArray(vCardImage.getInputStream()))); - } catch (MalformedURLException e) { - logger.error("invalid url", e); - } catch (IOException e) { - logger.warn("invalid resource", e); - } - xmpp.addIQHandler(MessageQuery.class, iq -> { - Message warningMessage = new Message(iq.getFrom(), Message.Type.CHAT); - warningMessage.setFrom(jid); - warningMessage.setBody("Your XMPP client constantly polls us with XMPP query which is unsupported for years, please find http://juick.com/query#messages in your client code and remove that"); - xmpp.send(warningMessage); - return iq.createError(new StanzaError(Condition.BAD_REQUEST, "Please stop this spam")); - }); - xmpp.addIQHandler(VCard.class, new AbstractIQHandler(IQ.Type.GET) { - @Override - protected IQ processRequest(IQ iq) { - if (iq.getTo().equals(jid) || iq.getTo().asBareJid().equals(jid.asBareJid()) - || iq.getTo().asBareJid().toEscapedString().equals(jid.getDomain())) { - return iq.createResult(vCard); - } - User user = userService.getUserByName(iq.getTo().getLocal()); - if (!user.isAnonymous()) { - User info = userService.getUserInfo(user); - VCard userVCard = new VCard(); - userVCard.setFormattedName(info.getFullName()); - userVCard.setNickname(user.getName()); - try { - userVCard.setPhoto(new VCard.Image(URI.create(webApp.getAvatarUrl(user)))); - if (info.getUrl() != null) { - userVCard.setUrl(new URL(info.getUrl())); - } - } catch (MalformedURLException e) { - logger.warn("url exception", e); - } - return iq.createResult(userVCard); - } - return iq.createError(Condition.BAD_REQUEST); - } - }); - xmpp.addInboundMessageListener(e -> { - ClientMessage result = incomingMessage(e.getMessage()); - if (result != null) { - xmpp.send(result); - } - }); - FileTransferManager fileTransferManager = xmpp.getManager(FileTransferManager.class); - fileTransferManager.addFileTransferOfferListener(e -> { - try { - List<String> allowedTypes = new ArrayList<>() {{ - add("png"); - add("jpg"); - }}; - String attachmentExtension = FilenameUtils.getExtension(e.getName()).toLowerCase(); - String targetFilename = String.format("%s.%s", - DigestUtils.md5Hex(String.format("%s-%s", - e.getInitiator().toString(), e.getSessionId()).getBytes()), attachmentExtension); - if (allowedTypes.contains(attachmentExtension)) { - Path filePath = Paths.get(tmpDir, targetFilename); - FileTransfer ft = e.accept(filePath).get(); - ft.addFileTransferStatusListener(st -> { - logger.debug("{}: received {} of {}", e.getName(), st.getBytesTransferred(), e.getSize()); - if (st.getStatus().equals(FileTransfer.Status.COMPLETED)) { - logger.info("transfer completed"); - try { - Jid initiator = e.getInitiator(); - ClientMessage result = incomingMessageJuick( - userService.getUserByJID(initiator.asBareJid().toEscapedString()), initiator, - jid.getLocal(), StringUtils.defaultString(e.getDescription()).trim(), URI.create(String.format("juick://%s", targetFilename))); - if (result != null) { - xmpp.send(result); - } - } catch (Exception e1) { - logger.error("ft error", e1); - } - - } else if (st.getStatus().equals(FileTransfer.Status.FAILED)) { - logger.info("transfer failed", ft.getException()); - Message msg = new Message(); - msg.setType(Message.Type.CHAT); - msg.setFrom(jid); - msg.setTo(e.getInitiator()); - msg.setBody("File transfer failed, please report to us"); - xmpp.sendMessage(msg); - } else if (st.getStatus().equals(FileTransfer.Status.CANCELED)) { - logger.info("transfer cancelled"); - } - }); - ft.transfer(); - logger.info("transfer started"); - } else { - e.reject(); - logger.info("transfer rejected"); - } - } catch (IOException | InterruptedException | ExecutionException e1) { - logger.error("ft error", e1); - } - }); - xmpp.addConnectionListener(event -> { - if (event.getType().equals(rocks.xmpp.core.session.ConnectionEvent.Type.RECONNECTION_SUCCEEDED)) { - logger.info("component connected"); - } - }); - xmpp.addSessionStatusListener(event -> { - logger.info("event: " + event.getStatus(), event.getThrowable()); - if (event.getStatus().equals(XmppSession.Status.AUTHENTICATED)) { - logger.info("Authenticated, broadcasting..."); - broadcastPresence(null); - } - }); - xmpp.addInboundPresenceListener(event -> { - incomingPresence(event.getPresence()); - }); - applicationTaskExecutor.execute(() -> { - try { - xmpp.connect(); - } catch (XmppException e) { - logger.warn("xmpp exception", e); - } - }); - serviceUser = userService.getUserByName(serviceUsername); - } - - private void sendJuickMessage(com.juick.model.Message jmsg, List<User> users) { - List<String> jids = new ArrayList<>(); - - for (User user : users) { - jids.addAll(userService.getJIDsbyUID(user.getUid())); - } - com.juick.model.Message fullMsg = messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); - String txt = "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(fullMsg) + "\n"; - String attachmentUrl = MessageUtils.attachmentUrl(fullMsg); - if (StringUtils.isNotEmpty(attachmentUrl)) { - txt += attachmentUrl + "\n"; - } - txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; - txt += "#" + jmsg.getMid() + " http://juick.com/m/" + jmsg.getMid(); - - Nickname nick = new Nickname("@" + jmsg.getUser().getName()); - - Message msg = new Message(); - msg.setFrom(jid); - msg.setBody(txt); - msg.setType(Message.Type.CHAT); - msg.setThread("juick-" + jmsg.getMid()); - msg.addExtension(jmsg); - msg.addExtension(nick); - if (StringUtils.isNotEmpty(attachmentUrl)) { - try { - OobX oob = new OobX(new URI(attachmentUrl)); - msg.addExtension(oob); - } catch (URISyntaxException e) { - logger.warn("uri exception", e); - } - } - for (String jid : jids) { - msg.setTo(Jid.of(jid)); - xmpp.send(ClientMessage.from(msg)); - } - } - - private void sendJuickComment(com.juick.model.Message jmsg, List<User> users) { - String replyQuote; - String replyTo; - - com.juick.model.Message replyMessage = jmsg.getReplyto() > 0 ? messagesService.getReply(jmsg.getMid(), jmsg.getReplyto()) - : messagesService.getMessage(jmsg.getMid()).orElseThrow(IllegalStateException::new); - replyTo = replyMessage.getUser().getName(); - com.juick.model.Message fullReply = messagesService.getReply(jmsg.getMid(), jmsg.getRid()); - replyQuote = StringUtils.defaultString(fullReply.getReplyQuote()); - - String txt = "Reply by @" + jmsg.getUser().getName() + ":\n" + replyQuote + "\n@" + replyTo + " "; - String attachmentUrl = MessageUtils.attachmentUrl(fullReply); - if (StringUtils.isNotEmpty(attachmentUrl)) { - txt += attachmentUrl + "\n"; - } - txt += StringUtils.defaultString(jmsg.getText()) + "\n\n" + "#" + jmsg.getMid() + "/" + jmsg.getRid() + " http://juick.com/m/" + jmsg.getMid() + "#" + jmsg.getRid(); - - Message msg = new Message(); - msg.setFrom(jid); - msg.setBody(txt); - msg.setType(Message.Type.CHAT); - msg.addExtension(jmsg); - for (User user : users) { - for (String jid : userService.getJIDsbyUID(user.getUid())) { - msg.setTo(Jid.of(jid)); - xmpp.send(ClientMessage.from(msg)); - } - } - } - - @Override - public void processSystemEvent(SystemEvent systemEvent) { - var activity = systemEvent.getActivity(); - var type = activity.getType(); - if (type.equals(SystemActivity.ActivityType.message)) { - processMessage(activity.getMessage(), activity.getTo()); - } else if (type.equals(SystemActivity.ActivityType.like)) { - if (systemEvent.getActivity().getFrom().equals(serviceUser)) { - processTop(systemEvent.getActivity().getMessage()); - } else { - processLike(activity.getFrom(), activity.getMessage(), activity.getTo()); - } - } - } - private void processMessage(com.juick.model.Message msg, List<User> subscribers) { - if (msg.isService()) { - return; - } - if (MessageUtils.isPM(msg)) { - userService.getJIDsbyUID(msg.getTo().getUid()) - .forEach(userJid -> { - Message mm = new Message(); - mm.setTo(Jid.of(userJid)); - mm.setType(Message.Type.CHAT); - boolean inroster = pmQueriesService.havePMinRoster(msg.getUser().getUid(), userJid); - if (inroster) { - mm.setFrom(Jid.of(msg.getUser().getName(), "juick.com", "Juick")); - mm.setBody(msg.getText()); - } else { - mm.setFrom(jid); - mm.setBody("Private message from @" + msg.getUser().getName() + ":\n" + msg.getText()); - } - xmpp.send(ClientMessage.from(mm)); - }); - } else if (MessageUtils.isReply(msg)) { - sendJuickComment(msg, subscribers); - } - else { - sendJuickMessage(msg, subscribers); - } - } - - private ClientMessage makeReply(Jid jidTo, String txt) { - Message reply = new Message(); - reply.setFrom(jid); - reply.setTo(jidTo); - reply.setType(Message.Type.CHAT); - reply.setBody(txt); - return ClientMessage.from(reply); - } - - public void processLike(User liker, com.juick.model.Message jmsg, List<User> users) { - if (!userService.isInBLAny(jmsg.getUser().getUid(), liker.getUid())) { - userService.getJIDsbyUID(jmsg.getUser().getUid()).forEach(authorJid -> { - Message xmppMessage = new Message(); - xmppMessage.setFrom(jid); - xmppMessage.setTo(Jid.of(authorJid)); - xmppMessage.setType(Message.Type.CHAT); - xmppMessage.addExtension(jmsg); - xmppMessage.setBody(String.format("%s recommended your post #%d. %s", - liker.getName(), jmsg.getMid(), PlainTextFormatter.formatUrl(jmsg))); - xmpp.send(ClientMessage.from(xmppMessage)); - }); - } - - String txt = "Recommended by @" + liker.getName() + ":\n"; - txt += "@" + jmsg.getUser().getName() + ":" + MessageUtils.getTagsString(jmsg) + "\n"; - String attachmentUrl = MessageUtils.attachmentUrl(jmsg); - if (StringUtils.isNotEmpty(attachmentUrl)) { - txt += attachmentUrl + "\n"; - } - txt += StringUtils.defaultString(jmsg.getText()) + "\n\n"; - txt += "#" + jmsg.getMid(); - if (jmsg.getReplies() > 0) { - if (jmsg.getReplies() % 10 == 1 && jmsg.getReplies() % 100 != 11) { - txt += " (" + jmsg.getReplies() + " reply)"; - } else { - txt += " (" + jmsg.getReplies() + " replies)"; - } - } - txt += " http://juick.com/m/" + jmsg.getMid(); - - Nickname nick = new Nickname("@" + jmsg.getUser().getName()); - - Message msg = new Message(); - msg.setFrom(jid); - msg.setBody(txt); - msg.setType(Message.Type.CHAT); - msg.setThread("juick-" + jmsg.getMid()); - msg.addExtension(jmsg); - msg.addExtension(nick); - if (StringUtils.isNotEmpty(attachmentUrl)) { - try { - OobX oob = new OobX(new URI(attachmentUrl)); - msg.addExtension(oob); - } catch (URISyntaxException e) { - logger.warn("uri exception", e); - } - } - - for (User user : users) { - for (String jid : userService.getJIDsbyUID(user.getUid())) { - msg.setTo(Jid.of(jid)); - xmpp.send(ClientMessage.from(msg)); - } - } - } - - @Override - public void processPingEvent(PingEvent pingEvent) { - userService.getJIDsbyUID(pingEvent.getPinger().getUid()) - .forEach(userJid -> { - Presence p = new Presence(Jid.of(userJid)); - p.setFrom(jid); - p.setPriority((byte) 10); - xmpp.send(ClientPresence.from(p)); - }); - } - - public void processTop(com.juick.model.Message message) { - try { - commandsManager.processCommand(serviceUser, String.format("! #%d", message.getMid()), URI.create(StringUtils.EMPTY)); - } catch (Exception e) { - logger.warn("XMPP error", e); - } - } - - private void incomingPresence(Presence p) { - final String username = p.getTo().getLocal(); - final boolean toJuick = username.equals(jid.getLocal()); - - if (p.getType() == null) { - Presence reply = new Presence(); - reply.setFrom(p.getTo().asBareJid()); - reply.setTo(p.getFrom().asBareJid()); - reply.setType(Presence.Type.UNSUBSCRIBE); - xmpp.send(ClientPresence.from(reply)); - } else if (p.getType().equals(Presence.Type.PROBE)) { - int uid_to = 0; - if (!toJuick) { - uid_to = userService.getUIDbyName(username); - } else { - User visitor = userService.getUserByJID(p.getFrom().asBareJid().toEscapedString()); - if (visitor != null) { - userService.updateLastSeen(visitor); - } - } - - if (toJuick || uid_to > 0) { - Presence reply = new Presence(); - reply.setFrom(p.getTo().withResource(jid.getResource())); - reply.setTo(p.getFrom()); - reply.setPriority((byte)10); - if (!userService.getActiveJIDs().contains(p.getFrom().asBareJid().toEscapedString())) { - reply.setStatus("Send ON to enable notifications"); - } - xmpp.send(ClientPresence.from(reply)); - } else { - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.ERROR); - reply.setId(p.getId()); - reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); - xmpp.send(ClientPresence.from(reply)); - } - } else if (p.getType().equals(Presence.Type.SUBSCRIBE)) { - boolean canSubscribe = false; - if (toJuick) { - canSubscribe = true; - } else { - int uid_to = userService.getUIDbyName(username); - if (uid_to > 0) { - pmQueriesService.addPMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); - canSubscribe = true; - } - } - if (canSubscribe) { - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.SUBSCRIBED); - xmpp.send(ClientPresence.from(reply)); - - reply.setFrom(reply.getFrom().withResource(jid.getResource())); - reply.setPriority((byte) 10); - reply.setType(null); - xmpp.send(ClientPresence.from(reply)); - } else { - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.ERROR); - reply.setId(p.getId()); - reply.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); - xmpp.send(ClientPresence.from(reply)); - } - } else if (p.getType().equals(Presence.Type.UNSUBSCRIBE)) { - if (!toJuick) { - int uid_to = userService.getUIDbyName(username); - if (uid_to > 0) { - pmQueriesService.removePMinRoster(uid_to, p.getFrom().asBareJid().toEscapedString()); - } - } - - Presence reply = new Presence(); - reply.setFrom(p.getTo()); - reply.setTo(p.getFrom()); - reply.setType(Presence.Type.UNSUBSCRIBED); - xmpp.send(ClientPresence.from(reply)); - } - } - - public ClientMessage incomingMessage(Message msg) { - if (msg.getType() != null && msg.getType().equals(Message.Type.ERROR)) { - StanzaError error = msg.getError(); - if (error != null && error.getCondition().equals(Condition.RESOURCE_CONSTRAINT)) { - // offline query is full, deactivating this jid - if (userService.setActiveStatusForJID(msg.getFrom().toEscapedString(), UserService.ActiveStatus.Inactive)) { - logger.info("{} is inactive now", msg.getFrom()); - return null; - } - } - return null; - } - Jid to = msg.getTo(); - if (to.getDomain().equals(xmpp.getDomain().toEscapedString()) || to.equals(this.jid)) { - User user_from = userService.getUserByJID(msg.getFrom().asBareJid().toEscapedString()); - if ((user_from == null || user_from.isAnonymous()) && !msg.getFrom().equals(jid)) { - String signuphash = userService.getSignUpHashByJID(msg.getFrom().asBareJid().toEscapedString()); - return makeReply(msg.getFrom(), "Для того, чтобы начать пользоваться сервисом, пожалуйста пройдите быструю регистрацию: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nЕсли у вас уже есть учетная запись на Juick, вы сможете присоединить этот JabberID к ней.\n\nTo start using Juick, please sign up: http://juick.com/signup?type=xmpp&hash=" + signuphash + "\nIf you already have an account on Juick, you will be proposed to attach this JabberID to your existing account."); - } - URI attachment = URI.create(StringUtils.EMPTY); - OobX oobX = msg.getExtension(OobX.class); - if (oobX != null) { - attachment = oobX.getUri(); - } - try { - return incomingMessageJuick(user_from, msg.getFrom(), msg.getTo().getLocal(), StringUtils.defaultString(msg.getBody()).trim(), attachment); - } catch (Exception e1) { - logger.warn("message exception", e1); - } - } - ClientMessage errorMessage = ClientMessage.from(msg); - errorMessage.setError(new StanzaError(StanzaError.Type.CANCEL, Condition.ITEM_NOT_FOUND)); - return errorMessage; - } - private ClientMessage incomingMessageJuick(User user_from, Jid from, String to, String command, @Nonnull URI attachment) { - if (StringUtils.isBlank(command) && attachment.toString().isEmpty()) { - return null; - } - - messagesService.getUnread(user_from).forEach(mid -> messagesService.setRead(user_from, mid)); - - int commandlen = command.length(); - - // COMPATIBILITY - if (commandlen > 7 && command.substring(0, 3).equalsIgnoreCase("PM ")) { - command = command.substring(3); - } - - if (!jid.getLocal().equals(to)) { - // PM - if (!StringUtils.isEmpty(command)) { - commandsManager.commandPM(user_from, null, to, command); - return null; - } - } - - try { - CommandResult result = commandsManager.processCommand(user_from, command, attachment); - if (StringUtils.isNotBlank(result.getText())) { - return makeReply(from, result.getText()); - } - } catch (Exception e) { - logger.warn("xmpp command exception", e); - return makeReply(from, "Error processing command"); - } - return null; - } - - private void broadcastPresence(Presence.Type type) { - Presence presence = new Presence(); - presence.setFrom(jid); - if (type != null) { - presence.setType(type); - } - userService.getActiveJIDs().forEach(j -> { - try { - presence.setTo(Jid.of(j)); - xmpp.send(ClientPresence.from(presence)); - } catch (IllegalArgumentException ex) { - logger.warn("Invalid jid: {}", j, ex); - } - }); - } - - @PreDestroy - public void close() throws Exception { - broadcastPresence(Presence.Type.UNAVAILABLE); - if (xmpp != null) { - xmpp.close(); - } - } -} diff --git a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java b/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java deleted file mode 100644 index 56edffa7..00000000 --- a/src/main/java/com/juick/server/configuration/ActivityPubClientConfig.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.juick.www.api.activity.model.Activity; -import com.juick.server.helpers.HeaderRequestInterceptor; -import org.apache.http.client.config.CookieSpecs; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.protocol.HttpContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.web.client.RestTemplate; - -import javax.inject.Inject; -import java.net.URI; -import java.util.Collections; - -@Configuration -public class ActivityPubClientConfig { - @Inject - ActivityPubClientErrorHandler activityPubClientErrorHandler; - @Inject - ObjectMapper jsonMapper; - @Bean - public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() { - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - converter.setObjectMapper(jsonMapper); - return converter; - } - @Bean - public RestTemplate apClient() { - RestTemplate restTemplate = new RestTemplate(); - restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory() { - @Override - protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { - HttpClientContext context = HttpClientContext.create(); - context.setRequestConfig(getRequestConfig()); - return context; - } - RequestConfig getRequestConfig() { - RequestConfig.Builder builder = RequestConfig.custom() - .setCookieSpec(CookieSpecs.IGNORE_COOKIES); - return builder.build(); - } - }); - restTemplate.getMessageConverters().add(0, mappingJacksonHttpMessageConverter()); - restTemplate.setErrorHandler(activityPubClientErrorHandler); - restTemplate.setInterceptors(Collections.singletonList( - new HeaderRequestInterceptor("Accept", Activity.ACTIVITY_MEDIA_TYPE))); - return restTemplate; - } -}
\ No newline at end of file diff --git a/src/main/java/com/juick/server/configuration/ActivityPubClientErrorHandler.java b/src/main/java/com/juick/server/configuration/ActivityPubClientErrorHandler.java deleted file mode 100644 index edabadd7..00000000 --- a/src/main/java/com/juick/server/configuration/ActivityPubClientErrorHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.service.activities.DeleteUserEvent; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.stereotype.Component; -import org.springframework.web.client.DefaultResponseErrorHandler; - -import javax.annotation.Nonnull; -import javax.inject.Inject; -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; - -@Component -public class ActivityPubClientErrorHandler extends DefaultResponseErrorHandler { - private static final Logger logger = LoggerFactory.getLogger("ActivityPub"); - - @Inject - private ApplicationEventPublisher applicationEventPublisher; - @Override - public void handleError(URI contextUri, HttpMethod method, @Nonnull ClientHttpResponse response) throws IOException { - logger.warn("HTTP ERROR {} {} : {}", response.getStatusCode().value(), - response.getStatusText(), IOUtils.toString(response.getBody(), StandardCharsets.UTF_8)); - if (response.getStatusCode().equals(HttpStatus.GONE)) { - logger.warn("Server report {} is gone, deleting", contextUri.toASCIIString()); - applicationEventPublisher.publishEvent(new DeleteUserEvent(this, contextUri.toASCIIString())); - } - } -} diff --git a/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java b/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java deleted file mode 100644 index 75d247bf..00000000 --- a/src/main/java/com/juick/server/configuration/ApiAppConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.www.rss.MessagesView; -import com.juick.www.rss.RepliesView; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.view.BeanNameViewResolver; -import org.springframework.web.servlet.view.feed.AbstractRssFeedView; - -/** - * Created by aalexeev on 11/12/16. - */ -@Configuration -@EnableAsync(proxyTargetClass = true) -@EnableScheduling -public class ApiAppConfiguration implements WebMvcConfigurer { - @Bean - public BeanNameViewResolver beanNameViewResolver() { - return new BeanNameViewResolver(); - } - @Bean - AbstractRssFeedView messagesView() { - return new MessagesView(); - } - @Bean - AbstractRssFeedView repliesView() { - return new RepliesView(); - } -} diff --git a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java b/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java deleted file mode 100644 index e84c0c40..00000000 --- a/src/main/java/com/juick/server/configuration/BaseWebConfiguration.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.server.KeystoreManager; -import com.overzealous.remark.Options; -import com.overzealous.remark.Remark; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter; - -/** - * Created by vitalyster on 28.06.2016. - */ -@Configuration -public class BaseWebConfiguration implements WebMvcConfigurer { - - @Value("${keystore:classpath:juick-test-key.p12}") - private Resource keystore; - @Value("${keystore_password:secret}") - private String keystorePassword; - - @Bean - public ResourceUrlEncodingFilter resourceUrlEncodingFilter() { - return new ResourceUrlEncodingFilter(); - } - - @Bean - public KeystoreManager keystoreManager() { - return new KeystoreManager(keystore, keystorePassword); - } - @Bean - public Remark remarkConverter() { - Options options = new Options(); - options.inlineLinks = true; - return new Remark(options); - } -} diff --git a/src/main/java/com/juick/server/configuration/MailConfiguration.java b/src/main/java/com/juick/server/configuration/MailConfiguration.java deleted file mode 100644 index 31034339..00000000 --- a/src/main/java/com/juick/server/configuration/MailConfiguration.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2008-2020, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.server.EmailManager; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConditionalOnProperty("service_email") -public class MailConfiguration { - @Bean - public EmailManager emailManager() { - return new EmailManager(); - } -} diff --git a/src/main/java/com/juick/server/configuration/SapeConfiguration.java b/src/main/java/com/juick/server/configuration/SapeConfiguration.java deleted file mode 100644 index 8892115d..00000000 --- a/src/main/java/com/juick/server/configuration/SapeConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import ru.sape.Sape; - -/** - * Created by vitalyster on 29.03.2017. - */ -@Configuration -@ConditionalOnProperty("sape_user") -public class SapeConfiguration { - @Value("${sape_user:}") - private String token; - - @Bean - public Sape sape() { - return new Sape(token, "juick.com", 2000, 3600); - } -} diff --git a/src/main/java/com/juick/server/configuration/SecurityConfig.java b/src/main/java/com/juick/server/configuration/SecurityConfig.java deleted file mode 100644 index 0fab087f..00000000 --- a/src/main/java/com/juick/server/configuration/SecurityConfig.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2008-2020, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.server.SignatureManager; -import com.juick.service.UserService; -import com.juick.service.security.HTTPSignatureAuthenticationFilter; -import com.juick.service.security.HashParamAuthenticationFilter; -import com.juick.service.security.JuickUserDetailsService; -import com.juick.service.security.entities.JuickUser; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.NullRememberMeServices; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; -import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import javax.annotation.Resource; -import javax.inject.Inject; -import java.util.Arrays; -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -/** - * Created by aalexeev on 11/21/16. - */ -@EnableWebSecurity -public class SecurityConfig { - @Resource - private UserService userService; - - private static final String COOKIE_NAME = "juick-remember-me"; - - @Bean - public UserDetailsService userDetailsService() { - return new JuickUserDetailsService(userService); - } - @Bean - static CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - - configuration.setAllowedOrigins(Collections.singletonList("*")); - configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE")); - configuration.setAllowedHeaders(Collections.singletonList("*")); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/api/**", configuration); - source.registerCorsConfiguration("/u/**", configuration); - source.registerCorsConfiguration("/n/**", configuration); - return source; - } - - @Configuration - @Order(1) - public static class ApiConfig extends WebSecurityConfigurerAdapter { - @Value("${auth_remember_me_key:secret}") - private String rememberMeKey; - @Resource - private UserService userService; - @Resource - private SignatureManager signatureManager; - ApiConfig() { - super(true); - } - @Bean - RememberMeServices apiTokenServices() { - return new NullRememberMeServices(); - } - @Bean - public HashParamAuthenticationFilter apiAuthenticationFilter() { - return new HashParamAuthenticationFilter(userService, apiTokenServices()); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.antMatcher("/api/**") - .addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class) - .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class) - .authorizeRequests() - .antMatchers(HttpMethod.OPTIONS).permitAll() - .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions", - "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", - "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", - "/api/_google", "/api/_applelogin", "/api/signup", "/api/inbox", "/api/events", "/api/info/**", - "/api/nodeinfo/2.0").permitAll() - .anyRequest().hasRole("USER") - .and() - .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) - .and() - .httpBasic().authenticationEntryPoint(juickAuthenticationEntryPoint()) - .and().cors().configurationSource(corsConfigurationSource()) - .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and().exceptionHandling().authenticationEntryPoint(juickAuthenticationEntryPoint()) - .and() - .rememberMe() - .alwaysRemember(true) - .tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(6 * 30)) - .rememberMeServices(apiTokenServices()) - .key(rememberMeKey) - .and() - .headers().defaultsDisabled().cacheControl(); - } - - @Bean - public AuthenticationEntryPoint juickAuthenticationEntryPoint() { - var entryPoint = new BasicAuthenticationEntryPoint(); - entryPoint.setRealmName("Juick"); - return entryPoint; - } - - @Override - public void configure(WebSecurity web) { - web.debug(false); - web.ignoring().antMatchers("/api/v2/api-docs", "/api/configuration/ui", "/api/swagger-resources/**", - "/api/configuration/**", "/swagger-ui.html", "/webjars/**", "/h2-console/**"); - } - } - - @Configuration - public static class WebConfig extends WebSecurityConfigurerAdapter { - @Value("${auth_remember_me_key:secret}") - private String rememberMeKey; - @Value("${web_domain:localhost}") - private String webDomain; - @Resource - private UserService userService; - @Inject - private UserDetailsService userDetailsService; - @Bean - @Qualifier("www") - public HashParamAuthenticationFilter wwwAuthenticationFilter() { - return new HashParamAuthenticationFilter(userService, hashCookieServices()); - } - @Bean - @Qualifier("www") - public RememberMeServices hashCookieServices() { - TokenBasedRememberMeServices services = new TokenBasedRememberMeServices( - rememberMeKey, userDetailsService); - - services.setCookieName(COOKIE_NAME); - services.setCookieDomain(webDomain); - services.setAlwaysRemember(true); - services.setTokenValiditySeconds(6 * 30 * 24 * 3600); - services.setUseSecureCookie(false); // TODO set true if https is supports - - return services; - } - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .addFilterBefore(wwwAuthenticationFilter(), BasicAuthenticationFilter.class) - .authorizeRequests() - .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/post2", "/comment") - .authenticated() - .antMatchers("/actuator/**").hasRole("ADMIN") - .anyRequest().permitAll() - .and() - .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) - .and().cors().configurationSource(corsConfigurationSource()) - .and().sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .invalidSessionUrl("/") - .and() - .logout() - .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) - .invalidateHttpSession(true) - .logoutUrl("/logout") - .logoutSuccessUrl("/") - .deleteCookies("hash", COOKIE_NAME) - .and() - .formLogin() - .loginPage("/login") - .permitAll() - .defaultSuccessUrl("/") - .loginProcessingUrl("/login") - .usernameParameter("username") - .passwordParameter("password") - .failureUrl("/login?error=1") - .and() - .rememberMe() - .rememberMeCookieDomain(webDomain).key(rememberMeKey) - .rememberMeServices(hashCookieServices()) - .and() - .csrf().disable() - .headers().defaultsDisabled().cacheControl(); - } - @Override - public void configure(WebSecurity web) { - web.debug(false); - web.ignoring().antMatchers("/style*.css", "/scripts*.js", "/h2-console/**", "/.well-known/**"); - } - } -} diff --git a/src/main/java/com/juick/server/configuration/SignInWithAppleConfig.java b/src/main/java/com/juick/server/configuration/SignInWithAppleConfig.java deleted file mode 100644 index 310c5899..00000000 --- a/src/main/java/com/juick/server/configuration/SignInWithAppleConfig.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.github.scribejava.apis.AppleClientSecretGenerator; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; - -import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; - -@Configuration -public class SignInWithAppleConfig { - @Value("${apple_app_id:com.example.app}") - private String appId; - @Value("${apple_team_id:teamid}") - private String teamId; - @Value("${apple_key_id:keyid}") - private String keyId; - @Value("${apple_key_path:classpath:testkey.p8}") - private Resource keyPath; - - @Bean - public AppleClientSecretGenerator clientSecretGenerator() throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { - return new AppleClientSecretGenerator(appId, teamId, keyId, keyPath.getFile().toPath()); - } -} diff --git a/src/main/java/com/juick/server/configuration/StorageConfiguration.java b/src/main/java/com/juick/server/configuration/StorageConfiguration.java deleted file mode 100644 index f4a80ece..00000000 --- a/src/main/java/com/juick/server/configuration/StorageConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.service.ImagesService; -import com.juick.service.ImagesServiceImpl; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class StorageConfiguration { - - @Value("${upload_tmp_dir:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String tmpDir; - @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String imgDir; - @Bean - public ImagesService imagesService() { - return new ImagesServiceImpl(imgDir, tmpDir); - } -} diff --git a/src/main/java/com/juick/server/configuration/TelegramConfig.java b/src/main/java/com/juick/server/configuration/TelegramConfig.java deleted file mode 100644 index c56d7d0e..00000000 --- a/src/main/java/com/juick/server/configuration/TelegramConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.server.TelegramBotManager; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConditionalOnProperty(name = "telegram_token") -public class TelegramConfig { - @Bean - public TelegramBotManager telegramBotManager() { - return new TelegramBotManager(); - } -} diff --git a/src/main/java/com/juick/server/configuration/WwwAppConfiguration.java b/src/main/java/com/juick/server/configuration/WwwAppConfiguration.java deleted file mode 100644 index 8e874e43..00000000 --- a/src/main/java/com/juick/server/configuration/WwwAppConfiguration.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.service.HelpService; -import com.juick.service.TagService; -import com.juick.service.UserService; -import com.mitchellbosecke.pebble.PebbleEngine; -import com.mitchellbosecke.pebble.extension.FormatterExtension; -import com.mitchellbosecke.pebble.loader.ClasspathLoader; -import com.mitchellbosecke.pebble.loader.Loader; -import com.mitchellbosecke.pebble.spring.extension.SpringExtension; -import com.mitchellbosecke.pebble.spring.servlet.PebbleViewResolver; -import org.apache.commons.codec.CharEncoding; -import org.commonmark.ext.autolink.AutolinkExtension; -import org.commonmark.node.Link; -import org.commonmark.parser.Parser; -import org.commonmark.renderer.html.HtmlRenderer; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.cache.caffeine.CaffeineCacheManager; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.CacheControl; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.servlet.resource.VersionResourceResolver; - -import javax.inject.Inject; -import java.net.MalformedURLException; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -/** - * Created by aalexeev on 11/22/16. - */ -@Configuration -@EnableCaching -public class WwwAppConfiguration implements WebMvcConfigurer { - @Value("${img_path:#{systemEnvironment['TEMP'] ?: '/tmp'}}") - private String imgDir; - @Bean - public CaffeineCacheManager cacheManager() { - return new CaffeineCacheManager("help"); - } - - @Bean - public HelpService helpService() { - return new HelpService("help"); - } - - @Bean - public Parser cmParser() { - return Parser.builder().extensions(Collections.singletonList(AutolinkExtension.create())).build(); - } - @Bean - public HtmlRenderer helpRenderer() { - return HtmlRenderer.builder() - .attributeProviderFactory(context -> (node, tagName, attributes) -> { - if (node instanceof Link) { - Link link = (Link) node; - if (link.getDestination().startsWith("/")) { - String destination = "/" + helpService().getHelpPath() + link.getDestination(); - link.setDestination(destination); - attributes.put("href", destination); - } - } - }) - .build(); - } - @Bean - public Loader templateLoader() { - return new ClasspathLoader(); - } - - @Bean - public SpringExtension springExtension() { - return new SpringExtension(); - } - - @Bean - public PebbleEngine pebbleEngine() { - boolean devToolsArePresent = false; - try { - Class.forName("org.springframework.boot.devtools.livereload.Connection"); - devToolsArePresent = true; - } catch (ClassNotFoundException e) { - // release mode - } - return new PebbleEngine.Builder() - .loader(this.templateLoader()) - .cacheActive(!devToolsArePresent) - .extension(springExtension()) - .extension(new FormatterExtension()) - .newLineTrimming(false) - .strictVariables(true) - .build(); - } - - @Bean - public ViewResolver viewResolver() { - PebbleViewResolver viewResolver = new PebbleViewResolver(); - viewResolver.setPrefix("templates"); - viewResolver.setSuffix(".html"); - viewResolver.setPebbleEngine(pebbleEngine()); - viewResolver.setCharacterEncoding(CharEncoding.UTF_8); - return viewResolver; - } - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - try { - registry - .addResourceHandler("/**", "/i/a/**") - .addResourceLocations("classpath:/static/", Paths.get(imgDir, "/a/").toUri().toURL().toString()) - .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)) - .resourceChain(false) - .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**", "/i/a/**")); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/com/juick/server/configuration/XMPPConfig.java b/src/main/java/com/juick/server/configuration/XMPPConfig.java deleted file mode 100644 index 62e19c71..00000000 --- a/src/main/java/com/juick/server/configuration/XMPPConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.configuration; - -import com.juick.server.XMPPManager; -import com.juick.server.xmpp.JidConverter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.ConversionService; -import org.springframework.format.support.DefaultFormattingConversionService; - -@Configuration -@ConditionalOnProperty("xmppbot_jid") -public class XMPPConfig { - @Bean - public static ConversionService conversionService() { - DefaultFormattingConversionService cs = new DefaultFormattingConversionService(); - cs.addConverter(new JidConverter()); - return cs; - } - @Bean - public XMPPManager xmppConnection() { - return new XMPPManager(); - } -} diff --git a/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java b/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java deleted file mode 100644 index 8fb21ac5..00000000 --- a/src/main/java/com/juick/server/helpers/HeaderRequestInterceptor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.helpers; - -import org.springframework.http.HttpRequest; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; - -import java.io.IOException; - -public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor { - - private final String headerName; - - private final String headerValue; - - public HeaderRequestInterceptor(String headerName, String headerValue) { - this.headerName = headerName; - this.headerValue = headerValue; - } - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().set(headerName, headerValue); - return execution.execute(request, body); - } -} diff --git a/src/main/java/com/juick/server/helpers/annotation/UserCommand.java b/src/main/java/com/juick/server/helpers/annotation/UserCommand.java deleted file mode 100644 index d25810d2..00000000 --- a/src/main/java/com/juick/server/helpers/annotation/UserCommand.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.helpers.annotation; - -import org.apache.commons.lang3.StringUtils; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Created by oxpa on 22.03.16. - */ -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface UserCommand { - /** - * - * @return a command pattern - */ - String pattern() default StringUtils.EMPTY; - - /** - * - * @return pattern flags - */ - int patternFlags() default 0; - - /** - * - * @return a string used in HELP command output. Basically, only 1 string - */ - String help() default StringUtils.EMPTY; -} diff --git a/src/main/java/com/juick/server/util/HttpBadRequestException.java b/src/main/java/com/juick/server/util/HttpBadRequestException.java deleted file mode 100644 index 6dfc165e..00000000 --- a/src/main/java/com/juick/server/util/HttpBadRequestException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -/** - * Created by vt on 24/11/2016. - */ -@ResponseStatus(value = HttpStatus.BAD_REQUEST) -public class HttpBadRequestException extends RuntimeException { - public HttpBadRequestException() { - super("the request was bad", null, false, false); - } -} diff --git a/src/main/java/com/juick/server/util/HttpForbiddenException.java b/src/main/java/com/juick/server/util/HttpForbiddenException.java deleted file mode 100644 index 0247f531..00000000 --- a/src/main/java/com/juick/server/util/HttpForbiddenException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -/** - * Created by vt on 24/11/2016. - */ -@ResponseStatus(value = HttpStatus.FORBIDDEN) -public class HttpForbiddenException extends RuntimeException { - public HttpForbiddenException() { - super(StringUtils.EMPTY, null, false, false); - } - -} diff --git a/src/main/java/com/juick/server/util/HttpNotFoundException.java b/src/main/java/com/juick/server/util/HttpNotFoundException.java deleted file mode 100644 index dd5a2e1b..00000000 --- a/src/main/java/com/juick/server/util/HttpNotFoundException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -/** - * Created by vt on 24/11/2016. - */ -@ResponseStatus(value = HttpStatus.NOT_FOUND) -public class HttpNotFoundException extends RuntimeException { - public HttpNotFoundException() { - super(StringUtils.EMPTY, null, false, false); - } -} diff --git a/src/main/java/com/juick/server/util/HttpUtils.java b/src/main/java/com/juick/server/util/HttpUtils.java deleted file mode 100644 index beef5d60..00000000 --- a/src/main/java/com/juick/server/util/HttpUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.MediaType; -import org.springframework.web.multipart.MultipartFile; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URL; -import java.net.URLConnection; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Iterator; -import java.util.UUID; - -/** - * - * @author Ugnich Anton - */ -public class HttpUtils { - private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); - - public static URI receiveMultiPartFile(MultipartFile attach, String tmpDir) throws IOException { - if (attach != null && !attach.isEmpty()) { - ImageInputStream iis = ImageIO.createImageInputStream(attach.getInputStream()); - Iterator<ImageReader> readers = ImageIO.getImageReaders(iis); - - String format = StringUtils.EMPTY; - while (readers.hasNext()) { - ImageReader read = readers.next(); - format = read.getFormatName(); - } - String attachmentType = attachmentTypeFromFormat(format); - if (attachmentType.equals("jpg") || attachmentType.equals("png")) { - String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType; - try { - Files.write(Paths.get(tmpDir, attachmentFName), - attach.getBytes()); - return URI.create(String.format("juick://%s", attachmentFName)); - } catch (IOException e) { - logger.warn("file receive error", e); - } - } - logger.warn("file type is unknown: {}", attach.getOriginalFilename()); - } - return URI.create(StringUtils.EMPTY); - } - - private static String attachmentTypeFromFormat(String format) throws IOException { - if (format != null && format.equals("JPEG")) { - return "jpg"; - } else if (format != null && format.equals("png")) { - return "png"; - } else { - throw new IOException("Wrong file type: " + format); - } - } - - public static String mediaType(String attachmentType) { - return attachmentType.equals("jpg") ? MediaType.IMAGE_JPEG_VALUE : MediaType.IMAGE_PNG_VALUE; - } - - public static URI downloadImage(URL url, String tmpDir) throws IOException { - ImageInputStream iis = ImageIO.createImageInputStream(url.openStream()); - Iterator<ImageReader> readers = ImageIO.getImageReaders(iis); - - String format = StringUtils.EMPTY; - while (readers.hasNext()) { - ImageReader read = readers.next(); - format = read.getFormatName(); - } - URLConnection urlConn; - try { - urlConn = url.openConnection(); - } catch (IOException e) { - logger.error(String.format("Failed open url: %s", url.toString())); - throw e; - } - - try (InputStream is = new BufferedInputStream(urlConn.getInputStream())) { - String attachmentType = attachmentTypeFromFormat(format); - - String attachmentFName = DigestUtils.md5Hex(UUID.randomUUID().toString()) + "." + attachmentType; - Files.copy(is, Paths.get(tmpDir, attachmentFName)); - return URI.create(String.format("juick://%s", attachmentFName)); - } catch (IOException e) { - logger.error(String.format("Failed download image by url: %s", url.toString()), e); - throw e; - } - } -} diff --git a/src/main/java/com/juick/server/util/ImageUtils.java b/src/main/java/com/juick/server/util/ImageUtils.java deleted file mode 100644 index e06339ba..00000000 --- a/src/main/java/com/juick/server/util/ImageUtils.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import com.juick.model.Attachment; -import org.apache.commons.imaging.ImageReadException; -import org.apache.commons.imaging.Imaging; -import org.apache.commons.imaging.common.ImageMetadata; -import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata; -import org.apache.commons.imaging.formats.tiff.TiffField; -import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; -import org.imgscalr.Scalr; -import org.imgscalr.Scalr.Rotation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.stream.ImageInputStream; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.Iterator; - -public class ImageUtils { - private static final Logger logger = LoggerFactory.getLogger(ImageUtils.class); - - private String imgDir; - private String tmpDir; - - public ImageUtils(String imgDir, String tmpDir) { - this.imgDir = imgDir; - this.tmpDir = tmpDir; - } -/** - * Returns <code>BufferedImage</code>, same as <code>ImageIO.read()</code> does. - * - * <p>JPEG images with EXIF metadata are rotated according to Orientation tag. - * - * @param imageFile a <code>File</code> to read from. - */ - private static BufferedImage readImageWithOrientation(File imageFile) - throws IOException { - - BufferedImage image = ImageIO.read(imageFile); - if (!FilenameUtils.getExtension(imageFile.getName()).equals("jpg")) { - return image; - } - - try { - ImageMetadata metadata = Imaging.getMetadata(imageFile); - - if (metadata instanceof JpegImageMetadata) { - JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata; - TiffField orientationField = jpegMetadata.findEXIFValue(TiffTagConstants.TIFF_TAG_ORIENTATION); - - if (orientationField != null) { - int orientation = orientationField.getIntValue(); - switch (orientation) { - case TiffTagConstants.ORIENTATION_VALUE_ROTATE_90_CW: - image = Scalr.rotate(image, Rotation.CW_90); - break; - case TiffTagConstants.ORIENTATION_VALUE_ROTATE_180: - image = Scalr.rotate(image, Rotation.CW_180); - break; - case TiffTagConstants.ORIENTATION_VALUE_ROTATE_270_CW: - image = Scalr.rotate(image, Rotation.CW_270); - break; - case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL: - image = Scalr.rotate(image, Rotation.FLIP_HORZ); - break; - case TiffTagConstants.ORIENTATION_VALUE_MIRROR_VERTICAL: - image = Scalr.rotate(image, Rotation.FLIP_VERT); - break; - case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_90_CW: - image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_90); - break; - case TiffTagConstants.ORIENTATION_VALUE_MIRROR_HORIZONTAL_AND_ROTATE_270_CW: - image = Scalr.rotate(Scalr.rotate(image, Rotation.FLIP_HORZ), Rotation.CW_270); - break; - case TiffTagConstants.ORIENTATION_VALUE_HORIZONTAL_NORMAL: - default: - // do nothing - break; - } - } - } - } catch (ImageReadException | IOException e) { - // failed to read metadata. - // nothing to do here, return image as is. - } - - return image; - } - - public void saveImageWithPreviews(String tempFilename, String outputFilename) - throws IOException { - String ext = FilenameUtils.getExtension(outputFilename); - - Path outputImagePath = Paths.get(imgDir, "p", outputFilename); - // this throws strange exceptions - // Files.move(Paths.get(tmpDir, tempFilename), outputImagePath); - FileUtils.moveFile(Paths.get(tmpDir, tempFilename).toFile(), outputImagePath.toFile()); - BufferedImage originalImage = readImageWithOrientation(outputImagePath.toFile()); - - int width = originalImage.getWidth(); - int height = originalImage.getHeight(); - int maxDimension = Math.max(width, height); - BufferedImage image1024 = (maxDimension > 1024) ? Scalr.resize(originalImage, 1024) : originalImage; - BufferedImage image0512 = (maxDimension > 512) ? Scalr.resize(originalImage, 512) : originalImage; - BufferedImage image0160 = (maxDimension > 160) ? Scalr.resize(originalImage, 160) : originalImage; - ImageIO.write(image1024, ext, Paths.get(imgDir, "photos-1024", outputFilename).toFile()); - ImageIO.write(image0512, ext, Paths.get(imgDir, "photos-512", outputFilename).toFile()); - ImageIO.write(image0160, ext, Paths.get(imgDir, "ps", outputFilename).toFile()); - } - - public void saveAvatar(String tempFilename, int uid) - throws IOException { - String ext = FilenameUtils.getExtension(tempFilename); - String originalName = String.format("%s.%s", uid, ext); - Path originalPath = Paths.get(imgDir, "ao", originalName); - Files.move(Paths.get(tmpDir, tempFilename), originalPath, StandardCopyOption.REPLACE_EXISTING); - BufferedImage originalImage = ImageIO.read(originalPath.toFile()); - - String targetExt = "png"; - String targetName = String.format("%s.%s", uid, targetExt); - ImageIO.write(Scalr.resize(originalImage, 96), targetExt, Paths.get(imgDir, "a", targetName).toFile()); - ImageIO.write(Scalr.resize(originalImage, 32), targetExt, Paths.get(imgDir, "as", targetName).toFile()); - } - public Attachment getAttachment(File imgFile) throws IOException { - Attachment attachment = new Attachment(); - try (ImageInputStream stream = ImageIO.createImageInputStream(imgFile)) { - Iterator<ImageReader> iter = ImageIO.getImageReaders(stream); - while (iter.hasNext()) { - ImageReader reader = iter.next(); - try { - reader.setInput(stream); - attachment.setWidth(reader.getWidth(reader.getMinIndex())); - attachment.setHeight(reader.getHeight(reader.getMinIndex())); - return attachment; - } catch (Exception e) { - logger.debug("Error reading {}, trying next reader", imgFile.getAbsolutePath()); - } finally { - reader.dispose(); - } - } - } - - logger.warn("Not a known image file {}", imgFile.getAbsolutePath()); - return attachment; - } -}
\ No newline at end of file diff --git a/src/main/java/com/juick/server/util/TagUtils.java b/src/main/java/com/juick/server/util/TagUtils.java deleted file mode 100644 index 754d8020..00000000 --- a/src/main/java/com/juick/server/util/TagUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import com.juick.model.Tag; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Created by aalexeev on 11/13/16. - */ -public class TagUtils { - private TagUtils() { - throw new IllegalStateException(); - } - - public static String toString(final List<Tag> tags) { - if (CollectionUtils.isEmpty(tags)) - return StringUtils.EMPTY; - - return tags.stream().map(t -> "*" + t.getName()) - .collect(Collectors.joining(" ")); - } -} diff --git a/src/main/java/com/juick/server/util/WebUtils.java b/src/main/java/com/juick/server/util/WebUtils.java deleted file mode 100644 index bc3ac63a..00000000 --- a/src/main/java/com/juick/server/util/WebUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.util; - -import java.util.regex.Pattern; - -/** - * Created by aalexeev on 11/28/16. - */ -public class WebUtils { - private WebUtils() { - throw new IllegalStateException(); - } - - private static final Pattern USER_NAME_PATTERN = Pattern.compile("[a-zA-Z-_\\d]{2,16}"); - - private static final Pattern POST_NUMBER_PATTERN = Pattern.compile("-?\\d+"); - - private static final Pattern JID_PATTERN = Pattern.compile("^[a-zA-Z0-9\\\\-\\\\_\\\\@\\\\.]{6,64}$"); - - - public static boolean isPostNumber(final String aString) { - return aString != null && POST_NUMBER_PATTERN.matcher(aString).matches(); - } - - public static boolean isNotPostNumber(final String aString) { - return !isPostNumber(aString); - } - - public static boolean isUserName(final String aString) { - return aString != null && USER_NAME_PATTERN.matcher(aString).matches(); - } - - public static boolean isNotUserName(final String aString) { - return !isUserName(aString); - } - - public static boolean isJid(final String aString) { - return aString != null && JID_PATTERN.matcher(aString).matches(); - } - - public static boolean isNotJid(final String aString) { - return !isJid(aString); - } - - -} diff --git a/src/main/java/com/juick/server/xmpp/JidConverter.java b/src/main/java/com/juick/server/xmpp/JidConverter.java deleted file mode 100644 index fdf80108..00000000 --- a/src/main/java/com/juick/server/xmpp/JidConverter.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.xmpp; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.lang.Nullable; -import rocks.xmpp.addr.Jid; - -import javax.annotation.Nonnull; - -public class JidConverter implements Converter<String, Jid> { - @Nullable - @Override - public Jid convert(@Nonnull String jidStr) { - return Jid.of(jidStr); - } -} diff --git a/src/main/java/com/juick/server/xmpp/iq/MessageQuery.java b/src/main/java/com/juick/server/xmpp/iq/MessageQuery.java deleted file mode 100644 index c973b624..00000000 --- a/src/main/java/com/juick/server/xmpp/iq/MessageQuery.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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 com.juick.server.xmpp.iq; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement(name = "query") -public class MessageQuery { - private MessageQuery() { - - } -} diff --git a/src/main/java/com/juick/server/xmpp/iq/package-info.java b/src/main/java/com/juick/server/xmpp/iq/package-info.java deleted file mode 100644 index 822fb8c4..00000000 --- a/src/main/java/com/juick/server/xmpp/iq/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2008-2019, Juick - * - * 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/>. - */ - -@XmlAccessorType(XmlAccessType.FIELD) -@XmlSchema(namespace = "http://juick.com/query#messages", elementFormDefault = XmlNsForm.QUALIFIED) -package com.juick.server.xmpp.iq; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlNsForm; -import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file |