├── src ├── cli-progress.d.ts.ts └── index.ts ├── tsconfig.json ├── package.json └── README.md /src/cli-progress.d.ts.ts: -------------------------------------------------------------------------------- 1 | declare module 'cli-progress' { 2 | class SingleBar { 3 | constructor(options: any, preset: any); 4 | start(total: number, startValue: number): void; 5 | update(value: number): void; 6 | stop(): void; 7 | } 8 | const Presets: { 9 | shades_classic: any; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "outDir": "./dist", 10 | "rootDir": "./src", 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src/**/*.ts"], 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitter-telegram-bot", 3 | "version": "1.0.0", 4 | "description": "A bot to monitor Twitter and send messages to Telegram", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "prebuild": "npm install --save-dev @types/node @types/dotenv @types/telegraf @types/tslog", 8 | "build": "tsc", 9 | "start": "node dist/index.js" 10 | }, 11 | "dependencies": { 12 | "cli-progress": "^3.12.0", 13 | "dotenv": "^10.0.0", 14 | "ora": "^8.0.1", 15 | "telegraf": "^4.0.0", 16 | "tslog": "^3.0.0", 17 | "twitter-api-sdk": "^1.0.0" 18 | }, 19 | "devDependencies": { 20 | "@types/cli-progress": "^3.11.5", 21 | "@types/node": "^20.12.13", 22 | "@types/dotenv": "^8.2.0", 23 | "@types/telegraf": "^4.0.0", 24 | "@types/tslog": "^3.0.0", 25 | "typescript": "^4.5.4" 26 | }, 27 | "author": "", 28 | "license": "ISC" 29 | } 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitter to Telegram Bot 2 | 3 | ## Overview 4 | 5 | Welcome to the **Twitter to Telegram Bot** repository! This project is designed to monitor a specific Twitter user's tweets and send real-time notifications to a designated Telegram chat. Built with Node.js, this bot leverages the Twitter API and Telegram Bot API to ensure seamless and efficient communication between platforms. 6 | 7 | ## Features 8 | 9 | - **Real-Time Monitoring:** Continuously monitors tweets from a specified Twitter user. 10 | - **Telegram Notifications:** Sends instant messages to a Telegram chat whenever a new tweet is posted. 11 | - **Robust Logging:** Utilizes `tslog` for detailed logging and error handling. 12 | - **Progress Bar:** Includes a CLI progress bar to visually represent the waiting period between checks. 13 | - **Environment Variables:** Securely manages sensitive data using environment variables. 14 | - **Error Handling:** Comprehensive error handling and logging for easy debugging. 15 | 16 | ## Installation 17 | 18 | To get started with the Twitter to Telegram Bot, follow these steps: 19 | 20 | 1. **Clone the repository:** 21 | ```bash 22 | git clone https://github.com/yourusername/twitter-to-telegram-bot.git 23 | cd twitter-to-telegram-bot 24 | ``` 25 | 26 | 2. **Install dependencies:** 27 | ```bash 28 | npm install 29 | ``` 30 | 31 | 3. **Create a `.env` file:** 32 | ```plaintext 33 | TWITTER_BEARER_TOKEN=your_twitter_bearer_token 34 | TELEGRAM_BOT_TOKEN=your_telegram_bot_token 35 | TELEGRAM_CHAT_ID=your_telegram_chat_id 36 | USER_TO_MONITOR=twitter_username_to_monitor 37 | ``` 38 | 39 | 4. **Run the bot:** 40 | ```bash 41 | npm start 42 | ``` 43 | 44 | ## Configuration 45 | 46 | Ensure you have the following environment variables set in your `.env` file: 47 | 48 | - `TWITTER_BEARER_TOKEN`: Your Twitter API Bearer Token. 49 | - `TELEGRAM_BOT_TOKEN`: Your Telegram Bot API Token. 50 | - `TELEGRAM_CHAT_ID`: The Telegram chat ID where notifications will be sent. 51 | - `USER_TO_MONITOR`: The Twitter username of the user to monitor. 52 | 53 | ## Usage 54 | 55 | After configuring the environment variables and starting the bot, it will: 56 | 57 | 1. **Initialize connections** to the Twitter and Telegram APIs. 58 | 2. **Send a test message** to the specified Telegram chat to confirm successful connection. 59 | 3. **Fetch the latest tweets** from the specified Twitter user. 60 | 4. **Send notifications** to the Telegram chat whenever new tweets are posted. 61 | 5. **Repeat the process** at regular intervals, using a CLI progress bar to show waiting times. 62 | 63 | ## Contributing 64 | 65 | We welcome contributions to the Twitter to Telegram Bot project! If you have suggestions for improvements or new features, feel free to open an issue or submit a pull request. 66 | 67 | ## License 68 | 69 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 70 | 71 | ## Acknowledgements 72 | 73 | This project uses the following libraries and APIs: 74 | 75 | - [dotenv](https://github.com/motdotla/dotenv) for managing environment variables. 76 | - [twitter-api-sdk](https://github.com/twitterdev/twitter-api-java-sdk) for interacting with the Twitter API. 77 | - [telegraf](https://github.com/telegraf/telegraf) for Telegram Bot API integration. 78 | - [tslog](https://github.com/fullstack-build/tslog) for logging. 79 | - [cli-progress](https://github.com/AndiDittrich/Node.CLI-Progress) for CLI progress bars. 80 | Created & Managed by Ricky Bharti 81 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import { Client } from 'twitter-api-sdk'; 3 | import { Telegraf } from 'telegraf'; 4 | import { Logger } from 'tslog'; 5 | import { setTimeout } from 'timers/promises'; 6 | import cliProgress from 'cli-progress'; 7 | 8 | config(); 9 | 10 | // Twitter API credentials from environment variables 11 | const TWITTER_BEARER_TOKEN = process.env.TWITTER_BEARER_TOKEN as string; 12 | const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN as string; 13 | const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID as string; 14 | const USER_TO_MONITOR = process.env.USER_TO_MONITOR as string; 15 | 16 | if (!TWITTER_BEARER_TOKEN || !TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID || !USER_TO_MONITOR) { 17 | throw new Error("Environment variables are not properly set."); 18 | } 19 | 20 | // Initialize logging 21 | const logger = new Logger({ name: "BotLogger" }); 22 | 23 | function logWithEmoji(message: string, emoji: string) { 24 | logger.info(`${emoji} ${message}`); 25 | } 26 | 27 | // Initialize Twitter API v2 client 28 | let client: Client; 29 | try { 30 | client = new Client(TWITTER_BEARER_TOKEN); 31 | logWithEmoji("Connected to Twitter API", "🐦"); 32 | } catch (e) { 33 | logger.error(`Error connecting to Twitter API: ${e}`); 34 | } 35 | 36 | // Initialize Telegram Bot 37 | let bot: Telegraf; 38 | try { 39 | bot = new Telegraf(TELEGRAM_BOT_TOKEN); 40 | logWithEmoji("Connected to Telegram Bot", "🤖"); 41 | } catch (e) { 42 | logger.error(`Error connecting to Telegram Bot: ${e}`); 43 | } 44 | 45 | // Send a test message to Telegram 46 | async function sendTestMessage() { 47 | try { 48 | await bot.telegram.sendMessage(TELEGRAM_CHAT_ID, "Bot started and connected successfully!"); 49 | logWithEmoji("Test message sent to Telegram", "✅"); 50 | } catch (e) { 51 | logger.error(`Error sending test message: ${e}`); 52 | } 53 | } 54 | 55 | async function sendTelegramMessage(message: string) { 56 | try { 57 | await bot.telegram.sendMessage(TELEGRAM_CHAT_ID, message); 58 | logWithEmoji("Message sent to Telegram", "📩"); 59 | } catch (e) { 60 | logger.error(`Error sending message: ${e}`); 61 | } 62 | } 63 | 64 | async function checkTweets(userId: string, lastTweetId: string | null, startTime: string) { 65 | try { 66 | const response = await client.tweets.usersIdTweets(userId, { 67 | since_id: lastTweetId || undefined, 68 | max_results: 5, 69 | 'tweet.fields': ['id', 'text', 'author_id', 'created_at'], 70 | expansions: ['author_id'], 71 | start_time: startTime 72 | }); 73 | let newTweetsFound = false; 74 | if (response.data && response.data.length > 0) { 75 | for (const tweet of response.data.reverse()) { 76 | const message = `New tweet posted by @${USER_TO_MONITOR}:\n\n${tweet.text}`; 77 | await sendTelegramMessage(message); 78 | lastTweetId = tweet.id; 79 | newTweetsFound = true; 80 | } 81 | } 82 | if (newTweetsFound) { 83 | logWithEmoji("New tweets found and processed", "✅"); 84 | } else { 85 | logWithEmoji("No new tweets found", "❌"); 86 | } 87 | logWithEmoji("Fetched tweets", "🔄"); 88 | return lastTweetId; 89 | } catch (e) { 90 | if ((e as any).code === 429) { 91 | logWithEmoji("Rate limit exceeded. Waiting before retrying...", "⏳"); 92 | await setTimeout(900000); // Wait for 15 minutes 93 | } else { 94 | logger.error(`Error fetching tweets: ${e}`); 95 | if (e instanceof Error) { 96 | logger.error(`Error message: ${e.message}`); 97 | logger.error(`Error stack: ${e.stack}`); 98 | // Log additional error details if available 99 | const errorResponse = (e as any).response; 100 | if (errorResponse) { 101 | logger.error(`Error response data: ${JSON.stringify(errorResponse.data)}`); 102 | logger.error(`Error response status: ${errorResponse.status}`); 103 | logger.error(`Error response headers: ${JSON.stringify(errorResponse.headers)}`); 104 | } else { 105 | logger.error(`Full error object: ${JSON.stringify(e)}`); 106 | } 107 | } 108 | } 109 | } 110 | return lastTweetId; 111 | } 112 | 113 | async function main() { 114 | // Get user ID to monitor 115 | let userId: string; 116 | try { 117 | const user = await client.users.findUserByUsername(USER_TO_MONITOR, { 'user.fields': ['id'] }); 118 | if (!user.data) { 119 | throw new Error("User not found"); 120 | } 121 | userId = user.data.id; 122 | logWithEmoji("Fetched user ID", "🆔"); 123 | } catch (e) { 124 | logger.error(`Error fetching user ID: ${e}`); 125 | if (e instanceof Error) { 126 | logger.error(`Error message: ${e.message}`); 127 | logger.error(`Error stack: ${e.stack}`); 128 | // Log additional error details if available 129 | const errorResponse = (e as any).response; 130 | if (errorResponse) { 131 | logger.error(`Error response data: ${JSON.stringify(errorResponse.data)}`); 132 | logger.error(`Error response status: ${errorResponse.status}`); 133 | logger.error(`Error response headers: ${JSON.stringify(errorResponse.headers)}`); 134 | } else { 135 | logger.error(`Full error object: ${JSON.stringify(e)}`); 136 | } 137 | } 138 | return; 139 | } 140 | 141 | // Initialize lastTweetId with the latest tweet ID 142 | let lastTweetId: string | null = null; 143 | const startTime = new Date().toISOString(); 144 | try { 145 | const lastTweetResponse = await client.tweets.usersIdTweets(userId, { max_results: 5 }); 146 | if (lastTweetResponse.data && lastTweetResponse.data.length > 0) { 147 | lastTweetId = lastTweetResponse.data[0].id; 148 | logWithEmoji("Initialized last tweet ID", "🔍"); 149 | } else { 150 | logWithEmoji("No initial tweet found", "⚠️"); 151 | } 152 | } catch (e) { 153 | logger.error(`Error fetching initial tweets: ${e}`); 154 | if (e instanceof Error) { 155 | logger.error(`Error message: ${e.message}`); 156 | logger.error(`Error stack: ${e.stack}`); 157 | // Log additional error details if available 158 | const errorResponse = (e as any).response; 159 | if (errorResponse) { 160 | logger.error(`Error response data: ${JSON.stringify(errorResponse.data)}`); 161 | logger.error(`Error response status: ${errorResponse.status}`); 162 | logger.error(`Error response headers: ${JSON.stringify(errorResponse.headers)}`); 163 | } else { 164 | logger.error(`Full error object: ${JSON.stringify(e)}`); 165 | } 166 | } 167 | } 168 | 169 | // Send a test message when the script starts 170 | await sendTestMessage(); 171 | 172 | while (true) { 173 | lastTweetId = await checkTweets(userId, lastTweetId, startTime); 174 | logWithEmoji("Waiting for next fetch cycle", "⏳"); 175 | 176 | // Initialize the progress bar 177 | const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic); 178 | progressBar.start(100, 0); 179 | 180 | // Simulate the waiting period with progress bar update 181 | const sleepDuration = 120000; // 100 seconds 182 | const updateInterval = sleepDuration / 100; // update every 1% of the duration 183 | 184 | for (let i = 0; i <= 100; i++) { 185 | await setTimeout(updateInterval); 186 | progressBar.update(i); 187 | } 188 | 189 | progressBar.stop(); 190 | } 191 | } 192 | 193 | main().catch(e => logger.error(e)); 194 | --------------------------------------------------------------------------------