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 <noreply@anthropic.com>
This commit is contained in:
IFM Telegram Bot
2026-02-14 10:50:42 +07:00
commit 2d44feddee
8 changed files with 553 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Telegram Bot Configuration
BOT_TOKEN=8165345099:AAGltLm-4YLACR3EG2b0rp79wAAihJjqzeE
BOT_USERNAME=AethelBetaBot
+3
View File
@@ -0,0 +1,3 @@
# Telegram Bot Configuration
BOT_TOKEN=your-bot-token-here
BOT_USERNAME=your_bot_username
+1
View File
@@ -0,0 +1 @@
3367690
Executable
+156
View File
@@ -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
+123
View File
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ifm.telegram</groupId>
<artifactId>ifm-telegram-bot</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>IFM Telegram Bot</name>
<description>Simple Telegram bot that prints all received messages for analysis</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<telegrambots.version>6.9.7.1</telegrambots.version>
<slf4j.version>2.0.9</slf4j.version>
<logback.version>1.4.14</logback.version>
<dotenv.version>3.0.0</dotenv.version>
<gson.version>2.11.0</gson.version>
</properties>
<dependencies>
<!-- Telegram Bots API -->
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>${telegrambots.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Bridge commons-logging to SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Environment Variables -->
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>${dotenv.version}</version>
</dependency>
<!-- JSON (for pretty printing Update objects) -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.ifm.telegram.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.ifm.telegram.App</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+39
View File
@@ -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);
}
}
}
@@ -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<PhotoSize> 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());
}
}
}
+15
View File
@@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Suppress noisy Telegram/HTTP logs -->
<logger name="org.apache.http" level="WARN"/>
<logger name="org.telegram" level="INFO"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>