diff --git a/.github/workflows/update-license.yml b/.github/workflows/update-license.yml
index 98f06bd..43f5ac4 100644
--- a/.github/workflows/update-license.yml
+++ b/.github/workflows/update-license.yml
@@ -16,7 +16,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
- ref: main
+ ref: develop
- name: Setup git
run: |
@@ -55,5 +55,5 @@ jobs:
# Only commit if there are changes
git diff --staged --quiet || git commit -m "Update LICENSE with repository information"
- # Push directly to main branch
- git push origin main
+ # Push directly to develop branch
+ git push origin develop
diff --git a/.gitignore b/.gitignore
index 280d3be..8c11a6b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
### Custom ###
code/.idea/*
+code/storage/*
+code/target/*
# Created by https://www.toptal.com/developers/gitignore/api/git,gpg,ssh,vim,linux,macos,windows,notepadpp,sublimetext,intellij+all,visualstudiocode,dotenv
# Edit at https://www.toptal.com/developers/gitignore?templates=git,gpg,ssh,vim,linux,macos,windows,notepadpp,sublimetext,intellij+all,visualstudiocode,dotenv
diff --git a/LICENSE b/LICENSE
index 43ad7db..6ab3d8e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,75 +1,75 @@
-Copyright (c) SegoCode
-All rights reserved.
-
-Section 1 - Definitions
-
-1.1 "NonCommercial" pertains to any use, distribution, or modification of the
- licensed material that does not primarily aim to achieve commercial
- advantage or generate monetary compensation. This includes, but is not
- limited to, activities such as distributing the licensed material as part
- of an application or product that is sold, using the licensed material in
- advertising, and creating products or services with the licensed material
- that are subsequently sold.
-
-1.2 "Adapted Material" refers to any work derived from or based upon the licensed
- material. This includes, but is not limited to, translations, alterations,
- rearrangements, transformations, source code modifications, compiled code
- alterations, architectural redesigns, or the integration of the licensed
- material into other software projects.
-
-1.3 "NonAdapted Material" refers to exact copies of the licensed material, either
- in source code or binary form, which are reproduced without any changes,
- modifications, or transformations.
-
-Section 2 - License Conditions
-
-2.1 Distribution and usage in source or binary forms are permitted solely for
- NonCommercial purposes for both NonAdapted Material and Adapted Material
- excluding NonAdapted Material cases in 2.4 section.
-
-2.2 Redistributions for any NonAdapted Material or Adapted Material, either
- in source code or binary form, must include the original copyright notice,
- this license, the disclaimer, and comply with the requirements specified
- herein. For binary form redistributions, these documents must be included
- in any provided documentation or materials or a clearly accessible link
- to this license ensuring that recipients can easily review the license terms.
-
-2.3 For any Adapted Material, whether in source code or binary form, and for any
- instance of the licensed material utilized in applications accessible over
- the internet that run on a server, the source code must be made accessible
- through a public repository, a downloadable archive, or an equivalent
- method, ensuring that users have the capability to access, review, and
- download the code, and must clearly credit the original work and author.
-
-2.4 For any NonAdapted Material that are offered on download sites, marketplaces, or
- software distribution platforms, are permitted under the condition that any
- economic benefit derived from such distribution is strictly indirect, including
- but not limited to advertisements or link redirectors. Direct sales or charges
- for access to the licensed material are not permitted under this license. Upon
- the original author distributing the material on the same platform, all alternative
- downloads and any associated revenue-generating mechanisms must be discontinued
- immediately. Acceptance of this license constitutes agreement that the copyright
- holder may request the immediate cessation of such downloads and activities on
- such platforms, and the distributor must comply with such request without delay.
-
-2.5 Distributing the licensed material alongside unrelated or harmful software, such as adware,
- malware, or spyware or implying endorsement or association with the original author
- or recognized entities without permission, is prohibited.
-
-Section 3 - Updates and Revisions
-
-3.1 The copyright holder retains the right to amend, update, or otherwise modify this
- license at any time without prior notice. Users should periodically review the
- license terms to stay informed of any changes.
-
-3.2 Continued use of the licensed material after such changes signifies acceptance of the
- revised license terms. It is the user's responsibility to ensure ongoing compliance
- with the most current version of the license.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Copyright (c) SegoCode
+All rights reserved.
+
+Section 1 - Definitions
+
+1.1 "NonCommercial" pertains to any use, distribution, or modification of the
+ licensed material that does not primarily aim to achieve commercial
+ advantage or generate monetary compensation. This includes, but is not
+ limited to, activities such as distributing the licensed material as part
+ of an application or product that is sold, using the licensed material in
+ advertising, and creating products or services with the licensed material
+ that are subsequently sold.
+
+1.2 "Adapted Material" refers to any work derived from or based upon the licensed
+ material. This includes, but is not limited to, translations, alterations,
+ rearrangements, transformations, source code modifications, compiled code
+ alterations, architectural redesigns, or the integration of the licensed
+ material into other software projects.
+
+1.3 "NonAdapted Material" refers to exact copies of the licensed material, either
+ in source code or binary form, which are reproduced without any changes,
+ modifications, or transformations.
+
+Section 2 - License Conditions
+
+2.1 Distribution and usage in source or binary forms are permitted solely for
+ NonCommercial purposes for both NonAdapted Material and Adapted Material
+ excluding NonAdapted Material cases in 2.4 section.
+
+2.2 Redistributions for any NonAdapted Material or Adapted Material, either
+ in source code or binary form, must include the original copyright notice,
+ this license, the disclaimer, and comply with the requirements specified
+ herein. For binary form redistributions, these documents must be included
+ in any provided documentation or materials or a clearly accessible link
+ to this license ensuring that recipients can easily review the license terms.
+
+2.3 For any Adapted Material, whether in source code or binary form, and for any
+ instance of the licensed material utilized in applications accessible over
+ the internet that run on a server, the source code must be made accessible
+ through a public repository, a downloadable archive, or an equivalent
+ method, ensuring that users have the capability to access, review, and
+ download the code, and must clearly credit the original work and author.
+
+2.4 For any NonAdapted Material that are offered on download sites, marketplaces, or
+ software distribution platforms, are permitted under the condition that any
+ economic benefit derived from such distribution is strictly indirect, including
+ but not limited to advertisements or link redirectors. Direct sales or charges
+ for access to the licensed material are not permitted under this license. Upon
+ the original author distributing the material on the same platform, all alternative
+ downloads and any associated revenue-generating mechanisms must be discontinued
+ immediately. Acceptance of this license constitutes agreement that the copyright
+ holder may request the immediate cessation of such downloads and activities on
+ such platforms, and the distributor must comply with such request without delay.
+
+2.5 Distributing the licensed material alongside unrelated or harmful software, such as adware,
+ malware, or spyware or implying endorsement or association with the original author
+ or recognized entities without permission, is prohibited.
+
+Section 3 - Updates and Revisions
+
+3.1 The copyright holder retains the right to amend, update, or otherwise modify this
+ license at any time without prior notice. Users should periodically review the
+ license terms to stay informed of any changes.
+
+3.2 Continued use of the licensed material after such changes signifies acceptance of the
+ revised license terms. It is the user's responsibility to ensure ongoing compliance
+ with the most current version of the license.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 95e5f8f..3aa2210 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# webdl
+
About •
@@ -18,6 +19,8 @@ Telegram bot in Java for downloading social media videos using yt-dlp
- Dynamic interaction with messages
+- Panel with usage statistics
+
## 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.
@@ -35,8 +38,11 @@ Use a temp volume for the download, it will delete after send.
```
mvn clean package
docker build -t webdl-image .
-docker run -e BOT_TOKEN=your-bot-token -v /mnt/drive/data/webdl:/downloads --name webdl webdl-image
+docker run -e BOT_TOKEN=your-bot-token -p 8080:8080 -v /mnt/drive/data/webdl:/downloads --name webdl webdl-image
```
+
+
+
---
diff --git a/code/pom.xml b/code/pom.xml
index 9c6c9da..bf1f834 100644
--- a/code/pom.xml
+++ b/code/pom.xml
@@ -25,6 +25,27 @@
logback-classic
1.4.14
+
+ org.projectlombok
+ lombok
+ 1.18.36
+ provided
+
+
+ org.eclipse.store
+ storage-embedded
+ 2.1.2
+
+
+ io.javalin
+ javalin
+ 6.5.0
+
+
+ com.google.code.gson
+ gson
+ 2.12.1
+
diff --git a/code/src/main/java/org/segocode/Main.java b/code/src/main/java/org/segocode/Main.java
index bbafafc..0b4b414 100644
--- a/code/src/main/java/org/segocode/Main.java
+++ b/code/src/main/java/org/segocode/Main.java
@@ -1,20 +1,31 @@
package org.segocode;
+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.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);
public static void main(String[] args) {
try {
- LOGGER.info("Starting the video download bot...");
+ LOGGER.info("Starting storage manager...");
+ final EmbeddedStorageManager storageManager = EmbeddedStorage.start();
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
- botsApi.registerBot(new Webdlbot());
+ LOGGER.info("Starting the video download bot...");
+ botsApi.registerBot(new Webdlbot(storageManager));
LOGGER.info("Bot started successfully and ready to download videos 🚀");
+ LOGGER.info("Starting webp anel app...");
+ PanelController.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/bot/Webdlbot.java
index ff7ca36..11e65f1 100644
--- a/code/src/main/java/org/segocode/bot/Webdlbot.java
+++ b/code/src/main/java/org/segocode/bot/Webdlbot.java
@@ -1,26 +1,32 @@
package org.segocode.bot;
-import org.segocode.bot.utils.Utils;
-import org.segocode.system.CommandExecutor;
-import org.segocode.service.VideoService;
+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.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.service.MessageService;
+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.utils.Utils.*;
+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");
+ private final StorageManager storageManager;
+
// Create a ThreadPoolExecutor with a single virtual thread
private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
1, // corePoolSize: the number of threads to keep in the pool, even if they are idle
@@ -31,6 +37,20 @@ public class Webdlbot extends TelegramLongPollingBot {
Thread.ofVirtual().factory() // the factory to use when creating new threads
);
+ public Webdlbot(StorageManager storageManager) {
+ this.storageManager = storageManager;
+ initializeRootContainer();
+ }
+
+ private void initializeRootContainer() {
+ Object root = storageManager.root();
+ if (!(root instanceof DataRootContainer)) {
+ 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());
+ }
+
@Override
public String getBotUsername() {
return "webdl";
@@ -51,7 +71,7 @@ public void onUpdateReceived(Update update) {
if (update.hasMessage() && update.getMessage().hasText()) {
try {
Integer queuedMessageId;
- if (executorService.getActiveCount() > 0 || executorService.getQueue().size() > 0) {
+ 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();
@@ -74,6 +94,9 @@ public void onUpdateReceived(Update update) {
} catch (Exception e) {
LOGGER.error("Failed on onUpdateReceived, error: {}", e.getMessage(), e);
handleDispatchError(update, e);
+ } finally {
+ storageManager.setRoot(loadMetricsData(update));
+ storageManager.storeRoot();
}
}
}
@@ -107,6 +130,29 @@ private void handleDispatchError(Update update, Exception e) {
cleanDownloadsFolder();
}
}
+
+ 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();
+
+ DataRootContainer rootContainer = (DataRootContainer) storageManager.root();
+
+ // Find existing user or create new one
+ String userId = telegramUser.getId().toString();
+ Optional existingUser = rootContainer.findUserById(userId);
+
+ if (existingUser.isPresent()) {
+ 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());
+ 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/bot/constants/Messages.java
index f836f72..8ab2826 100644
--- a/code/src/main/java/org/segocode/bot/constants/Messages.java
+++ b/code/src/main/java/org/segocode/bot/constants/Messages.java
@@ -4,5 +4,5 @@ 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 = "❌ An error occurred while processing your request: web not supported, private profile 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/bot/model/DataRootContainer.java
new file mode 100644
index 0000000..98ca8a0
--- /dev/null
+++ b/code/src/main/java/org/segocode/bot/model/DataRootContainer.java
@@ -0,0 +1,28 @@
+package org.segocode.bot.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DataRootContainer {
+ private List users = new ArrayList<>();
+
+ /**
+ * Finds a user by their ID
+ *
+ * @param id The ID of the user to find
+ * @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();
+ }
+}
diff --git a/code/src/main/java/org/segocode/bot/model/User.java b/code/src/main/java/org/segocode/bot/model/User.java
new file mode 100644
index 0000000..ac76581
--- /dev/null
+++ b/code/src/main/java/org/segocode/bot/model/User.java
@@ -0,0 +1,40 @@
+package org.segocode.bot.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class User {
+ // Unique identifiers
+ private String id;
+ private Long chatId;
+ private String userName;
+
+ // Personal information
+ private String firstName;
+ private String lastName;
+
+ // Preferences and metadata
+ private String languageCode;
+ private Boolean isPremium;
+
+ // Usage statistics
+ private Integer messageCount;
+ private String lastMessageTime;
+
+ /**
+ * Increments the message count and updates the last message time to current time
+ */
+ public void recordNewMessage() {
+ if (this.messageCount == null) {
+ this.messageCount = 0;
+ }
+ this.messageCount++;
+ this.lastMessageTime = LocalDateTime.now().toString();
+ }
+}
\ No newline at end of file
diff --git a/code/src/main/java/org/segocode/bot/service/MessageService.java b/code/src/main/java/org/segocode/bot/service/MessageService.java
new file mode 100644
index 0000000..9a2c996
--- /dev/null
+++ b/code/src/main/java/org/segocode/bot/service/MessageService.java
@@ -0,0 +1,32 @@
+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
new file mode 100644
index 0000000..79d853b
--- /dev/null
+++ b/code/src/main/java/org/segocode/bot/service/VideoService.java
@@ -0,0 +1,62 @@
+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/bot/utils/Utils.java b/code/src/main/java/org/segocode/bot/util/MessageUtil.java
similarity index 85%
rename from code/src/main/java/org/segocode/bot/utils/Utils.java
rename to code/src/main/java/org/segocode/bot/util/MessageUtil.java
index 6dca88f..66341ae 100644
--- a/code/src/main/java/org/segocode/bot/utils/Utils.java
+++ b/code/src/main/java/org/segocode/bot/util/MessageUtil.java
@@ -1,6 +1,6 @@
-package org.segocode.bot.utils;
+package org.segocode.bot.util;
-public class Utils {
+public class MessageUtil {
public static String extractUrlFromMessage(String messageText) {
int startIndex = messageText.indexOf("http");
int endIndex = messageText.indexOf(" ", startIndex);
diff --git a/code/src/main/java/org/segocode/panel/PanelController.java b/code/src/main/java/org/segocode/panel/PanelController.java
new file mode 100644
index 0000000..f8d6fc7
--- /dev/null
+++ b/code/src/main/java/org/segocode/panel/PanelController.java
@@ -0,0 +1,60 @@
+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/system/CommandExecutor.java b/code/src/main/java/org/segocode/system/command/CommandExecutor.java
similarity index 89%
rename from code/src/main/java/org/segocode/system/CommandExecutor.java
rename to code/src/main/java/org/segocode/system/command/CommandExecutor.java
index 392501d..164db28 100644
--- a/code/src/main/java/org/segocode/system/CommandExecutor.java
+++ b/code/src/main/java/org/segocode/system/command/CommandExecutor.java
@@ -1,8 +1,7 @@
-package org.segocode.system;
+package org.segocode.system.command;
import java.io.File;
import java.io.IOException;
-import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,11 +24,9 @@ public static void executeCommand(String url, String uuid) throws Exception {
String ytDlpCommand = osName.contains("win") ? "yt-dlp.exe" : "yt-dlp"; // This is needed?
String outputPath = "." + File.separator + "downloads" + File.separator + uuid + ".%(ext)s";
- // The --quiet option is important to prevent deadlocks by ensuring the output buffer is not filled.
+ // 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:420", "-o", outputPath, url };
-
- LOGGER.debug("Starting download command: {}", String.join(" ", command));
+ String[] command = { ytDlpCommand, "-q", "-S", "ext,res:720", "-o", outputPath, url };
int attempt = 0;
while (attempt++ < MAX_RETRIES) {
diff --git a/code/src/main/java/org/segocode/system/util/FileUtil.java b/code/src/main/java/org/segocode/system/util/FileUtil.java
index 9883215..4038d69 100644
--- a/code/src/main/java/org/segocode/system/util/FileUtil.java
+++ b/code/src/main/java/org/segocode/system/util/FileUtil.java
@@ -18,8 +18,8 @@ public class FileUtil {
*
* @param id the identifier of the file to delete
*/
-
- public static void deleteFileById(int id) { // Currently, since the queue is for a single user at a time,we do not need to delete by ID.
+ // 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 {
diff --git a/code/src/main/resources/views/admin.html b/code/src/main/resources/views/admin.html
new file mode 100644
index 0000000..6ec62bf
--- /dev/null
+++ b/code/src/main/resources/views/admin.html
@@ -0,0 +1,252 @@
+
+
+
+
+
+ Usage dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Avg messages/user
+
0
+
+
+
+
+
+
+
+
+
+
+
Country distribution
+
+
+
+
+
+
+
User activity
+
+
+
+
+ | Name |
+ Username |
+ Country |
+ Messages |
+ Last active |
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/media/demo3.png b/media/demo3.png
new file mode 100644
index 0000000..322d0a2
Binary files /dev/null and b/media/demo3.png differ
diff --git a/media/demoPanel.png b/media/demoPanel.png
new file mode 100644
index 0000000..59ff343
Binary files /dev/null and b/media/demoPanel.png differ