From 7ee59569709bfdcf5d2981c22e7675a6f9fe904e Mon Sep 17 00:00:00 2001 From: Fijxu Date: Thu, 22 Aug 2024 18:59:08 -0400 Subject: [PATCH] 0.8.9: Better frontend and idk what more --- public/script.js | 289 +++++++++++++++++++++++------------------- public/styles.css | 252 ++++++++++++++++++++++-------------- src/handling/admin.cr | 2 +- src/jobs.cr | 6 +- src/routing.cr | 40 +++--- src/utils.cr | 71 +++++++---- 6 files changed, 382 insertions(+), 278 deletions(-) diff --git a/public/script.js b/public/script.js index 281e61e..255e99f 100644 --- a/public/script.js +++ b/public/script.js @@ -1,140 +1,169 @@ +// By chatgpt becuase I hate frontend and javascript kill me document.addEventListener("DOMContentLoaded", () => { - const dropArea = document.getElementById("drop-area"); - const fileInput = document.getElementById("fileElem"); - const uploadStatus = document.getElementById("upload-status"); + const dropArea = document.getElementById("drop-area"); + const fileInput = document.getElementById("fileElem"); + const uploadStatus = document.getElementById("upload-status"); - // Prevent default drag behaviors - ["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => { - dropArea.addEventListener(eventName, preventDefaults, false); - document.body.addEventListener(eventName, preventDefaults, false); + // Prevent default drag behaviors + ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { + dropArea.addEventListener(eventName, preventDefaults, false); + document.body.addEventListener(eventName, preventDefaults, false); + }); + + // Highlight drop area when item is dragged over + ["dragenter", "dragover"].forEach((eventName) => { + dropArea.addEventListener(eventName, highlight, false); + }); + + ["dragleave", "drop"].forEach((eventName) => { + dropArea.addEventListener(eventName, unhighlight, false); + }); + + // Handle dropped files + dropArea.addEventListener("drop", handleDrop, false); + dropArea.addEventListener("click", () => fileInput.click()); + + // Handle file selection + fileInput.addEventListener( + "change", + () => { + const files = fileInput.files; + handleFiles(files); + }, + false + ); + + // Handle pasted files + document.addEventListener("paste", handlePaste, false); + + function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); + } + + function highlight() { + dropArea.classList.add("highlight"); + } + + function unhighlight() { + dropArea.classList.remove("highlight"); + } + + function handleDrop(e) { + const dt = e.dataTransfer; + const files = dt.files; + handleFiles(files); + } + + function handlePaste(e) { + const items = e.clipboardData.items; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind === "file") { + const file = item.getAsFile(); + handleFiles([file]); + } + } + } + + function handleFiles(files) { + if (files.length > 0) { + for (const file of files) { + uploadFile(file); + } + } + } + + function uploadFile(file) { + const url = "upload"; // Replace with your upload URL + const xhr = new XMLHttpRequest(); + + // Create a new upload status container and link elements + const uploadContainer = document.createElement("div"); + const statusLink = document.createElement("div"); + const uploadText = document.createElement("span"); + const buttons = document.createElement("div"); + const copyButton = document.createElement("button"); + const deleteButton = document.createElement("button"); + + uploadContainer.className = "upload-status"; // Use the existing CSS class for styling + uploadContainer.appendChild(uploadText); + uploadContainer.appendChild(statusLink); + buttons.appendChild(copyButton) + buttons.appendChild(deleteButton) + uploadContainer.appendChild(buttons) + uploadStatus.appendChild(uploadContainer); + + // Update upload text + uploadText.innerHTML = "0%"; + uploadText.className = "percent"; + statusLink.className = "status"; + copyButton.className = "copy-button"; // Add class for styling + copyButton.innerHTML = "Copiar"; // Set button text + deleteButton.className = "delete-button"; + deleteButton.innerHTML = "Borrar"; + copyButton.style.display = "none"; + deleteButton.style.display = "none"; + + // Update progress text + xhr.upload.addEventListener("progress", (e) => { + if (e.lengthComputable) { + const percentComplete = Math.round((e.loaded / e.total) * 100); + uploadText.innerHTML = `${percentComplete}%`; // Update the text with the percentage + } }); - // Highlight drop area when item is dragged over - ["dragenter", "dragover"].forEach(eventName => { - dropArea.addEventListener(eventName, highlight, false); - }); + xhr.onerror = () => { + console.error("Error:", xhr.status, xhr.statusText, xhr.responseText); + statusLink.textContent = "Error desconocido"; + }; - ["dragleave", "drop"].forEach(eventName => { - dropArea.addEventListener(eventName, unhighlight, false); - }); - - // Handle dropped files - dropArea.addEventListener("drop", handleDrop, false); - dropArea.addEventListener("click", () => fileInput.click()); - - // Handle file selection - fileInput.addEventListener("change", () => { - const files = fileInput.files; - handleFiles(files); - }, false); - - // Handle pasted files - document.addEventListener("paste", handlePaste, false); - - function preventDefaults(e) { - e.preventDefault(); - e.stopPropagation(); - } - - function highlight() { - dropArea.classList.add("highlight"); - } - - function unhighlight() { - dropArea.classList.remove("highlight"); - } - - function handleDrop(e) { - const dt = e.dataTransfer; - const files = dt.files; - handleFiles(files); - } - - function handlePaste(e) { - const items = e.clipboardData.items; - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (item.kind === "file") { - const file = item.getAsFile(); - handleFiles([file]); - } + xhr.onload = () => { + // console.log("Response Status:", xhr.status); + // console.log("Response Text:", xhr.responseText); + if (xhr.status === 200) { + try { + const response = JSON.parse(xhr.responseText); + const fileLink = response.link; + statusLink.innerHTML = `${fileLink}`; + copyButton.style.display = "inline"; + copyButton.onclick = () => copyToClipboard(fileLink); + deleteButton.style.display = "inline"; + deleteButton.onclick = () => { + window.open(response.deleteLink, "_blank"); + }; + } catch (error) { + statusLink.textContent = + "Error desconocido, habla con el administrador"; } - } - - function handleFiles(files) { - if (files.length > 0) { - for (const file of files) { - uploadFile(file); - } + } else if (xhr.status >= 400 && xhr.status < 500) { + try { + const errorResponse = JSON.parse(xhr.responseText); + statusLink.textContent = errorResponse.error || "Error del cliente."; + } catch (e) { + statusLink.textContent = "Error del cliente."; } - } + } else { + statusLink.textContent = "Error del servidor."; + } + }; - function uploadFile(file) { - const url = "upload"; // Replace with your upload URL - const xhr = new XMLHttpRequest(); + // Send file + const formData = new FormData(); + formData.append("file", file); + xhr.open("POST", url, true); + xhr.send(formData); + } - // Create a new upload status container and link elements - const uploadContainer = document.createElement("div"); - const statusLink = document.createElement("div"); - const uploadText = document.createElement("span"); - const copyButton = document.createElement("button"); - - uploadContainer.className = "upload-status"; // Use the existing CSS class for styling - uploadContainer.appendChild(uploadText); - uploadContainer.appendChild(statusLink); - uploadContainer.appendChild(copyButton); - uploadStatus.appendChild(uploadContainer); // Append to the main upload status container - - // Update upload text - uploadText.innerHTML = "0%"; - uploadText.className = "percent" - copyButton.className = "copy-button"; // Add class for styling - copyButton.innerHTML = "Copiar"; // Set button text - copyButton.style.display = "none"; // Hide initially - - // Update progress text - xhr.upload.addEventListener("progress", (e) => { - if (e.lengthComputable) { - const percentComplete = Math.round((e.loaded / e.total) * 100); - uploadText.innerHTML = `${percentComplete}%`; // Update the text with the percentage - } - }); - - // Handle response - xhr.onload = () => { - if (xhr.status === 200) { - try { - const response = JSON.parse(xhr.responseText); - const fileLink = response.link; // Assuming the response contains a key 'link' - statusLink.innerHTML = `${fileLink}`; - copyButton.style.display = "inline"; // Show the copy button - copyButton.onclick = () => copyToClipboard(fileLink); // Set the copy action - } catch (error) { - statusLink.textContent = "File uploaded but failed to parse response."; - } - } else { - statusLink.textContent = "File upload failed."; - } - }; - - // Handle errors - xhr.onerror = () => { - statusLink.textContent = "An error occurred during the file upload."; - }; - - // Send file - const formData = new FormData(); - formData.append("file", file); - xhr.open("POST", url, true); - xhr.send(formData); - } - - // Function to copy the link to the clipboard - function copyToClipboard(text) { - navigator.clipboard.writeText(text).then(() => { - // alert("Link copied to clipboard!"); // Notify the user - }).catch(err => { - console.error("Failed to copy: ", err); - }); - } + // Function to copy the link to the clipboard + function copyToClipboard(text) { + navigator.clipboard + .writeText(text) + .then(() => { + // alert("Link copied to clipboard!"); // Notify the user + }) + .catch((err) => { + console.error("Failed to copy: ", err); + }); + } }); diff --git a/public/styles.css b/public/styles.css index c8fb455..46d7799 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,172 +1,228 @@ @font-face { - font-family: "FG"; - font-weight: 500; - src: url('framd.ttf'); + font-family: "FG"; + font-weight: 500; + src: url('framd.ttf'); } @font-face { - font-family: "FG"; - font-weight: 900; - src: url('frahv.ttf'); + font-family: "FG"; + font-weight: 900; + src: url('frahv.ttf'); } + @font-face { - font-family: "XFG"; - font-weight: 900; - src: url('frahvmod.ttf'); + font-family: "XFG"; + font-weight: 900; + src: url('frahvmod.ttf'); } html { - font-family: "FG"; - background-image: linear-gradient(to bottom, - rgba(11, 11, 11, 0.92), - rgba(11, 11, 11, 0.92)), - url(./bliss-small.avif); - background-attachment: fixed; + font-family: "FG"; + background-image: linear-gradient(to bottom, + rgba(11, 11, 11, 0.92), + rgba(11, 11, 11, 0.92)), + url(./bliss-small.avif); + background-attachment: fixed; + background-repeat: no-repeat; + background-size: cover; } + body { -/* font-family: Arial, sans-serif; */ -/* background-color: #111; */ - margin: 0; - padding: 20px; + /* font-family: Arial, sans-serif; */ + /* background-color: #111; */ + margin: 0; + padding: 20px; } -p, h1, h2, h3, h4, h5 { - color: aliceblue +p, +h1, +h2, +h3, +h4, +h5 { + color: aliceblue } h1 { - font-family: "FG"; - font-weight: 200; - max-width: 100%; - overflow-wrap: break-word; + font-family: "FG"; + font-weight: 200; + max-width: 100%; + overflow-wrap: break-word; } a { - text-decoration: none; + text-decoration: none; } .bottom { - font-size: 0.9em; -/* margin-top: 1ch;*/ - flex: 1; - text-align: center; + font-size: 0.9em; + /* margin-top: 1ch;*/ + flex: 1; + text-align: center; } -.bottom > p { - margin: 10px 0px; +.bottom>p { + margin: 10px 0px; } .percent { - color: aliceblue + color: aliceblue } .container { - max-width: 700px; - margin: auto; -/* background: white; */ - /*! padding: 20px; */ - border-radius: 0px; - /*! box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); */ + max-width: 800px; + margin: auto; + /* background: white; */ + /*! padding: 20px; */ + border-radius: 0px; + /*! box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); */ } #drop-area { - /*! border: 2px solid #00ff00; */ - /*! border-radius: 6px; */ - /*! padding-left: 10px; */ - /*! padding-right: 10px; */ - text-align: center; - position: relative; - width: fit-content; - margin: 0 auto; /* Center the element */ - display: block; /* Ensure it behaves as a block-level element */ - background: rgba(202,230,190,.75); - border: 1px solid #b7d1a0; - border-radius: 4px; - color: #468847; - cursor: pointer; - /*! display: inline-block; */ - font-size: 24px; - padding: 28px 48px; - text-shadow: 0 1px hsla(0,0%,100%,.5); - transition: background-color .25s,width .5s,height .5s; + /*! border: 2px solid #00ff00; */ + /*! border-radius: 6px; */ + /*! padding-left: 10px; */ + /*! padding-right: 10px; */ + text-align: center; + position: relative; + width: fit-content; + margin: 0 auto; + /* Center the element */ + display: block; + /* Ensure it behaves as a block-level element */ + background: rgba(202, 230, 190, .75); + border: 1px solid #b7d1a0; + border-radius: 4px; + color: #468847; + cursor: pointer; + /*! display: inline-block; */ + font-size: 24px; + padding: 28px 48px; + text-shadow: 0 1px hsla(0, 0%, 100%, .5); + transition: background-color .25s, width .5s, height .5s; } .button { - display: inline-block; - padding: 10px 20px; -/* background: #; */ - color: white; - border-radius: 5px; - cursor: pointer; -/* margin-top: 10px; */ + display: inline-block; + padding: 10px 20px; + /* background: #; */ + color: white; + border-radius: 5px; + cursor: pointer; + /* margin-top: 10px; */ } .upload-status { - margin-top: 10px; + margin-top: 10px; } -nav a, nav > ul -{ - list-style: none; - margin: 0; - padding: 0; - text-align: center; +nav a, +nav>ul { + list-style: none; + margin: 0; + padding: 0; + text-align: center; } #upload-status { - margin: 20px; /* Adjust as needed */ + margin: 20px; + /* Adjust as needed */ } .upload-status { - display: flex; - align-items: center; - justify-content: space-between; - border: 2px solid #999; /* Optional styling for the status box */ - padding: 5px; /* Optional padding */ - /*! border-radius: 6px; */ /* Optional rounded corners */ - /*! background-color: #f9f9f9; */ /* Optional background color */ + display: flex; + align-items: center; + justify-content: space-between; + border: 2px solid #999; + /* Optional styling for the status box */ + padding: 5px; + /* Optional padding */ + /*! border-radius: 6px; */ + /* Optional rounded corners */ + /*! background-color: #f9f9f9; */ + /* Optional background color */ } .link-container { - display: flex; - align-items: center; - margin-left: auto; /* Pushes the link and button to the right */ + display: flex; + align-items: center; + margin-left: auto; + /* Pushes the link and button to the right */ } .link { - color: #ffb6c1; - text-decoration: none; /* Remove underline from link */ - margin-right: 5px; /* Space between link and button */ + color: #ffb6c1; + text-decoration: none; + /* Remove underline from link */ + margin-right: 5px; + /* Space between link and button */ } .link:hover { - text-decoration: underline; /* Optional: underline on hover */ + text-decoration: underline; + /* Optional: underline on hover */ } .copy-button { - display: inline; - background-color: #5e5e5e; /* Button background color */ - color: white; /* Button text color */ - border: none; /* Remove border */ - border-radius: 3px; /* Rounded corners for the button */ - padding: 5px 10px; /* Button padding */ - cursor: pointer; /* Pointer cursor on hover */ + display: inline; + background-color: #7a6fff; + /* Button background color */ + color: white; + /* Button text color */ + border: none; + /* Remove border */ + border-radius: 3px; + /* Rounded corners for the button */ + padding: 5px 10px; + /* Button padding */ + cursor: pointer; + /* Pointer cursor on hover */ + font-weight: bold; } +.delete-button { + display: inline; + background-color: #ff6f6f; + /* Button background color */ + color: white; + /* Button text color */ + border: none; + /* Remove border */ + border-radius: 3px; + /* Rounded corners for the button */ + padding: 5px 10px; + /* Button padding */ + cursor: pointer; + /* Pointer cursor on hover */ + margin-left: 6px; + font-weight: bold; +} + + .copy-button:hover { - background-color: #404040; /* Darker shade on hover */ + background-color: #6057ce; + /* Darker shade on hover */ +} + +.delete-button:hover { + background-color: #ce5757; + /* Darker shade on hover */ +} + +.status { + color: rgb(255, 132, 0); } a:link { - color: #ffb6c1 + color: #ffb6c1 } a:visited { - color: #ffb6c1 + color: #ffb6c1 } a:hover { - color: #ffb6c1 + color: #ffb6c1 } \ No newline at end of file diff --git a/src/handling/admin.cr b/src/handling/admin.cr index 0035f53..e3ad075 100644 --- a/src/handling/admin.cr +++ b/src/handling/admin.cr @@ -56,7 +56,7 @@ module Handling::Admin # Delete entry from db SQL.exec "DELETE FROM #{CONFIG.ipTableName} WHERE ip = ?", ip LOGGER.debug "Rate limit for '#{ip}' was deleted" - successfull_ips << ip + successfull_ips << ip rescue ex : DB::NoResultsError LOGGER.error("Rate limit for '#{ip}' doesn't exist or is not registered in the database: #{ex.message}") failed_ips << ip diff --git a/src/jobs.cr b/src/jobs.cr index 15cac1f..f24a5f9 100644 --- a/src/jobs.cr +++ b/src/jobs.cr @@ -20,6 +20,8 @@ module Jobs spawn do loop do Utils.retrieve_tor_exit_nodes + # Updates the @@exit_nodes array instantly + Routing.reload_exit_nodes sleep CONFIG.torExitNodesCheck end end @@ -28,9 +30,7 @@ module Jobs def self.kemal spawn do if !CONFIG.unix_socket.nil? - Kemal.run do |config| - config.server.not_nil!.bind_unix "#{CONFIG.unix_socket}" - end + Kemal.run &.server.not_nil!.bind_unix "#{CONFIG.unix_socket}" else Kemal.run end diff --git a/src/routing.cr b/src/routing.cr index 9211b43..6e7b2d8 100644 --- a/src/routing.cr +++ b/src/routing.cr @@ -3,19 +3,10 @@ require "./http-errors" module Routing extend self @@exit_nodes = Array(String).new - if CONFIG.blockTorAddresses - spawn do - # Wait a little for Utils.retrieve_tor_exit_nodes to execute first - # or it will load an old exit node list - # I think this can be replaced by channels which makes me able to - # receive data from fibers - sleep 5 - loop do - LOGGER.debug "Updating Tor exit nodes array" - @@exit_nodes = Utils.load_tor_exit_nodes - sleep CONFIG.torExitNodesCheck + 5 - end - end + + def reload_exit_nodes + LOGGER.debug "Updating Tor exit nodes array" + @@exit_nodes = Utils.load_tor_exit_nodes end before_post "/api/admin/*" do |env| @@ -86,20 +77,21 @@ module Routing Handling.sharex_config(env) end - self.register_admin + if CONFIG.adminEnabled + self.register_admin + end end def register_admin - if CONFIG.adminEnabled - # post "/api/admin/upload" do |env| - # Handling::Admin.delete_ip_limit(env) - # end - post "/api/admin/delete" do |env| - Handling::Admin.delete_file(env) - end - end - post "/api/admin/deleteiplimit" do |env| - Handling::Admin.delete_ip_limit(env) + # post "/api/admin/upload" do |env| + # Handling::Admin.delete_ip_limit(env) + # end + post "/api/admin/delete" do |env| + Handling::Admin.delete_file(env) end end + + post "/api/admin/deleteiplimit" do |env| + Handling::Admin.delete_ip_limit(env) + end end diff --git a/src/utils.cr b/src/utils.cr index 7f22cf8..879412a 100644 --- a/src/utils.cr +++ b/src/utils.cr @@ -135,7 +135,8 @@ module Utils end end - # Delete socket if the server has not been previously cleaned by the server (Due to unclean exits, crashes, etc.) + # Delete socket if the server has not been previously cleaned by the server + # (Due to unclean exits, crashes, etc.) def delete_socket if File.exists?("#{CONFIG.unix_socket}") LOGGER.info "Deleting old unix socket" @@ -167,21 +168,43 @@ module Utils msg("File '#{fileinfo[:filename]}' deleted successfully") end + MAGIC_BYTES = { + # Images + ".png" => "89504e470d0a1a0a", + ".heic" => "6674797068656963", + ".jpg" => "ffd8ff", + ".gif" => "474946383", + # Videos + ".mp4" => "66747970", + ".webm" => "1a45dfa3", + ".mov" => "6d6f6f76", + ".wmv" => "󠀀3026b2758e66cf11", + ".flv" => "󠀀464c5601", + ".mpeg" => "000001bx", + # Audio + ".mp3" => "󠀀494433", + ".aac" => "󠀀fff1", + ".wav" => "󠀀57415645666d7420", + ".flac" => "󠀀664c614300000022", + ".ogg" => "󠀀4f67675300020000000000000000", + ".wma" => "󠀀3026b2758e66cf11a6d900aa0062ce6c", + ".aiff" => "󠀀464f524d00", + # Whatever + ".7z" => "377abcaf271c", + ".gz" => "1f8b", + ".iso" => "󠀀4344303031", + # Documents + "pdf" => "󠀀25504446", + "html" => "", + } + def detect_extension(file) : String - magic_bytes = { - ".png" => "89504e470d0a1a0a", - ".jpg" => "ffd8ff", - ".webm" => "1a45dfa3", - ".mp4" => "66747970", - ".gif" => "474946383", - ".7z" => "377abcaf271c", - ".gz" => "1f8b", - } file = File.open(file) - slice = Bytes.new(8) + slice = Bytes.new(16) hex = IO::Hexdump.new(file) + # Reads the first 16 bytes of the file in Heap hex.read(slice) - magic_bytes.each do |ext, mb| + MAGIC_BYTES.each do |ext, mb| if slice.hexstring.includes?(mb) return ext end @@ -191,17 +214,21 @@ module Utils def retrieve_tor_exit_nodes LOGGER.debug "Retrieving Tor exit nodes list" - resp = HTTP::Client.get(CONFIG.torExitNodesUrl) do |res| - if res.success? && res.status_code == 200 - begin - File.open(CONFIG.torExitNodesFile, "w") do |output| - IO.copy(res.body_io, output) + HTTP::Client.get(CONFIG.torExitNodesUrl) do |res| + begin + if res.success? && res.status_code == 200 + begin + File.open(CONFIG.torExitNodesFile, "w") { |output| IO.copy(res.body_io, output) } + rescue ex + LOGGER.error "Failed to write to file: #{ex.message}" end - rescue ex - LOGGER.error "Failed to write to file: #{ex.message}" + else + LOGGER.error "Failed to retrieve exit nodes list. Status Code: #{res.status_code}" end - else - LOGGER.error "Failed to retrieve exit nodes list. Status Code: #{res.status_code}" + rescue ex : Socket::ConnectError + LOGGER.error "Failed to connect to #{CONFIG.torExitNodesUrl}: #{ex.message}" + rescue ex + LOGGER.error "Unknown error: #{ex.message}" end end end @@ -227,7 +254,7 @@ module Utils end def host(env) : String - begin + begin return env.request.headers.try &.["X-Forwarded-Host"] rescue return env.request.headers["Host"]