From d638d813f71cb9d9ab52c268e0e2944b40e06488 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Sun, 22 Sep 2024 11:27:56 -0400 Subject: [PATCH] Getting ready to publish --- .vscode/launch.json | 27 ++++++++ .vscode/snips.code-snippets | 30 +++++++++ package-lock.json | 128 ++++++++++++++++++++++++++++++++++++ package.json | 23 +++++++ src/Request.js | 27 -------- src/TenorJSv2.js | 59 ----------------- src/index.js | 109 ++++++++++++++++++++++++++++++ src/main.js | 45 ------------- src/test.js | 70 ++++++++++++++++++++ 9 files changed, 387 insertions(+), 131 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/snips.code-snippets create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 src/Request.js delete mode 100644 src/TenorJSv2.js create mode 100644 src/index.js delete mode 100644 src/main.js create mode 100644 src/test.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..74cbdc1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\src\\main.js" + }, + { + "type": "node", + "request": "launch", + "name": "Test Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\src\\TenorJSv2.test.js" + } + ] +} \ No newline at end of file diff --git a/.vscode/snips.code-snippets b/.vscode/snips.code-snippets new file mode 100644 index 0000000..1b63447 --- /dev/null +++ b/.vscode/snips.code-snippets @@ -0,0 +1,30 @@ +{ + // Place your custom-scripts workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + + "Banner Comment": { + "scope": "javascript,typescript", + "description": "Banner Comment", + "prefix": "///", + "body": [ + "// #############################################################", + "// $1", + "// #############################################################", + "$2" + ] + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..328b224 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,128 @@ +{ + "name": "tenorjs-v2", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tenorjs-v2", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^1.7.7", + "dotenv": "^16.4.5" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..12f82f3 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "tenorjs-v2", + "version": "1.0.1", + "description": "A crappy wrapper for Tenor.com API", + "main": "./src/index.js", + "scripts": { + "test": "node ./src/test.js" + }, + "repository": { + "type": "git", + "url": "https://git.vfsh.dev/voidf1sh/tenorjs-v2" + }, + "keywords": [ + "tenor", + "gif" + ], + "author": "Skylar Grant", + "license": "MIT", + "dependencies": { + "axios": "^1.7.7", + "dotenv": "^16.4.5" + } +} diff --git a/src/Request.js b/src/Request.js deleted file mode 100644 index e1cedcc..0000000 --- a/src/Request.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = class Request { - constructor() { - this.completed = false; - } - - get(url, callback) { - const request = new XMLHttpRequest(); - - // Set up the callback for when the response comes in - request.onreadystatechange = () => { - if (request.readyState === 4) { - this.completed = true; - if (request.status === 200) { - callback(null, request.responseText); - } else { - callback(new Error(`Request failed with status ${request.status}: ${request.statusText}`), null); - } - } - } - - // Open the request, as asynchronous - request.open("GET", url, true); - - // Send the request - request.send(null); - } -} \ No newline at end of file diff --git a/src/TenorJSv2.js b/src/TenorJSv2.js deleted file mode 100644 index 9350b97..0000000 --- a/src/TenorJSv2.js +++ /dev/null @@ -1,59 +0,0 @@ -const Request = require('./Request'); - -module.exports = { - Tenor: class Tenor { - constructor(tenorApiKey) { - this.token = tenorApiKey; - } - - async configure() { - // Sanity tests - if (!this.token) { - throw new Error('No API key provided'); - } - - // Search with an invalid key - try { - const response = await this.search('invalid', 'query', 1); - if (response.code !== 16) { - throw new Error('Unexpected response from known-bad API query.'); - } - } catch (error) { - throw new Error(`Error searching with invalid key: ${error.message}`); - } - - // Search with a valid key - try { - const response = await this.search(this.token, 'cat', 1); - if (response.code === 16) { - throw new Error(response.error); - } else if (response.results.length !== 1) { - throw new Error('Unexpected response from known-good API query.'); - } - } catch (error) { - throw new Error(`Error searching with valid key: ${error.message}`); - } - } - - search(token, query, limit) { - return new Promise((resolve, reject) => { - // Form the search url - const searchUrl = `https://g.tenor.com/v1/search?q=${query}&key=${token}&limit=${limit}`; - - // Submit the search - new Request().get(searchUrl, (error, response) => { - if (error) { - return reject(new Error(`Error fetching GIFs: ${error.message}`)); - } - try { - // Parse the json response - const responseJson = JSON.parse(response); - resolve(responseJson); - } catch (parseError) { - reject(new Error(`Error parsing response: ${parseError.message}`)); - } - }); - }); - } - } -} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..c7ee271 --- /dev/null +++ b/src/index.js @@ -0,0 +1,109 @@ +// ############################################################# +// Module Imports +// ############################################################# +const axios = require('axios'); + +// ############################################################# +// Variables +// ############################################################# +const baseUrl = "https://tenor.googleapis.com/v2/search?"; + +class Request { + constructor() { + this.completed = false; + } + + get(url) { + return new Promise(async (resolve, reject) => { + axios.get(url).then((response) => { + this.completed = true; + resolve(response.data); + }).catch((error) => { + this.completed = true; + reject(error); + }); + }); + } +} + +class Tenor { + constructor(tenorApiKey) { + this.token = tenorApiKey; + return this; + } + + async configure() { + return new Promise((resolve, reject) => { + if (!this.token) { + return reject(new Error('No API key provided')); + } + + this.search('cat', 1).then((gifs) => { // Search with a valid key + if (gifs.length !== 1) { // Check for a valid response + return reject(new Error('Unexpected response from known-good API query.')); + } else { // Key is valid + resolve('Tenor API key is valid and API is working correctly.'); + } + }).catch((error) => { + if (error.response && error.response.status === 401) { + return reject(new Error(`Invalid API Key`)); + } else { + return reject(error); + } + }); + }); + } + + search(query, options) { + return new Promise((resolve, reject) => { + // Form the search url + let searchUrl = baseUrl + `q=${query}`; + searchUrl += `&key=${this.token}`; + if (options) { + if (typeof options === 'number') { + searchUrl += `&limit=${options}`; + } else if (typeof options === 'object') { + if (options.limit) searchUrl += `&limit=${options.limit}`; // Default 20 max 50 + if (options.clientKey) searchUrl += `&client_key=${options.clientKey}`; + if (options.random) searchUrl += `&random=${options.random}`; + if (options.position) searchUrl += `&pos=${options.position}`; + if (options.country) searchUrl += `&country=${options.country}`; + if (options.locale) searchUrl += `&locale=${options.locale}`; + if (options.mediaFilter) searchUrl += `&media_filter=${options.mediaFilter}`; + if (options.contentFilter) searchUrl += `&contentfilter=${options.contentFilter}`; + } + } + // Submit the search + new Request().get(searchUrl).then((response) => { + // Validate the results + if (!response.results || response.results.length === 0) { + return reject(new Error('No results found')); + } + + // Create object to store results + const gifs = []; + // Manipulate the results + for (const gif of response.results) { + gifs.push(new Gif(gif)); + } + + // Return the results + resolve(gifs); + }).catch(error => { + return reject(error); + }); + }); + } +} + +class Gif { + constructor(gif) { + this.url = gif.url; + this.id = gif.id; + this.created = gif.created; + this.mediaUrl = gif.media_formats.gif.url; + return this; + } +} + +module.exports = Tenor; \ No newline at end of file diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 19369f4..0000000 --- a/src/main.js +++ /dev/null @@ -1,45 +0,0 @@ -// callback for the top 8 GIFs of search -function tenorCallback_search(responsetext) -{ - // parse the json response - var response_objects = JSON.parse(responsetext); - - top_10_gifs = response_objects["results"]; - - // load the GIFs -- for our example we will load the first GIFs preview size (nanogif) and share size (tinygif) - - document.getElementById("preview_gif").src = top_10_gifs[0]["media"][0]["nanogif"]["url"]; - - document.getElementById("share_gif").src = top_10_gifs[0]["media"][0]["tinygif"]["url"]; - - return; - -} - - -// function to call the trending and category endpoints -function grab_data() -{ - // set the apikey and limit - var apikey = "LIVDSRZULELA"; - var lmt = 8; - - // test search term - var search_term = "excited"; - - // using default locale of en_US - var search_url = "https://g.tenor.com/v1/search?q=" + search_term + "&key=" + - apikey + "&limit=" + lmt; - - httpGetAsync(search_url,tenorCallback_search); - - // data will be loaded by each call's callback - return; -} - - -// SUPPORT FUNCTIONS ABOVE -// MAIN BELOW - -// start the flow -grab_data(); \ No newline at end of file diff --git a/src/test.js b/src/test.js new file mode 100644 index 0000000..05358dc --- /dev/null +++ b/src/test.js @@ -0,0 +1,70 @@ +const Tenor = require('.'); +const dotenv = require('dotenv').config(); + +if (process.env.TENOR_API_KEY === undefined) { + console.error('Please set TENOR_API_KEY in .env'); + process.exit(1); +} + +const tests = [ + // Valid API Key + new Promise((resolve, reject) => { + const tenor = new Tenor(process.env.TENOR_API_KEY); + tenor.configure() + .then((result) => { + console.log('PASS: Valid API Key --', result); + resolve(); + }) + .catch((error) => { + console.error('FAIL: Valid API Key --', error.message); + reject(); + }); + }), + // Invalid API Key + new Promise((resolve, reject) => { + const tenor = new Tenor('invalidApiKey'); + tenor.configure() + .then((result) => { + console.log('FAIL: Invalid API Key'); + reject(); + }) + .catch((error) => { + console.error('PASS: Invalid API Key --', error.response.data.error.message); + resolve(); + }); + }), + // Valid query and limit + new Promise((resolve, reject) => { + const tenor = new Tenor(process.env.TENOR_API_KEY); + // Form the options for the search + const options = { + limit: 5 + }; + + if (process.env.TENOR_CLIENT_KEY) { + options.clientKey = process.env.TENOR_CLIENT_KEY; + } + tenor.search('dog', options) + .then((gifs) => { + if (Array.isArray(gifs) && gifs.length === 5) { + console.log('PASS: valid query and limit --', gifs); // Expected output: Array of five Gif objects + resolve(); + } else { + console.error('FAIL: valid query and limit --', 'Unexpected response format'); // Show only the FAIL message + reject(); + } + }) + .catch((error) => { + console.error('FAIL: valid query and limit --', error.message); // Show only the FAIL message + reject(); + }); + }) +]; + +Promise.all(tests) + .then(() => { + console.log('All tests passed!'); + }) + .catch(() => { + console.log('Some tests failed!'); + });