diff --git a/src/main/java/pl/jpetryk/redditbot/bot/impl/TweetsInCommentsBot.java b/src/main/java/pl/jpetryk/redditbot/bot/impl/TweetsInCommentsBot.java index ddea05c..c2c7b43 100644 --- a/src/main/java/pl/jpetryk/redditbot/bot/impl/TweetsInCommentsBot.java +++ b/src/main/java/pl/jpetryk/redditbot/bot/impl/TweetsInCommentsBot.java @@ -2,140 +2,132 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Named; import pl.jpetryk.redditbot.bot.AbstractRedditBot; import pl.jpetryk.redditbot.connectors.ImgurConnectorInterface; import pl.jpetryk.redditbot.connectors.RedditConnectorInterface; import pl.jpetryk.redditbot.connectors.TwitterConnectorInterface; import pl.jpetryk.redditbot.exceptions.ImgurException; import pl.jpetryk.redditbot.exceptions.TwitterApiException; -import pl.jpetryk.redditbot.model.*; +import pl.jpetryk.redditbot.model.Comment; +import pl.jpetryk.redditbot.model.ProcessCommentResult; +import pl.jpetryk.redditbot.model.RehostedImageEntity; +import pl.jpetryk.redditbot.model.Tweet; +import pl.jpetryk.redditbot.model.TweetWithRehostedImages; import pl.jpetryk.redditbot.parser.CommentParser; import pl.jpetryk.redditbot.utils.ResponseCommentCreatorInterface; -import javax.inject.Inject; -import javax.inject.Named; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - /** * Created by Jan on 10/01/15. */ public class TweetsInCommentsBot extends AbstractRedditBot { - private CommentParser commentParser; - - private TwitterConnectorInterface twitterConnector; - - private ResponseCommentCreatorInterface responseCommentCreator; - - private List userNameBlackList; - - private ImgurConnectorInterface imgurConnector; - - @Inject - public TweetsInCommentsBot(TwitterConnectorInterface twitterConnector, - RedditConnectorInterface redditConnectorInterface, - ImgurConnectorInterface imgurConnector, - CommentParser commentParser, - @Named("subreddits") String subreddits, - ResponseCommentCreatorInterface responseCommentCreator, - List userNameBlackList) { - super(redditConnectorInterface, subreddits); - this.twitterConnector = twitterConnector; - this.imgurConnector = imgurConnector; - this.commentParser = commentParser; - this.responseCommentCreator = responseCommentCreator; - this.userNameBlackList = new ArrayList<>(); - for (String string : userNameBlackList) { - this.userNameBlackList.add(string.toLowerCase()); - } - } - - @Override - protected ProcessCommentResult processComment(Comment comment) throws Exception { - ProcessCommentResult result; - if (userNameBlackList.contains(comment.getAuthor().toLowerCase())) { - result = ProcessCommentResult.doNotRespond(); - } else { - try { - Collection statusIdList = commentParser.getTwitterStatusIdsFromComment(comment); - List tweetList = readTweets(statusIdList); - List filteredTweetList = filterOutTweetsThatAreAlreadyInComment(comment, tweetList); - if (filteredTweetList.isEmpty()) { - result = ProcessCommentResult.doNotRespond(); - } else { - String response = responseCommentCreator. - createResponseComment(getTweetWithRehostedImagesList(filteredTweetList), comment); - result = ProcessCommentResult.respondWith(response); - } - } catch (TwitterApiException e) { - result = handleException(comment, e); - } - } - return result; - } - - private List getTweetWithRehostedImagesList(List tweetList) { - List result = new ArrayList<>(); - for (Tweet tweet : tweetList) { - result.add(new TweetWithRehostedImages(tweet, rehostTweetImages(tweet))); - } - return result; - } - - private Multimap rehostTweetImages(Tweet tweet) { - Multimap result = HashMultimap.create(); - try { - for (Map.Entry> entry : tweet.getImageEntities().asMap().entrySet()) { - for (String originalUrl : entry.getValue()) { - String rehostedImageUrl = imgurConnector.reuploadImage(originalUrl); - result.put(entry.getKey(), new RehostedImageEntity(originalUrl, rehostedImageUrl)); - } - } - } catch (ImgurException e) { - logger.error(e.getMessage(), e); - } - return result; - } - - private List readTweets(Collection statusIdList) throws TwitterApiException { - List tweetList = new ArrayList<>(); - for (String string : statusIdList) { - tweetList.add(twitterConnector.showStatus(Long.valueOf(string))); - } - return tweetList; - } - - private List filterOutTweetsThatAreAlreadyInComment(Comment comment, List tweetList) { - List result = new ArrayList<>(); - for (Tweet tweet : tweetList) { - if (!comment.getBody().contains(tweet.getBody())) { - result.add(tweet); - } - } - return result; - } - - private ProcessCommentResult handleException(Comment comment, TwitterApiException e) throws Exception { - if (e.isRateLimitExceeded()) { - logger.warn("Twitter rate limit exceeded. Sleeping for " + e.getMiliSecondsUntilReset() / 1000 + " seconds"); - sleepUntilRateLimitEnds(e.getMiliSecondsUntilReset()); - return processComment(comment); - } else { - logger.error("Twitter api error occured. Message: " + e.getMessage() + ". Error code: " + e.getErrorCode() - + " Comment removed from response queue"); - return ProcessCommentResult.doNotRespond(); - } - } - - private void sleepUntilRateLimitEnds(Long milis) { - try { - Thread.sleep(milis); - } catch (InterruptedException e) { - logger.error(e); - } - } + private CommentParser commentParser; + + private TwitterConnectorInterface twitterConnector; + + private ResponseCommentCreatorInterface responseCommentCreator; + + private List userNameBlackList; + + private ImgurConnectorInterface imgurConnector; + + @Inject + public TweetsInCommentsBot(TwitterConnectorInterface twitterConnector, + RedditConnectorInterface redditConnectorInterface, + ImgurConnectorInterface imgurConnector, + CommentParser commentParser, + @Named("subreddits") String subreddits, + ResponseCommentCreatorInterface responseCommentCreator, + List userNameBlackList) { + super(redditConnectorInterface, subreddits); + this.twitterConnector = twitterConnector; + this.imgurConnector = imgurConnector; + this.commentParser = commentParser; + this.responseCommentCreator = responseCommentCreator; + this.userNameBlackList = new ArrayList<>(); + for (String string : userNameBlackList) { + this.userNameBlackList.add(string.toLowerCase()); + } + } + + @Override + protected ProcessCommentResult processComment(Comment comment) throws Exception { + if (userNameBlackList.contains(comment.getAuthor().toLowerCase())) { + return ProcessCommentResult.doNotRespond(); + } + try { + Collection statusIdList = commentParser.getTwitterStatusIdsFromComment(comment); + List tweetList = readTweets(statusIdList); + List filteredTweetList = filterOutTweetsThatAreAlreadyInComment(comment, tweetList); + if (filteredTweetList.isEmpty()) { + return ProcessCommentResult.doNotRespond(); + } + String response = responseCommentCreator. + createResponseComment(getTweetWithRehostedImagesList(filteredTweetList), comment); + return ProcessCommentResult.respondWith(response); + } catch (TwitterApiException e) { + return handleException(comment, e); + } + } + + private List getTweetWithRehostedImagesList(List tweetList) { + return tweetList.stream() + .map(tweet -> new TweetWithRehostedImages(tweet, rehostTweetImages(tweet))) + .collect(Collectors.toList()); + } + + private Multimap rehostTweetImages(Tweet tweet) { + Multimap result = HashMultimap.create(); + try { + for (Map.Entry> entry : tweet.getImageEntities().asMap().entrySet()) { + for (String originalUrl : entry.getValue()) { + result.put(entry.getKey(), new RehostedImageEntity(originalUrl, imgurConnector.reuploadImage(originalUrl))); + } + } + } catch (ImgurException e) { + logger.error(e.getMessage(), e); + } + return result; + } + + private List readTweets(Collection statusIdList) throws TwitterApiException { + return statusIdList.stream() + .map(Long::valueOf) + .map(twitterConnector::showStatus) + .collect(Collectors.toList()); + } + + private List filterOutTweetsThatAreAlreadyInComment(Comment comment, List tweetList) { + return tweetList.stream() + .filter(tweet -> !comment.getBody().contains(tweet.getBody())) + .collect(Collectors.toList()); + } + + private ProcessCommentResult handleException(Comment comment, TwitterApiException e) throws Exception { + if (e.isRateLimitExceeded()) { + logger.warn("Twitter rate limit exceeded. Sleeping for " + e.getMiliSecondsUntilReset() / 1000 + " seconds"); + sleepUntilRateLimitEnds(e.getMiliSecondsUntilReset()); + return processComment(comment); + } else { + logger.error("Twitter api error occured. Message: " + e.getMessage() + ". Error code: " + e.getErrorCode() + + " Comment removed from response queue"); + return ProcessCommentResult.doNotRespond(); + } + } + + private void sleepUntilRateLimitEnds(Long milis) { + try { + Thread.sleep(milis); + } catch (InterruptedException e) { + logger.error(e); + } + } } diff --git a/src/main/java/pl/jpetryk/redditbot/exceptions/TwitterApiException.java b/src/main/java/pl/jpetryk/redditbot/exceptions/TwitterApiException.java index 4c6a0b3..a830c79 100644 --- a/src/main/java/pl/jpetryk/redditbot/exceptions/TwitterApiException.java +++ b/src/main/java/pl/jpetryk/redditbot/exceptions/TwitterApiException.java @@ -1,34 +1,32 @@ package pl.jpetryk.redditbot.exceptions; -import twitter4j.TwitterException; - /** * Created by Jan on 09/01/15. */ -public class TwitterApiException extends Exception { +public class TwitterApiException extends RuntimeException { - public TwitterApiException(Throwable cause, boolean rateLimitExceeded, long miliSecondsUntilReset, int errorCode) { - super(cause); - this.miliSecondsUntilReset = miliSecondsUntilReset; - this.rateLimitExceeded = rateLimitExceeded; - this.errorCode = errorCode; - } + public TwitterApiException(Throwable cause, boolean rateLimitExceeded, long miliSecondsUntilReset, int errorCode) { + super(cause); + this.miliSecondsUntilReset = miliSecondsUntilReset; + this.rateLimitExceeded = rateLimitExceeded; + this.errorCode = errorCode; + } - private boolean rateLimitExceeded; + private boolean rateLimitExceeded; - private long miliSecondsUntilReset; + private long miliSecondsUntilReset; - private int errorCode; + private int errorCode; - public boolean isRateLimitExceeded() { - return rateLimitExceeded; - } + public boolean isRateLimitExceeded() { + return rateLimitExceeded; + } - public long getMiliSecondsUntilReset() { - return miliSecondsUntilReset; - } + public long getMiliSecondsUntilReset() { + return miliSecondsUntilReset; + } - public int getErrorCode() { - return errorCode; - } + public int getErrorCode() { + return errorCode; + } } diff --git a/src/test/java/pl/jpetryk/redditbot/connectors/AbstractRedditConnectorITCase.java b/src/test/java/pl/jpetryk/redditbot/connectors/AbstractRedditConnectorITCase.java index afa9f6c..62a12e4 100644 --- a/src/test/java/pl/jpetryk/redditbot/connectors/AbstractRedditConnectorITCase.java +++ b/src/test/java/pl/jpetryk/redditbot/connectors/AbstractRedditConnectorITCase.java @@ -1,79 +1,82 @@ package pl.jpetryk.redditbot.connectors; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.List; import org.junit.Test; -import pl.jpetryk.redditbot.model.PostCommentResult; -import pl.jpetryk.redditbot.utils.PropertiesReader; -import pl.jpetryk.redditbot.exceptions.NetworkConnectionException; import pl.jpetryk.redditbot.exceptions.RedditApiException; import pl.jpetryk.redditbot.model.Comment; - -import java.util.List; +import pl.jpetryk.redditbot.model.PostCommentResult; +import pl.jpetryk.redditbot.utils.PropertiesReader; /** * Created by Jan on 06/12/14. */ public abstract class AbstractRedditConnectorITCase { - protected abstract T createValidInstance() throws Exception; - - protected abstract T createInvalidInstance() throws Exception; - - protected PropertiesReader testProperties = new PropertiesReader("testbot.properties"); - - protected T connector; - - - @Test - public void testLoginWithValidCredentials() throws Exception { - try { - connector = createValidInstance(); - } catch (RedditApiException e) { - fail(); - } - } - - @Test(expected = RedditApiException.class) - public void testLoginWithInvalidCredentials() throws Exception { - createInvalidInstance(); - } - - - @Test - public void testGetNewestSubredditComments() throws Exception { - connector = createValidInstance(); - List commentList = connector.getNewestSubredditComments("all"); - assertEquals(RedditConnectorInterface.MAX_COMMENTS_PER_REQUEST, commentList.size()); - for (Comment comment : commentList) { - assertAllFieldsAreSet(comment); - } - } - - private void assertAllFieldsAreSet(Comment comment) { - assertNotNull(comment.getCreated()); - assertNotNull(comment.getAuthor()); - assertNotNull(comment.getBody()); - assertNotNull(comment.getCommentFullName()); - assertNotNull(comment.getCommentId()); - assertNotNull(comment.getLinkId()); - assertNotNull(comment.getLinkUrl()); - assertNotNull(comment.getLinkTitle()); - } - - @Test - public void testPostComment() throws Exception { - connector = createValidInstance(); - String message = "This is bot integration test message. Please ignore."; - Comment commentToReply = prepareTestCommentToReply(); - PostCommentResult result = connector.replyToComment(commentToReply.getCommentFullName(), message); - assertTrue("Could not post test comment, possible reddit api ratelimit", result.isSuccess()); - } - - protected Comment prepareTestCommentToReply() { - return new Comment.Builder().linkId(testProperties.getProperty("comment-to-reply-link-id")) - .commentId(testProperties.getProperty("comment-to-reply-id")).build(); - } + protected abstract T createValidInstance() throws Exception; + + protected abstract T createInvalidInstance() throws Exception; + + protected PropertiesReader testProperties = new PropertiesReader("testbot.properties"); + + protected T connector; + + + @Test + public void testLoginWithValidCredentials() throws Exception { + try { + connector = createValidInstance(); + } catch (RedditApiException e) { + fail(); + } + } + + @Test(expected = RedditApiException.class) + public void testLoginWithInvalidCredentials() throws Exception { + createInvalidInstance(); + } + + + @Test + public void testGetNewestSubredditComments() throws Exception { + connector = createValidInstance(); + List commentList = connector.getNewestSubredditComments("all"); + assertEquals(RedditConnectorInterface.MAX_COMMENTS_PER_REQUEST, commentList.size()); + for (Comment comment : commentList) { + assertAllFieldsAreSet(comment); + } + } + + private void assertAllFieldsAreSet(Comment comment) { + assertNotNull(comment.getCreated()); + assertNotNull(comment.getAuthor()); + assertNotNull(comment.getBody()); + assertNotNull(comment.getCommentFullName()); + assertNotNull(comment.getCommentId()); + assertNotNull(comment.getLinkId()); + assertNotNull(comment.getLinkUrl()); + assertNotNull(comment.getLinkTitle()); + } + + @Test + public void testPostComment() throws Exception { + connector = createValidInstance(); + String message = "This is bot integration test message. Please ignore."; + Comment commentToReply = prepareTestCommentToReply(); + PostCommentResult result = connector.replyToComment(commentToReply.getCommentFullName(), message); + assertTrue("Could not post test comment, possible reddit api ratelimit", result.isSuccess()); + } + + protected Comment prepareTestCommentToReply() { + return new Comment.Builder() + .linkId(testProperties.getProperty("comment-to-reply-link-id")) + .commentId(testProperties.getProperty("comment-to-reply-id")) + .build(); + } }