diff --git a/src/main/java/io/github/redouane59/twitter/ITwitterClientV2.java b/src/main/java/io/github/redouane59/twitter/ITwitterClientV2.java index e307ad50..e8dc805c 100644 --- a/src/main/java/io/github/redouane59/twitter/ITwitterClientV2.java +++ b/src/main/java/io/github/redouane59/twitter/ITwitterClientV2.java @@ -32,6 +32,15 @@ public interface ITwitterClientV2 { + public static enum REQUEST_TWEET_FIELDS_SCOPE { + /** Retrieve public fields of tweets only (any token)*/ + PUBLIC, + /** Retrieve also non-public fields of tweets (needs user token) */ + NON_PUBLIC, + /** Retrieve non-public and promoted fields of tweets (needs user token, tweet must be promoted or request fails) */ + NON_PUBLIC_PROMOTED + } + /** * Retreive a user from his screen name calling https://api.twitter.com/2/users/ * @@ -101,20 +110,40 @@ public interface ITwitterClientV2 { /** * Get a tweet from its id calling https://api.twitter.com/2/tweets + * with public fields only * * @param tweetId id of the tweet * @return a tweet object */ Tweet getTweet(String tweetId); + + /** + * Get a tweet from its id calling https://api.twitter.com/2/tweets + * with given fields scope + * + * @param tweetId id of the tweet + * @return a tweet object + */ + Tweet getTweet(String tweetId, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope); /** * Get a tweet list from their id calling https://api.twitter.com/2/tweets + * with public fields only * * @param tweetIds the ids of the tweets * @return a tweet object list */ TweetList getTweets(List tweetIds); + /** + * Get a tweet list from their id calling https://api.twitter.com/2/tweets + * with given fields scope + * + * @param tweetIds the ids of the tweets + * @return a tweet object list + */ + TweetList getTweets(List tweetIds, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope); + /** * Hide/Unide a reply using https://api.twitter.com/labs/2/tweets/:id/hidden * @@ -304,6 +333,25 @@ public interface ITwitterClientV2 { */ TweetList getUserTimeline(String userId, AdditionalParameters additionalParameters); + /** + * Get the most recent Tweets posted by the user calling https://api.twitter.com/2/users/:id/tweets (time & tweet id arguments can be null) + * + * @param userId identifier of the Twitter account (user ID) for whom to return results. + * @param requestTweetFieldsScope + * @return a TweetList object containing a list of tweets and the next token if recursiveCall is set to false + */ + public TweetList getUserTimeline(final String userId, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope); + + /** + * Get the most recent Tweets posted by the user calling https://api.twitter.com/2/users/:id/tweets (time & tweet id arguments can be null) + * + * @param userId identifier of the Twitter account (user ID) for whom to return results. + * @param additionalParameters accepted parameters recursiveCall, startTime, endTime, sinceId, untilId, maxResults + * @param requestTweetFieldsScope + * @return a TweetList object containing a list of tweets and the next token if recursiveCall is set to false + */ + public TweetList getUserTimeline(String userId, AdditionalParameters additionalParameters, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope); + /** * Get the most recent mentions received posted by the user calling https://api.twitter.com/2/users/:id/mentions * diff --git a/src/main/java/io/github/redouane59/twitter/TwitterClient.java b/src/main/java/io/github/redouane59/twitter/TwitterClient.java index 192abb23..6fd49a99 100644 --- a/src/main/java/io/github/redouane59/twitter/TwitterClient.java +++ b/src/main/java/io/github/redouane59/twitter/TwitterClient.java @@ -101,9 +101,11 @@ public class TwitterClient implements ITwitterClientV1, ITwitterClientV2, ITwitterClientArchive { public static final String TWEET_FIELDS = "tweet.fields"; - public static final String - ALL_TWEET_FIELDS = - "attachments,author_id,created_at,entities,geo,id,in_reply_to_user_id,lang,possibly_sensitive,public_metrics,referenced_tweets,source,text,withheld,context_annotations,conversation_id,reply_settings"; + public static final String + ALL_TWEET_FIELDS_PUBLIC = + "attachments,author_id,created_at,entities,geo,id,in_reply_to_user_id,lang,possibly_sensitive,referenced_tweets,source,text,withheld,context_annotations,conversation_id,reply_settings,public_metrics"; + public static final String ALL_TWEET_FIELDS_NON_PUBLIC = ALL_TWEET_FIELDS_PUBLIC + ",non_public_metrics,organic_metrics"; + public static final String ALL_TWEET_FIELDS_NON_PUBLIC_PROMOTED = ALL_TWEET_FIELDS_NON_PUBLIC + ",promoted_metrics"; public static final String EXPANSION = "expansions"; public static final String ALL_EXPANSIONS = @@ -159,7 +161,12 @@ public TwitterClient() { public TwitterClient(TwitterCredentials credentials) { this(credentials, new ServiceBuilder(credentials.getApiKey()).apiSecret(credentials.getApiSecretKey())); } - + + public TwitterClient(TwitterCredentials credentials, boolean useConsumerKey) { + this(credentials, new ServiceBuilder(credentials.getApiKey()).apiSecret(credentials.getApiSecretKey())); + setUseConsumerKey(useConsumerKey); + } + public TwitterClient(TwitterCredentials credentials, HttpClient httpClient) { this(credentials, new ServiceBuilder(credentials.getApiKey()).apiSecret(credentials.getApiSecretKey()).httpClient(httpClient)); @@ -240,6 +247,15 @@ public void setAutomaticRetry(boolean automaticRetry) { requestHelperV2.setAutomaticRetry(automaticRetry); } + /** + * Set the behavior of authentication context used in requests. + * + * @param useConsumerKey Using consumer key for user context if true; or else app-only bearer token. Default is false. + */ + public void setUseConsumerKey(boolean useConsumerKey) { + requestHelperV2.setUseConsumerKey(useConsumerKey); + } + // can manage up to 5000 results / call . Max 15 calls / 15min ==> 75.000 // results max. / 15min private List getUserIdsByRelation(String url) { @@ -520,7 +536,7 @@ public TweetList getLikedTweets(final String userId) { public TweetList getLikedTweets(final String userId, AdditionalParameters additionalParameters) { String url = getUrlHelper().getLikedTweetsUrl(userId); Map parameters = new HashMap<>(); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); if (!additionalParameters.isRecursiveCall()) { return getRequestHelper().getRequestWithParameters(url, parameters, TweetList.class).orElseThrow(NoSuchElementException::new); } @@ -581,7 +597,7 @@ public UserList getMutedUsers() { parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(EXPANSION, PINNED_TWEET_ID); parameters.put(MAX_RESULTS, "1000"); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); return requestHelperV1.getRequestWithParameters(url, parameters, UserList.class).orElseThrow(NoSuchElementException::new); } @@ -651,7 +667,7 @@ public UserList getSpaceBuyers(final String spaceId) { Map parameters = new HashMap<>(); parameters.put(EXPANSION, PINNED_TWEET_ID); parameters.put(USER_FIELDS, ALL_USER_FIELDS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); return getRequestHelperV2().getRequestWithParameters(url, parameters, UserList.class) .orElseThrow(NoSuchElementException::new); } @@ -756,7 +772,7 @@ public UserList getListMembers(final String listId) { Map parameters = new HashMap<>(); parameters.put(EXPANSION, PINNED_TWEET_ID); parameters.put(USER_FIELDS, ALL_USER_FIELDS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); return getUsersRecursively(Integer.MAX_VALUE, url, parameters); } @@ -775,7 +791,7 @@ public TweetList getListTweets(String listId, AdditionalParameters additionalPar String url = getUrlHelper().getListTweetsUrl(listId); Map parameters = additionalParameters.getMapFromParameters(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); @@ -822,7 +838,7 @@ public DirectMessage getDirectMessageEvents(final AdditionalParameters additiona Map parameters = additionalParameters.getMapFromParameters(); parameters.put(DM_FIELDS, ALL_DM_FIELDS); parameters.put(EXPANSION, ALL_DM_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); return getRequestHelperV1().getRequestWithParameters(url, parameters, DirectMessage.class).orElseThrow(NoSuchElementException::new); @@ -839,7 +855,7 @@ public DirectMessage getDirectMessagesByConversation(String conversationId, fina Map parameters = additionalParameters.getMapFromParameters(); parameters.put(DM_FIELDS, ALL_DM_FIELDS); parameters.put(EXPANSION, ALL_DM_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); return getRequestHelperV1().getRequestWithParameters(url, parameters, DirectMessage.class).orElseThrow(NoSuchElementException::new); @@ -856,7 +872,7 @@ public DirectMessage getDirectMessagesByUser(final String participantId, final A Map parameters = additionalParameters.getMapFromParameters(); parameters.put(DM_FIELDS, ALL_DM_FIELDS); parameters.put(EXPANSION, ALL_DM_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); return getRequestHelperV1().getRequestWithParameters(url, parameters, DirectMessage.class).orElseThrow(NoSuchElementException::new); @@ -919,10 +935,15 @@ public PostDmResponse createUserDmConversation(String participantId, DmMessage m @Override public Tweet getTweet(String tweetId) { + return getTweet(tweetId, REQUEST_TWEET_FIELDS_SCOPE.PUBLIC); + } + + @Override + public Tweet getTweet(String tweetId, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope) { String url = getUrlHelper().getTweetUrl(tweetId); Map parameters = new HashMap<>(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, getFieldsForRequestTweetFieldsScope(requestTweetFieldsScope)); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); return getRequestHelper().getRequestWithParameters(url, parameters, TweetV2.class).orElseThrow(NoSuchElementException::new); @@ -930,10 +951,15 @@ public Tweet getTweet(String tweetId) { @Override public TweetList getTweets(List tweetIds) { + return getTweets(tweetIds, REQUEST_TWEET_FIELDS_SCOPE.PUBLIC); + } + + @Override + public TweetList getTweets(List tweetIds, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope) { String url = getUrlHelper().getTweetsUrl(); Map parameters = new HashMap<>(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, getFieldsForRequestTweetFieldsScope(requestTweetFieldsScope)); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); StringBuilder result = new StringBuilder(); @@ -972,7 +998,7 @@ public TweetList searchTweets(String query) { public TweetList searchTweets(String query, AdditionalParameters additionalParameters) { Map parameters = additionalParameters.getMapFromParameters(); parameters.put(QUERY, query); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(EXPANSION, ALL_EXPANSIONS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); @@ -996,10 +1022,10 @@ public TweetList searchAllTweets(final String query, AdditionalParameters additi Map parameters = additionalParameters.getMapFromParameters(); parameters.put(QUERY, query); if (additionalParameters.getMaxResults() <= 100) { - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); } else { - LOGGER.warn("Removing context_annotations from tweet_fields because max_result is greater 100"); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS.replace(",context_annotations", "")); + LOGGER.warn("removing context_annotations from tweet_fields because max_result is greater 100"); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC.replace(",context_annotations", "")); } parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(EXPANSION, ALL_EXPANSIONS); @@ -1135,7 +1161,7 @@ public Future startFilteredStream(Consumer consumer) { String url = urlHelper.getFilteredStreamUrl(); Map parameters = new HashMap<>(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); return requestHelperV2.getAsyncRequest(url, parameters, consumer); @@ -1151,7 +1177,7 @@ public Future startFilteredStream(IAPIEventListener listener, int back String url = urlHelper.getFilteredStreamUrl(); Map parameters = new HashMap<>(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); if (backfillMinutes > 0) { @@ -1243,7 +1269,7 @@ public Future startSampledStream(Consumer consumer) { String url = urlHelper.getSampledStreamUrl(); Map parameters = new HashMap<>(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); return requestHelperV2.getAsyncRequest(url, parameters, consumer); @@ -1259,7 +1285,7 @@ public Future startSampledStream(IAPIEventListener listener, int backf String url = urlHelper.getSampledStreamUrl(); Map parameters = new HashMap<>(); parameters.put(EXPANSION, ALL_EXPANSIONS); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(MEDIA_FIELD, ALL_MEDIA_FIELDS); if (backfillMinutes > 0) { @@ -1270,13 +1296,23 @@ public Future startSampledStream(IAPIEventListener listener, int backf @Override public TweetList getUserTimeline(final String userId) { - return getUserTimeline(userId, AdditionalParameters.builder().maxResults(100).build()); + return getUserTimeline(userId, AdditionalParameters.builder().maxResults(100).build(), REQUEST_TWEET_FIELDS_SCOPE.PUBLIC); + } + + @Override + public TweetList getUserTimeline(final String userId, AdditionalParameters additionalParameters) { + return getUserTimeline(userId, additionalParameters, REQUEST_TWEET_FIELDS_SCOPE.PUBLIC); + } + + @Override + public TweetList getUserTimeline(final String userId, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope) { + return getUserTimeline(userId, AdditionalParameters.builder().maxResults(100).build(), requestTweetFieldsScope); } @Override - public TweetList getUserTimeline(String userId, AdditionalParameters additionalParameters) { + public TweetList getUserTimeline(String userId, AdditionalParameters additionalParameters, REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope) { Map parameters = additionalParameters.getMapFromParameters(); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, getFieldsForRequestTweetFieldsScope(requestTweetFieldsScope)); parameters.put(USER_FIELDS, ALL_USER_FIELDS); parameters.put(PLACE_FIELDS, ALL_PLACE_FIELDS); parameters.put(POLL_FIELDS, ALL_POLL_FIELDS); @@ -1300,7 +1336,7 @@ public TweetList getUserMentions(final String userId) { @Override public TweetList getUserMentions(final String userId, AdditionalParameters additionalParameters) { Map parameters = additionalParameters.getMapFromParameters(); - parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS); + parameters.put(TWEET_FIELDS, ALL_TWEET_FIELDS_PUBLIC); String url = urlHelper.getUserMentionsUrl(userId); if (!additionalParameters.isRecursiveCall()) { return getRequestHelperV2().getRequestWithParameters(url, parameters, TweetList.class).orElseThrow(NoSuchElementException::new); @@ -1528,4 +1564,18 @@ public String getUserIdFromAccessToken() { } return accessToken.substring(0, accessToken.indexOf("-")); } + + public String getFieldsForRequestTweetFieldsScope(REQUEST_TWEET_FIELDS_SCOPE requestTweetFieldsScope) { + switch (requestTweetFieldsScope) { + case NON_PUBLIC: + return ALL_TWEET_FIELDS_NON_PUBLIC; + case NON_PUBLIC_PROMOTED: + return ALL_TWEET_FIELDS_NON_PUBLIC_PROMOTED; + case PUBLIC: + return ALL_TWEET_FIELDS_PUBLIC; + default: + throw new RuntimeException("REQUEST_TWEET_FIELDS_SCOPE " + requestTweetFieldsScope + " not implemented."); + } + } + } diff --git a/src/main/java/io/github/redouane59/twitter/dto/tweet/Attachments.java b/src/main/java/io/github/redouane59/twitter/dto/tweet/Attachments.java index f1e332b4..3de59dcb 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/tweet/Attachments.java +++ b/src/main/java/io/github/redouane59/twitter/dto/tweet/Attachments.java @@ -16,5 +16,7 @@ public class Attachments { @JsonProperty("media_keys") private String[] mediaKeys; + @JsonProperty("poll_ids") + private String[] pollIds; } diff --git a/src/main/java/io/github/redouane59/twitter/dto/tweet/Tweet.java b/src/main/java/io/github/redouane59/twitter/dto/tweet/Tweet.java index 2f11f621..f58019b3 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/tweet/Tweet.java +++ b/src/main/java/io/github/redouane59/twitter/dto/tweet/Tweet.java @@ -66,6 +66,177 @@ public interface Tweet { */ int getQuoteCount(); + /** + * Get the number of times the Tweet has been viewed. This is a private metric, and requires the + * use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times the Tweet has been viewed or null if no nonPublicMetrics exists. + */ + Integer getImpressionCount(); + + /** + * Get the number of times a user clicks on a URL link or URL preview card in a Tweet. This is a + * private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times a user clicks on a URL link or URL preview card in a Tweet or null + * if no nonPublicMetrics exists. + */ + Integer getUrlLinkClicks(); + + /** + * Get the number of times a user clicks the following portions of a Tweet - display name, user + * name, profile picture. This is a private metric, and requires the use of OAuth 1.0a or OAuth + * 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times a user clicks the following portions of a Tweet - display name, + * user name, profile picture or null if no nonPublicMetrics exists. + */ + Integer getUserProfileClicks(); + + /** + * Get the number of times the Tweet has been viewed organically. This is a private metric, and + * requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times the Tweet has been viewed organically or null if no organicMetrics + * exists. + */ + Integer getOrganicImpressionCount(); + + /** + * Get the number of likes the Tweet has received organically. This is a private metric, and + * requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of likes the Tweet has received organically or null if no organicMetrics + * exists. + */ + Integer getOrganicLikeCount(); + + /** + * Get the number of replies the Tweet has received organically. This is a private metric, and + * requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of replies the Tweet has received organically or null if no organicMetrics + * exists. + */ + Integer getOrganicReplyCount(); + + /** + * Get the number of times the Tweet has been Retweeted organically. This is a private metric, and + * requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times the Tweet has been Retweeted organically. This is a private metric, + * and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication or null if + * no organicMetrics exists. + */ + Integer getOrganicRetweetCount(); + + /** + * Get the number of times a user clicks on a URL link or URL preview card in a Tweet organically. + * This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context + * authentication. + * + * API v2 only! + * + * @return the number of times a user clicks on a URL link or URL preview card in a Tweet + * organically or null if no organicMetrics exists. + */ + Integer getOrganicUrlLinkClicks(); + + /** + * Get the number of times a user clicks the following portions of a Tweet organically - display + * name, user name, profile picture. This is a private metric, and requires the use of OAuth 1.0a + * or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times a user clicks the following portions of a Tweet organically - + * display name, user name, profile picture or null if no organicMetrics exists. + */ + Integer getOrganicUserProfileClicks(); + + /** + * Number of times the Tweet has been viewed when that Tweet is being promoted. This is a private + * metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times the Tweet has been viewed when that Tweet is being promoted or null + * if no promotedMetrics exists. + */ + Integer getPromotedImpressionCount(); + + /** + * Number of Likes of this Tweet when that Tweet is being promoted. This is a private metric, and + * requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of Likes of this Tweet when that Tweet is being promoted or null if no + * promotedMetrics exists. + */ + Integer getPromotedLikeCount(); + + /** + * Number of Replies to this Tweet when that Tweet is being promoted. This is a private metric, + * and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return number of Replies to this Tweet when that Tweet is being promoted or null if no + * promotedMetrics exists. + */ + Integer getPromotedReplyCount(); + + /** + * Number of times this Tweet has been Retweeted when that Tweet is being promoted. This is a + * private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times this Tweet has been Retweeted when that Tweet is being promoted or + * null if no promotedMetrics exists. + */ + Integer getPromotedRetweetCount(); + + /** + * Number of times a user clicks on a URL link or URL preview card in a Tweet when it is being + * promoted. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User + * Context authentication. + * + * API v2 only! + * + * @return the number of times a user clicks on a URL link or URL preview card in a Tweet when it + * is being promoted or null if no promotedMetrics exists. + */ + Integer getPromotedUrlLinkClicks(); + + /** + * Number of times a user clicks the following portions of a Tweet when it is being promoted - + * display name, user name, profile picture. This is a private metric, and requires the use of + * OAuth 1.0a or OAuth 2.0 User Context authentication. + * + * API v2 only! + * + * @return the number of times a user clicks the following portions of a Tweet when it is being + * promoted - display name, user name, profile picture or null if no promotedMetrics + * exists. + */ + Integer getPromotedUserProfileClicks(); + /** * Get the creation date of the tweet * diff --git a/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV1.java b/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV1.java index 3b438d18..0efe7b16 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV1.java +++ b/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV1.java @@ -1,6 +1,7 @@ package io.github.redouane59.twitter.dto.tweet; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import io.github.redouane59.twitter.dto.stream.StreamRules.StreamRule; @@ -146,6 +147,110 @@ public String getAuthorId() { return user.getId(); } + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getImpressionCount() { + throw new RuntimeException("Mectric Impression is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getUrlLinkClicks() { + throw new RuntimeException("Mectric UrlLinkClicks is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getUserProfileClicks() { + throw new RuntimeException("Mectric UserProfileClicks is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getOrganicImpressionCount() { + throw new RuntimeException("Mectric OrganicImpressionCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getOrganicLikeCount() { + throw new RuntimeException("Mectric OrganicLikeCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getOrganicReplyCount() { + throw new RuntimeException("Mectric OrganicReplyCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getOrganicRetweetCount() { + throw new RuntimeException("Mectric OrganicRetweetCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getOrganicUrlLinkClicks() { + throw new RuntimeException("Mectric OrganicUrlLinkClicks is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getOrganicUserProfileClicks() { + throw new RuntimeException("Mectric OrganicUserProfileClicks is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getPromotedImpressionCount() { + throw new RuntimeException("Mectric PromotedImpressionCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getPromotedLikeCount() { + throw new RuntimeException("Mectric PromotedLikeCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getPromotedReplyCount() { + throw new RuntimeException("Mectric PromotedReplyCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getPromotedRetweetCount() { + throw new RuntimeException("Mectric PromotedRetweetCount is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getPromotedUrlLinkClicks() { + throw new RuntimeException("Mectric PromotedUrlLinkClicks is not vailable in Twitter API v1."); + } + + /* Not available in API v1! */ + @Override + @JsonIgnore + public Integer getPromotedUserProfileClicks() { + throw new RuntimeException("Mectric PromotedUserProfileClicks is not vailable in Twitter API v1."); + } @Getter @Setter diff --git a/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV2.java b/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV2.java index d69b6b19..92a8d24a 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV2.java +++ b/src/main/java/io/github/redouane59/twitter/dto/tweet/TweetV2.java @@ -188,6 +188,126 @@ public int getQuoteCount() { return data.getPublicMetrics().getQuoteCount(); } + @Override + public Integer getImpressionCount() { + if (data == null || data.getNonPublicMetrics() == null) { + return null; + } + return data.getNonPublicMetrics().getImpressionCount(); + } + + @Override + public Integer getUrlLinkClicks() { + if (data == null || data.getNonPublicMetrics() == null) { + return null; + } + return data.getNonPublicMetrics().getUrlLinkClicks(); + } + + @Override + public Integer getUserProfileClicks() { + if (data == null || data.getNonPublicMetrics() == null) { + return null; + } + return data.getNonPublicMetrics().getUserProfileClicks(); + } + + @Override + public Integer getOrganicImpressionCount() { + if (data == null || data.getOrganicMetrics() == null) { + return null; + } + return data.getOrganicMetrics().getImpressionCount(); + } + + @Override + public Integer getOrganicLikeCount() { + if (data == null || data.getOrganicMetrics() == null) { + return null; + } + return data.getOrganicMetrics().getLikeCount(); + } + + @Override + public Integer getOrganicReplyCount() { + if (data == null || data.getOrganicMetrics() == null) { + return null; + } + return data.getOrganicMetrics().getReplyCount(); + } + + @Override + public Integer getOrganicRetweetCount() { + if (data == null || data.getOrganicMetrics() == null) { + return null; + } + return data.getOrganicMetrics().getRetweetCount(); + } + + @Override + public Integer getOrganicUrlLinkClicks() { + if (data == null || data.getOrganicMetrics() == null) { + return null; + } + return data.getOrganicMetrics().getUrlLinkClicks(); + } + + @Override + public Integer getOrganicUserProfileClicks() { + if (data == null || data.getOrganicMetrics() == null) { + return null; + } + return data.getOrganicMetrics().getUserProfileClicks(); + } + + @Override + public Integer getPromotedImpressionCount() { + if (data == null || data.getPromotedMetrics() == null) { + return null; + } + return data.getPromotedMetrics().getImpressionCount(); + } + + @Override + public Integer getPromotedLikeCount() { + if (data == null || data.getPromotedMetrics() == null) { + return null; + } + return data.getPromotedMetrics().getLikeCount(); + } + + @Override + public Integer getPromotedReplyCount() { + if (data == null || data.getPromotedMetrics() == null) { + return null; + } + return data.getPromotedMetrics().getReplyCount(); + } + + @Override + public Integer getPromotedRetweetCount() { + if (data == null || data.getPromotedMetrics() == null) { + return null; + } + return data.getPromotedMetrics().getRetweetCount(); + } + + @Override + public Integer getPromotedUrlLinkClicks() { + if (data == null || data.getPromotedMetrics() == null) { + return null; + } + return data.getPromotedMetrics().getUrlLinkClicks(); + } + + @Override + public Integer getPromotedUserProfileClicks() { + if (data == null || data.getPromotedMetrics() == null) { + return null; + } + return data.getPromotedMetrics().getUserProfileClicks(); + } + @Override public String getInReplyToUserId() { if (data == null) { @@ -240,6 +360,26 @@ public TweetType getTweetType() { return data.getReferencedTweets().get(0).getType(); } + /** Returns whether the TweetPublicMetricsDTO object exists. Probably the field public_metrics was not requested if not. */ + public boolean hasTweetPublicMetrics() { + return data!=null && data.hasPublicMetrics(); + } + + /** Returns whether the TweetNonPublicMetricsDTO object exists. Probably the field non_public_metrics was not requested if not. */ + public boolean hasTweetNonPublicMetrics() { + return data!=null && data.hasNonPublicMetrics(); + } + + /** Returns whether the TweetOrganicMetricsDTO object exists. Probably the field organic_metrics was not requested if not. */ + public boolean hasTweetOrganicMetrics() { + return data!=null && data.hasOrganicMetrics(); + } + + /** Returns whether the TweetPromotedMetricsDTO object exists. Probably the field promoted_metrics was not requested if not. */ + public boolean hasTweetPromotedMetrics() { + return data!=null && data.hasPromotedMetrics(); + } + @Getter @Setter @Builder @@ -262,6 +402,15 @@ public static class TweetData implements Tweet { @JsonProperty("public_metrics") @JsonInclude(Include.NON_NULL) private TweetPublicMetricsDTO publicMetrics; + @JsonProperty("non_public_metrics") + @JsonInclude(Include.NON_NULL) + private TweetNonPublicMetricsDTO nonPublicMetrics; + @JsonProperty("organic_metrics") + @JsonInclude(Include.NON_NULL) + private TweetOrganicMetricsDTO organicMetrics; + @JsonProperty("promoted_metrics") + @JsonInclude(Include.NON_NULL) + private TweetPromotedMetricsDTO promotedMetrics; @JsonProperty("possibly_sensitive") private boolean possiblySensitive; private String lang; @@ -301,6 +450,160 @@ public int getQuoteCount() { return publicMetrics.getQuoteCount(); } + /** Returns whether the TweetPublicMetricsDTO object exists. Probably the field public_metrics was not requested if not. */ + public boolean hasPublicMetrics() { + return publicMetrics!=null; + } + + @Override + @JsonIgnore + public Integer getImpressionCount() { + if (nonPublicMetrics == null) { + return null; + } + return nonPublicMetrics.getImpressionCount(); + } + + @Override + @JsonIgnore + public Integer getUrlLinkClicks() { + if (nonPublicMetrics == null) { + return null; + } + return nonPublicMetrics.getUrlLinkClicks(); + } + @Override + @JsonIgnore + public Integer getUserProfileClicks() { + if (nonPublicMetrics == null) { + return null; + } + return nonPublicMetrics.getUserProfileClicks(); + } + + /** Returns whether the TweetNonPublicMetricsDTO object exists. Probably the field non_public_metrics was not requested if not. */ + public boolean hasNonPublicMetrics() { + return nonPublicMetrics!=null; + } + + @Override + @JsonIgnore + public Integer getOrganicImpressionCount() { + if (organicMetrics == null) { + return null; + } + return organicMetrics.getImpressionCount(); + } + + @Override + @JsonIgnore + public Integer getOrganicLikeCount() { + if (organicMetrics == null) { + return null; + } + return organicMetrics.getLikeCount(); + } + + @Override + @JsonIgnore + public Integer getOrganicReplyCount() { + if (organicMetrics == null) { + return null; + } + return organicMetrics.getReplyCount(); + } + + @Override + @JsonIgnore + public Integer getOrganicRetweetCount() { + if (organicMetrics == null) { + return null; + } + return organicMetrics.getRetweetCount(); + } + + @Override + @JsonIgnore + public Integer getOrganicUrlLinkClicks() { + if (organicMetrics == null) { + return null; + } + return organicMetrics.getUrlLinkClicks(); + } + + @Override + @JsonIgnore + public Integer getOrganicUserProfileClicks() { + if (organicMetrics == null) { + return null; + } + return organicMetrics.getUserProfileClicks(); + } + + /** Returns whether the TweetOrganicMetricsDTO object exists. Probably the field organic_metrics was not requested if not. */ + public boolean hasOrganicMetrics() { + return organicMetrics!=null; + } + + @Override + @JsonIgnore + public Integer getPromotedImpressionCount() { + if (promotedMetrics == null) { + return null; + } + return promotedMetrics.getImpressionCount(); + } + + @Override + @JsonIgnore + public Integer getPromotedLikeCount() { + if (promotedMetrics == null) { + return null; + } + return promotedMetrics.getLikeCount(); + } + + @Override + @JsonIgnore + public Integer getPromotedReplyCount() { + if (promotedMetrics == null) { + return null; + } + return promotedMetrics.getReplyCount(); + } + + @Override + @JsonIgnore + public Integer getPromotedRetweetCount() { + if (promotedMetrics == null) { + return null; + } + return promotedMetrics.getRetweetCount(); + } + + @Override + @JsonIgnore + public Integer getPromotedUrlLinkClicks() { + if (promotedMetrics == null) { + return null; + } + return promotedMetrics.getUrlLinkClicks(); + } + + @Override + @JsonIgnore + public Integer getPromotedUserProfileClicks() { + if (promotedMetrics == null) { + return null; + } + return promotedMetrics.getUserProfileClicks(); + } + + /** Returns whether the TweetPromotedMetricsDTO object exists. Probably the field promoted_metrics was not requested if not. */ + public boolean hasPromotedMetrics() { + return promotedMetrics!=null; + } + @Override public String getInReplyToStatusId() { if (referencedTweets == null || referencedTweets.isEmpty()) { @@ -385,7 +688,6 @@ public static class Includes { private List places; } - @Getter @Setter public static class TweetPublicMetricsDTO { @@ -400,6 +702,75 @@ public static class TweetPublicMetricsDTO { private int quoteCount; } + /** Non-public engagement metrics for the Tweet at the time of the request. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. + * To return this field, add tweet.fields=non_public_metrics in the request's query parameter.*/ + @Getter + @Setter + public static class TweetNonPublicMetricsDTO { + + /** Number of times the Tweet has been viewed. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. **/ + @JsonProperty("impression_count") + private int impressionCount; + /** Number of times a user clicks on a URL link or URL preview card in a Tweet. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("url_link_clicks") + private int urlLinkClicks; + /** Number of times a user clicks the following portions of a Tweet - display name, user name, profile picture. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("user_profile_clicks") + private int userProfileClicks; + } + + /** Organic engagement metrics for the Tweet at the time of the request. Requires user context authentication. */ + @Getter + @Setter + public static class TweetOrganicMetricsDTO { + + /** Number of times the Tweet has been viewed organically. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("impression_count") + private int impressionCount; + /** Number of times a user clicks on a URL link or URL preview card in a Tweet organically. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("url_link_clicks") + private int urlLinkClicks; + /** Number of times a user clicks the following portions of a Tweet organically - display name, user name, profile picture. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("user_profile_clicks") + private int userProfileClicks; + /** Number of times the Tweet has been Retweeted organically. */ + @JsonProperty("retweet_count") + private int retweetCount; + /** Number of replies the Tweet has received organically. */ + @JsonProperty("reply_count") + private int replyCount; + /** Number of likes the Tweet has received organically. */ + @JsonProperty("like_count") + private int likeCount; + + } + + /** Engagement metrics for the Tweet at the time of the request in a promoted context. Requires user context authentication. */ + @Getter + @Setter + public static class TweetPromotedMetricsDTO { + + /** Number of times the Tweet has been viewed when that Tweet is being promoted. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("impression_count") + private int impressionCount; + /** Number of times a user clicks on a URL link or URL preview card in a Tweet when it is being promoted. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("url_link_clicks") + private int urlLinkClicks; + /** Number of times a user clicks the following portions of a Tweet when it is being promoted - display name, user name, profile picture. This is a private metric, and requires the use of OAuth 1.0a or OAuth 2.0 User Context authentication. */ + @JsonProperty("user_profile_clicks") + private int userProfileClicks; + /** Number of times this Tweet has been Retweeted when that Tweet is being promoted. */ + @JsonProperty("retweet_count") + private int retweetCount; + /** Number of Replies to this Tweet when that Tweet is being promoted. */ + @JsonProperty("reply_count") + private int replyCount; + /** Number of Likes of this Tweet when that Tweet is being promoted. */ + @JsonProperty("like_count") + private int likeCount; + + } + @Getter @Setter public static class EntitiesV2 implements Entities { diff --git a/src/main/java/io/github/redouane59/twitter/dto/user/User.java b/src/main/java/io/github/redouane59/twitter/dto/user/User.java index 9c1a6390..fab4d23d 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/user/User.java +++ b/src/main/java/io/github/redouane59/twitter/dto/user/User.java @@ -70,6 +70,20 @@ public interface User { */ int getTweetCount(); + /** + * Get the number of lists that include this user. + * + * @return number of lists that include this user. + */ + public int getListedCount(); + + /** + * Returns whether the users public metrics is included in the response. + * + * @return whether users public metrics exists + */ + public boolean hasPublicMetrics(); + /** * Get the language of the user * diff --git a/src/main/java/io/github/redouane59/twitter/dto/user/UserV1.java b/src/main/java/io/github/redouane59/twitter/dto/user/UserV1.java index 00774306..fe3bbe0b 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/user/UserV1.java +++ b/src/main/java/io/github/redouane59/twitter/dto/user/UserV1.java @@ -69,6 +69,19 @@ public int hashCode() { return id.hashCode(); } + /* This is not supported in API v1. */ + @Override + public int getListedCount() { + LOGGER.debug("UnsupportedOperation"); + return 0; + } + + /* Returns whether the users public metrics is included in the response. This is always the case in API v1. */ + @Override + public boolean hasPublicMetrics() { + return true; + } + @Override public LocalDateTime getDateOfCreation() { return ConverterHelper.getDateFromTwitterString(dateOfCreation); diff --git a/src/main/java/io/github/redouane59/twitter/dto/user/UserV2.java b/src/main/java/io/github/redouane59/twitter/dto/user/UserV2.java index 902d4276..a6a11f84 100644 --- a/src/main/java/io/github/redouane59/twitter/dto/user/UserV2.java +++ b/src/main/java/io/github/redouane59/twitter/dto/user/UserV2.java @@ -78,6 +78,18 @@ public int getTweetCount() { return data.getPublicMetrics().getTweetCount(); } + @Override + @JsonIgnore + public int getListedCount() { + return data.getPublicMetrics().getListedCount(); + } + + /* Returns whether the UserPublicMetrics object exists. Probably the field public_metrics was not requested if not. */ + @Override + public boolean hasPublicMetrics() { + return data!=null && data.hasPublicMetrics(); + } + @Override @JsonIgnore public String getLang() { @@ -184,11 +196,21 @@ public int getFollowingCount() { return publicMetrics.getFollowingCount(); } + @Override + public int getListedCount() { + return publicMetrics.getListedCount(); + } + @Override public int getTweetCount() { return publicMetrics.getTweetCount(); } + /* Returns whether the UserPublicMetrics object exists. Probably the field public_metrics was not requested if not. */ + public boolean hasPublicMetrics() { + return publicMetrics!=null; + } + @Override @JsonIgnore public Tweet getPinnedTweet() { diff --git a/src/main/java/io/github/redouane59/twitter/helpers/RequestHelperV2.java b/src/main/java/io/github/redouane59/twitter/helpers/RequestHelperV2.java index 11706f95..45796fcd 100644 --- a/src/main/java/io/github/redouane59/twitter/helpers/RequestHelperV2.java +++ b/src/main/java/io/github/redouane59/twitter/helpers/RequestHelperV2.java @@ -23,6 +23,8 @@ import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.stream.Collectors; +import lombok.Getter; +import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.http.NameValuePair; @@ -32,14 +34,28 @@ @Slf4j public class RequestHelperV2 extends AbstractRequestHelper { + /** Use consumer key for user context if true; or else app-only bearer token. Default is false. */ + @Getter @Setter + private boolean useConsumerKey = false; + public RequestHelperV2(TwitterCredentials twitterCredentials) { super(twitterCredentials); } + public RequestHelperV2(TwitterCredentials twitterCredentials, boolean useConsumerKey) { + this(twitterCredentials); + this.useConsumerKey = useConsumerKey; + } + public RequestHelperV2(TwitterCredentials twitterCredentials, OAuth10aService service) { super(twitterCredentials, service); } + public RequestHelperV2(TwitterCredentials twitterCredentials, OAuth10aService service, boolean useConsumerKey) { + this(twitterCredentials, service); + this.useConsumerKey = useConsumerKey; + } + @Override public Optional getRequest(String url, Class classType) { return getRequestWithParameters(url, null, classType); @@ -128,7 +144,11 @@ public Optional getRequestWithHeader(String url, Map head @Override protected void signRequest(OAuthRequest request) { - request.addHeader(OAuthConstants.HEADER, "Bearer " + getBearerToken()); + if (useConsumerKey) { + getService().signRequest(getTwitterCredentials().asAccessToken(), request); + } else { + request.addHeader(OAuthConstants.HEADER, "Bearer " + getBearerToken()); + } } public String getBearerToken() {