Replace ChatGPT with Google Gemini

This commit is contained in:
2025-10-29 21:57:07 +09:00
parent c39b41a21f
commit 8f38bc82a0
9 changed files with 182 additions and 145 deletions

View File

@@ -3,7 +3,8 @@
"allow": [
"Bash(tree:*)",
"Bash(mvn clean compile:*)",
"WebFetch(domain:github.com)"
"WebFetch(domain:github.com)",
"Bash(rm:*)"
],
"deny": [],
"ask": []

View File

@@ -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

3
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -11,7 +11,7 @@
<packaging>jar</packaging>
<name>TweetBot</name>
<description>Auto-posting tweets with ChatGPT generated content</description>
<description>Auto-posting tweets with Google Gemini generated content</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
@@ -20,13 +20,6 @@
</properties>
<dependencies>
<!-- OpenAI Java Client -->
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>
<version>0.18.2</version>
</dependency>
<!-- OkHttp for HTTP requests -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>

View File

@@ -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:");

View File

@@ -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");

View File

@@ -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");
}
}
}

View File

@@ -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<ChatMessage> 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");
}
}
}