diff --git a/README.md b/README.md
index 3aa2210..2f64978 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# webdl
-
About •
@@ -15,30 +14,59 @@ Telegram bot in Java for downloading social media videos using yt-dlp
## Features
-- Non-blocking message queue processing
+- Non-blocking message queue processing with virtual threads
-- Dynamic interaction with messages
+- Dynamic interaction with Telegram messages (send, delete, edit)
-- Panel with usage statistics
+- Web panel with usage statistics on port 8080
+
+- Automatic retry on download failures
## Quick Start & Information
Webdl accepts a video URL, downloads it using [yt-dlp](https://github.com/yt-dlp/yt-dlp), and sends it back to the user as a video message.
-Clone and run the project from source code.
+### From source
+
```
git clone https://github.com/SegoCode/webdl
-cd webdl\code
-mvn clean package
+cd webdl/code
+mvn clean package -DskipTests
java -jar target/webdl.jar
```
-For Docker deployment, make sure to set up environment variables.
-Use a temp volume for the download, it will delete after send.
+### Docker
+
```
-mvn clean package
+cd webdl/code
+mvn clean package -DskipTests
docker build -t webdl-image .
-docker run -e BOT_TOKEN=your-bot-token -p 8080:8080 -v /mnt/drive/data/webdl:/downloads --name webdl webdl-image
+docker run -d \
+ --name webdl \
+ --restart unless-stopped \
+ -e BOT_TOKEN=your-bot-token \
+ -p 8080:8080 \
+ -v /mnt/drive/data/webdl:/downloads \
+ webdl-image
+```
+
+### Project structure
+
+```
+code/src/main/java/org/segocode/webdl/
+├── Main.java # Entry point
+├── bot/
+│ ├── Webdlbot.java # Telegram long-polling bot
+│ ├── constants/Messages.java # User-facing message strings
+│ ├── model/{User,DataRootContainer}.java # EclipseStore persistence
+│ ├── service/{MessageService,VideoService}.java
+│ └── util/MessageUtil.java
+├── panel/
+│ ├── PanelApplication.java # Javalin web server bootstrap
+│ └── AdminController.java # Admin panel route handler
+└── system/
+ ├── command/CommandExecutor.java # yt-dlp subprocess with retry
+ └── util/FileUtil.java
```
diff --git a/code/Dockerfile b/code/Dockerfile
index 7121837..63189a7 100644
--- a/code/Dockerfile
+++ b/code/Dockerfile
@@ -8,17 +8,11 @@ COPY src ./src
RUN mvn package -DskipTests
# Runtime stage: Run the application
-FROM alpine:latest
+FROM eclipse-temurin:21-jre-alpine
-RUN apk update && \
- apk add --no-cache wget curl ffmpeg && \
+RUN apk add --no-cache ffmpeg python3 && \
wget -O /usr/local/bin/yt-dlp https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp && \
- chmod a+rx /usr/local/bin/yt-dlp && \
- rm -rf /var/cache/apk/*
-
-RUN apk add --no-cache openjdk21 python3 py3-pip
-
-ENV YT_DLP=/usr/bin/yt-dlp PYTHONUNBUFFERED=1
+ chmod a+rx /usr/local/bin/yt-dlp
WORKDIR /app
COPY --from=build /app/target/webdl.jar /app/webdl.jar
diff --git a/code/pom.xml b/code/pom.xml
index bf1f834..b4c1b7d 100644
--- a/code/pom.xml
+++ b/code/pom.xml
@@ -65,13 +65,25 @@
- org.segocode.Main
+ org.segocode.webdl.Main
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 3.6.0
+
+
+
+ 2.92.0
+
+
+
+
diff --git a/code/src/main/java/org/segocode/bot/service/MessageService.java b/code/src/main/java/org/segocode/bot/service/MessageService.java
deleted file mode 100644
index 9a2c996..0000000
--- a/code/src/main/java/org/segocode/bot/service/MessageService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.segocode.bot.service;
-
-import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
-import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage;
-
-public class MessageService {
-
- /**
- * Sends a text message to a specific chat in Telegram.
- *
- * @param chatId The ID of the chat where the message will be sent.
- * @param replyToMessageId The ID of the message to which this message will be a reply.
- * @param text The text content of the message to be sent.
- * @return The SendMessage object configured with the chat ID, text, and reply to message ID.
- */
- public static SendMessage sendTextMessage(Long chatId, Integer replyToMessageId, String text) {
- SendMessage message = new SendMessage(chatId.toString(), text);
- message.setReplyToMessageId(replyToMessageId);
- return message;
- }
-
- /**
- * Deletes a specific message in a Telegram chat.
- *
- * @param chatId The ID of the chat where the message is located.
- * @param messageId The ID of the message to be deleted.
- * @return A DeleteMessage object configured with the chat ID and message ID.
- */
- public static DeleteMessage deleteMessage(Long chatId, Integer messageId) {
- return new DeleteMessage(chatId.toString(), messageId);
- }
-}
diff --git a/code/src/main/java/org/segocode/bot/service/VideoService.java b/code/src/main/java/org/segocode/bot/service/VideoService.java
deleted file mode 100644
index 79d853b..0000000
--- a/code/src/main/java/org/segocode/bot/service/VideoService.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.segocode.bot.service;
-
-import org.segocode.system.util.FileUtil;
-import org.slf4j.LoggerFactory;
-import org.telegram.telegrambots.meta.api.methods.send.SendVideo;
-import org.telegram.telegrambots.meta.api.objects.InputFile;
-
-import java.io.File;
-import org.slf4j.Logger;
-
-
-public class VideoService {
- private static final Logger LOGGER = LoggerFactory.getLogger(VideoService.class);
-
- /**
- * Sends a video to a specific chat in Telegram.
- *
- * @param chatId The ID of the chat where the video will be sent.
- * @param replyToMessageId The ID of the message to which this video will be a reply.
- * @return The SendVideo object configured with the chat ID and reply to message ID.
- */
- public static SendVideo sendVideo(Long chatId, Integer replyToMessageId) {
- String filePath = buildFilePath(replyToMessageId);
- LOGGER.info("Locating video file for message ID {}: {}", replyToMessageId, filePath);
-
- File videoFile = FileUtil.locateVideoFile(filePath);
- LOGGER.info("Video file located successfully: {}", videoFile.getPath());
-
- SendVideo sendVideoRequest = createSendVideoRequest(chatId, replyToMessageId, videoFile);
- LOGGER.info("Video is sending. Chat ID: {}, Reply to Message ID: {}", chatId, replyToMessageId);
-
- return sendVideoRequest;
- }
-
- /**
- * Creates a SendVideo request for sending a video.
- *
- * @param chatId The ID of the chat where the video will be sent.
- * @param replyToMessageId The ID of the message to which this video will be a reply.
- * @param videoFile The video file to be sent.
- * @return A SendVideo object configured with the chat ID, reply ID, and video file.
- */
- private static SendVideo createSendVideoRequest(Long chatId, Integer replyToMessageId, File videoFile) {
- SendVideo sendVideoRequest = new SendVideo();
- sendVideoRequest.setChatId(chatId.toString());
- sendVideoRequest.setReplyToMessageId(replyToMessageId);
- sendVideoRequest.setVideo(new InputFile(videoFile));
- return sendVideoRequest;
- }
-
- /**
- * Builds the file path for the video file based on the given replyToMessageId.
- *
- * @param replyToMessageId The ID of the message to which this video will be a reply.
- * @return The file path as a String.
- */
- private static String buildFilePath(Integer replyToMessageId) {
- return "./downloads/" + replyToMessageId + ".mp4";
- }
-
-
-}
diff --git a/code/src/main/java/org/segocode/panel/PanelController.java b/code/src/main/java/org/segocode/panel/PanelController.java
deleted file mode 100644
index f8d6fc7..0000000
--- a/code/src/main/java/org/segocode/panel/PanelController.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.segocode.panel;
-
-import com.google.gson.Gson;
-import io.javalin.Javalin;
-import io.javalin.http.Context;
-import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager;
-import org.segocode.bot.model.DataRootContainer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-
-public class PanelController {
- private static final Logger LOGGER = LoggerFactory.getLogger(PanelController.class);
- private final Javalin app;
- private final EmbeddedStorageManager storageManager;
-
- // Singleton instance
- private static PanelController instance;
-
- // Private constructor to prevent instantiation
- private PanelController(EmbeddedStorageManager storageManager) {
- this.storageManager = storageManager;
- this.app = Javalin.create().start(8080);
-
- // Configure routes
- configureRoutes();
-
- LOGGER.info("Panel controller started on port 8080");
- }
-
- public static synchronized void start(EmbeddedStorageManager storageManager) {
- if (instance == null) {
- instance = new PanelController(storageManager);
- }
- }
-
- private void configureRoutes() {
- app.get("/", this::handleAdminRequest);
- }
-
-private void handleAdminRequest(Context ctx) {
- ctx.contentType("text/html");
-
- try {
- InputStream inputStream = getClass().getResourceAsStream("/views/admin.html");
- String htmlContent = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
- String jsonData = new Gson().toJson(((DataRootContainer)storageManager.root()).getUsers());
- htmlContent = htmlContent.replace("{{!user_data}}", jsonData);
-
- ctx.result(htmlContent);
- } catch (IOException e) {
- LOGGER.error("Failed to read admin HTML file", e);
- ctx.status(500);
- ctx.result("Error loading admin panel: " + e.getMessage());
- }
-}
-}
\ No newline at end of file
diff --git a/code/src/main/java/org/segocode/Main.java b/code/src/main/java/org/segocode/webdl/Main.java
similarity index 81%
rename from code/src/main/java/org/segocode/Main.java
rename to code/src/main/java/org/segocode/webdl/Main.java
index 0b4b414..a20f823 100644
--- a/code/src/main/java/org/segocode/Main.java
+++ b/code/src/main/java/org/segocode/webdl/Main.java
@@ -1,18 +1,14 @@
-package org.segocode;
+package org.segocode.webdl;
-import io.javalin.Javalin;
import org.eclipse.store.storage.embedded.types.EmbeddedStorage;
import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager;
-import org.eclipse.store.storage.types.StorageManager;
-import org.segocode.bot.Webdlbot;
-import org.segocode.panel.PanelController;
+import org.segocode.webdl.bot.Webdlbot;
+import org.segocode.webdl.panel.PanelApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.meta.TelegramBotsApi;
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;
-import java.util.Date;
-
public class Main {
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
@@ -25,7 +21,7 @@ public static void main(String[] args) {
botsApi.registerBot(new Webdlbot(storageManager));
LOGGER.info("Bot started successfully and ready to download videos 🚀");
LOGGER.info("Starting webp anel app...");
- PanelController.start(storageManager);
+ PanelApplication.start(storageManager);
} catch (Exception e) {
LOGGER.error("Error while attempting to start the bot. Error details:", e);
}
diff --git a/code/src/main/java/org/segocode/bot/Webdlbot.java b/code/src/main/java/org/segocode/webdl/bot/Webdlbot.java
similarity index 56%
rename from code/src/main/java/org/segocode/bot/Webdlbot.java
rename to code/src/main/java/org/segocode/webdl/bot/Webdlbot.java
index 11e65f1..7f77268 100644
--- a/code/src/main/java/org/segocode/bot/Webdlbot.java
+++ b/code/src/main/java/org/segocode/webdl/bot/Webdlbot.java
@@ -1,26 +1,25 @@
-package org.segocode.bot;
+package org.segocode.webdl.bot;
+import static org.segocode.webdl.bot.constants.Messages.*;
+import static org.segocode.webdl.bot.util.MessageUtil.*;
+import static org.segocode.webdl.system.util.FileUtil.*;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+import java.util.concurrent.*;
import org.eclipse.store.storage.types.StorageManager;
-import org.segocode.bot.model.DataRootContainer;
-import org.segocode.bot.model.User;
-import org.segocode.system.command.CommandExecutor;
-import org.segocode.bot.service.VideoService;
+import org.segocode.webdl.bot.model.DataRootContainer;
+import org.segocode.webdl.bot.model.User;
+import org.segocode.webdl.bot.service.MessageService;
+import org.segocode.webdl.bot.service.VideoService;
+import org.segocode.webdl.system.command.CommandExecutor;
+import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
-import org.segocode.bot.service.MessageService;
-import org.slf4j.Logger;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
-import java.time.LocalDateTime;
-import java.util.Optional;
-import java.util.concurrent.*;
-
-import static org.segocode.bot.constants.Messages.*;
-import static org.segocode.bot.util.MessageUtil.*;
-import static org.segocode.system.util.FileUtil.*;
-
public class Webdlbot extends TelegramLongPollingBot {
private static final Logger LOGGER = LoggerFactory.getLogger(Webdlbot.class);
private static final String BOT_TOKEN = System.getenv("BOT_TOKEN");
@@ -35,7 +34,7 @@ public class Webdlbot extends TelegramLongPollingBot {
TimeUnit.MILLISECONDS, // the time unit for the keepAliveTime argument
new LinkedBlockingQueue<>(), // the queue to use for holding tasks before they are executed
Thread.ofVirtual().factory() // the factory to use when creating new threads
- );
+ );
public Webdlbot(StorageManager storageManager) {
this.storageManager = storageManager;
@@ -48,7 +47,9 @@ private void initializeRootContainer() {
storageManager.setRoot(new DataRootContainer());
LOGGER.info("Initialized new DataRootContainer as db root object");
}
- LOGGER.info("Root db object contains {} users", ((DataRootContainer) storageManager.root()).getUsers().size());
+ LOGGER.info(
+ "Root db object contains {} users",
+ ((DataRootContainer) storageManager.root()).getUsers().size());
}
@Override
@@ -65,16 +66,22 @@ public String getBotToken() {
return BOT_TOKEN;
}
-
@Override
public void onUpdateReceived(Update update) {
if (update.hasMessage() && update.getMessage().hasText()) {
try {
+ long queuedTasks = executorService.getTaskCount() - executorService.getCompletedTaskCount();
Integer queuedMessageId;
- if (executorService.getActiveCount() > 0 || !executorService.getQueue().isEmpty()) {
- String messageTime = DOWNLOAD_REQUEST_QUEUED + " (<" + executorService.getQueue().size() + "m)";
- LOGGER.info("Executor is busy, queuing the message from @{}", update.getMessage().getFrom().getUserName());
- queuedMessageId = execute(MessageService.sendTextMessage(update.getMessage().getChatId(), update.getMessage().getMessageId(), messageTime)).getMessageId();
+ if (queuedTasks > 0) {
+ String messageTime = DOWNLOAD_REQUEST_QUEUED + " (<" + queuedTasks + "m)";
+ LOGGER.info(
+ "Executor is busy, queuing the message from @{}",
+ update.getMessage().getFrom().getUserName());
+ queuedMessageId = execute(MessageService.sendTextMessage(
+ update.getMessage().getChatId(),
+ update.getMessage().getMessageId(),
+ messageTime))
+ .getMessageId();
} else {
queuedMessageId = null;
}
@@ -82,48 +89,59 @@ public void onUpdateReceived(Update update) {
executorService.submit(() -> {
try {
if (queuedMessageId != null) {
- execute(MessageService.deleteMessage(update.getMessage().getChatId(), queuedMessageId));
+ execute(MessageService.deleteMessage(
+ update.getMessage().getChatId(), queuedMessageId));
}
- //Entry point download flow
+ // Entry point download flow
dispatch(update);
} catch (Exception e) {
LOGGER.error("Failed to launch dispatch, error: {}", e.getMessage(), e);
handleDispatchError(update, e);
+ } finally {
+ synchronized (storageManager) {
+ storageManager.setRoot(loadMetricsData(update));
+ storageManager.storeRoot();
+ }
}
});
} catch (Exception e) {
LOGGER.error("Failed on onUpdateReceived, error: {}", e.getMessage(), e);
handleDispatchError(update, e);
- } finally {
- storageManager.setRoot(loadMetricsData(update));
- storageManager.storeRoot();
}
}
}
private void dispatch(Update update) throws Exception {
- String url = "";
- Message message = update.getMessage();
- LOGGER.info("Starting message processing from @{}: {}", message.getFrom().getUserName(), message.getText());
-
- if(!update.getMessage().toString().contains("http")) {
- execute(MessageService.sendTextMessage(message.getChatId(), message.getMessageId(), NOT_VALID_LINK)).getMessageId();
- return;
- } else {
- url = extractUrlFromMessage(update.getMessage().getText());
- LOGGER.info("Extracted URL: {} from {}", url, message.getFrom().getUserName());
- }
+ String url = "";
+ Message message = update.getMessage();
+ LOGGER.info(
+ "Starting message processing from @{}: {}", message.getFrom().getUserName(), message.getText());
+
+ if (!update.getMessage().getText().contains("http")) {
+ execute(MessageService.sendTextMessage(message.getChatId(), message.getMessageId(), NOT_VALID_LINK))
+ .getMessageId();
+ return;
+ } else {
+ url = extractUrlFromMessage(update.getMessage().getText());
+ LOGGER.info("Extracted URL: {} from {}", url, message.getFrom().getUserName());
+ }
- final Integer responseId = execute(MessageService.sendTextMessage(message.getChatId(), message.getMessageId(), DOWNLOAD_REQUEST)).getMessageId();
- CommandExecutor.executeCommand(url, String.valueOf(message.getMessageId()));
- execute(VideoService.sendVideo(message.getChatId(), message.getMessageId()));
- execute(MessageService.deleteMessage(message.getChatId(), responseId));
- cleanDownloadsFolder();
+ final Integer responseId = execute(
+ MessageService.sendTextMessage(message.getChatId(), message.getMessageId(), DOWNLOAD_REQUEST))
+ .getMessageId();
+ CommandExecutor.executeCommand(url, String.valueOf(message.getMessageId()));
+ execute(VideoService.sendVideo(message.getChatId(), message.getMessageId()));
+ execute(MessageService.deleteMessage(message.getChatId(), responseId));
+ cleanDownloadsFolder();
}
private void handleDispatchError(Update update, Exception e) {
try {
- execute(MessageService.sendTextMessage(update.getMessage().getChatId(), update.getMessage().getMessageId(), DOWNLOAD_REQUEST_ERROR)).getMessageId();
+ execute(MessageService.sendTextMessage(
+ update.getMessage().getChatId(),
+ update.getMessage().getMessageId(),
+ DOWNLOAD_REQUEST_ERROR))
+ .getMessageId();
} catch (TelegramApiException ex) {
LOGGER.error("Failed to send error message, error: {}", ex.getMessage(), ex);
} finally {
@@ -133,7 +151,8 @@ private void handleDispatchError(Update update, Exception e) {
private DataRootContainer loadMetricsData(Update update) {
// Create or update user data from the Telegram update
- org.telegram.telegrambots.meta.api.objects.User telegramUser = update.getMessage().getFrom();
+ org.telegram.telegrambots.meta.api.objects.User telegramUser =
+ update.getMessage().getFrom();
DataRootContainer rootContainer = (DataRootContainer) storageManager.root();
@@ -145,14 +164,19 @@ private DataRootContainer loadMetricsData(Update update) {
User user = existingUser.get();
user.recordNewMessage();
} else {
- User newUser = new User(userId, update.getMessage().getChatId(), telegramUser.getUserName(), telegramUser.getFirstName(),
- telegramUser.getLastName(), telegramUser.getLanguageCode(), telegramUser.getIsPremium(),
- 1,LocalDateTime.now().toString());
+ User newUser = new User(
+ userId,
+ update.getMessage().getChatId(),
+ telegramUser.getUserName(),
+ telegramUser.getFirstName(),
+ telegramUser.getLastName(),
+ telegramUser.getLanguageCode(),
+ telegramUser.getIsPremium(),
+ 1,
+ LocalDateTime.now().toString());
rootContainer.getUsers().add(newUser);
LOGGER.info("New user registered: {}", newUser.getUserName());
}
return rootContainer;
}
}
-
-
diff --git a/code/src/main/java/org/segocode/bot/constants/Messages.java b/code/src/main/java/org/segocode/webdl/bot/constants/Messages.java
similarity index 61%
rename from code/src/main/java/org/segocode/bot/constants/Messages.java
rename to code/src/main/java/org/segocode/webdl/bot/constants/Messages.java
index 8ab2826..868c653 100644
--- a/code/src/main/java/org/segocode/bot/constants/Messages.java
+++ b/code/src/main/java/org/segocode/webdl/bot/constants/Messages.java
@@ -1,8 +1,9 @@
-package org.segocode.bot.constants;
+package org.segocode.webdl.bot.constants;
public class Messages {
public static final String NOT_VALID_LINK = "❌ That doesn't seem to be a valid link";
public static final String DOWNLOAD_REQUEST = "🔄 Download request processing...";
public static final String DOWNLOAD_REQUEST_QUEUED = "⌛ Your request is in queue...";
- public static final String DOWNLOAD_REQUEST_ERROR = "❌ Unable to download: web not supported, private post or reach limit";
+ public static final String DOWNLOAD_REQUEST_ERROR =
+ "❌ Unable to download: web not supported, private post or reach limit";
}
diff --git a/code/src/main/java/org/segocode/bot/model/DataRootContainer.java b/code/src/main/java/org/segocode/webdl/bot/model/DataRootContainer.java
similarity index 77%
rename from code/src/main/java/org/segocode/bot/model/DataRootContainer.java
rename to code/src/main/java/org/segocode/webdl/bot/model/DataRootContainer.java
index 98ca8a0..23c0951 100644
--- a/code/src/main/java/org/segocode/bot/model/DataRootContainer.java
+++ b/code/src/main/java/org/segocode/webdl/bot/model/DataRootContainer.java
@@ -1,12 +1,11 @@
-package org.segocode.bot.model;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+package org.segocode.webdl.bot.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@@ -21,8 +20,6 @@ public class DataRootContainer {
* @return An Optional containing the user if found, empty otherwise
*/
public Optional findUserById(String id) {
- return users.stream()
- .filter(user -> id.equals(user.getId()))
- .findFirst();
+ return users.stream().filter(user -> id.equals(user.getId())).findFirst();
}
}
diff --git a/code/src/main/java/org/segocode/bot/model/User.java b/code/src/main/java/org/segocode/webdl/bot/model/User.java
similarity index 95%
rename from code/src/main/java/org/segocode/bot/model/User.java
rename to code/src/main/java/org/segocode/webdl/bot/model/User.java
index ac76581..481a310 100644
--- a/code/src/main/java/org/segocode/bot/model/User.java
+++ b/code/src/main/java/org/segocode/webdl/bot/model/User.java
@@ -1,11 +1,10 @@
-package org.segocode.bot.model;
+package org.segocode.webdl.bot.model;
+import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
-import java.time.LocalDateTime;
-
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -37,4 +36,4 @@ public void recordNewMessage() {
this.messageCount++;
this.lastMessageTime = LocalDateTime.now().toString();
}
-}
\ No newline at end of file
+}
diff --git a/code/src/main/java/org/segocode/service/MessageService.java b/code/src/main/java/org/segocode/webdl/bot/service/MessageService.java
similarity index 97%
rename from code/src/main/java/org/segocode/service/MessageService.java
rename to code/src/main/java/org/segocode/webdl/bot/service/MessageService.java
index fc53fbd..e91e25b 100644
--- a/code/src/main/java/org/segocode/service/MessageService.java
+++ b/code/src/main/java/org/segocode/webdl/bot/service/MessageService.java
@@ -1,4 +1,4 @@
-package org.segocode.service;
+package org.segocode.webdl.bot.service;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage;
diff --git a/code/src/main/java/org/segocode/service/VideoService.java b/code/src/main/java/org/segocode/webdl/bot/service/VideoService.java
similarity index 71%
rename from code/src/main/java/org/segocode/service/VideoService.java
rename to code/src/main/java/org/segocode/webdl/bot/service/VideoService.java
index 88c1b94..3019d9e 100644
--- a/code/src/main/java/org/segocode/service/VideoService.java
+++ b/code/src/main/java/org/segocode/webdl/bot/service/VideoService.java
@@ -1,14 +1,12 @@
-package org.segocode.service;
+package org.segocode.webdl.bot.service;
-import org.segocode.system.util.FileUtil;
+import java.io.File;
+import org.segocode.webdl.system.util.FileUtil;
+import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.meta.api.methods.send.SendVideo;
import org.telegram.telegrambots.meta.api.objects.InputFile;
-import java.io.File;
-import org.slf4j.Logger;
-
-
public class VideoService {
private static final Logger LOGGER = LoggerFactory.getLogger(VideoService.class);
@@ -20,16 +18,19 @@ public class VideoService {
* @return The SendVideo object configured with the chat ID and reply to message ID.
*/
public static SendVideo sendVideo(Long chatId, Integer replyToMessageId) {
- String filePath = buildFilePath(replyToMessageId);
- LOGGER.info("Locating video file for message ID {}: {}", replyToMessageId, filePath);
+ String filePath = buildFilePath(replyToMessageId);
+ LOGGER.info("Locating video file for message ID {}: {}", replyToMessageId, filePath);
- File videoFile = FileUtil.locateVideoFile(filePath);
- LOGGER.info("Video file located successfully: {}", videoFile.getPath());
+ File videoFile = FileUtil.locateVideoFile(filePath);
+ if (videoFile == null) {
+ throw new RuntimeException("Video file not found for message ID: " + replyToMessageId);
+ }
+ LOGGER.info("Video file located successfully: {}", videoFile.getPath());
- SendVideo sendVideoRequest = createSendVideoRequest(chatId, replyToMessageId, videoFile);
- LOGGER.info("Video is sending. Chat ID: {}, Reply to Message ID: {}", chatId, replyToMessageId);
+ SendVideo sendVideoRequest = createSendVideoRequest(chatId, replyToMessageId, videoFile);
+ LOGGER.info("Video is sending. Chat ID: {}, Reply to Message ID: {}", chatId, replyToMessageId);
- return sendVideoRequest;
+ return sendVideoRequest;
}
/**
@@ -57,6 +58,4 @@ private static SendVideo createSendVideoRequest(Long chatId, Integer replyToMess
private static String buildFilePath(Integer replyToMessageId) {
return "./downloads/" + replyToMessageId + ".mp4";
}
-
-
}
diff --git a/code/src/main/java/org/segocode/bot/util/MessageUtil.java b/code/src/main/java/org/segocode/webdl/bot/util/MessageUtil.java
similarity index 90%
rename from code/src/main/java/org/segocode/bot/util/MessageUtil.java
rename to code/src/main/java/org/segocode/webdl/bot/util/MessageUtil.java
index 66341ae..576f565 100644
--- a/code/src/main/java/org/segocode/bot/util/MessageUtil.java
+++ b/code/src/main/java/org/segocode/webdl/bot/util/MessageUtil.java
@@ -1,4 +1,4 @@
-package org.segocode.bot.util;
+package org.segocode.webdl.bot.util;
public class MessageUtil {
public static String extractUrlFromMessage(String messageText) {
diff --git a/code/src/main/java/org/segocode/webdl/panel/AdminController.java b/code/src/main/java/org/segocode/webdl/panel/AdminController.java
new file mode 100644
index 0000000..c3e86f8
--- /dev/null
+++ b/code/src/main/java/org/segocode/webdl/panel/AdminController.java
@@ -0,0 +1,40 @@
+package org.segocode.webdl.panel;
+
+import com.google.gson.Gson;
+import io.javalin.http.Context;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager;
+import org.segocode.webdl.bot.model.DataRootContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AdminController {
+ private static final Logger LOGGER = LoggerFactory.getLogger(AdminController.class);
+ private final EmbeddedStorageManager storageManager;
+
+ public AdminController(EmbeddedStorageManager storageManager) {
+ this.storageManager = storageManager;
+ }
+
+ public void handleAdminRequest(Context ctx) {
+ ctx.contentType("text/html");
+
+ try {
+ InputStream inputStream = getClass().getResourceAsStream("/views/admin.html");
+ String htmlContent = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+ String jsonData;
+ synchronized (storageManager) {
+ jsonData = new Gson().toJson(((DataRootContainer) storageManager.root()).getUsers());
+ }
+ htmlContent = htmlContent.replace("{{!user_data}}", jsonData);
+
+ ctx.result(htmlContent);
+ } catch (IOException e) {
+ LOGGER.error("Failed to read admin HTML file", e);
+ ctx.status(500);
+ ctx.result("Error loading admin panel: " + e.getMessage());
+ }
+ }
+}
diff --git a/code/src/main/java/org/segocode/webdl/panel/PanelApplication.java b/code/src/main/java/org/segocode/webdl/panel/PanelApplication.java
new file mode 100644
index 0000000..ab354cd
--- /dev/null
+++ b/code/src/main/java/org/segocode/webdl/panel/PanelApplication.java
@@ -0,0 +1,28 @@
+package org.segocode.webdl.panel;
+
+import io.javalin.Javalin;
+import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PanelApplication {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PanelApplication.class);
+ private static PanelApplication instance;
+
+ private PanelApplication(EmbeddedStorageManager storageManager) {
+ AdminController adminController = new AdminController(storageManager);
+ Javalin app = Javalin.create().start(8080);
+ configureRoutes(app, adminController);
+ LOGGER.info("Panel application started on port 8080");
+ }
+
+ public static synchronized void start(EmbeddedStorageManager storageManager) {
+ if (instance == null) {
+ instance = new PanelApplication(storageManager);
+ }
+ }
+
+ private void configureRoutes(Javalin app, AdminController adminController) {
+ app.get("/", adminController::handleAdminRequest);
+ }
+}
diff --git a/code/src/main/java/org/segocode/system/command/CommandExecutor.java b/code/src/main/java/org/segocode/webdl/system/command/CommandExecutor.java
similarity index 83%
rename from code/src/main/java/org/segocode/system/command/CommandExecutor.java
rename to code/src/main/java/org/segocode/webdl/system/command/CommandExecutor.java
index 164db28..be467a6 100644
--- a/code/src/main/java/org/segocode/system/command/CommandExecutor.java
+++ b/code/src/main/java/org/segocode/webdl/system/command/CommandExecutor.java
@@ -1,4 +1,4 @@
-package org.segocode.system.command;
+package org.segocode.webdl.system.command;
import java.io.File;
import java.io.IOException;
@@ -26,14 +26,16 @@ public static void executeCommand(String url, String uuid) throws Exception {
// The -q option is important to prevent deadlocks by ensuring the output buffer is not filled.
// TODO: make a StreamGobbler to handle buffered output and avoid deadlocks.
- String[] command = { ytDlpCommand, "-q", "-S", "ext,res:720", "-o", outputPath, url };
+ String[] command = {ytDlpCommand, "-q", "-S", "ext,res:720", "-o", outputPath, url};
int attempt = 0;
while (attempt++ < MAX_RETRIES) {
- LOGGER.info("Attempt {} of " + MAX_RETRIES, attempt);
+ LOGGER.info("Attempt {} of {}", attempt, MAX_RETRIES);
try {
Process process = new ProcessBuilder(command).start();
- if (process.waitFor(TIMEOUT, TimeUnit.SECONDS) ? process.exitValue() == 0 : process.destroyForcibly() == null) {
+ if (process.waitFor(TIMEOUT, TimeUnit.SECONDS)
+ ? process.exitValue() == 0
+ : process.destroyForcibly() == null) {
LOGGER.info("Download successful for URL: {}", url);
return;
} else {
@@ -41,7 +43,7 @@ public static void executeCommand(String url, String uuid) throws Exception {
process.destroyForcibly();
}
} catch (IOException e) {
- LOGGER.error("I/O error occurred on attempt {} of " + MAX_RETRIES, attempt, e);
+ LOGGER.error("I/O error occurred on attempt {} of {}", attempt, MAX_RETRIES, e);
if (attempt >= MAX_RETRIES) {
throw e;
}
diff --git a/code/src/main/java/org/segocode/system/util/FileUtil.java b/code/src/main/java/org/segocode/webdl/system/util/FileUtil.java
similarity index 73%
rename from code/src/main/java/org/segocode/system/util/FileUtil.java
rename to code/src/main/java/org/segocode/webdl/system/util/FileUtil.java
index 4038d69..dfcb8ee 100644
--- a/code/src/main/java/org/segocode/system/util/FileUtil.java
+++ b/code/src/main/java/org/segocode/webdl/system/util/FileUtil.java
@@ -1,4 +1,4 @@
-package org.segocode.system.util;
+package org.segocode.webdl.system.util;
import java.io.File;
import java.nio.file.Files;
@@ -9,28 +9,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
public class FileUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);
- /**
- * Deletes a specific file based on the provided ID.
- *
- * @param id the identifier of the file to delete
- */
- // Currently, since the queue is for a single user at a time,we do not need to delete by ID.
- public static void deleteFileById(int id) {
-
- Path path = Paths.get("./downloads/" + id + ".mp4");
- try {
- boolean fileDeleted = Files.deleteIfExists(path);
- LOGGER.info(fileDeleted ? "File successfully deleted: " + path : "File not found: " + path);
- } catch (Exception e) {
- LOGGER.error("Error deleting the file: {}", path, e);
- cleanDownloadsFolder();
- }
- }
-
/**
* Deletes all files and folders within the 'downloads' folder.
*/
@@ -71,4 +52,4 @@ public static File locateVideoFile(String filePath) {
}
return null;
}
-}
\ No newline at end of file
+}
diff --git a/code/src/main/resources/views/admin.html b/code/src/main/resources/views/admin.html
index 77486dd..d5a472c 100644
--- a/code/src/main/resources/views/admin.html
+++ b/code/src/main/resources/views/admin.html
@@ -3,168 +3,455 @@
- Usage dashboard
-
+ Dashboard
+
+
+
+
-
-
-
-
-
-
-
-
Usage Dashboard
+
+
+
+
+
DASHBOARD
+
Bot activity & user statistics
-
-
-
Last updated:
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
Total messages processed
+
0
-
-
-
-
-
-
+
-
-
-
-
-
-
-
Avg messages/user
-
0
-
+
-
-
-
-
-
-
-
-
User activity
-
-
-
-
- | Name |
- Username |
- Country |
- Messages |
- Last active |
-
-
-
-
+
+
+
+
+
User activity table
+
Ordered by message volume
-
+
+
+
+ | Name |
+ Username |
+ Language |
+ Messages |
+ Last active |
+
+
+
+
+
-