├── .env.sample ├── .gitattributes ├── .gitignore ├── .well-known └── ai-plugin.json ├── LICENSE ├── README.md ├── index.js ├── logo.png ├── openapi.yaml ├── package-lock.json ├── package.json └── src └── app.js /.env.sample: -------------------------------------------------------------------------------- 1 | API_KEY="YOUR_API_KEY" 2 | PORT=3333 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | -------------------------------------------------------------------------------- /.well-known/ai-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": "v1", 3 | "name_for_human": "Airport info plugin", 4 | "name_for_model": "AirportInfo", 5 | "description_for_human": "ChatGPT plugin for airport data API. Returns airport info by city name. Aids developers in ChatGPT plugin development.", 6 | "description_for_model": "This plugin interacts with the a Ninja API and returns airports info based on the city the user inputs.", 7 | "auth": { 8 | "type": "none" 9 | }, 10 | "api": { 11 | "type": "openapi", 12 | "url": "http://localhost:3333/openapi.yaml", 13 | "is_user_authenticated": false 14 | }, 15 | "logo_url": "http://localhost:3333/logo.jpg", 16 | "contact_email": "YOUR_EMAIL", 17 | "legal_info_url": "" 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Soos3D 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Plugin Quickstart with Express.js 2 | 3 | Welcome to the ChatGPT Plugin Quickstart repository. This boilerplate is designed to kickstart your journey to develop custom ChatGPT plugins. This version is built using JavaScript and Express.js, providing a robust and scalable foundation for your plugin development. 4 | 5 | ## Project details 6 | 7 | This sample plugin is designed to interact with the API-Ninja's API, showcasing how ChatGPT can seamlessly integrate with external APIs to enhance its capabilities. 8 | 9 | The plugin is equipped with a single endpoint that fetches airport data based on a city name provided by the user. This serves as a simple yet functional example to demonstrate the potential of ChatGPT plugins. 10 | 11 | ## Quickstart 12 | 13 | 1. Clone the repository: 14 | 15 | ```sh 16 | git clone https://github.com/soos3d/chatgpt-plugin-development-quickstart-express.git 17 | ``` 18 | 19 | 2. Install dependencies: 20 | 21 | ```sh 22 | npm install 23 | ``` 24 | 25 | 3. Edit `.env.sample` file: 26 | 27 | Edit the `.env.sample` file to add your API Ninjas API key, then rename it to `.env`. 28 | 29 | Get your API key on the [API Ninjas website](https://api-ninjas.com/api). 30 | 31 | 4. Serve the plugin: 32 | 33 | ```sh 34 | node index 35 | ``` 36 | 37 | The plugin is now running on `http://localhost:3333` 38 | 39 | * Optional: I recommend utilizing Nodemon for your server applications for a smoother development process. This tool offers the convenience of automatic server restarts whenever changes are saved, enhancing your efficiency and productivity. 40 | 41 | ```sh 42 | npm install -g nodemon # or using yarn: yarn global add nodemon 43 | ``` 44 | 45 | * Serve the plugin with 46 | 47 | ```sh 48 | nodemon index 49 | ``` 50 | 51 | 5. Install the plugin in ChatGPT: 52 | 53 | Navigate to the **Plugin Store** and select the **Develop your own plugin** option. In the provided field, enter `localhost:3333`. The system will then locate the manifest and the OpenAPI schema, installing the plugin for you. 54 | 55 | ## How Does a ChatGPT Plugin Work? 56 | 57 | The operation of a ChatGPT plugin is straightforward and can be broken down into four key components: 58 | 59 | 1. **An application**: There is an application that performs a specific function. This could range from executing an API call to retrieve data or interact with other services to implementing various types of logic, such as performing arithmetic operations or even handling more complex tasks. 60 | 61 | 2. **A server**: There is a server with endpoints that facilitate communication with your application and invoke its functions. ChatGPT interacts with the plugin through this server. 62 | 63 | 3. **OpenAPI Schema**: This is a comprehensive documentation of the available server endpoints. ChatGPT uses this schema to understand the plugin's capabilities and determine when to call a specific endpoint. 64 | 65 | 4. **Plugin manifest**: A JSON file that contains detailed information about the plugin. It provides ChatGPT with a clear understanding of the plugin's purpose and functionality and the URLs needed to locate the server and the logo. 66 | 67 | Upon installing a plugin via the ChatGPT user interface, the system establishes a connection to the server, locates the manifest and the OpenAPI schema, and subsequently prepares the plugin for use. 68 | 69 | ## The ChatGPT plugin structure 70 | 71 | The fundamental structure of this plugin is outlined below. Please note that it can be tailored to meet your specific requirements. 72 | 73 | ```sh 74 | root-directory 75 | │ 76 | ├── .well-known 77 | │ └── ai-plugin.json 78 | │ 79 | ├── src 80 | │ └── app.js 81 | │ 82 | ├── .env 83 | │ 84 | ├── index.js 85 | │ 86 | ├── openapi.yaml 87 | │ 88 | └── logo.png 89 | 90 | ``` 91 | 92 | In this structure: 93 | 94 | * .well-known/ai-plugin.json is the plugin manifest. 95 | * src/app.js is the main application file. 96 | * .env is the environment variables file. 97 | * index.js is the main server file. 98 | * openapi.yaml is the OpenAPI schema. 99 | * logo.png is the logo of the plugin. 100 | 101 | ## The application 102 | 103 | In this example, the application is straightforward and is the `app.js` file. It is a simple Node.js module that fetches airport data for a given city using the API Ninjas Airports API. Here's a step-by-step breakdown: 104 | 105 | 1. `require('dotenv').config();` - This line loads environment variables from a `.env` file into `process.env`. This is a common practice to keep sensitive data, like API keys, out of the code. 106 | 107 | 2. `const axios = require('axios');` - This line imports the Axios library, a popular, promise-based HTTP client for making requests. 108 | 109 | 3. `const API_KEY = process.env.API_KEY` - This line retrieves the API key from the environment variables and assigns it to `API_KEY`. 110 | 111 | 4. `async function getAirportData(city) {...}` - This is an asynchronous function that fetches airport data for a given city. 112 | - Inside this function, an `options` object is created with the necessary details for the API request: the HTTP method, the URL (which includes the city name), and the headers (which includes the API key). 113 | - A `try/catch` block is used to handle potential errors during the API request. If the request is successful, the function returns the data from the response. If an error occurs, it logs the error message to the console. 114 | 115 | 5. `module.exports = { getAirportData }` - This line exports the `getAirportData` function so it can be imported and used in the server application. 116 | 117 | This is the application itself. 118 | 119 | ## The server 120 | 121 | The server is the `index.js` file. 122 | 123 | This code sets up a simple Express.js server with various endpoints to serve a ChatGPT plugin. Here's a step-by-step breakdown: 124 | 125 | 1. The code begins by importing the necessary modules, including Express, path, cors (for handling Cross-Origin Resource Sharing neessery for ChatGPT to find the plugin), fs (for file system operations), and body-parser (for parsing incoming request bodies). It also imports a custom module, `getAirportData`, from `./src/app`. 126 | 127 | 2. The Express application is initialized, and the port number is set based on the environment variable `PORT` or defaults to 3000 if `PORT` is not set. 128 | 129 | 3. The application is configured to parse JSON in the body of incoming requests using `bodyParser.json()`. 130 | 131 | 4. CORS is configured to allow requests from `https://chat.openai.com` and to send a 200 status code for successful preflight requests for compatibility with some older browsers. 132 | 133 | 5. A helper function, `readFileAndSend`, is defined. This function reads a file and sends its contents as a response with the appropriate content type. 134 | 135 | 6. Several routes are set up: 136 | - The `/.well-known/ai-plugin.json` route serves the plugin manifest. 137 | - The `/openapi.yaml` route serves the OpenAPI schema. 138 | - The `/logo.jpg` route serves the plugin's logo image. 139 | - The `/airportData` route accepts POST requests and fetches airport data for the city specified in the request body. If an error occurs while fetching the data, it sends a 500 status code and an error message. 140 | 141 | 7. A catch-all route is set up to handle any other requests. This route sends a 501 status code and a message indicating that the requested method and path are not implemented. 142 | 143 | 8. Finally, the server is started and listens for requests on the specified port. A message is logged to the console indicating that the server is running and on which port. 144 | 145 | This serves as the heart of the plugin, enabling ChatGPT to establish a connection, locate the manifest and OpenAPI schema to comprehend its functionality, and ultimately access the endpoints that interact with the application. 146 | 147 | ## The OpenAPI schema 148 | 149 | The OpenAPI schema, also known as an OpenAPI specification, is a powerful tool for describing and documenting APIs. It's a standard, language-agnostic specification for RESTful APIs, which allows both humans and computers to understand the capabilities of a service without needing to access the source code, additional documentation, or network traffic inspection. 150 | 151 | In an OpenAPI schema, you define all the aspects of your API. This includes: 152 | 153 | 1. **Endpoints (Paths)**: These are the routes or URLs where your API can be accessed. For example, in a weather API, you might have an endpoint like `/weather` to get the current weather. 154 | 155 | 2. **Operations**: These are the actions that can be performed on each endpoint, such as GET, POST, PUT, DELETE, etc. Each operation will have its parameters, request body, and responses defined. 156 | 157 | 3. **Parameters and Request Body**: These define what data can be passed to the API, either as part of the URL, as query parameters, or in the body of a POST or PUT request. 158 | 159 | 4. **Response**: This defines what the API returns after an operation, including various status codes, headers, and the body of the response. 160 | 161 | 5. **Models (Schemas)**: These are definitions of the data structures that the API uses. For example, a User model might include fields for the user's name, email, and password. 162 | 163 | By using an OpenAPI schema, developers can understand how to use your API without having to read through all your code or rely on extensive external documentation. It also enables the use of automated tools for tasks like generating code, testing, or creating interactive API documentation. 164 | 165 | 166 | > The 'description' field of the endpoint serves as a prompt for ChatGPT, providing an area where you can include additional details. Please be aware that this field has a character limit of 300. 167 | 168 | ## The plugin manifest 169 | 170 | The manifest file is a JSON file that provides essential information about the plugin to ChatGPT. Here's a breakdown of what each field represents: 171 | 172 | - `schema_version`: This field indicates the version of the schema that the plugin is using. In this case, it's "v1". 173 | 174 | - `name_for_human`: This is the name of the plugin as it should be displayed to humans. Here, it's the "Airport info plugin". 175 | 176 | - `name_for_model`: This is the name of the plugin as it should be referred to by the model. Here, it's "AirportInfo". 177 | 178 | - `description_for_human`: This is a description of the plugin for humans. It should provide a clear explanation of what the plugin does. In this case, it's "ChatGPT plugin for airport data API. Returns airport info by city name. Aids developers in ChatGPT plugin development." 179 | 180 | - `description_for_model`: This is a description of the plugin for the model. It should provide a clear explanation of what the plugin does. In this case, "This plugin interacts with the Ninja API and returns airports info based on the city the user inputs." 181 | 182 | - `auth`: This field describes the type of authentication required by the plugin. In this case, it's "none", meaning no authentication is required. 183 | 184 | - `api`: This field provides information about the API used by the plugin. It includes the type of API (in this case, "openapi"), the URL where the OpenAPI schema can be found, and a boolean indicating whether user authentication is required. 185 | 186 | - `logo_url`: This is the URL where the logo for the plugin can be found. 187 | 188 | - `contact_email`: This is the contact email for the developer or team responsible for the plugin. 189 | 190 | - `legal_info_url`: This is the URL where legal information about the plugin can be found. In this case, it's empty. 191 | 192 | 193 | > Find more information on the [OpenAI docs](https://platform.openai.com/docs/plugins/introduction). 194 | 195 | ## Conclusion 196 | 197 | In conclusion, this tutorial provides a comprehensive guide to developing custom plugins for ChatGPT using JavaScript and Express.js. It offers a practical example of how to create a plugin that interacts with an external API (in this case, the API-Ninjas Airports API) to fetch and return airport data based on a city name provided by the user. 198 | 199 | The tutorial covers all the key components of a ChatGPT plugin, including the application that performs the specific function, the server with endpoints that facilitate communication with the application, the OpenAPI schema that documents the available server endpoints, and the plugin manifest that provides essential information about the plugin to ChatGPT. 200 | 201 | By following this guide, developers can gain a solid understanding of how to create a functional ChatGPT plugin, from setting up the server and defining the endpoints, to creating the OpenAPI schema and the plugin manifest. The tutorial also provides valuable tips and best practices for developing plugins, such as using Nodemon for automatic server restarts during development, and the importance of providing clear and concise descriptions in the OpenAPI schema and the plugin manifest. 202 | 203 | This knowledge can be extremely useful for developers looking to extend the capabilities of ChatGPT and create more interactive and dynamic conversational experiences. Whether you're looking to integrate ChatGPT with external APIs, databases, or other services, developing custom plugins can open up a world of possibilities for enhancing the functionality and versatility of ChatGPT. 204 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Import required modules 2 | const express = require('express'); 3 | const path = require('path'); 4 | const cors = require('cors'); 5 | const fs = require('fs'); 6 | const bodyParser = require('body-parser'); 7 | require('dotenv').config(); 8 | 9 | // Import custom modules 10 | const { getAirportData } = require('./src/app'); 11 | 12 | // Initialize Express application 13 | const app = express(); 14 | 15 | // Set the port number 16 | const PORT = process.env.PORT || 3000; 17 | 18 | // Configure Express to parse JSON 19 | app.use(bodyParser.json()); 20 | 21 | // Configure CORS options 22 | const corsOptions = { 23 | origin: 'https://chat.openai.com', 24 | optionsSuccessStatus: 200 // For compatibility with legacy browsers (IE11, various SmartTVs) 25 | }; 26 | 27 | // Use CORS middleware with the specified options 28 | app.use(cors(corsOptions)); 29 | 30 | // Define content types for different file extensions 31 | const contentTypes = { 32 | '.json': 'application/json', 33 | '.yaml': 'text/yaml', 34 | }; 35 | 36 | // Function to read a file and send its contents as a response 37 | const readFileAndSend = (filePath, res, contentType) => { 38 | fs.readFile(filePath, 'utf8', (err, data) => { 39 | if (err) { 40 | console.error(`Error reading file: ${err}`); 41 | return res.status(500).send('An error occurred while reading the file.'); 42 | } 43 | res.setHeader('Content-Type', contentType); 44 | res.send(data); 45 | }); 46 | }; 47 | 48 | // Route to serve the plugin manifest 49 | app.get('/.well-known/ai-plugin.json', (req, res) => { 50 | readFileAndSend(path.join(__dirname, '.well-known/ai-plugin.json'), res, contentTypes['.json']); 51 | }); 52 | 53 | // Route to serve the OpenAPI schema 54 | app.get('/openapi.yaml', (req, res) => { 55 | readFileAndSend(path.join(__dirname, 'openapi.yaml'), res, contentTypes['.yaml']); 56 | }); 57 | 58 | // Route to serve the logo image 59 | app.get('/logo.jpg', (req, res) => { 60 | res.sendFile(path.join(__dirname, 'logo.png')); 61 | }); 62 | 63 | // Route to fetch airport data 64 | app.post('/airportData', async (req, res) => { 65 | const city = req.body.city; 66 | try { 67 | const data = await getAirportData(city); 68 | res.json(data); 69 | } catch (error) { 70 | console.error(`Error fetching airport data: ${error}`); 71 | res.status(500).send('Error occurred while fetching airport data'); 72 | } 73 | }); 74 | 75 | // Default route for unimplemented methods and paths 76 | app.all('/*', (req, res) => { 77 | res.status(501).send(`Method ${req.method} not implemented for path ${req.path}`); 78 | }); 79 | 80 | // Start the server 81 | app.listen(PORT, () => { 82 | console.log(`Server is running on http://localhost:${PORT}`); 83 | }); 84 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soos3d/chatgpt-plugin-development-quickstart-express/a67e5554a717789afcbb46b3f3d6713681b3b6ee/logo.png -------------------------------------------------------------------------------- /openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Airports API 4 | description: Interact with the airport API 5 | version: 1.0.0 6 | servers: 7 | - url: http://localhost:3333 8 | paths: 9 | /airportData: 10 | post: 11 | summary: Get airpot data based on a city 12 | operationId: airportData 13 | description: This API returns the details of airports present in the city gave by the user. Only take the name of the city, this endpoint does not accept states or regions and returns all the airports matching city. 14 | requestBody: 15 | required: true 16 | content: 17 | application/json: 18 | schema: 19 | type: object 20 | properties: 21 | city: 22 | type: string 23 | description: The city to find airports at 24 | responses: 25 | '200': 26 | description: Request successful 27 | content: 28 | application/json: 29 | schema: 30 | type: array 31 | items: 32 | type: object 33 | description: The airports details 34 | '404': 35 | description: Not found 36 | '500': 37 | description: Server error -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-plugin-development-quickstart-express", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "chatgpt-plugin-development-quickstart-express", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "axios": "^1.4.0", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.0.3", 15 | "express": "^4.18.2" 16 | }, 17 | "devDependencies": {} 18 | }, 19 | "node_modules/accepts": { 20 | "version": "1.3.8", 21 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 22 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 23 | "dependencies": { 24 | "mime-types": "~2.1.34", 25 | "negotiator": "0.6.3" 26 | }, 27 | "engines": { 28 | "node": ">= 0.6" 29 | } 30 | }, 31 | "node_modules/array-flatten": { 32 | "version": "1.1.1", 33 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 34 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 35 | }, 36 | "node_modules/asynckit": { 37 | "version": "0.4.0", 38 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 39 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 40 | }, 41 | "node_modules/axios": { 42 | "version": "1.4.0", 43 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", 44 | "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", 45 | "dependencies": { 46 | "follow-redirects": "^1.15.0", 47 | "form-data": "^4.0.0", 48 | "proxy-from-env": "^1.1.0" 49 | } 50 | }, 51 | "node_modules/body-parser": { 52 | "version": "1.20.1", 53 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 54 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 55 | "dependencies": { 56 | "bytes": "3.1.2", 57 | "content-type": "~1.0.4", 58 | "debug": "2.6.9", 59 | "depd": "2.0.0", 60 | "destroy": "1.2.0", 61 | "http-errors": "2.0.0", 62 | "iconv-lite": "0.4.24", 63 | "on-finished": "2.4.1", 64 | "qs": "6.11.0", 65 | "raw-body": "2.5.1", 66 | "type-is": "~1.6.18", 67 | "unpipe": "1.0.0" 68 | }, 69 | "engines": { 70 | "node": ">= 0.8", 71 | "npm": "1.2.8000 || >= 1.4.16" 72 | } 73 | }, 74 | "node_modules/bytes": { 75 | "version": "3.1.2", 76 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 77 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 78 | "engines": { 79 | "node": ">= 0.8" 80 | } 81 | }, 82 | "node_modules/call-bind": { 83 | "version": "1.0.2", 84 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 85 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 86 | "dependencies": { 87 | "function-bind": "^1.1.1", 88 | "get-intrinsic": "^1.0.2" 89 | }, 90 | "funding": { 91 | "url": "https://github.com/sponsors/ljharb" 92 | } 93 | }, 94 | "node_modules/combined-stream": { 95 | "version": "1.0.8", 96 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 97 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 98 | "dependencies": { 99 | "delayed-stream": "~1.0.0" 100 | }, 101 | "engines": { 102 | "node": ">= 0.8" 103 | } 104 | }, 105 | "node_modules/content-disposition": { 106 | "version": "0.5.4", 107 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 108 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 109 | "dependencies": { 110 | "safe-buffer": "5.2.1" 111 | }, 112 | "engines": { 113 | "node": ">= 0.6" 114 | } 115 | }, 116 | "node_modules/content-type": { 117 | "version": "1.0.5", 118 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 119 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 120 | "engines": { 121 | "node": ">= 0.6" 122 | } 123 | }, 124 | "node_modules/cookie": { 125 | "version": "0.5.0", 126 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 127 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 128 | "engines": { 129 | "node": ">= 0.6" 130 | } 131 | }, 132 | "node_modules/cookie-signature": { 133 | "version": "1.0.6", 134 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 135 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 136 | }, 137 | "node_modules/cors": { 138 | "version": "2.8.5", 139 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 140 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 141 | "dependencies": { 142 | "object-assign": "^4", 143 | "vary": "^1" 144 | }, 145 | "engines": { 146 | "node": ">= 0.10" 147 | } 148 | }, 149 | "node_modules/debug": { 150 | "version": "2.6.9", 151 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 152 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 153 | "dependencies": { 154 | "ms": "2.0.0" 155 | } 156 | }, 157 | "node_modules/delayed-stream": { 158 | "version": "1.0.0", 159 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 160 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 161 | "engines": { 162 | "node": ">=0.4.0" 163 | } 164 | }, 165 | "node_modules/depd": { 166 | "version": "2.0.0", 167 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 168 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 169 | "engines": { 170 | "node": ">= 0.8" 171 | } 172 | }, 173 | "node_modules/destroy": { 174 | "version": "1.2.0", 175 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 176 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 177 | "engines": { 178 | "node": ">= 0.8", 179 | "npm": "1.2.8000 || >= 1.4.16" 180 | } 181 | }, 182 | "node_modules/dotenv": { 183 | "version": "16.0.3", 184 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", 185 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", 186 | "engines": { 187 | "node": ">=12" 188 | } 189 | }, 190 | "node_modules/ee-first": { 191 | "version": "1.1.1", 192 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 193 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 194 | }, 195 | "node_modules/encodeurl": { 196 | "version": "1.0.2", 197 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 198 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 199 | "engines": { 200 | "node": ">= 0.8" 201 | } 202 | }, 203 | "node_modules/escape-html": { 204 | "version": "1.0.3", 205 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 206 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 207 | }, 208 | "node_modules/etag": { 209 | "version": "1.8.1", 210 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 211 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 212 | "engines": { 213 | "node": ">= 0.6" 214 | } 215 | }, 216 | "node_modules/express": { 217 | "version": "4.18.2", 218 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 219 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 220 | "dependencies": { 221 | "accepts": "~1.3.8", 222 | "array-flatten": "1.1.1", 223 | "body-parser": "1.20.1", 224 | "content-disposition": "0.5.4", 225 | "content-type": "~1.0.4", 226 | "cookie": "0.5.0", 227 | "cookie-signature": "1.0.6", 228 | "debug": "2.6.9", 229 | "depd": "2.0.0", 230 | "encodeurl": "~1.0.2", 231 | "escape-html": "~1.0.3", 232 | "etag": "~1.8.1", 233 | "finalhandler": "1.2.0", 234 | "fresh": "0.5.2", 235 | "http-errors": "2.0.0", 236 | "merge-descriptors": "1.0.1", 237 | "methods": "~1.1.2", 238 | "on-finished": "2.4.1", 239 | "parseurl": "~1.3.3", 240 | "path-to-regexp": "0.1.7", 241 | "proxy-addr": "~2.0.7", 242 | "qs": "6.11.0", 243 | "range-parser": "~1.2.1", 244 | "safe-buffer": "5.2.1", 245 | "send": "0.18.0", 246 | "serve-static": "1.15.0", 247 | "setprototypeof": "1.2.0", 248 | "statuses": "2.0.1", 249 | "type-is": "~1.6.18", 250 | "utils-merge": "1.0.1", 251 | "vary": "~1.1.2" 252 | }, 253 | "engines": { 254 | "node": ">= 0.10.0" 255 | } 256 | }, 257 | "node_modules/finalhandler": { 258 | "version": "1.2.0", 259 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 260 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 261 | "dependencies": { 262 | "debug": "2.6.9", 263 | "encodeurl": "~1.0.2", 264 | "escape-html": "~1.0.3", 265 | "on-finished": "2.4.1", 266 | "parseurl": "~1.3.3", 267 | "statuses": "2.0.1", 268 | "unpipe": "~1.0.0" 269 | }, 270 | "engines": { 271 | "node": ">= 0.8" 272 | } 273 | }, 274 | "node_modules/follow-redirects": { 275 | "version": "1.15.2", 276 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 277 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 278 | "funding": [ 279 | { 280 | "type": "individual", 281 | "url": "https://github.com/sponsors/RubenVerborgh" 282 | } 283 | ], 284 | "engines": { 285 | "node": ">=4.0" 286 | }, 287 | "peerDependenciesMeta": { 288 | "debug": { 289 | "optional": true 290 | } 291 | } 292 | }, 293 | "node_modules/form-data": { 294 | "version": "4.0.0", 295 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 296 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 297 | "dependencies": { 298 | "asynckit": "^0.4.0", 299 | "combined-stream": "^1.0.8", 300 | "mime-types": "^2.1.12" 301 | }, 302 | "engines": { 303 | "node": ">= 6" 304 | } 305 | }, 306 | "node_modules/forwarded": { 307 | "version": "0.2.0", 308 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 309 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 310 | "engines": { 311 | "node": ">= 0.6" 312 | } 313 | }, 314 | "node_modules/fresh": { 315 | "version": "0.5.2", 316 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 317 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 318 | "engines": { 319 | "node": ">= 0.6" 320 | } 321 | }, 322 | "node_modules/function-bind": { 323 | "version": "1.1.1", 324 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 325 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 326 | }, 327 | "node_modules/get-intrinsic": { 328 | "version": "1.2.1", 329 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", 330 | "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", 331 | "dependencies": { 332 | "function-bind": "^1.1.1", 333 | "has": "^1.0.3", 334 | "has-proto": "^1.0.1", 335 | "has-symbols": "^1.0.3" 336 | }, 337 | "funding": { 338 | "url": "https://github.com/sponsors/ljharb" 339 | } 340 | }, 341 | "node_modules/has": { 342 | "version": "1.0.3", 343 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 344 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 345 | "dependencies": { 346 | "function-bind": "^1.1.1" 347 | }, 348 | "engines": { 349 | "node": ">= 0.4.0" 350 | } 351 | }, 352 | "node_modules/has-proto": { 353 | "version": "1.0.1", 354 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 355 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 356 | "engines": { 357 | "node": ">= 0.4" 358 | }, 359 | "funding": { 360 | "url": "https://github.com/sponsors/ljharb" 361 | } 362 | }, 363 | "node_modules/has-symbols": { 364 | "version": "1.0.3", 365 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 366 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 367 | "engines": { 368 | "node": ">= 0.4" 369 | }, 370 | "funding": { 371 | "url": "https://github.com/sponsors/ljharb" 372 | } 373 | }, 374 | "node_modules/http-errors": { 375 | "version": "2.0.0", 376 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 377 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 378 | "dependencies": { 379 | "depd": "2.0.0", 380 | "inherits": "2.0.4", 381 | "setprototypeof": "1.2.0", 382 | "statuses": "2.0.1", 383 | "toidentifier": "1.0.1" 384 | }, 385 | "engines": { 386 | "node": ">= 0.8" 387 | } 388 | }, 389 | "node_modules/iconv-lite": { 390 | "version": "0.4.24", 391 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 392 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 393 | "dependencies": { 394 | "safer-buffer": ">= 2.1.2 < 3" 395 | }, 396 | "engines": { 397 | "node": ">=0.10.0" 398 | } 399 | }, 400 | "node_modules/inherits": { 401 | "version": "2.0.4", 402 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 403 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 404 | }, 405 | "node_modules/ipaddr.js": { 406 | "version": "1.9.1", 407 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 408 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 409 | "engines": { 410 | "node": ">= 0.10" 411 | } 412 | }, 413 | "node_modules/media-typer": { 414 | "version": "0.3.0", 415 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 416 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 417 | "engines": { 418 | "node": ">= 0.6" 419 | } 420 | }, 421 | "node_modules/merge-descriptors": { 422 | "version": "1.0.1", 423 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 424 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 425 | }, 426 | "node_modules/methods": { 427 | "version": "1.1.2", 428 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 429 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 430 | "engines": { 431 | "node": ">= 0.6" 432 | } 433 | }, 434 | "node_modules/mime": { 435 | "version": "1.6.0", 436 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 437 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 438 | "bin": { 439 | "mime": "cli.js" 440 | }, 441 | "engines": { 442 | "node": ">=4" 443 | } 444 | }, 445 | "node_modules/mime-db": { 446 | "version": "1.52.0", 447 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 448 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 449 | "engines": { 450 | "node": ">= 0.6" 451 | } 452 | }, 453 | "node_modules/mime-types": { 454 | "version": "2.1.35", 455 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 456 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 457 | "dependencies": { 458 | "mime-db": "1.52.0" 459 | }, 460 | "engines": { 461 | "node": ">= 0.6" 462 | } 463 | }, 464 | "node_modules/ms": { 465 | "version": "2.0.0", 466 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 467 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 468 | }, 469 | "node_modules/negotiator": { 470 | "version": "0.6.3", 471 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 472 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 473 | "engines": { 474 | "node": ">= 0.6" 475 | } 476 | }, 477 | "node_modules/object-assign": { 478 | "version": "4.1.1", 479 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 480 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 481 | "engines": { 482 | "node": ">=0.10.0" 483 | } 484 | }, 485 | "node_modules/object-inspect": { 486 | "version": "1.12.3", 487 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 488 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 489 | "funding": { 490 | "url": "https://github.com/sponsors/ljharb" 491 | } 492 | }, 493 | "node_modules/on-finished": { 494 | "version": "2.4.1", 495 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 496 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 497 | "dependencies": { 498 | "ee-first": "1.1.1" 499 | }, 500 | "engines": { 501 | "node": ">= 0.8" 502 | } 503 | }, 504 | "node_modules/parseurl": { 505 | "version": "1.3.3", 506 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 507 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 508 | "engines": { 509 | "node": ">= 0.8" 510 | } 511 | }, 512 | "node_modules/path-to-regexp": { 513 | "version": "0.1.7", 514 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 515 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 516 | }, 517 | "node_modules/proxy-addr": { 518 | "version": "2.0.7", 519 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 520 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 521 | "dependencies": { 522 | "forwarded": "0.2.0", 523 | "ipaddr.js": "1.9.1" 524 | }, 525 | "engines": { 526 | "node": ">= 0.10" 527 | } 528 | }, 529 | "node_modules/proxy-from-env": { 530 | "version": "1.1.0", 531 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 532 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 533 | }, 534 | "node_modules/qs": { 535 | "version": "6.11.0", 536 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 537 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 538 | "dependencies": { 539 | "side-channel": "^1.0.4" 540 | }, 541 | "engines": { 542 | "node": ">=0.6" 543 | }, 544 | "funding": { 545 | "url": "https://github.com/sponsors/ljharb" 546 | } 547 | }, 548 | "node_modules/range-parser": { 549 | "version": "1.2.1", 550 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 551 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 552 | "engines": { 553 | "node": ">= 0.6" 554 | } 555 | }, 556 | "node_modules/raw-body": { 557 | "version": "2.5.1", 558 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 559 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 560 | "dependencies": { 561 | "bytes": "3.1.2", 562 | "http-errors": "2.0.0", 563 | "iconv-lite": "0.4.24", 564 | "unpipe": "1.0.0" 565 | }, 566 | "engines": { 567 | "node": ">= 0.8" 568 | } 569 | }, 570 | "node_modules/safe-buffer": { 571 | "version": "5.2.1", 572 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 573 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 574 | "funding": [ 575 | { 576 | "type": "github", 577 | "url": "https://github.com/sponsors/feross" 578 | }, 579 | { 580 | "type": "patreon", 581 | "url": "https://www.patreon.com/feross" 582 | }, 583 | { 584 | "type": "consulting", 585 | "url": "https://feross.org/support" 586 | } 587 | ] 588 | }, 589 | "node_modules/safer-buffer": { 590 | "version": "2.1.2", 591 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 592 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 593 | }, 594 | "node_modules/send": { 595 | "version": "0.18.0", 596 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 597 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 598 | "dependencies": { 599 | "debug": "2.6.9", 600 | "depd": "2.0.0", 601 | "destroy": "1.2.0", 602 | "encodeurl": "~1.0.2", 603 | "escape-html": "~1.0.3", 604 | "etag": "~1.8.1", 605 | "fresh": "0.5.2", 606 | "http-errors": "2.0.0", 607 | "mime": "1.6.0", 608 | "ms": "2.1.3", 609 | "on-finished": "2.4.1", 610 | "range-parser": "~1.2.1", 611 | "statuses": "2.0.1" 612 | }, 613 | "engines": { 614 | "node": ">= 0.8.0" 615 | } 616 | }, 617 | "node_modules/send/node_modules/ms": { 618 | "version": "2.1.3", 619 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 620 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 621 | }, 622 | "node_modules/serve-static": { 623 | "version": "1.15.0", 624 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 625 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 626 | "dependencies": { 627 | "encodeurl": "~1.0.2", 628 | "escape-html": "~1.0.3", 629 | "parseurl": "~1.3.3", 630 | "send": "0.18.0" 631 | }, 632 | "engines": { 633 | "node": ">= 0.8.0" 634 | } 635 | }, 636 | "node_modules/setprototypeof": { 637 | "version": "1.2.0", 638 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 639 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 640 | }, 641 | "node_modules/side-channel": { 642 | "version": "1.0.4", 643 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 644 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 645 | "dependencies": { 646 | "call-bind": "^1.0.0", 647 | "get-intrinsic": "^1.0.2", 648 | "object-inspect": "^1.9.0" 649 | }, 650 | "funding": { 651 | "url": "https://github.com/sponsors/ljharb" 652 | } 653 | }, 654 | "node_modules/statuses": { 655 | "version": "2.0.1", 656 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 657 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 658 | "engines": { 659 | "node": ">= 0.8" 660 | } 661 | }, 662 | "node_modules/toidentifier": { 663 | "version": "1.0.1", 664 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 665 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 666 | "engines": { 667 | "node": ">=0.6" 668 | } 669 | }, 670 | "node_modules/type-is": { 671 | "version": "1.6.18", 672 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 673 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 674 | "dependencies": { 675 | "media-typer": "0.3.0", 676 | "mime-types": "~2.1.24" 677 | }, 678 | "engines": { 679 | "node": ">= 0.6" 680 | } 681 | }, 682 | "node_modules/unpipe": { 683 | "version": "1.0.0", 684 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 685 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 686 | "engines": { 687 | "node": ">= 0.8" 688 | } 689 | }, 690 | "node_modules/utils-merge": { 691 | "version": "1.0.1", 692 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 693 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 694 | "engines": { 695 | "node": ">= 0.4.0" 696 | } 697 | }, 698 | "node_modules/vary": { 699 | "version": "1.1.2", 700 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 701 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 702 | "engines": { 703 | "node": ">= 0.8" 704 | } 705 | } 706 | } 707 | } 708 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^1.4.0", 4 | "cors": "^2.8.5", 5 | "dotenv": "^16.0.3", 6 | "express": "^4.18.2" 7 | }, 8 | "name": "chatgpt-plugin-development-quickstart-express", 9 | "description": "This repository is a boilerplate for a ChatGPT plugin. This will allow you to easily develop your own. This version is made in JavaScript using Express.js.", 10 | "version": "1.0.0", 11 | "main": "index.js", 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [ 16 | "chat", 17 | "gpt", 18 | "plugin", 19 | "api", 20 | "example", 21 | "quickstart" 22 | ], 23 | "author": "Davide Zambiasi", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | // Import required modules 2 | const dotenv = require('dotenv'); 3 | const axios = require('axios'); 4 | 5 | // Load environment variables from .env file 6 | dotenv.config(); 7 | 8 | // Retrieve API key from environment variables 9 | const API_KEY = process.env.API_KEY; 10 | 11 | /** 12 | * Fetches airport data for a given city using the API Ninjas Airports API. 13 | * 14 | * @param {string} city - The city for which to fetch airport data. 15 | * @returns {Promise} The airport data for the specified city. 16 | * @throws {Error} If an error occurs during the API request. 17 | */ 18 | async function getAirportData(city) { 19 | const options = { 20 | method: 'GET', 21 | url: `https://api.api-ninjas.com/v1/airports?name=${city}`, 22 | headers: { 23 | 'X-Api-Key': API_KEY 24 | } 25 | }; 26 | 27 | try { 28 | const response = await axios.request(options); 29 | return response.data; 30 | } catch (error) { 31 | 32 | console.error(`An error occurred while fetching airport data: ${error.response.data}`); 33 | throw error; 34 | } 35 | } 36 | 37 | module.exports = { 38 | getAirportData 39 | }; 40 | --------------------------------------------------------------------------------