diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 10c14e2..24244a5 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -3,7 +3,8 @@
"allow": [
"Bash(tree:*)",
"Bash(mvn clean compile:*)",
- "WebFetch(domain:github.com)"
+ "WebFetch(domain:github.com)",
+ "Bash(rm:*)"
],
"deny": [],
"ask": []
diff --git a/.env.example b/.env.example
index 4e94538..47211e8 100644
--- a/.env.example
+++ b/.env.example
@@ -1,12 +1,13 @@
-# OpenAI API Configuration
-OPENAI_API_KEY=your_openai_api_key_here
+# Google Gemini API Configuration
+# Get your free API key from: https://aistudio.google.com/app/apikey
+GEMINI_API_KEY=your_gemini_api_key_here
-# Twitter API Configuration (OAuth 2.0)
+# Twitter API Configuration (OAuth 1.0a)
+# Get your credentials from Twitter Developer Portal: https://developer.twitter.com/
TWITTER_API_KEY=your_twitter_api_key_here
TWITTER_API_SECRET=your_twitter_api_secret_here
TWITTER_ACCESS_TOKEN=your_twitter_access_token_here
TWITTER_ACCESS_TOKEN_SECRET=your_twitter_access_token_secret_here
-TWITTER_BEARER_TOKEN=your_twitter_bearer_token_here
# Tweet Generation Configuration
TWEET_PROMPT=Write a short, engaging tweet about technology trends
diff --git a/.gitignore b/.gitignore
index b9a60e3..a86511d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# Environment variables (IMPORTANT: Never commit API keys!)
.env
+# OAuth 2.0 tokens (IMPORTANT: Never commit access tokens!)
+.twitter_tokens.json
+
# Maven
target/
pom.xml.tag
diff --git a/README.md b/README.md
index 4cef3a7..09a721e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# TweetBot - AI-Powered Twitter Bot
-An automated Twitter bot that generates and posts tweets using OpenAI's ChatGPT API. This Java application allows you to create engaging social media content with custom prompts.
+An automated Twitter bot that generates and posts tweets using Google Gemini's free API. This Java application allows you to create engaging social media content with custom prompts.
## Features
-- Generate tweet content using ChatGPT (GPT-3.5-turbo)
+- Generate tweet content using Google Gemini (gemini-2.0-flash-exp) - **Free Tier**
- **Three operation modes:**
- Interactive mode with manual approval
- Auto-post mode for one-time tweets
@@ -22,17 +22,24 @@ An automated Twitter bot that generates and posts tweets using OpenAI's ChatGPT
- Java 17 or higher
- Maven 3.6 or higher
-- OpenAI API key
+- Google Gemini API key (free tier available)
- Twitter Developer Account with API credentials
## Setup Instructions
-### 1. Get OpenAI API Key
+### 1. Get Google Gemini API Key (Free)
-1. Go to [OpenAI Platform](https://platform.openai.com/)
-2. Sign up or log in to your account
-3. Navigate to API keys section
-4. Create a new API key and save it securely
+1. Go to [Google AI Studio](https://aistudio.google.com/app/apikey)
+2. Sign in with your Google account
+3. Click "Create API Key"
+4. Select or create a Google Cloud project
+5. Copy the API key and save it securely
+
+**Note:** The free tier of Google Gemini includes:
+- 15 requests per minute
+- 1 million tokens per minute
+- 1,500 requests per day
+- More than enough for a tweet bot!
### 2. Get Twitter API Credentials
@@ -44,7 +51,6 @@ An automated Twitter bot that generates and posts tweets using OpenAI's ChatGPT
- API Secret (Consumer Secret)
- Access Token
- Access Token Secret
- - Bearer Token
**Important:** Ensure your Twitter app has **Read and Write** permissions:
- Go to your app settings
@@ -60,12 +66,11 @@ An automated Twitter bot that generates and posts tweets using OpenAI's ChatGPT
2. Edit `.env` and add your API credentials:
```
- OPENAI_API_KEY=sk-your-openai-api-key-here
+ GEMINI_API_KEY=your-gemini-api-key-here
TWITTER_API_KEY=your-twitter-api-key
TWITTER_API_SECRET=your-twitter-api-secret
TWITTER_ACCESS_TOKEN=your-twitter-access-token
TWITTER_ACCESS_TOKEN_SECRET=your-twitter-access-token-secret
- TWITTER_BEARER_TOKEN=your-twitter-bearer-token
TWEET_PROMPT=Write a short, engaging tweet about technology trends
IMAGE_PATH=/path/to/your/image.jpg
```
@@ -80,7 +85,7 @@ An automated Twitter bot that generates and posts tweets using OpenAI's ChatGPT
- Supported formats: JPEG, PNG, GIF
- Maximum file size: 5MB (Twitter limit)
-### 5. Build the Project
+### 4. Build the Project
```bash
mvn clean package
@@ -216,7 +221,7 @@ tweetbot/
│ ├── config/
│ │ └── Config.java # Configuration loader
│ └── service/
- │ ├── OpenAIService.java # ChatGPT integration
+ │ ├── GeminiService.java # Google Gemini integration
│ └── TwitterService.java # Twitter API integration
└── resources/
└── logback.xml # Logging configuration
@@ -228,12 +233,11 @@ tweetbot/
| Variable | Required | Description |
|----------|----------|-------------|
-| `OPENAI_API_KEY` | Yes | Your OpenAI API key |
+| `GEMINI_API_KEY` | Yes | Your Google Gemini API key (free tier available) |
| `TWITTER_API_KEY` | Yes | Twitter API Key (Consumer Key) |
| `TWITTER_API_SECRET` | Yes | Twitter API Secret (Consumer Secret) |
| `TWITTER_ACCESS_TOKEN` | Yes | Twitter Access Token |
| `TWITTER_ACCESS_TOKEN_SECRET` | Yes | Twitter Access Token Secret |
-| `TWITTER_BEARER_TOKEN` | Yes | Twitter Bearer Token |
| `TWEET_PROMPT` | No | Default prompt for tweet generation |
| `IMAGE_PATH` | No | Path to image file to attach to every tweet (jpg, png, gif) |
@@ -371,9 +375,10 @@ Ensure your `.env` file exists and contains all required variables.
### "Failed to generate tweet"
-1. Verify your OpenAI API key is valid
-2. Check that you have sufficient API credits
+1. Verify your Gemini API key is valid
+2. Check that you're within the free tier rate limits (15 RPM, 1500 RPD)
3. Review the error message in logs
+4. Ensure your Google Cloud project is properly configured
### Twitter API Rate Limits
@@ -385,9 +390,7 @@ Twitter API has rate limits:
## Dependencies
-- **Twitter API Java SDK** (2.0.3) - Twitter API client
-- **OpenAI GPT-3 Java** (0.18.2) - OpenAI API client
-- **OkHttp** (4.12.0) - HTTP client
+- **OkHttp** (4.12.0) - HTTP client for API requests
- **Jackson** (2.16.1) - JSON processing
- **SLF4J & Logback** (2.0.9) - Logging
- **Dotenv** (3.0.0) - Environment variable management
@@ -406,7 +409,6 @@ private static final int SCHEDULE_INTERVAL_MINUTES = 30; // Change this value
### Other Enhancement Ideas
-- Add support for images and media attachments
- Implement tweet threading for longer content
- Add sentiment analysis before posting
- Create multiple prompt templates with rotation
@@ -429,4 +431,4 @@ For issues or questions:
1. Check the troubleshooting section
2. Review application logs in `tweetbot.log`
3. Verify API credentials and permissions
-4. Check Twitter and OpenAI API status pages
+4. Check Twitter and Google AI Studio status pages
diff --git a/pom.xml b/pom.xml
index 81e143a..cad0354 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
jar
TweetBot
- Auto-posting tweets with ChatGPT generated content
+ Auto-posting tweets with Google Gemini generated content
17
@@ -20,13 +20,6 @@
-
-
- com.theokanning.openai-gpt3-java
- service
- 0.18.2
-
-
com.squareup.okhttp3
diff --git a/src/main/java/com/voidcode/tweetbot/TweetBot.java b/src/main/java/com/voidcode/tweetbot/TweetBot.java
index ecc7b26..24c9136 100644
--- a/src/main/java/com/voidcode/tweetbot/TweetBot.java
+++ b/src/main/java/com/voidcode/tweetbot/TweetBot.java
@@ -1,7 +1,7 @@
package com.voidcode.tweetbot;
import com.voidcode.tweetbot.config.Config;
-import com.voidcode.tweetbot.service.OpenAIService;
+import com.voidcode.tweetbot.service.GeminiService;
import com.voidcode.tweetbot.service.TwitterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit;
public class TweetBot {
private static final Logger logger = LoggerFactory.getLogger(TweetBot.class);
private static final int SCHEDULE_INTERVAL_MINUTES = 30;
- private static OpenAIService openAIService;
+ private static GeminiService geminiService;
private static TwitterService twitterService;
private static String tweetPrompt;
private static String imagePath;
@@ -48,7 +48,7 @@ public class TweetBot {
// Load configuration
logger.info("Loading configuration...");
- String openAIApiKey = Config.getOpenAIApiKey();
+ String geminiApiKey = Config.getGeminiApiKey();
String twitterApiKey = Config.getTwitterApiKey();
String twitterApiSecret = Config.getTwitterApiSecret();
String twitterAccessToken = Config.getTwitterAccessToken();
@@ -66,7 +66,7 @@ public class TweetBot {
// Initialize services
logger.info("Initializing services...");
- openAIService = new OpenAIService(openAIApiKey);
+ geminiService = new GeminiService(geminiApiKey);
twitterService = new TwitterService(
twitterApiKey,
twitterApiSecret,
@@ -114,8 +114,8 @@ public class TweetBot {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("Shutdown signal received, stopping TweetBot...");
isRunning = false;
- if (openAIService != null) {
- openAIService.shutdown();
+ if (geminiService != null) {
+ geminiService.shutdown();
}
logger.info("TweetBot stopped gracefully");
}));
@@ -157,7 +157,7 @@ public class TweetBot {
try {
// Generate tweet content
logger.info("Generating tweet content...");
- String tweetContent = openAIService.generateTweet(tweetPrompt);
+ String tweetContent = geminiService.generateTweet(tweetPrompt);
// Display generated content
System.out.println("\n" + "=".repeat(60));
@@ -196,8 +196,8 @@ public class TweetBot {
} finally {
// Cleanup
- if (openAIService != null) {
- openAIService.shutdown();
+ if (geminiService != null) {
+ geminiService.shutdown();
}
logger.info("TweetBot finished");
}
@@ -209,7 +209,7 @@ public class TweetBot {
logger.info("[{}] Starting scheduled tweet posting...", timestamp);
// Generate tweet content
- String tweetContent = openAIService.generateTweet(tweetPrompt);
+ String tweetContent = geminiService.generateTweet(tweetPrompt);
logger.info("Generated tweet: {}", tweetContent);
// Post tweet
@@ -233,7 +233,7 @@ public class TweetBot {
}
private static void printUsage() {
- System.out.println("TweetBot - Automated Twitter posting with ChatGPT");
+ System.out.println("TweetBot - Automated Twitter posting with Google Gemini");
System.out.println("\nUsage:");
System.out.println(" java -jar tweetbot.jar [OPTIONS] [CUSTOM_PROMPT]");
System.out.println("\nOptions:");
diff --git a/src/main/java/com/voidcode/tweetbot/config/Config.java b/src/main/java/com/voidcode/tweetbot/config/Config.java
index bba5060..d8b41aa 100644
--- a/src/main/java/com/voidcode/tweetbot/config/Config.java
+++ b/src/main/java/com/voidcode/tweetbot/config/Config.java
@@ -8,12 +8,12 @@ public class Config {
.ignoreIfMissing()
.load();
- // OpenAI Configuration
- public static String getOpenAIApiKey() {
- return getEnvOrThrow("OPENAI_API_KEY");
+ // Google Gemini Configuration
+ public static String getGeminiApiKey() {
+ return getEnvOrThrow("GEMINI_API_KEY");
}
- // Twitter Configuration
+ // Twitter Configuration (OAuth 1.0a)
public static String getTwitterApiKey() {
return getEnvOrThrow("TWITTER_API_KEY");
}
@@ -30,10 +30,6 @@ public class Config {
return getEnvOrThrow("TWITTER_ACCESS_TOKEN_SECRET");
}
- public static String getTwitterBearerToken() {
- return getEnvOrThrow("TWITTER_BEARER_TOKEN");
- }
-
// Tweet Generation Configuration
public static String getTweetPrompt() {
return dotenv.get("TWEET_PROMPT", "Write a short, engaging tweet about technology");
diff --git a/src/main/java/com/voidcode/tweetbot/service/GeminiService.java b/src/main/java/com/voidcode/tweetbot/service/GeminiService.java
new file mode 100644
index 0000000..f8abf9e
--- /dev/null
+++ b/src/main/java/com/voidcode/tweetbot/service/GeminiService.java
@@ -0,0 +1,132 @@
+package com.voidcode.tweetbot.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+public class GeminiService {
+ private static final Logger logger = LoggerFactory.getLogger(GeminiService.class);
+ private static final int MAX_TWEET_LENGTH = 280;
+ private static final String GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent";
+ private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
+
+ private final String apiKey;
+ private final OkHttpClient httpClient;
+ private final ObjectMapper objectMapper;
+
+ public GeminiService(String apiKey) {
+ this.apiKey = apiKey;
+ this.httpClient = new OkHttpClient.Builder()
+ .connectTimeout(60, TimeUnit.SECONDS)
+ .readTimeout(60, TimeUnit.SECONDS)
+ .writeTimeout(60, TimeUnit.SECONDS)
+ .build();
+ this.objectMapper = new ObjectMapper();
+ logger.info("Gemini service initialized with model: gemini-2.0-flash-exp");
+ }
+
+ /**
+ * Generate tweet content using Google Gemini
+ * @param prompt The prompt to generate the tweet
+ * @return Generated tweet text
+ */
+ public String generateTweet(String prompt) {
+ try {
+ logger.info("Generating tweet with prompt: {}", prompt);
+
+ // Build the request body
+ String systemInstruction = "You are a creative social media content creator. Generate engaging tweets that are concise, " +
+ "interesting, and within Twitter's character limit. Do not include hashtags unless specifically " +
+ "requested. Keep it under 280 characters.";
+
+ String userPrompt = prompt + " Keep it under " + MAX_TWEET_LENGTH + " characters.";
+
+ String jsonBody = String.format(
+ "{\"contents\":[{\"parts\":[{\"text\":\"%s\\n\\n%s\"}]}],\"generationConfig\":{\"temperature\":0.8,\"maxOutputTokens\":100}}",
+ escapeJson(systemInstruction),
+ escapeJson(userPrompt)
+ );
+
+ // Build the request
+ Request request = new Request.Builder()
+ .url(GEMINI_API_URL + "?key=" + apiKey)
+ .post(RequestBody.create(jsonBody, JSON))
+ .build();
+
+ // Execute the request
+ try (Response response = httpClient.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ String errorBody = response.body() != null ? response.body().string() : "No error body";
+ logger.error("Gemini API request failed: {} - {}", response.code(), errorBody);
+ throw new IOException("Gemini API request failed: " + response.code() + " - " + errorBody);
+ }
+
+ String responseBody = response.body().string();
+ logger.debug("Gemini API response: {}", responseBody);
+
+ // Parse the response
+ JsonNode rootNode = objectMapper.readTree(responseBody);
+ JsonNode candidates = rootNode.get("candidates");
+
+ if (candidates == null || candidates.isEmpty()) {
+ throw new RuntimeException("No candidates in Gemini response");
+ }
+
+ JsonNode content = candidates.get(0).get("content");
+ if (content == null) {
+ throw new RuntimeException("No content in Gemini response");
+ }
+
+ JsonNode parts = content.get("parts");
+ if (parts == null || parts.isEmpty()) {
+ throw new RuntimeException("No parts in Gemini response");
+ }
+
+ String generatedText = parts.get(0).get("text").asText().trim();
+
+ // Remove quotes if the AI wrapped the tweet in quotes
+ generatedText = generatedText.replaceAll("^[\"']|[\"']$", "");
+
+ // Ensure it's within Twitter's character limit
+ if (generatedText.length() > MAX_TWEET_LENGTH) {
+ logger.warn("Generated tweet exceeds {} characters, truncating", MAX_TWEET_LENGTH);
+ generatedText = generatedText.substring(0, MAX_TWEET_LENGTH - 3) + "...";
+ }
+
+ logger.info("Generated tweet: {}", generatedText);
+ return generatedText;
+
+ }
+ } catch (IOException e) {
+ logger.error("Error generating tweet", e);
+ throw new RuntimeException("Failed to generate tweet: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Escape special characters for JSON
+ */
+ private String escapeJson(String text) {
+ return text.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+
+ /**
+ * Shutdown the Gemini service
+ */
+ public void shutdown() {
+ if (httpClient != null) {
+ httpClient.dispatcher().executorService().shutdown();
+ httpClient.connectionPool().evictAll();
+ logger.info("Gemini service shut down");
+ }
+ }
+}
diff --git a/src/main/java/com/voidcode/tweetbot/service/OpenAIService.java b/src/main/java/com/voidcode/tweetbot/service/OpenAIService.java
deleted file mode 100644
index cf93e2d..0000000
--- a/src/main/java/com/voidcode/tweetbot/service/OpenAIService.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.voidcode.tweetbot.service;
-
-import com.theokanning.openai.completion.chat.ChatCompletionRequest;
-import com.theokanning.openai.completion.chat.ChatCompletionResult;
-import com.theokanning.openai.completion.chat.ChatMessage;
-import com.theokanning.openai.completion.chat.ChatMessageRole;
-import com.theokanning.openai.service.OpenAiService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-
-public class OpenAIService {
- private static final Logger logger = LoggerFactory.getLogger(OpenAIService.class);
- private final OpenAiService openAiService;
- private static final int MAX_TWEET_LENGTH = 280;
-
- public OpenAIService(String apiKey) {
- this.openAiService = new OpenAiService(apiKey, Duration.ofSeconds(60));
- logger.info("OpenAI service initialized");
- }
-
- /**
- * Generate tweet content using ChatGPT
- * @param prompt The prompt to generate the tweet
- * @return Generated tweet text
- */
- public String generateTweet(String prompt) {
- try {
- logger.info("Generating tweet with prompt: {}", prompt);
-
- // Create the system message to set context
- ChatMessage systemMessage = new ChatMessage(
- ChatMessageRole.SYSTEM.value(),
- "You are a creative social media content creator. Generate engaging tweets that are concise, " +
- "interesting, and within Twitter's character limit. Do not include hashtags unless specifically " +
- "requested. Keep it under 280 characters."
- );
-
- // Create the user message with the prompt
- ChatMessage userMessage = new ChatMessage(
- ChatMessageRole.USER.value(),
- prompt + " Keep it under " + MAX_TWEET_LENGTH + " characters."
- );
-
- List messages = new ArrayList<>();
- messages.add(systemMessage);
- messages.add(userMessage);
-
- // Create the chat completion request
- ChatCompletionRequest request = ChatCompletionRequest.builder()
- .model("gpt-3.5-turbo")
- .messages(messages)
- .temperature(0.8)
- .maxTokens(100)
- .build();
-
- // Get the response
- ChatCompletionResult result = openAiService.createChatCompletion(request);
- String generatedText = result.getChoices().get(0).getMessage().getContent().trim();
-
- // Remove quotes if the AI wrapped the tweet in quotes
- generatedText = generatedText.replaceAll("^[\"']|[\"']$", "");
-
- // Ensure it's within Twitter's character limit
- if (generatedText.length() > MAX_TWEET_LENGTH) {
- logger.warn("Generated tweet exceeds {} characters, truncating", MAX_TWEET_LENGTH);
- generatedText = generatedText.substring(0, MAX_TWEET_LENGTH - 3) + "...";
- }
-
- logger.info("Generated tweet: {}", generatedText);
- return generatedText;
-
- } catch (Exception e) {
- logger.error("Error generating tweet", e);
- throw new RuntimeException("Failed to generate tweet: " + e.getMessage(), e);
- }
- }
-
- /**
- * Shutdown the OpenAI service
- */
- public void shutdown() {
- if (openAiService != null) {
- openAiService.shutdownExecutor();
- logger.info("OpenAI service shut down");
- }
- }
-}