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 + + + + + + +

+
+
+
+ + + +
+

Usage Dashboard

+
+
+ + + + Last updated: +
+
+
+ + + +
+ +
+ +
+
+
+ + + +
+
+

Total users

+

0

+
+
+
+
+
+
+ + + +
+
+

Total messages

+

0

+
+
+
+
+
+
+ + + +
+
+

Avg messages/user

+

0

+
+
+
+
+
+
+ + + +
+
+

Active Countrys

+

0

+
+
+
+
+ + +
+
+

Messages per user

+
+
+
+

Country distribution

+
+
+
+ + +
+

User activity

+
+ + + + + + + + + + + +
NameUsernameCountryMessagesLast 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