commit 2d44feddee18bdffbbda29c4562cd8fb23b25c5d Author: IFM Telegram Bot Date: Sat Feb 14 10:50:42 2026 +0700 Initial commit: IFM Telegram Bot (Beta/AethelBetaBot) - Telegram bot for receiving and analyzing messages, photos, and updates - Photo download support with fileId-based naming - Random photo send feature (text "image" triggers random photo from downloads) - Renamed from itm-telegram-bot to ifm-telegram-bot Co-Authored-By: Claude Opus 4.6 diff --git a/.env b/.env new file mode 100644 index 0000000..80bc6a9 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +# Telegram Bot Configuration +BOT_TOKEN=8165345099:AAGltLm-4YLACR3EG2b0rp79wAAihJjqzeE +BOT_USERNAME=AethelBetaBot diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5b4c089 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Telegram Bot Configuration +BOT_TOKEN=your-bot-token-here +BOT_USERNAME=your_bot_username diff --git a/bot.pid b/bot.pid new file mode 100644 index 0000000..5ee7533 --- /dev/null +++ b/bot.pid @@ -0,0 +1 @@ +3367690 diff --git a/bot.sh b/bot.sh new file mode 100755 index 0000000..06ec6a3 --- /dev/null +++ b/bot.sh @@ -0,0 +1,156 @@ +#!/bin/bash + +# IFM Telegram Bot - Process Manager (for testing/analysis) +# Usage: ./bot.sh {start|stop|restart|status|logs|tail} + +BOT_DIR="$(cd "$(dirname "$0")" && pwd)" +JAR_FILE="$BOT_DIR/target/ifm-telegram-bot-1.0.0-jar-with-dependencies.jar" +PID_FILE="$BOT_DIR/bot.pid" +LOG_DIR="$BOT_DIR/logs" +TMUX_SESSION="ifm-bot" +STOP_TIMEOUT=10 + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +get_pid() { + if [ -f "$PID_FILE" ]; then + local pid + pid=$(cat "$PID_FILE") + if kill -0 "$pid" 2>/dev/null; then + echo "$pid" + return 0 + fi + rm -f "$PID_FILE" + fi + return 1 +} + +do_start() { + local pid + if pid=$(get_pid); then + echo -e "${YELLOW}Bot is already running (PID: $pid)${NC}" + return 1 + fi + + if [ ! -f "$JAR_FILE" ]; then + echo -e "${RED}JAR not found: $JAR_FILE${NC}" + echo -e "${YELLOW}Run: mvn clean package -DskipTests${NC}" + return 1 + fi + + mkdir -p "$LOG_DIR" + + # Kill any stale tmux session + tmux kill-session -t "$TMUX_SESSION" 2>/dev/null + + # Start bot inside a detached tmux session (UTF-8 for Thai text) + tmux new-session -d -s "$TMUX_SESSION" \ + "cd '$BOT_DIR' && LANG=C.UTF-8 LC_ALL=C.UTF-8 exec java -Dfile.encoding=UTF-8 -jar '$JAR_FILE' >> '$LOG_DIR/console.log' 2>&1" + + sleep 3 + + # Find the actual java PID from the tmux session + local bot_pid + bot_pid=$(tmux list-panes -t "$TMUX_SESSION" -F '#{pane_pid}' 2>/dev/null) + # The pane_pid is the shell; find the java child + local java_pid + java_pid=$(pgrep -P "$bot_pid" -f java 2>/dev/null | head -1) + + if [ -n "$java_pid" ] && kill -0 "$java_pid" 2>/dev/null; then + echo "$java_pid" > "$PID_FILE" + echo -e "${GREEN}Bot started (PID: $java_pid)${NC}" + elif [ -n "$bot_pid" ] && kill -0 "$bot_pid" 2>/dev/null; then + # Fallback: use the tmux pane pid + echo "$bot_pid" > "$PID_FILE" + echo -e "${GREEN}Bot started (PID: $bot_pid)${NC}" + else + echo -e "${RED}Bot failed to start. Check logs:${NC}" + tail -5 "$LOG_DIR/console.log" 2>/dev/null + return 1 + fi +} + +do_stop() { + local pid + if ! pid=$(get_pid); then + echo -e "${YELLOW}Bot is not running${NC}" + # Cleanup stale tmux just in case + tmux kill-session -t "$TMUX_SESSION" 2>/dev/null + return 0 + fi + + echo -e "${YELLOW}Stopping bot (PID: $pid)...${NC}" + kill "$pid" + + for i in $(seq 1 "$STOP_TIMEOUT"); do + if ! kill -0 "$pid" 2>/dev/null; then + rm -f "$PID_FILE" + tmux kill-session -t "$TMUX_SESSION" 2>/dev/null + echo -e "${GREEN}Bot stopped${NC}" + return 0 + fi + sleep 1 + done + + echo -e "${RED}Force killing...${NC}" + kill -9 "$pid" 2>/dev/null + rm -f "$PID_FILE" + tmux kill-session -t "$TMUX_SESSION" 2>/dev/null + echo -e "${GREEN}Bot stopped (forced)${NC}" +} + +do_status() { + local pid + if pid=$(get_pid); then + local uptime + uptime=$(ps -o etime= -p "$pid" 2>/dev/null | xargs) + local mem + mem=$(ps -o rss= -p "$pid" 2>/dev/null | awk '{printf "%.1f MB", $1/1024}') + echo -e "${GREEN}Bot is running${NC}" + echo " PID: $pid" + echo " Uptime: $uptime" + echo " Memory: $mem" + else + echo -e "${RED}Bot is not running${NC}" + return 1 + fi +} + +do_logs() { + if [ -f "$LOG_DIR/console.log" ]; then + tail -50 "$LOG_DIR/console.log" + else + echo -e "${YELLOW}No log file found${NC}" + fi +} + +do_tail() { + if [ -f "$LOG_DIR/console.log" ]; then + tail -f "$LOG_DIR/console.log" + else + echo -e "${YELLOW}No log file found. Start the bot first.${NC}" + fi +} + +case "$1" in + start) do_start ;; + stop) do_stop ;; + restart) do_stop && sleep 1 && do_start ;; + status) do_status ;; + logs) do_logs ;; + tail) do_tail ;; + *) + echo "Usage: $0 {start|stop|restart|status|logs|tail}" + echo "" + echo " start - Start bot in background (tmux)" + echo " stop - Stop bot" + echo " restart - Restart bot" + echo " status - Show PID, uptime, memory" + echo " logs - Show last 50 lines of log" + echo " tail - Follow log in real-time (Ctrl+C to exit)" + exit 1 + ;; +esac diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6cfd60e --- /dev/null +++ b/pom.xml @@ -0,0 +1,123 @@ + + + 4.0.0 + + com.ifm.telegram + ifm-telegram-bot + 1.0.0 + jar + + IFM Telegram Bot + Simple Telegram bot that prints all received messages for analysis + + + UTF-8 + 21 + 21 + 6.9.7.1 + 2.0.9 + 1.4.14 + 3.0.0 + 2.11.0 + + + + + + org.telegram + telegrambots + ${telegrambots.version} + + + commons-logging + commons-logging + + + + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + + + io.github.cdimascio + dotenv-java + ${dotenv.version} + + + + + com.google.code.gson + gson + ${gson.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + com.ifm.telegram.App + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + + com.ifm.telegram.App + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + diff --git a/src/main/java/com/ifm/telegram/App.java b/src/main/java/com/ifm/telegram/App.java new file mode 100644 index 0000000..072fd71 --- /dev/null +++ b/src/main/java/com/ifm/telegram/App.java @@ -0,0 +1,39 @@ +package com.ifm.telegram; + +import io.github.cdimascio.dotenv.Dotenv; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.telegram.telegrambots.meta.TelegramBotsApi; +import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; + +public class App { + + private static final Logger logger = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) { + // Load .env + Dotenv dotenv = Dotenv.configure() + .ignoreIfMissing() + .load(); + + String botToken = dotenv.get("BOT_TOKEN"); + String botUsername = dotenv.get("BOT_USERNAME", "ifm_bot"); + + if (botToken == null || botToken.isEmpty()) { + logger.error("BOT_TOKEN is required. Set it in .env file."); + System.exit(1); + } + + logger.info("Starting IFM Telegram Bot (username: {})", botUsername); + + try { + TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class); + MessagePrinterBot bot = new MessagePrinterBot(botToken, botUsername); + botsApi.registerBot(bot); + logger.info("Bot registered and polling for updates..."); + } catch (Exception e) { + logger.error("Failed to start bot", e); + System.exit(1); + } + } +} diff --git a/src/main/java/com/ifm/telegram/MessagePrinterBot.java b/src/main/java/com/ifm/telegram/MessagePrinterBot.java new file mode 100644 index 0000000..6d4d5ad --- /dev/null +++ b/src/main/java/com/ifm/telegram/MessagePrinterBot.java @@ -0,0 +1,213 @@ +package com.ifm.telegram; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.telegram.telegrambots.bots.TelegramLongPollingBot; +import org.telegram.telegrambots.meta.api.methods.GetFile; +import org.telegram.telegrambots.meta.api.methods.send.SendPhoto; +import org.telegram.telegrambots.meta.api.objects.File; +import org.telegram.telegrambots.meta.api.objects.InputFile; +import org.telegram.telegrambots.meta.api.objects.Message; +import org.telegram.telegrambots.meta.api.objects.PhotoSize; +import org.telegram.telegrambots.meta.api.objects.Update; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +/** + * Simple Telegram bot that prints all received updates to console. + * Useful for analyzing message structure from Telegram. + */ +public class MessagePrinterBot extends TelegramLongPollingBot { + + private static final Logger logger = LoggerFactory.getLogger(MessagePrinterBot.class); + private static final Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); + + private static final String DOWNLOAD_DIR = "downloads"; + private static final Random random = new Random(); + + private final String botUsername; + + public MessagePrinterBot(String botToken, String botUsername) { + super(botToken); + this.botUsername = botUsername; + // Ensure download directory exists + try { + Files.createDirectories(Paths.get(DOWNLOAD_DIR)); + } catch (Exception e) { + logger.warn("Could not create download directory: {}", e.getMessage()); + } + } + + @Override + public String getBotUsername() { + return botUsername; + } + + @Override + public void onUpdateReceived(Update update) { + // Print full Update JSON + System.out.println("========== UPDATE RECEIVED =========="); + System.out.println(gson.toJson(update)); + System.out.println("======================================"); + + // Print human-readable summary + if (update.hasMessage()) { + Message msg = update.getMessage(); + printMessageSummary(msg); + + // If text is "image", send a random photo from downloads + if (msg.hasText() && msg.getText().trim().equalsIgnoreCase("image")) { + sendRandomPhoto(msg.getChatId().toString()); + } + } else if (update.hasEditedMessage()) { + System.out.println("[EDITED MESSAGE]"); + printMessageSummary(update.getEditedMessage()); + } else if (update.hasCallbackQuery()) { + System.out.println("[CALLBACK QUERY] data=" + update.getCallbackQuery().getData() + + " from=" + update.getCallbackQuery().getFrom().getUserName()); + } else if (update.hasChannelPost()) { + System.out.println("[CHANNEL POST]"); + printMessageSummary(update.getChannelPost()); + } else { + System.out.println("[OTHER UPDATE TYPE] updateId=" + update.getUpdateId()); + } + + System.out.println(); + } + + private void printMessageSummary(Message msg) { + StringBuilder sb = new StringBuilder(); + sb.append("[MESSAGE SUMMARY]\n"); + sb.append(" chatId: ").append(msg.getChatId()).append("\n"); + sb.append(" chatType: ").append(msg.getChat().getType()).append("\n"); + sb.append(" messageId: ").append(msg.getMessageId()).append("\n"); + sb.append(" from: ").append(msg.getFrom() != null ? msg.getFrom().getUserName() : "null").append("\n"); + sb.append(" firstName: ").append(msg.getFrom() != null ? msg.getFrom().getFirstName() : "null").append("\n"); + sb.append(" date: ").append(msg.getDate()).append("\n"); + + // Text + if (msg.hasText()) { + sb.append(" text: ").append(msg.getText()).append("\n"); + } + + // Photo + if (msg.hasPhoto()) { + List photos = msg.getPhoto(); + sb.append(" photo: ").append(photos.size()).append(" sizes\n"); + PhotoSize largest = photos.stream() + .max(Comparator.comparingInt(PhotoSize::getFileSize)) + .orElse(null); + if (largest != null) { + sb.append(" largest: fileId=").append(largest.getFileId()).append("\n"); + sb.append(" size=").append(largest.getWidth()).append("x").append(largest.getHeight()).append("\n"); + sb.append(" fileSize=").append(largest.getFileSize()).append(" bytes\n"); + } + if (msg.getCaption() != null) { + sb.append(" caption: ").append(msg.getCaption()).append("\n"); + } + // Download the largest photo + if (largest != null) { + downloadPhoto(largest); + } + } + + // Document + if (msg.hasDocument()) { + sb.append(" document: ").append(msg.getDocument().getFileName()).append("\n"); + sb.append(" mimeType: ").append(msg.getDocument().getMimeType()).append("\n"); + sb.append(" fileId: ").append(msg.getDocument().getFileId()).append("\n"); + sb.append(" fileSize: ").append(msg.getDocument().getFileSize()).append(" bytes\n"); + if (msg.getCaption() != null) { + sb.append(" caption: ").append(msg.getCaption()).append("\n"); + } + } + + // Voice + if (msg.hasVoice()) { + sb.append(" voice: duration=").append(msg.getVoice().getDuration()).append("s\n"); + sb.append(" fileId: ").append(msg.getVoice().getFileId()).append("\n"); + } + + // Video + if (msg.hasVideo()) { + sb.append(" video: ").append(msg.getVideo().getDuration()).append("s\n"); + sb.append(" fileId: ").append(msg.getVideo().getFileId()).append("\n"); + } + + // Sticker + if (msg.hasSticker()) { + sb.append(" sticker: ").append(msg.getSticker().getEmoji()).append("\n"); + sb.append(" fileId: ").append(msg.getSticker().getFileId()).append("\n"); + } + + // Location + if (msg.hasLocation()) { + sb.append(" location: ").append(msg.getLocation().getLatitude()) + .append(",").append(msg.getLocation().getLongitude()).append("\n"); + } + + // Contact + if (msg.hasContact()) { + sb.append(" contact: ").append(msg.getContact().getFirstName()) + .append(" ").append(msg.getContact().getPhoneNumber()).append("\n"); + } + + // Reply + if (msg.getReplyToMessage() != null) { + sb.append(" replyTo: messageId=").append(msg.getReplyToMessage().getMessageId()).append("\n"); + } + + System.out.println(sb); + } + + private void sendRandomPhoto(String chatId) { + try { + java.io.File dir = new java.io.File(DOWNLOAD_DIR); + java.io.File[] images = dir.listFiles((d, name) -> + name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".png")); + + if (images == null || images.length == 0) { + logger.info("No images in downloads/ to send"); + return; + } + + java.io.File picked = images[random.nextInt(images.length)]; + SendPhoto sendPhoto = new SendPhoto(); + sendPhoto.setChatId(chatId); + sendPhoto.setPhoto(new InputFile(picked)); + sendPhoto.setCaption("Random image: " + picked.getName()); + execute(sendPhoto); + logger.info("Sent random photo: {} to chatId: {}", picked.getName(), chatId); + } catch (Exception e) { + logger.error("Failed to send random photo: {}", e.getMessage()); + } + } + + private void downloadPhoto(PhotoSize photo) { + try { + GetFile getFile = new GetFile(photo.getFileId()); + File file = execute(getFile); + String filePath = file.getFilePath(); + + // Use fileId as filename, keep extension from Telegram's file_path + String extension = ""; + if (filePath != null && filePath.contains(".")) { + extension = filePath.substring(filePath.lastIndexOf(".")); + } + String savedName = photo.getFileId() + extension; + Path savePath = Paths.get(DOWNLOAD_DIR, savedName); + + java.io.File downloaded = downloadFile(file, savePath.toFile()); + logger.info("Photo saved: {} ({} bytes)", downloaded.getAbsolutePath(), downloaded.length()); + } catch (Exception e) { + logger.error("Failed to download photo: {}", e.getMessage()); + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..3a38296 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + +