From ac3f0da0b9b4d792035ba8f7fbadbb1192af4d6a Mon Sep 17 00:00:00 2001 From: SolninjaA <51935570+SolninjaA@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:27:40 +1000 Subject: [PATCH] chg: Implemented short link generation based on website's title --- .gitignore | 1 + README.md | 2 +- background.js | 67 +++++++++++++++++++++++++++++++++++--------- options.html | 17 +++++++++++- options.js | 77 +++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 147 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 1536c39..9c55de6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ web-ext-artifacts/**/* +backups/**/* diff --git a/README.md b/README.md index 25455e1..e798558 100644 --- a/README.md +++ b/README.md @@ -115,4 +115,4 @@ This project was inspired by and modified from Edward Shen's [Shlink extension][ [clipboard-api]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/clipboard [storage-api]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage [host-permission]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#host_permissions -[shlink-extension]: https://github.com/edward-shen/shlink \ No newline at end of file +[shlink-extension]: https://github.com/edward-shen/shlink diff --git a/background.js b/background.js index 92ad1b2..a171e92 100644 --- a/background.js +++ b/background.js @@ -39,6 +39,7 @@ * @property {string} chhotoHost The location of the Chhoto URL instance. * @property {string} chhotoKey The API key to communicate with a Chhoto URL instance with. * @property {string} longUrl The requested URL to shorten. + * @property {string} title The title of the website */ /** @@ -50,6 +51,7 @@ * @type {object} * @property {number} status * @property {{success: boolean, error: boolean, shorturl: string}} json + * @property {string} requestedLink The link that tried to be created. This only activates, and is only accurate, if the generateWithTitle option is enabled. */ /** @@ -71,9 +73,10 @@ * Validates the URL, as in, checks if the protocol is allowed. * * @param {!URL} url - * @returns {!URL} + * @param {!string} title + * @returns {!Promise<[URL, string], Error>} */ -function validateURL(url) { +function validateURL(url, title) { return browser.storage.local.get("allowedProtocols").then(({ allowedProtocols }) => { // Initialize a list of protocols that are allowed if unset. // This needs to be synced with the initialization code in options.js. @@ -93,20 +96,21 @@ function validateURL(url) { return Promise.reject(new Error(`The current page's protocol (${url.protocol}) is unsupported.`)); } - // Return URL - return Promise.resolve(url); + // Return URL and title + return Promise.resolve([url, title]); }); } /** * Parses the URL outputted in the previous function, and gets the full link (i.e. URI included). * - * @param {!URL} url + * @param {[!URL, string]} url - Holds long URL + * title - Title of the website * @returns {!Promise} * If all the data was obtained, return ChhotoRequest. * Else, return an error */ -function generateChhotoRequest(url) { +function generateChhotoRequest([url, title]) { return browser.storage.local.get().then((data) => { // If the user didn't specify an API key if (!data.chhotoKey) { @@ -114,12 +118,40 @@ function generateChhotoRequest(url) { "Missing API Key. Please configure the Chhoto URL extension. See https://git.solomon.tech/solomon/Chhoto-URL-Extension#installation for more information." )); } + // If the user didn't specify an API key or a host if (!data.chhotoKey || !data.chhotoHost) { return Promise.reject(new Error("Please configure the Chhoto URL extension. See https://git.solomon.tech/solomon/Chhoto-URL-Extension#installation for more information.")); } + + // Set URL and title data.longUrl = url.href; + // Make the title an empty string by default + // This will create a randomly generated string if sent to the Chhoto URL server + data.title = ""; + + // If "generateWithTitle" is true + if (data.generateWithTitle) { + // Get the configured word limit + let wordLimit = data.titleWordLimit; + + // Format title name + // Replace all occurences of ' - ' to '-' + // Replace all occurences of ' ' to '-' + // Replace all characters except for 'a-z', '0-9', '-' and '_' with '' + let titleName = title.toLowerCase().replace(/ - /g, '-').replace(/\s+/g, '-').replace(/[^a-z0-9-_]/g, ''); + + // If the wordLimit is not 0, and thus limited + if (wordLimit !== "0") { + // Limit the length of the short URL to the configured number + titleName = titleName.split('-', wordLimit).join('-'); + } + + // Set the title + data.title = titleName; + }; + // Return return Promise.resolve(data); }); @@ -130,7 +162,7 @@ function generateChhotoRequest(url) { * * @param {!ChhotoRequest} chhotoRequest An object containing all the variables * needed to request a shortened link from a Chhoto URL instance. - * @returns {!ChhotoResponse} The HTTP response from the Chhoto URL instance. + * @returns {!Promise} The HTTP response from the Chhoto URL instance, or an error */ function requestChhoto(chhotoRequest) { const headers = new Headers(); @@ -146,20 +178,20 @@ function requestChhoto(chhotoRequest) { method: 'POST', headers, body: JSON.stringify({ - shortlink: "", + shortlink: chhotoRequest.title, longlink: chhotoRequest.longUrl, }), }, - // Add the HTTP status code to the response - )).then(r => r.json().then(data => ({status: r.status, json: data}))) - // If there was an error + // Add the HTTP status code, and requestedLink (see ChhotoResponse in type definitions for details) to the response + )).then(r => r.json().then(data => ({ status: r.status, json: data, requestedLink: `${chhotoRequest.chhotoHost}/${chhotoRequest.title}` }))) + // If there was a HTTP error + // This does not activate if the Chhoto server returns a JSON response with an error .catch(err => { // Change the error message, if there was a NetworkError if (err.message === "NetworkError when attempting to fetch resource.") { err.message = "Failed to access the Chhoto URL instance. Is the instance online?" }; - // Return error return Promise.reject(new Error( `Error: ${err.message}` )); @@ -180,6 +212,15 @@ function validateChhotoResponse(httpResp) { if (httpResp.json.success) { return httpResp.json; } else { + if (httpResp.status === 409) { + const json = { + success: true, + error: false, + shorturl: httpResp.requestedLink, + } + return json; + } + return Promise.reject(new Error( `Error (${httpResp.status}): ${httpResp.json.reason}.` )); @@ -254,7 +295,7 @@ function notifyError(error) { function generateChhoto() { browser.tabs .query({ active: true, currentWindow: true }) - .then(tabData => validateURL(new URL(tabData[0].url))) + .then(tabData => validateURL(new URL(tabData[0].url), tabData[0].title)) .then(generateChhotoRequest) .then(requestChhoto) .then(validateChhotoResponse) diff --git a/options.html b/options.html index 4c97e02..8f01db4 100644 --- a/options.html +++ b/options.html @@ -57,7 +57,8 @@ flex-direction: column; } - #message { + #message, + #message2 { display: none; } @@ -135,6 +136,20 @@

Disabling all filters will allow a link to be created from any URL.

+
+

URL generation

+ + +

Inputted value is not a number.

+
+ diff --git a/options.js b/options.js index bac047a..e89d9c4 100644 --- a/options.js +++ b/options.js @@ -36,6 +36,10 @@ const AllowHttpsEle = document.getElementById("allow-https"); const AllowFileEle = document.getElementById("allow-file"); const AllowFtpEle = document.getElementById("allow-ftp"); const messageEle = document.getElementById("message"); +const generateWithTitleEle = document.getElementById("generate-with-title"); +const titleWordLimitLabelEle = document.getElementById("title-word-limit-label"); +const titleWordLimitEle = document.getElementById("title-word-limit"); +const message2Ele = document.getElementById("message2"); // Get the browser storage const browserStorage = browser.storage.local; @@ -117,11 +121,54 @@ for (const [ele, protocol] of allowProtocolsMapping) { }; } -function setCurrentChoice({ chhotoHost, chhotoKey, allowedProtocols }) { +/* + * URL Generation Options + */ + +// Generate with title +generateWithTitleEle.onclick = () => { + browserStorage.get("generateWithTitle").then(({ generateWithTitle }) => { + // Get value + generateWithTitle = generateWithTitleEle.checked; + + // Set default display option + let display = "none"; + + // If generateWithTitle is true + if (generateWithTitle) { + // Reassign variable + display = "block"; + } + + // Set display and save + titleWordLimitLabelEle.style.display = display; + browserStorage.set({ generateWithTitle }); + }); +}; + +// Title character limit +titleWordLimitEle.oninput = (event) => { + if (event.type === "click") { + event.preventDefault(); + }; + + // Get the inputted value + const titleWordLimit = titleWordLimitEle.value; + + // If the inputted value is not a number (NaN) + if (isNaN(titleWordLimit)) { + message2Ele.classList.add("warning"); + } else { + message2Ele.classList.remove("warning"); + browserStorage.set({ titleWordLimit }); + }; +} + +function setCurrentChoice({ chhotoHost, chhotoKey, allowedProtocols, generateWithTitle, titleWordLimit }) { hostKeyEle.value = chhotoHost || ""; apiKeyEle.value = chhotoKey || ""; - // If "allowedProtocols" is undefined, set default protocols + // If "allowedProtocols" doesn't exist, set default protocols // If the user deselects every protocol, this does not activate // since the value would be empty, not undefined. // @@ -132,6 +179,18 @@ function setCurrentChoice({ chhotoHost, chhotoKey, allowedProtocols }) { browserStorage.set({ allowedProtocols: allowedProtocols }); } + // If generateWithTitle is undefined, set the default value + if (generateWithTitle === undefined) { + generateWithTitle = false; + browserStorage.set({ generateWithTitle: generateWithTitle }); + } + + // If titleWordLimit is undefined, set the default value + if (titleWordLimit === undefined) { + titleWordLimit = "0"; + browserStorage.set({ titleWordLimit: titleWordLimit }); + } + // Initialize a list of protocols that are allowed if unset. This needs // to be synced with the initialization code in background.js#validateURL. allowedProtocols = new Set(allowedProtocols); @@ -141,6 +200,20 @@ function setCurrentChoice({ chhotoHost, chhotoKey, allowedProtocols }) { AllowFileEle.checked = allowedProtocols.has("file:"); AllowFtpEle.checked = allowedProtocols.has("ftp:"); + generateWithTitleEle.checked = generateWithTitle; + + // Set default display + let display = "none"; + + // If generateWithTitle is true, reassign variable + if (generateWithTitle === true) { + display = "block"; + } + + // Save + titleWordLimitLabelEle.style.display = display; + titleWordLimitEle.value = titleWordLimit || "0"; + } browserStorage.get().then(setCurrentChoice, (error) => {