mirror of
https://github.com/Nezumi-2711/cloudflare-gateway-pihole-scripts.git
synced 2026-09-22 05:31:50 +00:00
Initial version
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
CLOUDFLARE_API_KEY=""
|
||||||
|
CLOUDFLARE_ACCOUNT_ID=""
|
||||||
|
CLOUDFLARE_ACCOUNT_EMAIL=""
|
||||||
|
CLOUDFLARE_LIST_ITEM_LIMIT="300000"
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -7,6 +7,10 @@ yarn-error.log*
|
|||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# CGPS
|
||||||
|
output
|
||||||
|
input.csv
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,45 @@
|
|||||||
# cloudflare-gateway-pihole-scripts
|
# Cloudflare Gateway Pi-hole Scripts (CGPS)
|
||||||
Use Cloudflare Gateway VPN to block ads and tracking domains - free alternative to NextDNS, Pi-hole and Adguard
|
|
||||||
|

|
||||||
|
|
||||||
|
Cloudflare Gateway allows you to create custom rules to filter HTTP, DNS, and network traffic based on your firewall policies. This is a collection of scripts that can be used to get a similar experience as if you were using Pi-hole, but with Cloudflare Gateway - so no servers to maintain or need to buy a Raspberry Pi!
|
||||||
|
|
||||||
|
## About the individual scripts
|
||||||
|
|
||||||
|
- `cf_list_delete.js` - Deletes all lists created by CGPS from Cloudflare Gateway. This is useful for subsequent runs.
|
||||||
|
- `cf_list_create.js` - Takes an input.csv file containing domains and creates lists in Cloudflare Gateway
|
||||||
|
- `cf_gateway_rule_create.js` - Creates a Cloudflare Gateway rule to block all traffic if it matches the lists created by CGPS.
|
||||||
|
- `cf_gateway_rule_delete.js` - Deletes the Cloudflare Gateway rule created by CGPS. Useful for subsequent runs.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Support for hosts files
|
||||||
|
- Full support for domain lists
|
||||||
|
- Automatically cleans up filter lists: removes duplicates, invalid domains, comments and more
|
||||||
|
- Works fully unattended
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. Node.js installed on your machine
|
||||||
|
2. Cloudflare Zero Trust account - the Free plan is enough. Use the Cloudflare documentation for details.
|
||||||
|
3. Cloudflare email, API key (NOT the API token), and account ID
|
||||||
|
4. A filter list of domains you want to block - **max 300,000 domains for the free plan** - in the working directory named `input.csv`. Mullvad provides awesome [DNS blocklists](https://github.com/mullvad/dns-blocklists) that work well with this project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
1. Clone this repository.
|
||||||
|
2. Run `npm install` to install dependencies.
|
||||||
|
3. Copy `.env.example` to `.env` and fill in the values.
|
||||||
|
4. If this is a subsequent run, execute `cf_gateway_rule_delete.js` and `cf_list_delete.js` (in order) to clean up.
|
||||||
|
5. If you're on Linux and haven't downloaded any filters yourself, use the `get_recommended_filters.sh` script to download recommended filter lists (about 250 000 domains).
|
||||||
|
6. Run `cf_list_create.js` to create the lists in Cloudflare Gateway.
|
||||||
|
7. Run `cf_gateway_rule_create.js` to create the firewall rule in Cloudflare Gateway.
|
||||||
|
8. Profit!
|
||||||
|
|
||||||
|
### Running in GitHub Actions
|
||||||
|
|
||||||
|
This project can be run in GitHub Actions so your filter lists will be automatically updated and pushed to Cloudflare Gateway.
|
||||||
|
|
||||||
|
[work in progress]
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const API_TOKEN = process.env.CLOUDFLARE_API_KEY;
|
||||||
|
const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||||
|
const ACCOUNT_EMAIL = process.env.CLOUDFLARE_ACCOUNT_EMAIL;
|
||||||
|
|
||||||
|
// Function to read Cloudflare Zero Trust lists
|
||||||
|
async function getZeroTrustLists() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/lists`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
;(async() => {
|
||||||
|
const lists = await getZeroTrustLists();
|
||||||
|
const filtered_lists = lists.filter(list => list.name.startsWith('CGPS List'));
|
||||||
|
|
||||||
|
let wirefilter_expression = '';
|
||||||
|
|
||||||
|
// Build the wirefilter expression
|
||||||
|
for (const list of filtered_lists) {
|
||||||
|
wirefilter_expression += `dns.fqdn in \$${list.id} or `;
|
||||||
|
}
|
||||||
|
// Remove the trailing ' or '
|
||||||
|
if (wirefilter_expression.endsWith(' or ')) {
|
||||||
|
wirefilter_expression = wirefilter_expression.slice(0, -4);
|
||||||
|
}
|
||||||
|
wirefilter_expression = wirefilter_expression.trim().replace('\n', '');
|
||||||
|
console.log(`Firewall expression contains ${wirefilter_expression.length} characters, and checks against ${filtered_lists.length} filter lists.`)
|
||||||
|
|
||||||
|
const resp = await axios.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/rules`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
"name": "CGPS Filter Lists",
|
||||||
|
"description": "Filter lists created by Cloudflare Gateway Pi-hole Scripts. Avoid editing this rule. Changing the name of this rule will break the script.",
|
||||||
|
"enabled": true,
|
||||||
|
"action": "block",
|
||||||
|
"filters": ["dns"],
|
||||||
|
"traffic": wirefilter_expression,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Success:', resp.data.success);
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const API_TOKEN = process.env.CLOUDFLARE_API_KEY;
|
||||||
|
const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||||
|
const ACCOUNT_EMAIL = process.env.CLOUDFLARE_ACCOUNT_EMAIL;
|
||||||
|
|
||||||
|
// Function to read Cloudflare Zero Trust rules
|
||||||
|
async function getZeroTrustRules() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/rules`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
;(async() => {
|
||||||
|
const rules = await getZeroTrustRules();
|
||||||
|
const [filtered_rule] = rules.filter(rule => rule.name === "CGPS Filter Lists");
|
||||||
|
|
||||||
|
if (!filtered_rule) return console.warn("No rule with matching name found - this is not an issue if you haven't run the create script yet. Exiting.");
|
||||||
|
|
||||||
|
console.log(`Deleting rule ${filtered_rule.name} with ID ${filtered_rule.id}`);
|
||||||
|
|
||||||
|
const resp = await axios.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/rules/${filtered_rule.id}`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Success: ', resp.data.success);
|
||||||
|
await sleep(350); // Cloudflare API rate limit is 1200 requests per 5 minutes, so we sleep for 350ms to be safe
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const fs = require('fs');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const API_TOKEN = process.env.CLOUDFLARE_API_KEY;
|
||||||
|
const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||||
|
const ACCOUNT_EMAIL = process.env.CLOUDFLARE_ACCOUNT_EMAIL;
|
||||||
|
const LIST_ITEM_LIMIT = Number.isSafeInteger(Number(process.env.CLOUDFLARE_LIST_ITEM_LIMIT)) ? Number(process.env.CLOUDFLARE_LIST_ITEM_LIMIT) : 300000;
|
||||||
|
|
||||||
|
console.log(`List item limit set to ${LIST_ITEM_LIMIT}`);
|
||||||
|
|
||||||
|
// Read input.csv and parse domains
|
||||||
|
fs.readFile('input.csv', 'utf8', async (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error reading input.csv:', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert into array and cleanup input
|
||||||
|
const domainValidationPattern = /^(?!-)[A-Za-z0-9-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,6}$/;
|
||||||
|
let domains = data.split('\n').filter(domain => {
|
||||||
|
// Remove entire lines starting with "127.0.0.1" or "::1", empty lines or comments
|
||||||
|
return domain && !domain.startsWith('#') && !domain.startsWith('//') && !domain.startsWith('/*') && !domain.startsWith('*/') && !(domain === '\r');
|
||||||
|
}).map(domain => {
|
||||||
|
// Remove "\r", "0.0.0.0 ", "127.0.0.1 ", "::1 " and similar from domain items
|
||||||
|
return domain
|
||||||
|
.replace('\r', '')
|
||||||
|
.replace('0.0.0.0 ', '')
|
||||||
|
.replace('127.0.0.1 ', '')
|
||||||
|
.replace('::1 ', '')
|
||||||
|
.replace(':: ', '');
|
||||||
|
}).filter(domain => {
|
||||||
|
return domainValidationPattern.test(domain);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trim array to 300,000 domains if it's longer than that
|
||||||
|
if (domains.length > LIST_ITEM_LIMIT) {
|
||||||
|
domains = trimArray(domains, LIST_ITEM_LIMIT);
|
||||||
|
console.warn(`More than ${LIST_ITEM_LIMIT} domains found in input.csv - input has to be trimmed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicates in domains array
|
||||||
|
let uniqueDomains = [];
|
||||||
|
let seen = new Set(); // Use a set to store seen values
|
||||||
|
for (let domain of domains) {
|
||||||
|
if (!seen.has(domain)) { // If the domain is not in the set
|
||||||
|
seen.add(domain); // Add it to the set
|
||||||
|
uniqueDomains.push(domain); // Push the domain to the uniqueDomains array
|
||||||
|
} else { // If the domain is in the set
|
||||||
|
console.warn(`Duplicate domain found: ${domain} - removing`); // Log the duplicate domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace domains array with uniqueDomains array
|
||||||
|
domains = uniqueDomains;
|
||||||
|
|
||||||
|
const listsToCreate = Math.ceil(domains.length / 1000);
|
||||||
|
|
||||||
|
console.log(`Found ${domains.length} valid domains in input.csv after cleanup - ${listsToCreate} list(s) will be created`);
|
||||||
|
|
||||||
|
// Separate domains into chunks of 1000 (Cloudflare list cap)
|
||||||
|
const chunks = chunkArray(domains, 1000);
|
||||||
|
|
||||||
|
// Create Cloudflare Zero Trust lists
|
||||||
|
for (const [index, chunk] of chunks.entries()) {
|
||||||
|
const listName = `CGPS List - Chunk ${index}`;
|
||||||
|
|
||||||
|
let properList = [];
|
||||||
|
|
||||||
|
chunk.forEach(domain => {
|
||||||
|
properList.push({ "value": domain })
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createZeroTrustList(listName, properList, (index+1), listsToCreate);
|
||||||
|
await sleep(350); // Sleep for 350ms between list additions
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error creating list "${listName}":`, error.response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function trimArray(arr, size) {
|
||||||
|
return arr.slice(0, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check if a domain is valid
|
||||||
|
function isValidDomain(domain) {
|
||||||
|
const regex = /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/;
|
||||||
|
return regex.test(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to split an array into chunks
|
||||||
|
function chunkArray(array, chunkSize) {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < array.length; i += chunkSize) {
|
||||||
|
chunks.push(array.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to create a Cloudflare Zero Trust list
|
||||||
|
async function createZeroTrustList(name, items, currentItem, totalItems) {
|
||||||
|
const response = await axios.post(
|
||||||
|
`https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/lists`,
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
type: 'DOMAIN', // Set list type to DOMAIN
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const listId = response.data.result.id;
|
||||||
|
console.log(`Created Zero Trust list "${name}" with ID ${listId} - ${totalItems - currentItem} left`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function percentage(percent, total) {
|
||||||
|
return Math.round((percent / 100) * total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to sleep for a specified duration
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
require("dotenv").config();
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const API_TOKEN = process.env.CLOUDFLARE_API_KEY;
|
||||||
|
const ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
|
||||||
|
const ACCOUNT_EMAIL = process.env.CLOUDFLARE_ACCOUNT_EMAIL;
|
||||||
|
|
||||||
|
// Function to read Cloudflare Zero Trust lists
|
||||||
|
async function getZeroTrustLists() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/lists`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
;(async() => {
|
||||||
|
const lists = await getZeroTrustLists();
|
||||||
|
if (!lists) return console.warn("No file lists found - this is not an issue if it's your first time running this script. Exiting.");
|
||||||
|
const cgps_lists = lists.filter(list => list.name.startsWith('CGPS List'));
|
||||||
|
if (!cgps_lists.length) return console.warn("No lists with matching name found - this is not an issue if you haven't created any filter lists before. Exiting.");
|
||||||
|
|
||||||
|
console.log(`Got ${lists.length} lists, ${cgps_lists.length} of which are CGPS lists that will be deleted.`);
|
||||||
|
|
||||||
|
for (const list of cgps_lists) {
|
||||||
|
console.log(`Deleting list ${list.name} with ID ${list.id}`);
|
||||||
|
const resp = await axios.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/gateway/lists/${list.id}`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${API_TOKEN}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Auth-Email': ACCOUNT_EMAIL,
|
||||||
|
'X-Auth-Key': API_TOKEN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log('Success:', resp.data.success);
|
||||||
|
await sleep(350); // Cloudflare API rate limit is 1200 requests per 5 minutes, so we sleep for 350ms to be safe
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# create an empty input.csv file
|
||||||
|
touch input.csv
|
||||||
|
|
||||||
|
# declare an array of urls
|
||||||
|
urls=(
|
||||||
|
https://raw.githubusercontent.com/mullvad/dns-blocklists/main/output/doh/doh_adblock.txt
|
||||||
|
https://raw.githubusercontent.com/mullvad/dns-blocklists/main/output/doh/doh_gambling.txt
|
||||||
|
https://raw.githubusercontent.com/mullvad/dns-blocklists/main/output/doh/doh_privacy.txt
|
||||||
|
https://raw.githubusercontent.com/FadeMind/hosts.extras/master/add.Risk/hosts
|
||||||
|
https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt
|
||||||
|
https://rescure.me/rescure_domain_blacklist.txt
|
||||||
|
https://adaway.org/hosts.txt
|
||||||
|
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||||
|
)
|
||||||
|
|
||||||
|
# loop through the urls and download each file with curl
|
||||||
|
for url in "${urls[@]}"; do
|
||||||
|
# get the file name from the url
|
||||||
|
file=$(basename "$url")
|
||||||
|
# download the file with curl and save it as file.txt
|
||||||
|
curl -o "$file.txt" "$url"
|
||||||
|
# append the file contents to input.csv and add a newline
|
||||||
|
cat "$file.txt" >> input.csv
|
||||||
|
echo "" >> input.csv
|
||||||
|
# remove the file.txt
|
||||||
|
rm "$file.txt"
|
||||||
|
done
|
||||||
|
|
||||||
|
# print a message when done
|
||||||
|
echo "Done. The input.csv file contains merged data from recommended filter lists."
|
||||||
Generated
+111
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"name": "cloudflare-gateway-pihole-scripts",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"dotenv": "^16.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.0",
|
||||||
|
"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==",
|
||||||
|
"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==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||||
|
"integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||||
|
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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==",
|
||||||
|
"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==",
|
||||||
|
"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==",
|
||||||
|
"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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"dotenv": "^16.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user