diff --git a/.gitignore b/.gitignore index 7d14d53..a086d19 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ bin/ ### Mac OS ### .DS_Store /src/main/resources/installer-data/dependencies.json +/.idea/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 1cbcde6..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/hotswap_agent.xml b/.idea/hotswap_agent.xml deleted file mode 100644 index 807b3ca..0000000 --- a/.idea/hotswap_agent.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index cf247ce..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index c787cd5..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/build.gradle b/build.gradle index d053eee..1ecf8ec 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import org.mangorage.mangobotgradle.util.GitVersion - buildscript { repositories { @@ -18,7 +16,7 @@ buildscript { } dependencies { - classpath 'org.mangorage:MangoBotGradle:6.0.22' + classpath 'org.mangorage:MangoBotGradle:6.0.24' } } @@ -31,7 +29,7 @@ apply plugin: 'maven-publish' apply plugin: 'MangoBotGradle' group = 'org.mangorage' -version = GitVersion.getGitVersion().getVersionAsString() +version = "0.0.0" println("Version: " + version) @@ -93,8 +91,8 @@ dependencies { launchtarget("org.mangorage:mangobotlaunchtarget:0.1.8") - plugin('org.mangorage:mangobot:12.0.96') - plugin('org.mangorage:mangobotplugin:12.0.57') + plugin('org.mangorage:mangobot:12.0.109') + plugin('org.mangorage:mangobotplugin:12.0.63') library('org.eclipse.jetty:jetty-server:11.0.16') library('org.eclipse.jetty:jetty-servlet:11.0.16') diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index e151ca7..7043258 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -12,10 +12,11 @@ requires org.mangorage.bootstrap; // Files - opens templates.file; - opens templates.general; opens templates; + // TODO: Deal with it + exports org.mangorage.mangobotsite.website.servlet.data; + exports org.mangorage.mangobotsite; exports org.mangorage.mangobotsite.website.file; exports org.mangorage.mangobotsite.website.servlet.entity; diff --git a/src/main/java/org/mangorage/mangobotsite/website/WebServer.java b/src/main/java/org/mangorage/mangobotsite/website/WebServer.java index 31a2231..786f176 100644 --- a/src/main/java/org/mangorage/mangobotsite/website/WebServer.java +++ b/src/main/java/org/mangorage/mangobotsite/website/WebServer.java @@ -19,17 +19,7 @@ import org.mangorage.mangobotsite.website.filters.RequestInterceptorFilter; import org.mangorage.mangobotsite.website.handlers.DefaultErrorHandler; import org.mangorage.mangobotsite.website.impl.ObjectMap; -import org.mangorage.mangobotsite.website.servlet.AccountServlet; - -import org.mangorage.mangobotsite.website.servlet.EntitiesServlet; -import org.mangorage.mangobotsite.website.servlet.EntityServlet; -import org.mangorage.mangobotsite.website.servlet.FileServlet; -import org.mangorage.mangobotsite.website.servlet.FileUploadServlet; import org.mangorage.mangobotsite.website.servlet.HomeServlet; -import org.mangorage.mangobotsite.website.servlet.InfoServlet; -import org.mangorage.mangobotsite.website.servlet.LoginServlet; -import org.mangorage.mangobotsite.website.servlet.StreamingServlet; -import org.mangorage.mangobotsite.website.servlet.TestAuthServlet; import org.mangorage.mangobotsite.website.servlet.TricksServlet; import org.mangorage.mangobotsite.website.util.ResolveString; import org.mangorage.mangobotsite.website.util.ServletContextHandlerBuilder; @@ -111,22 +101,10 @@ public static void startWebServer(ObjectMap objectMap) throws Exception { h.setErrorHandler(new DefaultErrorHandler()); }) - .addServlet(StreamingServlet.class, "/watch") .addHttpServlet(HomeServlet.class, "/home") - .addHttpServlet(InfoServlet.class, "/info") - .addHttpServlet(TricksServlet.class, "/trick") - .addHttpServlet(FileServlet.class, "/file") - .addHttpServlet(TestAuthServlet.class, "/testAuth") - .addHttpServlet(LoginServlet.class, "/login") - .addHttpServlet(AccountServlet.class, "/account") - .addHttpServlet(FileUploadServlet.class, "/upload", h -> { - h.getRegistration().setMultipartConfig( - new MultipartConfigElement("/tmp/uploads") - ); - }) - .addHttpServlet(EntityServlet.class, "/api/entity/*") - .addHttpServlet(EntitiesServlet.class, "/api/entities/*") + .addServlet(TricksServlet.class, "/tricks") + .setAttribute(WebConstants.WEB_OBJECT_ID, objectMap) .addFilter(RequestInterceptorFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)) .configureLoginBuilder(security -> { diff --git a/src/main/java/org/mangorage/mangobotsite/website/WebsiteConstants.java b/src/main/java/org/mangorage/mangobotsite/website/WebsiteConstants.java new file mode 100644 index 0000000..9636410 --- /dev/null +++ b/src/main/java/org/mangorage/mangobotsite/website/WebsiteConstants.java @@ -0,0 +1,14 @@ +package org.mangorage.mangobotsite.website; + +import org.mangorage.mangobotsite.website.servlet.data.HeaderData; + +import java.util.List; + +public final class WebsiteConstants { + public static final List headers = List.of( + new HeaderData("/home", "Home", true), + new HeaderData("/home#plugins", "Plugins", true), + new HeaderData("/tricks", "Tricks", true), + new HeaderData("/home#contact", "Contact", true) + ); +} diff --git a/src/main/java/org/mangorage/mangobotsite/website/filters/RequestInterceptorFilter.java b/src/main/java/org/mangorage/mangobotsite/website/filters/RequestInterceptorFilter.java index 6ebf43c..1acf8ea 100644 --- a/src/main/java/org/mangorage/mangobotsite/website/filters/RequestInterceptorFilter.java +++ b/src/main/java/org/mangorage/mangobotsite/website/filters/RequestInterceptorFilter.java @@ -11,7 +11,6 @@ import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.mangorage.mangobotcore.api.util.log.LogHelper; -import org.mangorage.mangobotsite.website.servlet.StreamingServlet; import java.io.IOException; @@ -36,9 +35,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha var ip = main.getHeader("X-Forwarded-For"); LogHelper.info("Intercepted Request from %s for %s -> https://mangobot.mangorage.org%s".formatted(ip == null ? request.getRemoteAddr() : ip, main.getMethod(), main.getOriginalURI())); - if (main.getOriginalURI().contains("mode")) { - new StreamingServlet().service(request, response); - } } else if (request instanceof HttpServletRequest http) { var ip = http.getHeader("X-Forwarded-For"); LogHelper.info("Unknown Type (Class) From %s -> %s".formatted(ip == null ? http.getRemoteAddr() : ip, request.getClass())); diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/AccountServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/AccountServlet.java deleted file mode 100644 index 5ecd699..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/AccountServlet.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.security.SecurityHandler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.UserIdentity; -import org.mangorage.mangobotsite.website.impl.ObjectMap; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.util.WebConstants; - - -import java.io.IOException; - -public class AccountServlet extends StandardHttpServlet { - public UserIdentity getUserIdentity(HttpServletRequest request) { - Request baseRequest = Request.getBaseRequest(request); - return baseRequest != null ? baseRequest.getUserIdentity() : null; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - ObjectMap map = (ObjectMap) getServletConfig().getServletContext().getAttribute(WebConstants.WEB_OBJECT_ID); - var service = map.get(WebConstants.LOGIN_SERVICE, SecurityHandler.class); - - var identity = getUserIdentity(req); - - if (identity != null && service.getLoginService().validate(identity)) { - resp.getWriter().write("Welcome %s!".formatted(identity.getUserPrincipal().getName())); - } else { - resp.getWriter().write("No Account Signed In"); - } - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/ChatServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/ChatServlet.java deleted file mode 100644 index 0264e29..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/ChatServlet.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mangorage.mangobotsite.website.util.WebUtil.processTemplate; - -public class ChatServlet extends StandardHttpServlet { - - private static final List messages = new ArrayList<>(); - - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.setContentType("text/html;charset=UTF-8"); - - Map model = new HashMap<>(); - model.put("messages", messages); - model.put("self", this); - - var username = req.getParameter("username"); - model.put("hasUsername", username != null); - if (username != null) - model.put("username", username); - - processTemplate( - model, - "chat.ftl", - resp.getWriter() - ); - } - - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String username = req.getParameter("username"); - String message = req.getParameter("message"); - if (username != null && !username.trim().isEmpty() && message != null && !message.trim().isEmpty()) { - messages.add("[%s]: %s".formatted(username, message)); - } - resp.sendRedirect("/chat?username=%s".formatted(username)); - } - - @Override - public boolean useDefaultStyles() { - return false; - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/EntitiesServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/EntitiesServlet.java deleted file mode 100644 index 1b1aa55..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/EntitiesServlet.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.servlet.entity.EntityManager; -import org.mangorage.mangobotsite.website.util.WebConstants; - -import java.io.IOException; -import java.util.ArrayList; - -public final class EntitiesServlet extends StandardHttpServlet { - private static final Gson GSON = new GsonBuilder() - .setPrettyPrinting() - .create(); - - public record Entity(String id, long age) {} - - public record StandardList(ArrayList entities) {} - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - final var entityId = req.getRequestURI().replaceFirst("/api/entities/", ""); - final var entityManager = getObjectMap().get(WebConstants.ENTITY_MANAGER, EntityManager.class); - - final var list = entityManager.findAllByType(entityId) - .stream() - .map(e -> new Entity(e.getId(), e.getAge())) - .toList(); - - if (list.isEmpty()) { - resp.getWriter().write("No entities found!"); - } else { - resp.getWriter().write( - GSON.toJson(new StandardList(new ArrayList<>(list))) - ); - } - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/EntityServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/EntityServlet.java deleted file mode 100644 index 99b4697..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/EntityServlet.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.servlet.entity.EntityManager; -import org.mangorage.mangobotsite.website.util.WebConstants; - -import java.io.IOException; - -public class EntityServlet extends StandardHttpServlet { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - final var entityId = req.getRequestURI().replaceFirst("/api/entity/", ""); - final var entityManager = getObjectMap().get(WebConstants.ENTITY_MANAGER, EntityManager.class); - - final var entity = entityManager.findEntity(entityId); - if (entity.isPresent()) { - resp.getWriter().write(entity.get().getJson()); - } else { - resp.getWriter().write("Unable to find anything"); - } - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/FileServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/FileServlet.java deleted file mode 100644 index 7040f1c..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/FileServlet.java +++ /dev/null @@ -1,318 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.file.FileUploadManager; -import org.mangorage.mangobotsite.website.impl.ObjectMap; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.file.TargetFile; -import org.mangorage.mangobotsite.website.file.UploadConfig; -import org.mangorage.mangobotsite.website.util.MapBuilder; -import org.mangorage.mangobotsite.website.util.WebConstants; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.mangorage.mangobotsite.website.util.WebUtil.processTemplate; - -public class FileServlet extends StandardHttpServlet { - - private static final Map EXTENSIONS = new HashMap<>(); - - private static void put(String type, List extensions) { - extensions.forEach(ext -> EXTENSIONS.put(ext, type)); - } - - static { - // Images - put( - "image/jpeg", - List.of( - ".jpg", - ".jpeg" - ) - ); - - EXTENSIONS.put(".png", "image/png"); - EXTENSIONS.put(".gif", "image/gif"); - EXTENSIONS.put(".bmp", "image/bmp"); - EXTENSIONS.put(".webp", "image/webp"); - EXTENSIONS.put(".ico", "image/x-icon"); - EXTENSIONS.put(".svg", "image/svg+xml"); - EXTENSIONS.put(".tif", "image/tiff"); - EXTENSIONS.put(".tiff", "image/tiff"); - - // Documents - EXTENSIONS.put(".pdf", "application/pdf"); - put( - "text/plain", - List.of( - ".txt", - ".log" - ) - ); - EXTENSIONS.put(".csv", "text/csv"); - EXTENSIONS.put(".json", "application/json"); - EXTENSIONS.put(".xml", "application/xml"); - - // Audio - EXTENSIONS.put(".mp3", "audio/mpeg"); - EXTENSIONS.put(".wav", "audio/wav"); - EXTENSIONS.put(".ogg", "audio/ogg"); - EXTENSIONS.put(".flac", "audio/flac"); - EXTENSIONS.put(".aac", "audio/aac"); - - // Video - EXTENSIONS.put(".mp4", "video/mp4"); - EXTENSIONS.put(".avi", "video/x-msvideo"); - EXTENSIONS.put(".mov", "video/quicktime"); - EXTENSIONS.put(".wmv", "video/x-ms-wmv"); - EXTENSIONS.put(".flv", "video/x-flv"); - EXTENSIONS.put(".webm", "video/webm"); - - // Fonts - EXTENSIONS.put(".ttf", "font/ttf"); - EXTENSIONS.put(".otf", "font/otf"); - EXTENSIONS.put(".woff", "font/woff"); - EXTENSIONS.put(".woff2", "font/woff2"); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - ObjectMap map = (ObjectMap) getServletContext().getAttribute(WebConstants.WEB_OBJECT_ID); - FileUploadManager manager = map.get(WebConstants.FILE_MANAGER, FileUploadManager.class); - - // Retrieve parameters - String id = request.getParameter("id"); - String target = request.getParameter("target"); // optional - String download = request.getParameter("dl"); // optional - String delete = request.getParameter("delete"); // optional - - if (id == null || id.isBlank()) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "File ID is required."); - return; - } - - UploadConfig config = manager.getUploadConfig(id); - if (config == null) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid File ID"); - return; - } - - boolean isOwner = config.isAccount(request, response); - boolean header = request.getParameter("header") != null; - - // Insufficient permission to delete - if (!isOwner && delete != null) { - processTemplate( - MapBuilder.of() - .self(this) - .put("url", "/file?id=" + id) - .put("title", "Insufficient Permission") - .put("message", "Insufficient Permission") - .get(), - "general/redirect.ftl", - response.getWriter() - ); - return; - } - - // Delete action - if (isOwner && delete != null) { - if (target != null) { - TargetFile targetFile = config.targets().get(target); - if (targetFile != null) { - config.targets().remove(target); - manager.deleteTarget(targetFile); - manager.saveUpload(config); - } else { - processTemplate( - MapBuilder.of() - .self(this) - .put("url", "/file?id=" + id) - .put("title", "Invalid Target") - .put("message", "Invalid Target") - .get(), - "general/redirect.ftl", - response.getWriter() - ); - return; - } - } else { - manager.deleteUpload(config); - } - - processTemplate( - MapBuilder.of() - .self(this) - .put("title", "Deleted Target") - .put("message", "Deleted File") - .put("url", "/file?id=" + id) - .get(), - "general/redirect.ftl", - response.getWriter() - ); - - return; - } - - // Display file management page if no specific target is provided - if (target == null) { - processTemplate( - MapBuilder.of() - .self(this) - .put("id", id) - .put("isOwner", isOwner) - .put("config", config) - .get(), - "file/files.ftl", - response.getWriter() - ); - return; - } else if (download == null) { - // Display file inline - TargetFile targetFile = config.targets().get(target); - if (targetFile != null) { - handleFileRequest(manager, targetFile, id, false, header, response, request); - return; - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Target"); - } - } else { - // Download file - TargetFile targetFile = config.targets().get(target); - if (targetFile != null) { - handleFileRequest(manager, targetFile, id, true, false, response, request); - return; - } else { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Target"); - return; - } - } - } - - private void handleFileRequest(FileUploadManager manager, TargetFile targetFile, String id, boolean download, boolean header, HttpServletResponse response, HttpServletRequest request) throws IOException { - if (header) { - response.setContentType("text/html"); - processTemplate( - MapBuilder.of() - .self(this) - .put("title", "MangoBot Upload") - .put("contentURL", "https://mangobot.mangorage.org/file?id=%s&target=%s".formatted(id, targetFile.index())) - .put("url", "https://mangobot.mangorage.org/file?id=%s&target=%s".formatted(id, targetFile.index())) - .get(), - "general/embed.ftl", - response.getWriter() - ); - } else { - File file = manager.getTargetPath(targetFile).toFile(); - - if (file.exists() && file.isFile()) { - String contentType = download ? "application/octet-stream" : EXTENSIONS.getOrDefault(targetFile.extension(), "text/plain"); - boolean isTextFile = contentType.contains("text"); - boolean isVideoFile = contentType.startsWith("video"); - - if (isTextFile) { - response.setContentType("text/html"); - List text = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line; - while ((line = reader.readLine()) != null) { - text.add(line); - } - } - - processTemplate( - MapBuilder.of() - .put("lines", text) - .put("name", targetFile.name()) - .get(), - "file.ftl", - response.getWriter() - ); - - } else if (isVideoFile) { - response.setContentType(contentType); - - // Optional but recommended headers for video streaming: - response.setHeader("Accept-Ranges", "bytes"); // Allow range requests for seeking - // Caching headers (adjust as needed): - response.setHeader("Cache-Control", "public, max-age=3600"); // Example: Cache for 1 hour - - String range = request.getHeader("Range"); - if (range != null) { - handleVideoRangeRequest(file, range, response); - } else { - // If no range is specified, send the whole video content - response.setContentLengthLong(file.length()); - try (InputStream fileInputStream = new FileInputStream(file)) { - fileInputStream.transferTo(response.getOutputStream()); - } - } - - } else { - response.setContentType(contentType); - if (download) { - response.setHeader("Content-Disposition", "attachment; filename=\"%s\"".formatted(targetFile.name())); - } - try (InputStream fileInputStream = new FileInputStream(file)) { - fileInputStream.transferTo(response.getOutputStream()); - } - } - } else { - response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found."); - } - } - } - - private void handleVideoRangeRequest(File file, String range, HttpServletResponse response) throws IOException { - // Parsing range - String[] ranges = range.replace("bytes=", "").split("-"); - long start = Long.parseLong(ranges[0]); - long end = ranges.length > 1 ? Long.parseLong(ranges[1]) : file.length() - 1; - - // Ensure the range is valid - if (start >= file.length() || end >= file.length() || start > end) { - response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); - return; - } - - // Set the appropriate headers for partial content - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - response.setContentType("video/mp4"); - response.setContentLengthLong(end - start + 1); - response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + file.length()); - - // Send the video content for the range - try (RandomAccessFile videoFile = new RandomAccessFile(file, "r")) { - videoFile.seek(start); - byte[] buffer = new byte[8192]; // Buffer size - int bytesRead; - while ((bytesRead = videoFile.read(buffer)) != -1) { - if (start + bytesRead > end) { - bytesRead = (int) (end - start + 1); - } - response.getOutputStream().write(buffer, 0, bytesRead); - start += bytesRead; - if (start > end) { - break; - } - } - } - } - - @Override - public boolean useDefaultStyles() { - return false; - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/FileUploadServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/FileUploadServlet.java deleted file mode 100644 index 76800af..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/FileUploadServlet.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.annotation.MultipartConfig; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.file.FileStream; -import org.mangorage.mangobotsite.website.file.FileUploadManager; -import org.mangorage.mangobotsite.website.impl.ObjectMap; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.util.MapBuilder; -import org.mangorage.mangobotsite.website.util.WebConstants; -import org.mangorage.mangobotsite.website.util.WebUtil; - -import java.io.IOException; -import java.util.HashMap; - -import static org.mangorage.mangobotsite.website.util.WebUtil.processTemplate; - -@MultipartConfig -public class FileUploadServlet extends StandardHttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - processTemplate( - MapBuilder.of() - .self(this) - .get(), - "file/upload.ftl", - resp.getWriter() - ); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - - ObjectMap map = (ObjectMap) getServletContext().getAttribute(WebConstants.WEB_OBJECT_ID); - - FileUploadManager manager = map.get(WebConstants.FILE_MANAGER, FileUploadManager.class); - if (req.getParts().isEmpty()) return; - var id = manager.createUpload( - req - .getParts() - .stream() - .map(part -> new FileStream(part.getSubmittedFileName(), part::getInputStream)) - .toList(), - WebUtil.getOrCreateUserToken(req, resp) - ); - - resp.sendRedirect("/file?id=" + id); - } - - @Override - public boolean useDefaultStyles() { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/HomeServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/HomeServlet.java index bd09f6d..19f92da 100644 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/HomeServlet.java +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/HomeServlet.java @@ -3,17 +3,48 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.mangorage.mangobotcore.api.plugin.v1.PluginContainer; +import org.mangorage.mangobotcore.api.plugin.v1.PluginManager; +import org.mangorage.mangobotplugin.entrypoint.MangoBot; +import org.mangorage.mangobotsite.website.Header; +import org.mangorage.mangobotsite.website.WebsiteConstants; import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; +import org.mangorage.mangobotsite.website.servlet.data.HeaderData; +import org.mangorage.mangobotsite.website.servlet.data.PluginData; import org.mangorage.mangobotsite.website.util.MapBuilder; import org.mangorage.mangobotsite.website.util.WebUtil; import java.io.IOException; +import java.util.List; public class HomeServlet extends StandardHttpServlet { + + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/html; charset=UTF-8"); + resp.setCharacterEncoding("UTF-8"); + WebUtil.processTemplate( - MapBuilder.of().get(), + MapBuilder.of() + .put("pluginCount", 1) + .put("guildCount", 12) + .put("headers", WebsiteConstants.headers) + .put( + "plugins", + PluginManager.getInstance().getPlugins().stream() + .map(PluginContainer::getMetadata) + .map(p -> + new PluginData( + p.getType(), + p.getName(), + "Description not available", + p.getVersion() + ) + ) + .toList() + ) + .get(), "home.ftl", resp.getWriter() ); diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/InfoServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/InfoServlet.java deleted file mode 100644 index 343ae04..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/InfoServlet.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.annotation.WebServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.mangorage.mangobotcore.api.plugin.v1.Metadata; -import org.mangorage.mangobotcore.api.plugin.v1.PluginContainer; -import org.mangorage.mangobotcore.api.plugin.v1.PluginManager; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.util.MapBuilder; - -import java.io.IOException; - -import static org.mangorage.mangobotsite.website.util.WebUtil.processTemplate; - -@WebServlet -public class InfoServlet extends StandardHttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // Set content type for HTML response - resp.setContentType("text/html"); - - var plugins = PluginManager.getInstance().getPlugins().stream() - .map(PluginContainer::getMetadata) - .map(MyMetadata::new) - .toList(); - - processTemplate( - MapBuilder.of() - .put("plugins", plugins) - .get(), - "info.ftl", - resp.getWriter() - ); - - } - - @Override - public boolean useDefaultStyles() { - return false; - } - - public record MyMetadata(Metadata metadata) { - public String getName() { - return metadata.getName(); - } - - public String getType() { - return metadata.getType(); - } - - public String getId() { - return metadata.getId(); - } - - public String getVersion() { - return metadata.getVersion(); - } - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/LoginServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/LoginServlet.java deleted file mode 100644 index 9962748..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/LoginServlet.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.security.Authenticator; -import org.eclipse.jetty.security.ServerAuthException; -import org.eclipse.jetty.server.Authentication; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.UserIdentity; -import org.mangorage.mangobotsite.website.impl.ObjectMap; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.util.WebConstants; - -import java.io.IOException; - -public class LoginServlet extends StandardHttpServlet { - - public UserIdentity getUserIdentity(HttpServletRequest request) { - Request baseRequest = Request.getBaseRequest(request); - return baseRequest != null ? baseRequest.getUserIdentity() : null; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // Retrieve LoginService and Authenticator from ObjectMap - ObjectMap map = (ObjectMap) getServletConfig().getServletContext().getAttribute(WebConstants.WEB_OBJECT_ID); - Authenticator authenticator = map.get("auth", Authenticator.class); - - // Use Jetty's existing Basic Auth - Authentication authentication = null; - try { - authentication = authenticator.validateRequest(req, resp, true); - } catch (ServerAuthException e) { - throw new RuntimeException(e); - } - - if (authentication instanceof Authentication.User) { - UserIdentity identity = ((Authentication.User) authentication).getUserIdentity(); - if (identity != null) { - // Store user identity and redirect - req.setAttribute("org.eclipse.jetty.server.UserIdentity", identity); - resp.sendRedirect("/account"); - return; - } - } - - // If not authenticated, force Basic Auth popup - resp.setHeader("WWW-Authenticate", "Basic realm=\"My Secure Area\""); - resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - resp.getWriter().println("HTTP 401 - Unauthorized"); - } -} \ No newline at end of file diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/StreamingServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/StreamingServlet.java deleted file mode 100644 index d6dc329..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/StreamingServlet.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; -import org.mangorage.mangobotsite.website.util.MapBuilder; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.mangorage.mangobotsite.website.util.WebUtil.processTemplate; - - -public class StreamingServlet extends StandardHttpServlet { - - private final Path videoDirectory = Path.of("stream"); - - - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String videoName = req.getParameter("v"); - String mode = req.getParameter("mode"); // "stream" means serve bytes - - if (videoName == null || videoName.isBlank()) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing video parameter"); - return; - } - - File videoFile = videoDirectory.resolve(videoName).toFile(); - if (!videoFile.exists() || !videoFile.isFile()) { - resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Video not found"); - return; - } - - if ("stream".equals(mode)) { - System.out.println("STREAM"); - streamVideo(req, resp, videoFile); - } else { - resp.setContentType("text/html"); - processTemplate( - MapBuilder.of() - .self(this) - .put("videoName", videoName) - .put("title", videoFile.getName()) - .get(), - "stream.ftl", - resp.getWriter() - ); - } - } - - private void streamVideo(HttpServletRequest req, HttpServletResponse resp, File videoFile) throws IOException { - long fileLength = videoFile.length(); - String range = req.getHeader("Range"); - - String contentType = Files.probeContentType(videoFile.toPath()); - if (contentType == null) contentType = "video/mp4"; - - resp.setHeader("Accept-Ranges", "bytes"); - resp.setContentType(contentType); - - try (RandomAccessFile raf = new RandomAccessFile(videoFile, "r"); - OutputStream out = resp.getOutputStream()) { - - if (range != null && range.startsWith("bytes=")) { - String[] parts = range.substring(6).split("-"); - long start = 0; - long end = fileLength - 1; - - try { - start = Long.parseLong(parts[0]); - if (parts.length > 1 && !parts[1].isEmpty()) { - end = Long.parseLong(parts[1]); - } - } catch (NumberFormatException ignored) { - } - - if (start > end || start < 0 || end >= fileLength) { - resp.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); - resp.setHeader("Content-Range", "bytes */" + fileLength); - return; - } - - long contentLength = end - start + 1; - resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - resp.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength); - resp.setContentLengthLong(contentLength); - - raf.seek(start); - writeStream(raf, out, contentLength); - } else { - resp.setContentLengthLong(fileLength); - writeStream(raf, out, fileLength); - } - out.flush(); - } - } - - private void writeStream(RandomAccessFile raf, OutputStream out, long length) throws IOException { - byte[] buffer = new byte[8192]; - long remaining = length; - - while (remaining > 0) { - int read = raf.read(buffer, 0, (int) Math.min(buffer.length, remaining)); - if (read == -1) break; - out.write(buffer, 0, read); - remaining -= read; - } - } - - @Override - public boolean useDefaultStyles() { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/TestAuthServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/TestAuthServlet.java deleted file mode 100644 index f830816..0000000 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/TestAuthServlet.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.mangorage.mangobotsite.website.servlet; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; - -import java.io.IOException; - -public class TestAuthServlet extends StandardHttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - resp.getWriter().write("Test"); - } -} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/TricksServlet.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/TricksServlet.java index 967c8d4..b0ace05 100644 --- a/src/main/java/org/mangorage/mangobotsite/website/servlet/TricksServlet.java +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/TricksServlet.java @@ -3,144 +3,72 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import net.dv8tion.jda.api.JDA; -import org.mangorage.mangobotplugin.commands.trick.Trick; -import org.mangorage.mangobotplugin.commands.trick.TrickManager; +import org.mangorage.mangobotcore.api.plugin.v1.PluginManager; +import org.mangorage.mangobotplugin.entrypoint.MangoBot; +import org.mangorage.mangobotsite.website.WebsiteConstants; import org.mangorage.mangobotsite.website.impl.StandardHttpServlet; +import org.mangorage.mangobotsite.website.servlet.data.GuildsData; +import org.mangorage.mangobotsite.website.servlet.data.TrickData; +import org.mangorage.mangobotsite.website.servlet.data.TrickInfoData; import org.mangorage.mangobotsite.website.util.MapBuilder; +import org.mangorage.mangobotsite.website.util.WebUtil; + import java.io.IOException; -import java.time.Instant; -import java.util.Comparator; -import java.util.Date; -import static org.mangorage.mangobotsite.website.util.WebUtil.processTemplate; +public final class TricksServlet extends StandardHttpServlet { -public class TricksServlet extends StandardHttpServlet { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/html; charset=UTF-8"); + resp.setCharacterEncoding("UTF-8"); - public record Guild(String id, String name) {} + final var plugin = PluginManager.getInstance().getPlugin("mangobot").getInstance(MangoBot.class); + final var manager = plugin.getTrickManager(); + final var selectedGuildId = req.getParameter("guildId"); + final var selectedTrickId = req.getParameter("trickId"); - public static String getUser(JDA jda, long id) { - var user = jda.getUserById(id); - return user != null ? user.getName() : ""; - } + final var guildsList = GuildsData.get(manager.getAllGuilds(), plugin.getJDA()); - public static String getGuild(JDA jda, long id) { - var guild = jda.getGuildById(id); - return guild != null ? guild.getName() : ""; - } - private static long getLong(String value) { - try { - return Long.valueOf(value); - } catch (Exception ignored) { - return -1; - } - } + final MapBuilder mapBuilder = MapBuilder.of() + .put("headers", WebsiteConstants.headers); - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // Retrieve shared objects from the servlet context - var map = getObjectMap(); - var trickManager = map.get("trickManager", TrickManager.class); - var jda = map.get("jda", JDA.class); - - resp.setContentType("text/html"); - var guildId = req.getParameter("guildId"); - var trickId = req.getParameter("trickId"); - - if (guildId != null) { - try { - long id = Long.parseLong(guildId); - if (trickManager.getTricksForGuild(id).isEmpty()) { - processTemplate( - MapBuilder.of() - .self(this) - .put("title", "No Tricks Exist") - .put("message", "No Tricks Exist") - .put("url", "/trick") - .get(), - "general/redirect.ftl", - resp.getWriter() - ); - return; - } - } catch (Exception e) { - processTemplate( - MapBuilder.of() - .self(this) - .put("title", "Invalid GuildID") - .put("message", "Invalid GuildID") - .put("url", "/trick") - .get(), - "general/redirect.ftl", - resp.getWriter() + if (selectedTrickId != null) { + final var trick = manager.getTrickForGuildByName(Long.parseLong(selectedGuildId), selectedTrickId); + mapBuilder.put("trick", new TrickData(trick)); + mapBuilder.put("selectedGuildId", selectedGuildId); + WebUtil.processTemplate( + mapBuilder.get(), + "tricks.ftl", + resp.getWriter() + ); + return; + } else { + if (selectedGuildId != null) { + mapBuilder.put("selectedGuild", guildsList.stream().filter(g -> g.getId().equals(selectedGuildId)).findFirst().orElse(null)); + mapBuilder.put("selectedGuildId", selectedGuildId); + mapBuilder.put("tricks", + TrickInfoData.get(manager, Long.parseLong(selectedGuildId)) ); - return; } - } - var trick = guildId != null && trickId != null ? trickManager.getTrickForGuildByName(Long.parseLong(guildId), trickId) : null; - - if (trickId != null && guildId != null && trick == null) { - processTemplate( - MapBuilder.of() - .self(this) - .put("title", "Invalid Trick") - .put("message", "Invalid Trick") - .put("url", "/trick?guildId=%s".formatted(guildId)) - .get(), - "general/redirect.ftl", + mapBuilder.put( + "guilds", + guildsList + ); + + + WebUtil.processTemplate( + mapBuilder.get(), + "guilds.ftl", resp.getWriter() ); - return; } - processTemplate( - MapBuilder.of() - .self(this) - .put("guildId", guildId) - .put("trickId", trickId) - .dynamic(b -> { - - if (guildId == null) { - b.put( - "guilds", - trickManager.getAllGuilds() - .stream() - .map(id -> new Guild(id.toString(), getGuild(jda, id))) - .toList() - ); - } - - if (guildId != null) { - b.put("tricks", - trickManager.getTricksForGuild(Long.parseLong(guildId)) - .stream() - .sorted(Comparator.comparing(Trick::getTrickID)) - .toList() - ); - } - - if (trick != null) { - b.put("trick", trick); - b.put("ownerName", getUser(jda, trick.getOwnerID())); - b.put("guildName", getGuild(jda, Long.parseLong(guildId))); - b.put("lastUserName", getUser(jda, trick.getLastUserEdited())); - b.put("lastEdited", Date.from(Instant.ofEpochMilli(trick.getLastEdited())).toString()); - b.put("created", Date.from(Instant.ofEpochMilli(trick.getCreated())).toString()); - } - }) - .get(), - "tricks.ftl", - resp.getWriter() - ); - } - @Override - public boolean useDefaultStyles() { - return false; } } + + diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/data/GuildsData.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/GuildsData.java new file mode 100644 index 0000000..221d71b --- /dev/null +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/GuildsData.java @@ -0,0 +1,29 @@ +package org.mangorage.mangobotsite.website.servlet.data; + +import net.dv8tion.jda.api.JDA; + +import java.util.List; +import java.util.Objects; + +public record GuildsData( + String getId, + String getName, + String getIconUrl, + int getMemberCount +) { + public static List get(List guilds, JDA jda) { + return guilds.stream().map(guildId -> { + var guild = jda.getGuildById(guildId); + if (guild != null) { + return new GuildsData( + guild.getId(), + guild.getName(), + guild.getIconUrl(), + guild.getMemberCount() + ); + } else { + return null; + } + }).filter(Objects::nonNull).toList(); + } +} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/data/HeaderData.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/HeaderData.java new file mode 100644 index 0000000..8aca897 --- /dev/null +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/HeaderData.java @@ -0,0 +1,8 @@ +package org.mangorage.mangobotsite.website.servlet.data; + +public record HeaderData( + String page, + String text, + boolean active +) {} + diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/data/PluginData.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/PluginData.java new file mode 100644 index 0000000..c715fe5 --- /dev/null +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/PluginData.java @@ -0,0 +1,25 @@ +package org.mangorage.mangobotsite.website.servlet.data; + +public record PluginData( + String type, + String name, + String description, + String version +) { + // FreeMarker compatibility getters + public String getType() { + return type; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getVersion() { + return version; + } +} \ No newline at end of file diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/data/TrickData.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/TrickData.java new file mode 100644 index 0000000..7b6c2c1 --- /dev/null +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/TrickData.java @@ -0,0 +1,78 @@ +package org.mangorage.mangobotsite.website.servlet.data; + +import org.mangorage.mangobotplugin.commands.trick.Trick; + +import java.util.Objects; + +public final class TrickData { + private final Trick trick; + + public TrickData(Trick trick) { + this.trick = Objects.requireNonNull(trick, "trick"); + } + + public String getId() { + return Objects.requireNonNullElse(trick.getTrickID(), ""); + } + + public String getGuildId() { + return trick.getGuildID() + ""; + } + + public String getName() { + return Objects.requireNonNullElse(trick.getTrickID(), "Unknown Trick"); + } + + public String getType() { + return trick.getType() == null ? "N/A" : trick.getType().name(); + } + + public String getContent() { + switch (trick.getType()) { + case NORMAL -> { + return trick.getContent(); + } + case SCRIPT -> { + return trick.getScript(); + } + case ALIAS -> { + return trick.getAliasTarget(); + } + default -> { + return "N/A"; + } + } + } + + public boolean isLocked() { + return trick.isLocked(); + } + + public boolean isSuppressEmbeds() { + return trick.isSuppressed(); + } + + public String getOwnerName() { + return "Not Implemented"; + } + + public String getOwnerId() { + return trick.getOwnerID() + ""; + } + + public long getUsageCount() { + return Math.max(0, trick.getTimesUsed()); + } + + public String getCreatedDate() { + return trick.getCreated() + ""; + } + + public String getLastEditedDate() { + return trick.getLastEdited() + ""; + } + + public String getLastModifiedByName() { + return trick.getLastUserEdited() + ""; + } +} diff --git a/src/main/java/org/mangorage/mangobotsite/website/servlet/data/TrickInfoData.java b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/TrickInfoData.java new file mode 100644 index 0000000..a451bc9 --- /dev/null +++ b/src/main/java/org/mangorage/mangobotsite/website/servlet/data/TrickInfoData.java @@ -0,0 +1,30 @@ +package org.mangorage.mangobotsite.website.servlet.data; + +import org.mangorage.mangobotplugin.commands.trick.Trick; +import org.mangorage.mangobotplugin.commands.trick.TrickManager; + +public final class TrickInfoData { + private final Trick trick; + + public TrickInfoData(Trick trick) { + this.trick = trick; + } + + public static Object get(TrickManager manager, long guildId) { + return manager.getTricksForGuild(guildId).stream() + .map(TrickInfoData::new) + .toList(); + } + + public String getId() { + return trick.getTrickID(); + } + + public String getName() { + return getId(); + } + + public String getType() { + return trick.getType() == null ? "Invalid" : trick.getType().name(); + } +} diff --git a/src/main/resources/templates/chat.ftl b/src/main/resources/templates/chat.ftl deleted file mode 100644 index c17a042..0000000 --- a/src/main/resources/templates/chat.ftl +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Chat - - - -
-

Chat Room

-
- <#list messages as message> -

${message}

- -
-
- <#if !hasUsername> - - <#else> - - - - -
-
- - diff --git a/src/main/resources/templates/file.ftl b/src/main/resources/templates/file.ftl deleted file mode 100644 index eaff4d9..0000000 --- a/src/main/resources/templates/file.ftl +++ /dev/null @@ -1,92 +0,0 @@ - - - - - Log Viewer - - - -
-
-
📄 ${name?html}
- Raw -
-
- <#list lines as line> -
-
${line?index + 1}
-
${line?html}
-
- -
-
- - diff --git a/src/main/resources/templates/file/files.ftl b/src/main/resources/templates/file/files.ftl deleted file mode 100644 index eab2d2c..0000000 --- a/src/main/resources/templates/file/files.ftl +++ /dev/null @@ -1,50 +0,0 @@ - - - - File Management - - - - - -
-

MangoBot

- -
- -
-

File Management

-
- - -
- - <#if isOwner> - - - - <#list config.targetList() as targetFile> -
-

- ${targetFile.name()} -

- Download - - <#if isOwner> - Delete - -
- -
- - \ No newline at end of file diff --git a/src/main/resources/templates/file/upload.ftl b/src/main/resources/templates/file/upload.ftl deleted file mode 100644 index 5b9e691..0000000 --- a/src/main/resources/templates/file/upload.ftl +++ /dev/null @@ -1,35 +0,0 @@ - - - - File Upload Page - - - - - - -
-

MangoBot

- -
- -

Upload a File

- -
-
-

Drag and drop a file here or click to select

- -
-
- -
- - - - diff --git a/src/main/resources/templates/general/embed.ftl b/src/main/resources/templates/general/embed.ftl deleted file mode 100644 index c90ece7..0000000 --- a/src/main/resources/templates/general/embed.ftl +++ /dev/null @@ -1,12 +0,0 @@ - -${title} - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/templates/general/error.ftl b/src/main/resources/templates/general/error.ftl deleted file mode 100644 index c44c115..0000000 --- a/src/main/resources/templates/general/error.ftl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Status - - -

${code}

-

${message}

- - \ No newline at end of file diff --git a/src/main/resources/templates/general/redirect.ftl b/src/main/resources/templates/general/redirect.ftl deleted file mode 100644 index 4834096..0000000 --- a/src/main/resources/templates/general/redirect.ftl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - ${title} - - -

${message}

-

Redirecting you back in 5 seconds...

- - - diff --git a/src/main/resources/templates/guilds.ftl b/src/main/resources/templates/guilds.ftl new file mode 100644 index 0000000..0ba6593 --- /dev/null +++ b/src/main/resources/templates/guilds.ftl @@ -0,0 +1,181 @@ + + + + + + Guilds - MangoBot + + + + + + + + + + + + + + + +
+
+
+
+ Community +

Server Guilds

+

Explore tricks and commands from our community servers

+
+
+
+ +
+
+
+ +
+ + + + +
+
+ + <#if selectedGuild??> +
+
+
+ <#if selectedGuild.getIconUrl()??> + ${selectedGuild.getName()} + <#else> + + + + + + +
+
+

${selectedGuild.getName()!"Unknown Guild"}

+

${selectedGuild.getMemberCount()!0} members

+
+
+ +
+

+ + + + Available Tricks (${tricks?size}) +

+
+ <#if tricks?? && (tricks?size > 0)> + <#list tricks as trick> + + ${trick.getName()!"unknown"} + + ${trick.getType()!"NORMAL"} + + + + + + + <#else> +
+

No tricks available for this guild

+
+ +
+
+
+ <#else> +
+
+ + + + + + +
+

Select a Guild

+

Choose a server from the dropdown above to view available tricks and commands

+
+ +
+
+ +
+
+
+

Want Your Server Featured?

+

Join our Discord community and add MangoBot to your server to get started

+ + + + + Join Discord + +
+
+
+
+ +
+
+ +
+
+ + + + diff --git a/src/main/resources/templates/home.ftl b/src/main/resources/templates/home.ftl index 100882c..3c30adf 100644 --- a/src/main/resources/templates/home.ftl +++ b/src/main/resources/templates/home.ftl @@ -3,45 +3,246 @@ - MangoBot Main Page - - - - + MangoBot - Discord Bot + + + + + + - - + + -
-

MangoBot

-
-
-
-

Welcome to Our Website

-

Explore our content and learn more about us.

-
-
-

About Us

-

MangoBot is a versatile Discord bot providing useful utilities, owned and developed by MangoRage.

-
-
-

Contact Us

-

Contact MangoRage at - - discord + + + + + Join Discord -

-
-
+ + + + +
+
+
+
+
+
+
+
Discord Bot
+

+ Meet MangoBot +

+

+ A versatile Discord bot providing powerful utilities, owned and developed by MangoRage. + Enhance your server with smart features and seamless integrations. +

+ +
+
+ ${pluginCount!0}+ + Plugins +
+
+
+ ${guildCount!0}+ + Guilds +
+
+
+ 24/7 + Uptime +
+
+
+
+ +
+
+
+ Features +

Everything You Need

+

Powerful utilities designed to make your Discord server better

+
+
+
+
+ + + +
+

Modular Plugins

+

Extend functionality with a powerful plugin system. Add only what you need.

+
+
+
+ + + +
+

GitHub Integration

+

Connect your repositories and get notifications, issue tracking, and more.

+
+
+
+ + + +
+

Custom Tricks

+

Create and manage custom commands and tricks for your server members.

+
+
+
+ + + +
+

File Upload

+

Upload and share files seamlessly within your Discord community.

+
+
+
+ + + + +
+

24/7 Uptime

+

Always online and ready to serve your community around the clock.

+
+
+
+ + + + + +
+

Multi-Guild Support

+

Manage multiple servers with ease. One bot, endless possibilities.

+
+
+
+
+ +
+
+
+ Architecture +

Installed Plugins

+

Built on a robust modular architecture

+
+
+ <#list plugins as plugin> +
+
+
core<#elseif plugin.getType() == "JDA">jda<#elseif plugin.getType() == "Mixin">mixin<#else>addon"> + <#if plugin.getType() == "Core"> + + + + + <#elseif plugin.getType() == "JDA"> + + + + <#elseif plugin.getType() == "Mixin"> + + + + + <#else> + + + + + + +
+ ${plugin.getType()!"Unknown"} +
+

${plugin.getName()!"Unknown"}

+

${plugin.getDescription()!"Plugin for MangoBot"}

+ v${plugin.getVersion()!"0.0.0"} +
+ +
+
+
+ +
+
+
+
+ Get Started +

Ready to Enhance Your Server?

+

Join our Discord community to get MangoBot, receive support, and stay updated with the latest features.

+ + + + + Join Our Discord + +
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
- \ No newline at end of file + diff --git a/src/main/resources/templates/info.ftl b/src/main/resources/templates/info.ftl deleted file mode 100644 index be64b2d..0000000 --- a/src/main/resources/templates/info.ftl +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - MangoBot Plugins - - - - - - - - -
-

MangoBot

- -
- -
-

Installed Plugins:

- <#list plugins as plugin> -
-

Plugin Details:

-
-

Id: ${plugin.getId()!"Unknown"}

-

Name: ${plugin.getName()!"Unknown"}

-

Type: ${plugin.getType()!"Unknown"}

-

Version: ${plugin.getVersion()!"Unknown"}

-
-
- -
- - diff --git a/src/main/resources/templates/stream.ftl b/src/main/resources/templates/stream.ftl deleted file mode 100644 index 47df718..0000000 --- a/src/main/resources/templates/stream.ftl +++ /dev/null @@ -1,19 +0,0 @@ - - - - - ${title!"Untitled Video"} - - - -

${title!"Untitled Video"}

- - - \ No newline at end of file diff --git a/src/main/resources/templates/tricks.ftl b/src/main/resources/templates/tricks.ftl index b0a5219..3ca7141 100644 --- a/src/main/resources/templates/tricks.ftl +++ b/src/main/resources/templates/tricks.ftl @@ -2,152 +2,207 @@ - Mangobot Tricks - - - - + + ${trick.getName()!"Trick"} - MangoBot + + + + + + + + + + + - - -
-

MangoBot

- -
- -
- - <#if guildId??> -
-

Tricks

- <#if tricks?? && tricks?size gt 0> - - <#else> -

No tricks found. Maybe don't run a bot on a ghost town server?

- + + -
- <#if trickId?? && trick??> -

Trick Details

-
-

ID: ${trick.getTrickID()}

-

Type: ${trick.getType()}

-

Guild: ${trick.getGuildID()?c} ${guildName}

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

${trick.getName()!"Unknown Trick"}

+
+ ${trick.getType()!"NORMAL"} + <#if trick.isLocked()!false> + + + + + + Locked + + +
+
+
+
- <#switch trick.getType()> - <#case "ALIAS"> -

Alias Target

-
${trick.getAliasTarget()}
- <#break> - <#case "NORMAL"> -

Content

-
- +
+

+ + + + Message Content +

+
+
${trick.getContent()!"No content"}
+
- <#break> - <#case "SCRIPT"> -

Script

-
- +
+ + +
+
+
+
+ +
+
+
- <#else> - -

Select Guild

-
- - - -
- - -
+ diff --git a/src/main/resources/webpage-internal/css/ChatServlet.css b/src/main/resources/webpage-internal/css/ChatServlet.css deleted file mode 100644 index 4d4a068..0000000 --- a/src/main/resources/webpage-internal/css/ChatServlet.css +++ /dev/null @@ -1,36 +0,0 @@ -body { - font-family: Arial, sans-serif; - background-color: #f4f4f4; - text-align: center; -} - -.chat-container { - width: 50%; - margin: 20px auto; - padding: 20px; - background: white; - border-radius: 8px; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); -} - -.messages { - border: 1px solid #ddd; - padding: 10px; - height: 200px; - overflow-y: auto; - background: #fafafa; -} - -input { - width: 80%; - padding: 8px; - margin: 10px 0; -} - -button { - padding: 8px 12px; - background: blue; - color: white; - border: none; - cursor: pointer; -} diff --git a/src/main/resources/webpage-internal/css/FileServlet.css b/src/main/resources/webpage-internal/css/FileServlet.css deleted file mode 100644 index 4896a8f..0000000 --- a/src/main/resources/webpage-internal/css/FileServlet.css +++ /dev/null @@ -1,81 +0,0 @@ -/* General container styling */ -.container { - max-width: 800px; - margin: 0 auto; - padding: 20px; - font-family: Arial, sans-serif; -} - -/* Title styling */ -.title { - text-align: center; - color: #333; -} - -/* URL section styling */ -.url-section { - margin-bottom: 20px; - text-align: center; -} - -.url-section input[type="text"] { - width: 80%; - padding: 10px; - font-size: 14px; - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 10px; -} - -.copy-button { - display: inline-block; - padding: 10px 20px; - font-size: 14px; - color: #fff; - background-color: #007bff; - border: none; - border-radius: 4px; - cursor: pointer; - text-decoration: none; -} - -.copy-button:hover { - background-color: #0056b3; -} - -/* Owner actions styling */ -.owner-actions { - text-align: center; - margin-bottom: 20px; -} - -.owner-actions a { - color: #d9534f; - text-decoration: none; - font-weight: bold; -} - -.owner-actions a:hover { - text-decoration: underline; -} - -/* Target section styling */ -.target-section { - border-top: 1px solid #eee; - padding: 10px 0; -} - -.target-section h4 { - margin: 0; - font-size: 16px; -} - -.target-section a { - margin-right: 15px; - color: #007bff; - text-decoration: none; -} - -.target-section a:hover { - text-decoration: underline; -} diff --git a/src/main/resources/webpage-internal/css/FileUploadServlet.css b/src/main/resources/webpage-internal/css/FileUploadServlet.css deleted file mode 100644 index a9ac78c..0000000 --- a/src/main/resources/webpage-internal/css/FileUploadServlet.css +++ /dev/null @@ -1,82 +0,0 @@ -/* FileUploadServlet.css */ - -/* 1. Basic reset and body styling */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: Arial, sans-serif; - background-color: #f9f9f9; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; /* Make the page at least full viewport height */ -} - -/* 2. Heading styling */ -h1 { - font-size: 2rem; - margin-bottom: 1rem; - color: #333; -} - -/* 3. Form styling */ -form { - display: flex; - flex-direction: column; - align-items: center; -} - -/* 4. Drop area styling */ -#drop-area { - border: 3px dashed #ccc; - border-radius: 10px; - width: 550px; - height: 150px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background-color: #fff; - cursor: pointer; - transition: border-color 0.3s ease; - margin-bottom: 1rem; -} - -#drop-area:hover { - border-color: #999; -} - -#drop-area p { - font-size: 1rem; - color: #666; - margin-bottom: 0.5rem; -} - -/* 5. File input styling */ -/* Hide the native file input; the drop area is used as a label. */ -#file-input { - opacity: 0; - position: absolute; - pointer-events: none; -} - -/* 6. Upload button styling */ -input[type="submit"] { - padding: 0.75rem 1.5rem; - background-color: #4caf50; - color: #fff; - border: none; - border-radius: 5px; - font-size: 1rem; - cursor: pointer; - transition: background-color 0.3s ease; -} - -input[type="submit"]:hover { - background-color: #43a047; -} \ No newline at end of file diff --git a/src/main/resources/webpage-internal/css/InfoServlet.css b/src/main/resources/webpage-internal/css/InfoServlet.css deleted file mode 100644 index 5df34c6..0000000 --- a/src/main/resources/webpage-internal/css/InfoServlet.css +++ /dev/null @@ -1,110 +0,0 @@ -/* General body styling */ -body { - font-family: 'Helvetica Neue', Arial, sans-serif; - background-color: black; - margin: 0; - padding: 0; - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - color: #333; -} - -/* Main container for the page */ -.container { - background-color: gray; - border-radius: 12px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - padding: 30px; - width: 90%; - max-width: 700px; -} - -/* Page Title */ -.page-title { - text-align: center; - color: white; - font-size: 32px; - margin-bottom: 30px; - font-weight: 600; -} - -/* Plugin item styling */ -.plugin-item { - background-color: #ecf0f1; - border-left: 5px solid #3498db; - margin-bottom: 20px; - padding: 20px; - transition: transform 0.3s ease, box-shadow 0.3s ease; - border-radius: 8px; -} - -.plugin-item:hover { - transform: translateY(-5px); - box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); -} - -/* Plugin details styling */ -.plugin-details { - padding-left: 20px; -} - -/* Plugin ID styling */ -.plugin-id { - font-weight: bold; - color: #3498db; - font-size: 16px; -} - -/* Plugin name styling */ -.plugin-name { - font-weight: 600; - color: #2c3e50; - font-size: 18px; -} - -/* Plugin type styling */ -.plugin-type { - font-style: italic; - color: #7f8c8d; - font-size: 16px; -} - -/* Plugin version styling */ -.plugin-version { - color: #2ecc71; - font-size: 16px; -} - -/* Paragraph styling for each field */ -.plugin-details p { - margin: 8px 0; -} - -/* Styling for the container of plugin items */ -.plugin-item h2 { - margin: 0; - font-size: 18px; - line-height: 1.6; -} - -/* Responsive Design */ -@media screen and (max-width: 768px) { - .container { - padding: 20px; - width: 95%; - } - - .plugin-item { - padding: 15px; - } - - .plugin-id, .plugin-name, .plugin-type, .plugin-version { - font-size: 14px; - } - - .page-title { - font-size: 28px; - } -} diff --git a/src/main/resources/webpage-internal/css/TricksServlet.css b/src/main/resources/webpage-internal/css/TricksServlet.css deleted file mode 100644 index 7db5135..0000000 --- a/src/main/resources/webpage-internal/css/TricksServlet.css +++ /dev/null @@ -1,72 +0,0 @@ -/* styles.css */ -body.page-body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: #f7f9fc; - color: #333; - margin: 0; - padding: 0; -} - -.container { - max-width: 800px; - margin: 3rem auto; - background: #fff; - padding: 2rem; - border-radius: 8px; - box-shadow: 0 4px 10px rgba(0,0,0,0.1); -} - -.title { - text-align: center; - margin-bottom: 1.5rem; - color: #007bff; -} - -.form-group { - margin-bottom: 1.5rem; -} - -.label { - display: block; - margin-bottom: 0.5rem; - font-weight: bold; -} - -.select-input, .btn { - font-size: 1rem; - padding: 0.5rem; - border-radius: 4px; -} - -.select-input { - width: 100%; - border: 1px solid #ccc; - margin-bottom: 1rem; -} - -.btn { - cursor: pointer; - background-color: #007bff; - color: #fff; - border: none; - transition: background-color 0.3s ease; -} - -.btn:hover { - background-color: #0056b3; -} - -.trick-info p, -.trick-meta p { - margin: 0.5rem 0; -} - -.section-title { - margin-top: 1.5rem; - color: #343a40; -} - -.error { - color: red; - text-align: center; -} diff --git a/src/main/resources/webpage-internal/css/header.css b/src/main/resources/webpage-internal/css/header.css deleted file mode 100644 index 95c4cdb..0000000 --- a/src/main/resources/webpage-internal/css/header.css +++ /dev/null @@ -1,38 +0,0 @@ -/* Header styling */ -header { - background-color: #333; - padding: 1rem; - text-align: center; - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 1000; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5); -} - -header h1 { - color: #fff; - margin-bottom: 0.5rem; -} - -header nav ul { - list-style: none; - margin: 0; - padding: 0; -} - -header nav ul li { - display: inline-block; - margin: 0 1rem; -} - -header nav ul li a { - color: #fff; - text-decoration: none; - font-weight: bold; -} - -header nav ul li a:hover { - text-decoration: underline; -} \ No newline at end of file diff --git a/src/main/resources/webpage-internal/css/main.css b/src/main/resources/webpage-internal/css/main.css new file mode 100644 index 0000000..4f3ab95 --- /dev/null +++ b/src/main/resources/webpage-internal/css/main.css @@ -0,0 +1,1241 @@ +:root { + --color-primary: #ff9500; + --color-primary-light: #ffb347; + --color-primary-dark: #e68600; + --color-secondary: #5865f2; + --color-secondary-light: #7289da; + + --color-bg: #0a0a0b; + --color-bg-secondary: #111113; + --color-bg-tertiary: #18181b; + --color-bg-card: #1a1a1d; + --color-bg-elevated: #222225; + + --color-text: #ffffff; + --color-text-secondary: #a1a1aa; + --color-text-muted: #71717a; + + --color-border: #27272a; + --color-border-light: #3f3f46; + + --gradient-primary: linear-gradient(135deg, var(--color-primary) 0%, #ff6b00 100%); + --gradient-hero: radial-gradient(ellipse at 50% 0%, rgba(255, 149, 0, 0.15) 0%, transparent 50%); + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5); + --shadow-glow: 0 0 40px rgba(255, 149, 0, 0.3); + + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 16px; + --radius-xl: 24px; + --radius-full: 9999px; + + --transition-fast: 150ms ease; + --transition-base: 250ms ease; + --transition-slow: 350ms ease; + + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; +} + +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: var(--font-sans); + background-color: var(--color-bg); + color: var(--color-text); + line-height: 1.6; + overflow-x: hidden; +} + +a { + text-decoration: none; + color: inherit; +} + +ul { + list-style: none; +} + +img { + max-width: 100%; + height: auto; +} + +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; +} + +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: rgba(10, 10, 11, 0.8); + backdrop-filter: blur(20px); + border-bottom: 1px solid var(--color-border); +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 0 24px; + height: 72px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.nav-logo { + display: flex; + align-items: center; + gap: 10px; + font-weight: 700; + font-size: 1.25rem; +} + +.logo-icon { + font-size: 1.5rem; +} + +.logo-text { + background: var(--gradient-primary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.nav-menu { + display: flex; + align-items: center; + gap: 8px; +} + +.nav-link { + padding: 8px 16px; + font-size: 0.9rem; + font-weight: 500; + color: var(--color-text-secondary); + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.nav-link:hover { + color: var(--color-text); + background: var(--color-bg-tertiary); +} + +.nav-link.active { + color: var(--color-primary); + background: rgba(255, 149, 0, 0.1); +} + +.nav-cta { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + font-size: 0.9rem; + font-weight: 600; + color: white; + background: var(--color-secondary); + border-radius: var(--radius-full); + transition: all var(--transition-base); +} + +.nav-cta:hover { + background: var(--color-secondary-light); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(88, 101, 242, 0.4); +} + +.nav-toggle { + display: none; + flex-direction: column; + gap: 5px; + padding: 8px; + background: none; + border: none; + cursor: pointer; +} + +.nav-toggle span { + display: block; + width: 24px; + height: 2px; + background: var(--color-text); + border-radius: 2px; + transition: all var(--transition-fast); +} + +.hero { + position: relative; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 120px 24px 80px; + overflow: hidden; +} + +.hero-bg { + position: absolute; + inset: 0; + z-index: 0; +} + +.hero-gradient { + position: absolute; + inset: 0; + background: var(--gradient-hero); +} + +.hero-particles { + position: absolute; + inset: 0; + opacity: 0.5; +} + +.hero-content { + position: relative; + z-index: 1; + text-align: center; + max-width: 800px; +} + +.hero-badge { + display: inline-block; + padding: 8px 16px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--color-primary); + background: rgba(255, 149, 0, 0.1); + border: 1px solid rgba(255, 149, 0, 0.2); + border-radius: var(--radius-full); + margin-bottom: 24px; +} + +.hero-title { + font-size: clamp(2.5rem, 8vw, 4.5rem); + font-weight: 800; + line-height: 1.1; + margin-bottom: 24px; + letter-spacing: -0.02em; +} + +.gradient-text { + background: var(--gradient-primary); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtitle { + font-size: 1.25rem; + color: var(--color-text-secondary); + max-width: 600px; + margin: 0 auto 40px; + line-height: 1.7; +} + +.hero-buttons { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 60px; +} + +.btn { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 14px 28px; + font-size: 1rem; + font-weight: 600; + border-radius: var(--radius-full); + transition: all var(--transition-base); + cursor: pointer; + border: none; +} + +.btn-primary { + color: #000; + background: var(--gradient-primary); + box-shadow: var(--shadow-glow); +} + +.btn-primary:hover { + transform: translateY(-3px); + box-shadow: 0 0 60px rgba(255, 149, 0, 0.4); +} + +.btn-secondary { + color: var(--color-text); + background: var(--color-bg-tertiary); + border: 1px solid var(--color-border); +} + +.btn-secondary:hover { + background: var(--color-bg-elevated); + border-color: var(--color-border-light); + transform: translateY(-2px); +} + +.btn-large { + padding: 18px 36px; + font-size: 1.1rem; +} + +.hero-stats { + display: flex; + align-items: center; + justify-content: center; + gap: 40px; + flex-wrap: wrap; +} + +.stat { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; +} + +.stat-number { + font-size: 2rem; + font-weight: 800; + color: var(--color-text); +} + +.stat-label { + font-size: 0.9rem; + color: var(--color-text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.stat-divider { + width: 1px; + height: 40px; + background: var(--color-border); +} + +section { + padding: 100px 0; +} + +.section-header { + text-align: center; + max-width: 600px; + margin: 0 auto 60px; +} + +.section-badge { + display: inline-block; + padding: 6px 14px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--color-primary); + background: rgba(255, 149, 0, 0.1); + border-radius: var(--radius-full); + margin-bottom: 16px; +} + +.section-title { + font-size: clamp(2rem, 5vw, 3rem); + font-weight: 800; + margin-bottom: 16px; + letter-spacing: -0.02em; +} + +.section-subtitle { + font-size: 1.1rem; + color: var(--color-text-secondary); + line-height: 1.7; +} + +.features { + background: var(--color-bg-secondary); +} + +.features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 24px; +} + +.feature-card { + padding: 32px; + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + transition: all var(--transition-base); +} + +.feature-card:hover { + transform: translateY(-4px); + border-color: var(--color-border-light); + box-shadow: var(--shadow-lg); +} + +.feature-icon { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 149, 0, 0.1); + border-radius: var(--radius-md); + margin-bottom: 20px; + color: var(--color-primary); +} + +.feature-card h3 { + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 12px; +} + +.feature-card p { + color: var(--color-text-secondary); + line-height: 1.6; +} + +.plugins-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; +} + +.plugin-card { + padding: 24px; + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + transition: all var(--transition-base); +} + +.plugin-card:hover { + transform: translateY(-2px); + border-color: var(--color-border-light); +} + +.plugin-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.plugin-icon { + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); +} + +.plugin-icon.core { + background: rgba(255, 149, 0, 0.15); + color: var(--color-primary); +} + +.plugin-icon.jda { + background: rgba(88, 101, 242, 0.15); + color: var(--color-secondary); +} + +.plugin-icon.addon { + background: rgba(16, 185, 129, 0.15); + color: #10b981; +} + +.plugin-icon.mixin { + background: rgba(168, 85, 247, 0.15); + color: #a855f7; +} + +.plugin-type { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 4px 10px; + border-radius: var(--radius-full); + background: var(--color-bg-elevated); + color: var(--color-text-muted); +} + +.plugin-card h3 { + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 8px; +} + +.plugin-card p { + font-size: 0.9rem; + color: var(--color-text-secondary); + margin-bottom: 16px; +} + +.plugin-version { + font-size: 0.8rem; + font-weight: 500; + color: var(--color-text-muted); + font-family: 'SF Mono', 'Fira Code', monospace; +} + +.contact { + background: var(--color-bg-secondary); +} + +.contact-card { + position: relative; + padding: 60px; + background: var(--gradient-primary); + border-radius: var(--radius-xl); + overflow: hidden; +} + +.contact-content { + position: relative; + z-index: 1; + text-align: center; + max-width: 500px; + margin: 0 auto; +} + +.contact-content h2 { + font-size: 2rem; + font-weight: 800; + color: #000; + margin-bottom: 16px; +} + +.contact-content p { + font-size: 1.1rem; + color: rgba(0, 0, 0, 0.7); + margin-bottom: 32px; + line-height: 1.6; +} + +.contact-content .btn-primary { + background: #000; + color: #fff; + box-shadow: var(--shadow-lg); +} + +.contact-content .btn-primary:hover { + background: #1a1a1a; +} + +.contact-decoration { + position: absolute; + inset: 0; + pointer-events: none; +} + +.decoration-circle { + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.1); +} + +.decoration-circle:nth-child(1) { + width: 300px; + height: 300px; + top: -100px; + right: -100px; +} + +.decoration-circle:nth-child(2) { + width: 200px; + height: 200px; + bottom: -50px; + left: -50px; +} + +.decoration-circle:nth-child(3) { + width: 100px; + height: 100px; + top: 50%; + left: 20%; +} + +.guilds-hero { + padding-top: 140px; + padding-bottom: 40px; + background: var(--gradient-hero); +} + +.guilds-content { + padding: 40px 0 80px; +} + +.guild-selector { + max-width: 400px; + margin: 0 auto 40px; +} + +.guild-label { + display: block; + font-size: 0.9rem; + font-weight: 600; + color: var(--color-text-secondary); + margin-bottom: 8px; +} + +.select-wrapper { + position: relative; +} + +.guild-select { + width: 100%; + padding: 16px 48px 16px 20px; + font-family: var(--font-sans); + font-size: 1rem; + color: var(--color-text); + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + cursor: pointer; + appearance: none; + transition: all var(--transition-fast); +} + +.guild-select:hover { + border-color: var(--color-border-light); +} + +.guild-select:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(255, 149, 0, 0.1); +} + +.guild-select option { + background: var(--color-bg-card); + color: var(--color-text); + padding: 12px; +} + +.select-arrow { + position: absolute; + right: 16px; + top: 50%; + transform: translateY(-50%); + color: var(--color-text-muted); + pointer-events: none; +} + +.guild-info { + max-width: 800px; + margin: 0 auto; + animation: fadeIn 0.3s ease; +} + +.guild-info.hidden { + display: none; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.guild-header { + display: flex; + align-items: center; + gap: 20px; + padding: 24px; + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + margin-bottom: 24px; +} + +.guild-avatar { + width: 72px; + height: 72px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 149, 0, 0.1); + border-radius: var(--radius-lg); + color: var(--color-primary); +} + +.guild-details h2 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 4px; +} + +.guild-details p { + color: var(--color-text-secondary); +} + +.tricks-section { + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 24px; +} + +.tricks-title { + display: flex; + align-items: center; + gap: 10px; + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 20px; + color: var(--color-text); +} + +.tricks-title svg { + color: var(--color-primary); +} + +.tricks-list { + display: grid; + gap: 12px; +} + +.trick-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + background: var(--color-bg-tertiary); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + transition: all var(--transition-fast); +} + +.trick-item:hover { + border-color: var(--color-border-light); + background: var(--color-bg-elevated); +} + +.trick-link { + text-decoration: none; + cursor: pointer; +} + +.trick-link:hover { + border-color: var(--color-primary); +} + +.trick-link:hover .trick-name { + color: var(--color-primary); +} + +.trick-name { + font-weight: 600; + color: var(--color-text); + transition: color var(--transition-fast); +} + +.trick-usage { + font-size: 0.85rem; + font-family: 'SF Mono', 'Fira Code', monospace; + color: var(--color-primary); + background: rgba(255, 149, 0, 0.1); + padding: 4px 10px; + border-radius: var(--radius-sm); +} + +.trick-meta { + display: flex; + align-items: center; + gap: 8px; +} + +.trick-type { + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 4px 8px; + border-radius: var(--radius-full); + background: rgba(88, 101, 242, 0.15); + color: var(--color-secondary); +} + +.trick-meta svg { + color: var(--color-text-muted); + transition: transform var(--transition-fast); +} + +.trick-link:hover .trick-meta svg { + transform: translateX(4px); + color: var(--color-primary); +} + +.empty-state { + text-align: center; + padding: 80px 24px; +} + +.empty-icon { + color: var(--color-text-muted); + margin-bottom: 24px; + opacity: 0.5; +} + +.empty-state h3 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 12px; +} + +.empty-state p { + color: var(--color-text-secondary); + max-width: 400px; + margin: 0 auto; +} + +.guilds-cta { + background: var(--color-bg-secondary); + padding: 80px 0; +} + +.cta-card { + text-align: center; + padding: 48px; + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-xl); +} + +.cta-card h2 { + font-size: 1.75rem; + font-weight: 700; + margin-bottom: 12px; +} + +.cta-card p { + color: var(--color-text-secondary); + margin-bottom: 24px; +} + +.footer { + background: var(--color-bg-secondary); + border-top: 1px solid var(--color-border); + padding: 40px 0; +} + +.footer-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + text-align: center; +} + +.footer-brand { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 1.1rem; +} + +.footer-text { + color: var(--color-text-muted); + font-size: 0.9rem; +} + +.footer-links { + display: flex; + gap: 24px; +} + +.footer-links a { + font-size: 0.9rem; + color: var(--color-text-secondary); + transition: color var(--transition-fast); +} + +.footer-links a:hover { + color: var(--color-primary); +} + +@media (max-width: 768px) { + .nav-menu { + position: fixed; + top: 72px; + left: 0; + right: 0; + background: var(--color-bg); + border-bottom: 1px solid var(--color-border); + flex-direction: column; + padding: 16px; + gap: 4px; + transform: translateY(-100%); + opacity: 0; + visibility: hidden; + transition: all var(--transition-base); + } + + .nav-menu.active { + transform: translateY(0); + opacity: 1; + visibility: visible; + } + + .nav-link { + width: 100%; + padding: 12px 16px; + } + + .nav-cta { + display: none; + } + + .nav-toggle { + display: flex; + } + + .nav-toggle.active span:nth-child(1) { + transform: rotate(45deg) translate(5px, 5px); + } + + .nav-toggle.active span:nth-child(2) { + opacity: 0; + } + + .nav-toggle.active span:nth-child(3) { + transform: rotate(-45deg) translate(5px, -5px); + } + + .hero { + padding: 100px 20px 60px; + } + + .hero-buttons { + flex-direction: column; + } + + .btn { + width: 100%; + justify-content: center; + } + + .hero-stats { + gap: 24px; + } + + .stat-divider { + display: none; + } + + section { + padding: 60px 0; + } + + .features-grid, + .plugins-grid { + grid-template-columns: 1fr; + } + + .contact-card { + padding: 40px 24px; + } + + .guild-header { + flex-direction: column; + text-align: center; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-10px); + } +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +html { + scroll-padding-top: 80px; +} + +.trick-hero { + padding-top: 100px; + padding-bottom: 20px; + background: var(--gradient-hero); +} + +.breadcrumb { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.9rem; + color: var(--color-text-muted); +} + +.breadcrumb a { + color: var(--color-text-secondary); + transition: color var(--transition-fast); +} + +.breadcrumb a:hover { + color: var(--color-primary); +} + +.breadcrumb span { + color: var(--color-text); +} + +.breadcrumb svg { + opacity: 0.5; +} + +.trick-content { + padding: 40px 0 80px; +} + +.trick-layout { + display: grid; + grid-template-columns: 1fr 320px; + gap: 24px; + align-items: start; +} + +.trick-main { + display: flex; + flex-direction: column; + gap: 24px; +} + +.trick-header-card { + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 24px; +} + +.trick-title-row { + display: flex; + align-items: center; + gap: 16px; +} + +.trick-icon { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 149, 0, 0.1); + border-radius: var(--radius-md); + color: var(--color-primary); +} + +.trick-title-info h1 { + font-size: 1.75rem; + font-weight: 700; + margin-bottom: 8px; +} + +.trick-badges { + display: flex; + gap: 8px; +} + +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + border-radius: var(--radius-full); +} + +.badge-type { + background: rgba(88, 101, 242, 0.15); + color: var(--color-secondary); +} + +.badge-locked { + background: rgba(239, 68, 68, 0.15); + color: #ef4444; +} + +.trick-message-card { + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 24px; +} + +.trick-message-card h2 { + display: flex; + align-items: center; + gap: 10px; + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 16px; + color: var(--color-text); +} + +.trick-message-card h2 svg { + color: var(--color-primary); +} + +.message-content { + background: var(--color-bg-tertiary); + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + padding: 16px; + overflow-x: auto; +} + +.message-content pre { + margin: 0; +} + +.message-content code { + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; + font-size: 0.9rem; + color: var(--color-text); + white-space: pre-wrap; + word-break: break-word; +} + +.trick-sidebar { + display: flex; + flex-direction: column; + gap: 16px; + position: sticky; + top: 96px; +} + +.sidebar-card { + background: var(--color-bg-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + padding: 20px; +} + +.sidebar-card h3 { + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-text-muted); + margin-bottom: 16px; +} + +.detail-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.detail-item { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.detail-label { + font-size: 0.9rem; + color: var(--color-text-secondary); +} + +.detail-value { + font-size: 0.9rem; + font-weight: 500; + color: var(--color-text); + text-align: right; +} + +.detail-mono { + font-family: 'SF Mono', 'Fira Code', monospace; + font-size: 0.8rem; + color: var(--color-text-muted); +} + +.status-yes { + color: #10b981; +} + +.status-no { + color: var(--color-text-muted); +} + +.btn-full { + width: 100%; + justify-content: center; +} + +@media (max-width: 900px) { + .trick-layout { + grid-template-columns: 1fr; + } + + .trick-sidebar { + position: static; + } +} + +@media (max-width: 768px) { + .trick-title-row { + flex-direction: column; + text-align: center; + } + + .trick-badges { + justify-content: center; + } + + .breadcrumb { + flex-wrap: wrap; + justify-content: center; + } +} diff --git a/src/main/resources/webpage-internal/css/root.css b/src/main/resources/webpage-internal/css/root.css deleted file mode 100644 index 517d663..0000000 --- a/src/main/resources/webpage-internal/css/root.css +++ /dev/null @@ -1,28 +0,0 @@ -/* 1) Basic Reset and Body */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: Arial, sans-serif; - background-color: #f4f4f4; - color: #333; -} - -/* 3) Main Content */ -main { - padding: 2rem 1rem; /* Some space around sections */ - display: flex; - flex-direction: column; - align-items: center; /* Center sections horizontally as a group */ -} - -/* 4) Sections */ -section { - max-width: 600px; /* Limit width so text isn’t too wide */ - width: 100%; /* Let it scale down on small screens */ - text-align: center; /* Center text content */ - margin-bottom: 2rem; /* Space between sections */ -} diff --git a/src/main/resources/webpage-internal/css/styles.css b/src/main/resources/webpage-internal/css/styles.css deleted file mode 100644 index 5ba4629..0000000 --- a/src/main/resources/webpage-internal/css/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: #4d4d4d; -} \ No newline at end of file diff --git a/src/main/resources/webpage-internal/js/dragDropUpload.js b/src/main/resources/webpage-internal/js/dragDropUpload.js deleted file mode 100644 index 06c87b6..0000000 --- a/src/main/resources/webpage-internal/js/dragDropUpload.js +++ /dev/null @@ -1,75 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - const dropArea = document.getElementById("drop-area"); - const fileInput = document.getElementById("file-input"); - const upload = document.getElementById("upload"); - const form = document.querySelector("form"); // Get the form element - - function updateDropArea(files) { - // Find the existing

inside drop-area (or create a new one if it doesn't exist) - let paragraph = dropArea.querySelector("p"); - if (!paragraph) { - paragraph = document.createElement("p"); - dropArea.appendChild(paragraph); - } - - // Create a list of selected file names - const fileNames = Array.from(files).map(file => file.name).join(", "); - - // Set the selected file names in the

element - paragraph.textContent = `Selected files: ${fileNames}`; - - // Enable the upload button if there are files selected, otherwise disable it - upload.disabled = files.length === 0; - if (upload.disabled) { - upload.style.backgroundColor = ""; // Reset color if button is disabled - } - } - - // Allow drag over - dropArea.addEventListener("dragover", function (e) { - e.preventDefault(); - dropArea.classList.add("highlight"); - }); - - // Remove highlight when drag leaves - dropArea.addEventListener("dragleave", function () { - dropArea.classList.remove("highlight"); - }); - - // Handle drop event - dropArea.addEventListener("drop", function (e) { - e.preventDefault(); - dropArea.classList.remove("highlight"); - - const files = e.dataTransfer.files; // Get the dropped files - fileInput.files = e.dataTransfer.files; // Assign the files to the file input - updateDropArea(files); - }); - - // Allow user to click on the drop area to open the file dialog - dropArea.addEventListener("click", function () { - fileInput.click(); - }); - - // When a file is selected via the file input - fileInput.addEventListener("change", function () { - if (fileInput.files.length > 0) { - updateDropArea(fileInput.files); - } - }); - - // Disable the upload button initially - upload.disabled = true; - - // Handle upload button click - upload.addEventListener("click", function () { - if (upload.disabled) return; // Prevent click if button is disabled - - // Disable the button, make it red, and show the text "Please Wait, uploading..." - upload.disabled = true; - upload.style.backgroundColor = "red"; // Change color to red - upload.value = "Please Wait, uploading..."; // Update text - - form.submit(); // Submit the form manually - }); -}); diff --git a/src/main/resources/webpage-internal/js/main.js b/src/main/resources/webpage-internal/js/main.js new file mode 100644 index 0000000..5f00531 --- /dev/null +++ b/src/main/resources/webpage-internal/js/main.js @@ -0,0 +1,205 @@ +document.addEventListener('DOMContentLoaded', () => { + const navToggle = document.querySelector('.nav-toggle'); + const navMenu = document.querySelector('.nav-menu'); + const navLinks = document.querySelectorAll('.nav-link'); + + navToggle?.addEventListener('click', () => { + navToggle.classList.toggle('active'); + navMenu.classList.toggle('active'); + }); + + navLinks.forEach(link => { + link.addEventListener('click', () => { + navToggle?.classList.remove('active'); + navMenu?.classList.remove('active'); + }); + }); + + const sections = document.querySelectorAll('section[id]'); + + function updateActiveLink() { + const scrollY = window.scrollY; + + sections.forEach(section => { + const sectionHeight = section.offsetHeight; + const sectionTop = section.offsetTop - 100; + const sectionId = section.getAttribute('id'); + + if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { + navLinks.forEach(link => { + link.classList.remove('active'); + if (link.getAttribute('href') === `#${sectionId}`) { + link.classList.add('active'); + } + }); + } + }); + } + + window.addEventListener('scroll', updateActiveLink); + + const navbar = document.querySelector('.navbar'); + + function updateNavbar() { + if (window.scrollY > 50) { + navbar?.classList.add('scrolled'); + } else { + navbar?.classList.remove('scrolled'); + } + } + + window.addEventListener('scroll', updateNavbar); +}); + +function createParticles() { + const particlesContainer = document.getElementById('particles'); + if (!particlesContainer) return; + + const particleCount = 50; + + for (let i = 0; i < particleCount; i++) { + const particle = document.createElement('div'); + particle.className = 'particle'; + + particle.style.left = Math.random() * 100 + '%'; + particle.style.top = Math.random() * 100 + '%'; + + const size = Math.random() * 4 + 1; + particle.style.width = size + 'px'; + particle.style.height = size + 'px'; + + particle.style.animationDuration = (Math.random() * 3 + 2) + 's'; + particle.style.animationDelay = Math.random() * 2 + 's'; + + particle.style.cssText += ` + position: absolute; + background: rgba(255, 149, 0, ${Math.random() * 0.5 + 0.2}); + border-radius: 50%; + pointer-events: none; + animation: particleFloat ${Math.random() * 3 + 2}s ease-in-out infinite; + animation-delay: ${Math.random() * 2}s; + `; + + particlesContainer.appendChild(particle); + } + + const style = document.createElement('style'); + style.textContent = ` + @keyframes particleFloat { + 0%, 100% { + transform: translateY(0) translateX(0); + opacity: 0; + } + 10% { + opacity: 1; + } + 90% { + opacity: 1; + } + 100% { + transform: translateY(-100px) translateX(${Math.random() * 50 - 25}px); + opacity: 0; + } + } + `; + document.head.appendChild(style); +} + +document.addEventListener('DOMContentLoaded', createParticles); + +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function(e) { + const href = this.getAttribute('href'); + if (href === '#') return; + + e.preventDefault(); + const target = document.querySelector(href); + + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); +}); + +document.addEventListener('DOMContentLoaded', () => { + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-in'); + observer.unobserve(entry.target); + } + }); + }, observerOptions); + + document.querySelectorAll('.feature-card, .plugin-card').forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(20px)'; + el.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; + observer.observe(el); + }); + + const style = document.createElement('style'); + style.textContent = ` + .animate-in { + opacity: 1 !important; + transform: translateY(0) !important; + } + `; + document.head.appendChild(style); +}); + +function animateCounter(element, target, duration = 2000) { + let start = 0; + const increment = target / (duration / 16); + + function updateCounter() { + start += increment; + if (start < target) { + element.textContent = Math.floor(start) + '+'; + requestAnimationFrame(updateCounter); + } else { + element.textContent = target + '+'; + } + } + + updateCounter(); +} + +document.addEventListener('DOMContentLoaded', () => { + const statsObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const statNumbers = entry.target.querySelectorAll('.stat-number'); + statNumbers.forEach(stat => { + const text = stat.textContent; + if (text.includes('+')) { + const num = parseInt(text); + if (!isNaN(num)) { + animateCounter(stat, num); + } + } + }); + statsObserver.unobserve(entry.target); + } + }); + }, { threshold: 0.5 }); + + const heroStats = document.querySelector('.hero-stats'); + if (heroStats) { + statsObserver.observe(heroStats); + } +}); + +console.log('%c🥭 MangoBot', 'font-size: 24px; font-weight: bold; color: #ff9500;'); +console.log('%cA versatile Discord bot by MangoRage', 'font-size: 14px; color: #a1a1aa;'); +console.log('%cJoin us: https://discord.mangorage.org/', 'font-size: 12px; color: #5865f2;');