Getting ready to publish

This commit is contained in:
Skylar Grant 2024-09-22 11:27:56 -04:00
parent 0e8a2bbd2c
commit d638d813f7
9 changed files with 387 additions and 131 deletions

27
.vscode/launch.json vendored Normal file
View File

@ -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": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\src\\main.js"
},
{
"type": "node",
"request": "launch",
"name": "Test Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\src\\TenorJSv2.test.js"
}
]
}

30
.vscode/snips.code-snippets vendored Normal file
View File

@ -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"
]
}
}

128
package-lock.json generated Normal file
View File

@ -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"
}
}
}

23
package.json Normal file
View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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}`));
}
});
});
}
}
}

109
src/index.js Normal file
View File

@ -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;

View File

@ -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();

70
src/test.js Normal file
View File

@ -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!');
});