diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a47149a..10c14e2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(tree:*)", - "Bash(mvn clean compile:*)" + "Bash(mvn clean compile:*)", + "WebFetch(domain:github.com)" ], "deny": [], "ask": [] diff --git a/pom.xml b/pom.xml index 8b84b5d..81e143a 100644 --- a/pom.xml +++ b/pom.xml @@ -20,13 +20,6 @@ - - - com.twitter - twitter-api-java-sdk - 2.0.3 - - com.theokanning.openai-gpt3-java diff --git a/src/main/java/com/voidcode/tweetbot/service/TwitterService.java b/src/main/java/com/voidcode/tweetbot/service/TwitterService.java index dddd43e..edf4571 100644 --- a/src/main/java/com/voidcode/tweetbot/service/TwitterService.java +++ b/src/main/java/com/voidcode/tweetbot/service/TwitterService.java @@ -1,16 +1,12 @@ package com.voidcode.tweetbot.service; -import com.twitter.clientlib.TwitterCredentialsOAuth2; -import com.twitter.clientlib.api.TwitterApi; -import com.twitter.clientlib.ApiException; -import com.twitter.clientlib.model.TweetCreateRequest; -import com.twitter.clientlib.model.TweetCreateRequestMedia; -import com.twitter.clientlib.model.TweetCreateResponse; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -22,37 +18,26 @@ import javax.crypto.spec.SecretKeySpec; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; +import java.io.BufferedReader; +import java.io.InputStreamReader; public class TwitterService { private static final Logger logger = LoggerFactory.getLogger(TwitterService.class); - private final TwitterApi apiInstance; private final String apiKey; private final String apiSecret; private final String accessToken; private final String accessTokenSecret; + private final ObjectMapper objectMapper; private static final String MEDIA_UPLOAD_URL = "https://upload.twitter.com/1.1/media/upload.json"; + private static final String TWEET_CREATE_URL = "https://api.twitter.com/1.1/statuses/update.json"; public TwitterService(String apiKey, String apiSecret, String accessToken, String accessTokenSecret) { - try { - this.apiKey = apiKey; - this.apiSecret = apiSecret; - this.accessToken = accessToken; - this.accessTokenSecret = accessTokenSecret; - - // Initialize Twitter API with OAuth 1.0a credentials - TwitterCredentialsOAuth2 credentials = new TwitterCredentialsOAuth2( - apiKey, - apiSecret, - accessToken, - accessTokenSecret - ); - - apiInstance = new TwitterApi(credentials); - logger.info("Twitter service initialized successfully"); - } catch (Exception e) { - logger.error("Failed to initialize Twitter service", e); - throw new RuntimeException("Failed to initialize Twitter service: " + e.getMessage(), e); - } + this.apiKey = apiKey; + this.apiSecret = apiSecret; + this.accessToken = accessToken; + this.accessTokenSecret = accessTokenSecret; + this.objectMapper = new ObjectMapper(); + logger.info("Twitter service initialized successfully with OAuth 1.0a"); } /** @@ -74,9 +59,9 @@ public class TwitterService { try { logger.info("Posting tweet: {}", tweetText); - // Create the tweet request - TweetCreateRequest tweetCreateRequest = new TweetCreateRequest(); - tweetCreateRequest.setText(tweetText); + // Build query parameters + Map params = new TreeMap<>(); + params.put("status", tweetText); // Upload media if image path is provided if (imagePath != null && !imagePath.isEmpty()) { @@ -87,30 +72,64 @@ public class TwitterService { try { String mediaId = uploadMedia(imageFile); logger.info("Media uploaded successfully. Media ID: {}", mediaId); - - // Attach media to tweet - TweetCreateRequestMedia media = new TweetCreateRequestMedia(); - media.addMediaIdsItem(mediaId); - tweetCreateRequest.setMedia(media); + params.put("media_ids", mediaId); } catch (Exception e) { logger.error("Failed to upload media, posting tweet without image", e); } } } - // Post the tweet - TweetCreateResponse result = apiInstance.tweets().createTweet(tweetCreateRequest).execute(); + // Build URL with query parameters + StringBuilder urlBuilder = new StringBuilder(TWEET_CREATE_URL); + urlBuilder.append("?"); + for (Map.Entry entry : params.entrySet()) { + if (urlBuilder.charAt(urlBuilder.length() - 1) != '?') { + urlBuilder.append("&"); + } + urlBuilder.append(urlEncode(entry.getKey())).append("=").append(urlEncode(entry.getValue())); + } + String fullUrl = urlBuilder.toString(); + + // Create OAuth header (include query params in signature) + String authHeader = generateOAuthHeader("POST", TWEET_CREATE_URL, params); + + // Make HTTP request + HttpURLConnection connection = (HttpURLConnection) new URL(fullUrl).openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Authorization", authHeader); + connection.setRequestProperty("Content-Length", "0"); + + // Read response + int responseCode = connection.getResponseCode(); + + BufferedReader reader; + if (responseCode >= 200 && responseCode < 300) { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } else { + reader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + } + + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + + if (responseCode != 200) { + logger.error("Failed to post tweet. Status code: {}, Response: {}", responseCode, response.toString()); + throw new IOException("Failed to post tweet. Status code: " + responseCode + ", Response: " + response.toString()); + } + + // Parse tweet ID from response (v1.1 format) + JsonNode responseJson = objectMapper.readTree(response.toString()); + String tweetId = responseJson.get("id_str").asText(); - String tweetId = result.getData().getId(); logger.info("Tweet posted successfully. Tweet ID: {}", tweetId); logger.info("View tweet at: https://twitter.com/user/status/{}", tweetId); return tweetId; - } catch (ApiException e) { - logger.error("Error posting tweet. Status code: {}, Response: {}", - e.getCode(), e.getResponseBody(), e); - throw new RuntimeException("Failed to post tweet: " + e.getMessage(), e); } catch (Exception e) { logger.error("Unexpected error posting tweet", e); throw new RuntimeException("Failed to post tweet: " + e.getMessage(), e); @@ -264,10 +283,30 @@ public class TwitterService { public boolean verifyCredentials() { try { logger.info("Verifying Twitter credentials..."); - // Try to get the authenticated user's information - apiInstance.users().findMyUser().execute(); - logger.info("Twitter credentials verified successfully"); - return true; + + String verifyUrl = "https://api.twitter.com/1.1/account/verify_credentials.json"; + String authHeader = generateOAuthHeader("GET", verifyUrl, new TreeMap<>()); + + HttpURLConnection connection = (HttpURLConnection) new URL(verifyUrl).openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Authorization", authHeader); + + int responseCode = connection.getResponseCode(); + + if (responseCode == 200) { + logger.info("Twitter credentials verified successfully"); + return true; + } else { + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + StringBuilder response = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + logger.error("Failed to verify credentials. Status: {}, Response: {}", responseCode, response.toString()); + return false; + } } catch (Exception e) { logger.error("Failed to verify Twitter credentials", e); return false;