diff --git a/FirefoxExtension/background.js b/FirefoxExtension/background.js new file mode 100644 index 0000000..8485f9a --- /dev/null +++ b/FirefoxExtension/background.js @@ -0,0 +1,86 @@ +if (typeof browser === "undefined") { + var browser = chrome; +} + +let autoRecordInterval = null; +let autoRecordAllowedPrefixes = []; // Global storage for allowed URL prefixes + +browser.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === "login") { + fetch("http://192.168.0.226:25570/api/login", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username: message.username, password: message.password }) + }) + .then(response => response.json()) + .then(data => { + console.log("Login response:", data); + sendResponse(data); + }) + .catch(err => sendResponse({ error: err.toString() })); + return true; + } else if (message.action === "register") { + fetch("http://192.168.0.226:25570/api/register", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username: message.username, password: message.password }) + }) + .then(response => response.json()) + .then(data => { + console.log("Registration response:", data); + sendResponse(data); + }) + .catch(err => sendResponse({ error: err.toString() })); + return true; + } else if (message.action === "startAutoRecord") { + if (message.allowedPrefixes) { + autoRecordAllowedPrefixes = message.allowedPrefixes + .split(",") + .map(s => s.trim()) + .filter(s => s.length > 0); + } else { + autoRecordAllowedPrefixes = []; + } + if (autoRecordInterval) { + clearInterval(autoRecordInterval); + } + autoRecordInterval = setInterval(() => { + browser.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs && tabs.length > 0) { + const currentUrl = tabs[0].url; + let recordUrl = false; + for (let prefix of autoRecordAllowedPrefixes) { + if (currentUrl.startsWith(prefix)) { + recordUrl = true; + break; + } + } + if (recordUrl) { + fetch("http://192.168.0.226:25570/record", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url: currentUrl }) + }) + .then(response => response.json()) + .then(data => console.log("Recorded URL:", data)) + .catch(err => console.error("Recording error:", err)); + } else { + console.log("URL does not match allowed prefixes, skipping: " + currentUrl); + } + } + }); + }, message.interval || 10000); + sendResponse({ success: true, message: "Auto recording started" }); + return true; + } else if (message.action === "stopAutoRecord") { + if (autoRecordInterval) { + clearInterval(autoRecordInterval); + autoRecordInterval = null; + } + sendResponse({ success: true, message: "Auto recording stopped" }); + return true; + } +}); diff --git a/FirefoxExtension/icon.png b/FirefoxExtension/icon.png new file mode 100644 index 0000000..f59e63c Binary files /dev/null and b/FirefoxExtension/icon.png differ diff --git a/FirefoxExtension/manifest.json b/FirefoxExtension/manifest.json new file mode 100644 index 0000000..3784303 --- /dev/null +++ b/FirefoxExtension/manifest.json @@ -0,0 +1,19 @@ +{ + "manifest_version": 2, + "name": "NovelBin Auto Recorder", + "version": "1.0", + "description": "Records the current chapter URL from NovelBin via the server, with account registration.", + "permissions": [ + "tabs", + "storage", + "http://192.168.0.226:25570/*" + ], + "background": { + "scripts": ["background.js"], + "persistent": true + }, + "browser_action": { + "default_popup": "popup.html", + "default_icon": "icon.png" + } +} diff --git a/FirefoxExtension/popup.html b/FirefoxExtension/popup.html new file mode 100644 index 0000000..f703dac --- /dev/null +++ b/FirefoxExtension/popup.html @@ -0,0 +1,79 @@ + + + + NovelBin Auto Recorder + + + + +
+
+

Login

+
+
+ +

Don't have an account? Register here

+
+ +
+ + + + + + + +
+ + + diff --git a/FirefoxExtension/popup.js b/FirefoxExtension/popup.js new file mode 100644 index 0000000..9063f3f --- /dev/null +++ b/FirefoxExtension/popup.js @@ -0,0 +1,200 @@ +if (typeof browser === "undefined") { + var browser = chrome; +} + +document.addEventListener('DOMContentLoaded', function() { + // Authentication elements + const authSection = document.getElementById('authSection'); + const loginSection = document.getElementById('loginSection'); + const registerSection = document.getElementById('registerSection'); + const loginBtn = document.getElementById('loginBtn'); + const loginUsernameInput = document.getElementById('loginUsername'); + const loginPasswordInput = document.getElementById('loginPassword'); + const showRegisterLink = document.getElementById('showRegisterLink'); + const registerBtn = document.getElementById('registerBtn'); + const registerUsernameInput = document.getElementById('registerUsername'); + const registerPasswordInput = document.getElementById('registerPassword'); + const registerConfirmPasswordInput = document.getElementById('registerConfirmPassword'); + const showLoginLink = document.getElementById('showLoginLink'); + + // Recorder and list elements + const recorderSection = document.getElementById('recorderSection'); + const autoRecordToggle = document.getElementById('autoRecordToggle'); + const allowedPrefixesInput = document.getElementById('allowedPrefixes'); + const logoutBtn = document.getElementById('logoutBtn'); + const openResizableBtn = document.getElementById('openResizableBtn'); + const chapterListSection = document.getElementById('chapterListSection'); + const searchQueryInput = document.getElementById('searchQuery'); + const excludeWordsInput = document.getElementById('excludeWords'); + const chapterListElement = document.getElementById('chapterList'); + const statusDiv = document.getElementById('status'); + + // Load stored allowedPrefixes from browser.storage + browser.storage.local.get('allowedPrefixes', function(result) { + if (result.allowedPrefixes) { + allowedPrefixesInput.value = result.allowedPrefixes; + } + }); + allowedPrefixesInput.addEventListener('change', function() { + const allowedPrefixes = allowedPrefixesInput.value.trim(); + browser.storage.local.set({ allowedPrefixes: allowedPrefixes }); + }); + + // Toggle between login and registration views + showRegisterLink.addEventListener('click', function(e) { + e.preventDefault(); + loginSection.style.display = "none"; + registerSection.style.display = "block"; + }); + showLoginLink.addEventListener('click', function(e) { + e.preventDefault(); + registerSection.style.display = "none"; + loginSection.style.display = "block"; + }); + + // Check login status on load + fetch("http://192.168.0.226:25570/api/status", { + method: "GET", + credentials: "include" + }) + .then(response => response.json()) + .then(data => { + if (data.loggedIn) { + authSection.style.display = "none"; + recorderSection.style.display = "block"; + chapterListSection.style.display = "block"; + statusDiv.textContent = "Logged in as " + data.username; + } else { + authSection.style.display = "block"; + recorderSection.style.display = "none"; + chapterListSection.style.display = "none"; + } + }) + .catch(err => { + console.error("Error checking login status:", err); + }); + + // Login handler + loginBtn.addEventListener('click', function() { + const username = loginUsernameInput.value.trim(); + const password = loginPasswordInput.value.trim(); + if (!username || !password) { + statusDiv.textContent = "Please enter username and password."; + return; + } + browser.runtime.sendMessage({ action: "login", username, password }, (response) => { + console.log("Login response:", response); + if (response.success) { + statusDiv.textContent = response.message; + authSection.style.display = "none"; + recorderSection.style.display = "block"; + chapterListSection.style.display = "block"; + } else { + statusDiv.textContent = response.message || "Login failed."; + } + }); + }); + + // Registration handler + registerBtn.addEventListener('click', function() { + const username = registerUsernameInput.value.trim(); + const password = registerPasswordInput.value.trim(); + const confirmPassword = registerConfirmPasswordInput.value.trim(); + if (!username || !password || !confirmPassword) { + statusDiv.textContent = "Please fill in all registration fields."; + return; + } + if (password !== confirmPassword) { + statusDiv.textContent = "Passwords do not match."; + return; + } + browser.runtime.sendMessage({ action: "register", username, password }, (response) => { + console.log("Registration response:", response); + if (response.success) { + statusDiv.textContent = response.message; + authSection.style.display = "none"; + recorderSection.style.display = "block"; + chapterListSection.style.display = "block"; + } else { + statusDiv.textContent = response.message || "Registration failed."; + } + }); + }); + + // Auto record toggle handler + autoRecordToggle.addEventListener('change', function(e) { + if (e.target.checked) { + const allowedPrefixes = allowedPrefixesInput.value.trim(); + browser.runtime.sendMessage({ action: "startAutoRecord", interval: 10000, allowedPrefixes }, (response) => { + statusDiv.textContent = response.message || response.error; + }); + } else { + browser.runtime.sendMessage({ action: "stopAutoRecord" }, (response) => { + statusDiv.textContent = response.message || response.error; + }); + } + }); + + // Open resizable window button + openResizableBtn.addEventListener('click', function() { + browser.windows.create({ + url: browser.runtime.getURL("popup.html"), + type: "popup", + width: 600, + height: 600 + }); + }); + + // Logout handler + logoutBtn.addEventListener('click', function() { + window.location.href = "http://192.168.0.226:25570/logout"; + }); + + // Function to update the chapter list + function updateChapterList() { + fetch("http://192.168.0.226:25570/logs", { + method: "GET", + credentials: "include" + }) + .then(response => response.json()) + .then(data => { + let chapters = data.updates || []; + // Sort alphabetically by URL + chapters.sort((a, b) => a.url.localeCompare(b.url)); + // Filter by search query (case-insensitive) + const searchQuery = searchQueryInput.value.trim().toLowerCase(); + if (searchQuery) { + chapters = chapters.filter(item => item.url.toLowerCase().includes(searchQuery)); + } + // Filter out entries with excluded words + const excludeWordsStr = excludeWordsInput.value.trim().toLowerCase(); + if (excludeWordsStr) { + const excludeWords = excludeWordsStr.split(",").map(s => s.trim()).filter(s => s.length > 0); + if (excludeWords.length > 0) { + chapters = chapters.filter(item => { + for (const word of excludeWords) { + if (item.url.toLowerCase().includes(word)) { + return false; + } + } + return true; + }); + } + } + // Update the chapter list UI + chapterListElement.innerHTML = ""; + chapters.forEach(item => { + const li = document.createElement("li"); + li.textContent = item.url + " (" + new Date(item.timestamp).toLocaleTimeString() + ")"; + chapterListElement.appendChild(li); + }); + }) + .catch(err => { + console.error("Error fetching logs:", err); + }); + } + + setInterval(updateChapterList, 5000); + searchQueryInput.addEventListener('input', updateChapterList); + excludeWordsInput.addEventListener('input', updateChapterList); +});