فهرست منبع

invisible char filter + reliability stuff

Sv443 3 سال پیش
والد
کامیت
0fa4b00064
8فایلهای تغییر یافته به همراه52 افزوده شده و 30 حذف شده
  1. 1 0
      .eslintrc.js
  2. 10 10
      README.md
  3. 2 0
      changelog.md
  4. 2 2
      package.json
  5. 3 3
      src/error.js
  6. 1 1
      src/index.js
  7. 12 6
      src/server.js
  8. 21 8
      src/songMeta.js

+ 1 - 0
.eslintrc.js

@@ -14,5 +14,6 @@ module.exports = {
         "comma-dangle": [ "error" , "always-multiline" ],
         "array-bracket-newline": [ "error", "consistent" ],
         "function-paren-newline": [ "error", "multiline" ],
+        "no-control-regex": ["off"],
     },
 };

+ 10 - 10
README.md

@@ -1,7 +1,7 @@
 # geniURL
 
 Simple JSON and XML REST API to search for song metadata and the lyrics URL on [genius](https://genius.com/)  
-Obtaining actual lyrics sadly isn't possible yet (ideas are welcome lol)
+Obtaining actual lyrics sadly isn't possible yet (suggestions are welcome lol)
 
 <br><br>
 
@@ -33,9 +33,9 @@ All routes support gzip and deflate compression.
 >
 > **Parameters:**  
 > `?q=search%20query` (required)  
-> This parameter should contain both song and artist name(s) if possible (order doesn't matter, separate with a whitespace).  
-> Sometimes the song name alone might be enough but the results may vary.  
-> If the search query contains special characters, they need to be [percent/URL-encoded.](https://en.wikipedia.org/wiki/Percent-encoding)  
+> This parameter should contain both the song and artist name (for best result artist name should come first, separate with a whitespace).  
+> Sometimes the song name alone might be enough but the results vary greatly.  
+> Make sure the search query is [percent/URL-encoded.](https://en.wikipedia.org/wiki/Percent-encoding)  
 >   
 > `?format=json/xml` (optional)  
 > Use this parameter to change the response format from the default (`json`) to `xml`  
@@ -67,7 +67,7 @@ All routes support gzip and deflate compression.
 >         "id": 42069
 >     },
 >     "all": [
->         "// This array contains 10 objects with the same structure as 'top', sorted best match first",
+>         "// This array contains up to 10 objects with the same structure as 'top', sorted best match first",
 >         "// The first item of this array is exactly the same as 'top'"
 >     ],
 >     "timestamp": 1234567890123
@@ -99,9 +99,9 @@ All routes support gzip and deflate compression.
 >
 > **Parameters:**  
 > `?q=search%20query` (required)  
-> This parameter should contain both song and artist name(s) if possible (order doesn't matter, separate with a whitespace).  
-> Sometimes the song name alone might be enough but the results may vary.  
-> If the search query contains special characters, they need to be [percent/URL-encoded.](https://en.wikipedia.org/wiki/Percent-encoding)  
+> This parameter should contain both the song and artist name (for best result artist name should come first, separate with a whitespace).  
+> Sometimes the song name alone might be enough but the results vary greatly.  
+> Make sure the search query is [percent/URL-encoded.](https://en.wikipedia.org/wiki/Percent-encoding)  
 >   
 > `?format=json/xml` (optional)  
 > Use this parameter to change the response format from the default (`json`) to `xml`  
@@ -125,8 +125,8 @@ All routes support gzip and deflate compression.
 >         }
 >     },
 >     "resources": {
->         "thumbnail": "https://images.genius.com/8485557225af0345d2c550af8bae731b.300x300x1.png",
->         "image": "https://images.genius.com/13d7b13ef827a9f007a5d24c115b9ebb.1000x1000x1.png"
+>         "thumbnail": "https://images.genius.com/123456789abcdef.300x300x1.png",
+>         "image": "https://images.genius.com/123456789abcdef.1000x1000x1.png"
 >     },
 >     "lyricsState": "complete",
 >     "id": 42069,

+ 2 - 0
changelog.md

@@ -6,6 +6,8 @@
 
 ## v0.2.0
 - Added XML format
+- API now filters out invisible characters ([#1](https://github.com/Sv443/geniURL/issues/1))
+- Improvements to reliability
 
 <br><br>
 

+ 2 - 2
package.json

@@ -1,7 +1,7 @@
 {
   "name": "geniurl",
   "version": "0.2.0",
-  "description": "Simple \"REST proxy\" to search for lyrics on genius.com",
+  "description": "Simple JSON and XML REST API to search for song metadata and the lyrics URL on genius.com",
   "main": "src/index.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
@@ -25,7 +25,7 @@
   "bugs": {
     "url": "https://github.com/Sv443/geniURL/issues"
   },
-  "homepage": "https://github.com/Sv443/geniURL#readme",
+  "homepage": "https://github.com/Sv443/geniURL",
   "dependencies": {
     "axios": "^0.26.0",
     "compression": "^1.7.4",

+ 3 - 3
src/error.js

@@ -1,9 +1,9 @@
 const k = require("kleur");
 
 /**
- * @param {string} msg
- * @param {Error} [err]
- * @param {boolean} [fatal=false] 
+ * @param {string} msg Error message
+ * @param {Error} [err] Error instance
+ * @param {boolean} [fatal=false] Exits with code 1 if set to true
  */
 function error(msg, err, fatal = false)
 {

+ 1 - 1
src/index.js

@@ -18,7 +18,7 @@ async function init()
     }
     catch(err)
     {
-        error(`Error while ${stage}`, err);
+        error(`Error while ${stage}`, err, true);
     }
 }
 

+ 12 - 6
src/server.js

@@ -55,7 +55,7 @@ async function init()
             }
             catch(rlRejected)
             {
-                res.set("Retry-After", rlRejected?.msBeforeNext ? String(Math.round(rlRejected.msBeforeNext / 1000)) || 1 : 1);
+                res.set("Retry-After", String(rlRejected?.msBeforeNext ? Math.round(rlRejected.msBeforeNext / 1000) : 1));
                 return respond(res, 429, { message: "You are being rate limited" }, req?.query?.format);
             }
 
@@ -74,7 +74,7 @@ function registerEndpoints()
 {
     try
     {
-        app.get("/", (req, res) => {
+        app.get("/", (_req, res) => {
             res.redirect(packageJson.homepage);
         });
 
@@ -86,21 +86,27 @@ function registerEndpoints()
 
             const meta = await getMeta(q);
 
-            // js2xmlparser needs special treatment when using arrays to produce a good XML structure
+            if(!meta)
+                return respond(res, "clientError", "Found no results matching your search query", format);
+
+            // js2xmlparser needs special treatment when using arrays to produce a decent XML structure
             const response = format !== "xml" ? meta : { ...meta, all: { "result": meta.all } };
 
-            return respond(res, "success", response, req?.query?.format);
+            return respond(res, "success", response, format);
         });
 
         app.get("/search/top", async (req, res) => {
-            const { q } = req.query;
+            const { q, format } = req.query;
 
             if(typeof q !== "string" || q.length === 0)
                 return respond(res, "clientError", "No query parameter (?q=...) provided or it is invalid", req?.query?.format);
 
             const meta = await getMeta(q);
 
-            return respond(res, "success", meta.top, req?.query?.format);
+            if(!meta)
+                return respond(res, "clientError", "Found no results matching your search query", format);
+
+            return respond(res, "success", meta.top, format);
         });
     }
     catch(err)

+ 21 - 8
src/songMeta.js

@@ -2,32 +2,35 @@ const { default: axios } = require("axios");
 
 /** @typedef {import("./types").SongMeta} SongMeta */
 
-const accessToken = process.env.GENIUS_ACCESS_TOKEN || "ERR_NO_ENV";
-
 /**
- * Returns meta information about the top 10 results of a search through the genius API
+ * Returns meta information about the top results of a search using the genius API
  * @param {string} search
- * @returns {Promise<{ top: SongMeta, all: SongMeta[] }>}
+ * @returns {Promise<{ top: SongMeta, all: SongMeta[] } | null>} Resolves null if no results are found
  */
 async function getMeta(search)
 {
+    const accessToken = process.env.GENIUS_ACCESS_TOKEN ?? "ERR_NO_ENV";
+
     const { data: { response } } = await axios.get(`https://api.genius.com/search?q=${encodeURIComponent(search)}`, {
         headers: { "Authorization": `Bearer ${accessToken}` },
     });
 
     if(Array.isArray(response?.hits))
     {
+        if(response.hits.length === 0)
+            return null;
+
         const hits = response.hits
             .filter(h => h.type === "song")
             .map(({ result }) => ({
                 url: result.url,
                 path: result.path,
                 meta: {
-                    title: result.title,
-                    fullTitle: result.full_title,
-                    artists: result.artist_names,
+                    title: normalizeString(result.title),
+                    fullTitle: normalizeString(result.full_title),
+                    artists: normalizeString(result.artist_names),
                     primaryArtist: {
-                        name: result.primary_artist.name,
+                        name: normalizeString(result.primary_artist.name),
                         url: result.primary_artist.url,
                     },
                 },
@@ -46,4 +49,14 @@ async function getMeta(search)
     }
 }
 
+/**
+ * Removes invisible characters and control characters from a string
+ * @param {string} str
+ * @returns {string}
+ */
+function normalizeString(str)
+{
+    return str.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, "").replace(/\u00A0/g, " ");
+}
+
 module.exports = { getMeta };