UNPKG

@moartube/moartube-node

Version:

A free, open-source, self-hosted, anonymous, decentralized video/live stream platform. Scalable via Cloudflare, works in the cloud or from home WiFi.

904 lines (719 loc) 35.9 kB
const axios = require('axios').default; const path = require('path'); const fs = require('fs'); const { logDebugMessageToConsole } = require('./logger'); const { getNodeSettings, getIsDeveloperMode, getNodebaseUrl, getExternalVideosBaseUrl, getExternalResourcesBaseUrl } = require('../utils/helpers'); const { getVideosDirectoryPath } = require('../utils/paths'); const { performDatabaseReadJob_ALL, performDatabaseReadJob_GET } = require('../utils/database'); const { s3_listObjectsWithPrefix } = require('../utils/s3-communications'); async function cloudflare_purgeWatchPages(videoIds) { try { const nodeBaseUrl = getNodebaseUrl(); const files = []; for (const videoId of videoIds) { files.push(`${nodeBaseUrl}/watch?v=${videoId}`); } await cloudflare_purgeCache(files, 'cloudflare_purgeWatchPages'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeAllWatchPages() { try { const videos = await performDatabaseReadJob_ALL('SELECT video_id FROM videos', []); const videoIds = videos.map(video => video.video_id); await cloudflare_purgeWatchPages(videoIds); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeNodePage() { try { const videos = await performDatabaseReadJob_ALL('SELECT tags FROM videos', []); const tags = Array.from(new Set(videos.map(video => video.tags.split(',')).flat())); const nodeBaseUrl = getNodebaseUrl(); const files = []; files.push(`${nodeBaseUrl}/node`); files.push(`${nodeBaseUrl}/node?searchTerm=&sortTerm=latest&tagTerm=`); files.push(`${nodeBaseUrl}/node?searchTerm=&sortTerm=popular&tagTerm=`); files.push(`${nodeBaseUrl}/node?searchTerm=&sortTerm=oldest&tagTerm=`); files.push(`${nodeBaseUrl}/node/search?searchTerm=&sortTerm=latest&tagTerm=`); files.push(`${nodeBaseUrl}/node/search?searchTerm=&sortTerm=popular&tagTerm=`); files.push(`${nodeBaseUrl}/node/search?searchTerm=&sortTerm=oldest&tagTerm=`); for (const tag of tags) { files.push(`${nodeBaseUrl}/node?searchTerm=&sortTerm=latest&tagTerm=${tag}`); files.push(`${nodeBaseUrl}/node?searchTerm=&sortTerm=popular&tagTerm=${tag}`); files.push(`${nodeBaseUrl}/node?searchTerm=&sortTerm=oldest&tagTerm=${tag}`); files.push(`${nodeBaseUrl}/node/search?searchTerm=&sortTerm=latest&tagTerm=${tag}`); files.push(`${nodeBaseUrl}/node/search?searchTerm=&sortTerm=popular&tagTerm=${tag}`); files.push(`${nodeBaseUrl}/node/search?searchTerm=&sortTerm=oldest&tagTerm=${tag}`); } await cloudflare_purgeCache(files, 'cloudflare_purgeNodePage'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeEmbedVideoPages(videoIds) { try { const nodeBaseUrl = getNodebaseUrl(); const files = []; for (const videoId of videoIds) { files.push(`${nodeBaseUrl}/watch/embed/video/${videoId}`); files.push(`${nodeBaseUrl}/watch/embed/video/${videoId}?autostart=0`); files.push(`${nodeBaseUrl}/watch/embed/video/${videoId}?autostart=1`); } await cloudflare_purgeCache(files, 'cloudflare_purgeEmbedVideoPages'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeAllEmbedVideoPages() { try { const videos = await performDatabaseReadJob_ALL('SELECT video_id FROM videos', []); const videoIds = videos.map(video => video.video_id); await cloudflare_purgeEmbedVideoPages(videoIds); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeNodeImages() { try { const externalResourcesbaseUrl = getExternalResourcesBaseUrl(); const files = []; files.push(`${externalResourcesbaseUrl}/external/resources/images/icon.png`); files.push(`${externalResourcesbaseUrl}/external/resources/images/avatar.png`); files.push(`${externalResourcesbaseUrl}/external/resources/images/banner.png`); await cloudflare_purgeCache(files, 'cloudflare_purgeNodeImages'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeVideoThumbnailImages(videoIds) { try { const externalVideosBaseUrl = getExternalVideosBaseUrl(); const files = []; for (const videoId of videoIds) { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/images/thumbnail.jpg`); } await cloudflare_purgeCache(files, 'cloudflare_purgeVideoThumbnailImages'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeVideoPreviewImages(videoIds) { try { const externalVideosBaseUrl = getExternalVideosBaseUrl(); const files = []; for (const videoId of videoIds) { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/images/preview.jpg`); } await cloudflare_purgeCache(files, 'cloudflare_purgeVideoPreviewImages'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeVideoPosterImages(videoIds) { try { const externalVideosBaseUrl = getExternalVideosBaseUrl(); const files = []; for (const videoId of videoIds) { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/images/poster.jpg`); } await cloudflare_purgeCache(files, 'cloudflare_purgeVideoPosterImages'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeVideo(videoId, format, resolution) { try { const nodeSettings = getNodeSettings(); const externalVideosBaseUrl = getExternalVideosBaseUrl(); const storageMode = nodeSettings.storageConfig.storageMode; const video = await performDatabaseReadJob_GET('SELECT outputs FROM videos WHERE video_id = ?', [videoId]); let files = []; if (video != null) { const outputs = JSON.parse(video.outputs); const resolutions = outputs[format]; if (resolutions.includes(resolution)) { if (format === 'm3u8') { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/static/manifests/manifest-master.m3u8`); files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/static/manifests/manifest-${resolution}.m3u8`); files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/dynamic/manifests/manifest-master.m3u8`); files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/dynamic/manifests/manifest-${resolution}.m3u8`); if (storageMode === 'filesystem') { const adaptiveM3u8ResolutionDirectory = path.join(getVideosDirectoryPath(), videoId + '/adaptive/m3u8/' + resolution); if (fs.existsSync(adaptiveM3u8ResolutionDirectory)) { const segments = fs.readdirSync(adaptiveM3u8ResolutionDirectory); files = files.concat(Array.from({ length: segments.length }, (_, i) => `${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/${resolution}/segments/segment-${resolution}-${i}.ts`)); } } else if (storageMode === 's3provider') { const s3Config = nodeSettings.storageConfig.s3Config; const prefix = 'external/videos/' + videoId + '/adaptive/m3u8/' + resolution + '/segments/'; const keys = await s3_listObjectsWithPrefix(s3Config, prefix); for (const key of keys) { files.push(`${externalVideosBaseUrl}/${key}`); } } } else if (format === 'mp4' || format === 'webm' || format === 'ogv') { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/progressive/${format}/${resolution}.${format}`); } } } await cloudflare_purgeCache(files, 'cloudflare_purgeVideo'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeAdaptiveVideos(videoIds) { try { const nodeSettings = getNodeSettings(); const externalVideosBaseUrl = getExternalVideosBaseUrl(); const storageMode = nodeSettings.storageConfig.storageMode; const videos = await performDatabaseReadJob_ALL('SELECT video_id, outputs FROM videos WHERE video_id IN (:videoIds)', { videoIds }); let files = []; for (const video of videos) { const videoId = video.video_id; const resolutions = JSON.parse(video.outputs).m3u8; files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/static/manifests/manifest-master.m3u8`); files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/dynamic/manifests/manifest-master.m3u8`); for (const resolution of resolutions) { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/static/manifests/manifest-${resolution}.m3u8`); files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/dynamic/manifests/manifest-${resolution}.m3u8`); if (storageMode === 'filesystem') { const adaptiveM3u8ResolutionDirectory = path.join(getVideosDirectoryPath(), videoId + '/adaptive/m3u8/' + resolution); if (fs.existsSync(adaptiveM3u8ResolutionDirectory)) { if (fs.statSync(adaptiveM3u8ResolutionDirectory).isDirectory()) { const segments = fs.readdirSync(adaptiveM3u8ResolutionDirectory); files = files.concat(Array.from({ length: segments.length }, (_, i) => `${externalVideosBaseUrl}/external/videos/${videoId}/adaptive/m3u8/${resolution}/segments/segment-${resolution}-${i}.ts`)); } } } else if (storageMode === 's3provider') { const s3Config = nodeSettings.storageConfig.s3Config; const prefix = 'external/videos/' + videoId + '/adaptive/m3u8/' + resolution + '/segments/'; const keys = await s3_listObjectsWithPrefix(s3Config, prefix); for (const key of keys) { files.push(`${externalVideosBaseUrl}/${key}`); } } } } await cloudflare_purgeCache(files, 'cloudflare_purgeAdaptiveVideos'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_purgeProgressiveVideos(videoIds) { try { const nodeSettings = getNodeSettings(); const externalVideosBaseUrl = getExternalVideosBaseUrl(); const storageMode = nodeSettings.storageConfig.storageMode; const videos = await performDatabaseReadJob_ALL('SELECT video_id, outputs FROM videos WHERE video_id IN (:videoIds)', { videoIds }); let files = []; for (const video of videos) { const videoId = video.video_id; const outputs = JSON.parse(video.outputs); for (const output in outputs) { const resolutions = outputs[output]; for (const resolution of resolutions) { if (storageMode === 'filesystem') { files.push(`${externalVideosBaseUrl}/external/videos/${videoId}/progressive/${output}/${resolution}.${output}`); } else if (storageMode === 's3provider') { const s3Config = nodeSettings.storageConfig.s3Config; const prefix = `external/videos/${videoId}/progressive/${output}`; const keys = await s3_listObjectsWithPrefix(s3Config, prefix); for (const key of keys) { files.push(`${externalVideosBaseUrl}/${key}`); } } } } } await cloudflare_purgeCache(files, 'cloudflare_purgeProgressiveVideos'); } catch (error) { logDebugMessageToConsole(null, error, null); } } async function cloudflare_setCdnConfiguration(cloudflareEmailAddress, cloudflareZoneId, cloudflareGlobalApiKey) { logDebugMessageToConsole('setting Cloudflare CDN configuration for MoarTube Node', null, null); const headers = { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey }; // create new http_request_cache_settings phase rule set in the zone and initialize it with rules logDebugMessageToConsole('creating zone http_request_cache_settings phase rule set', null, null); const newZoneRuleSet = { "rules": [ { "description": "Node External - Video", "action": "set_cache_settings", "enabled": true, "expression": "(starts_with(http.request.uri, \"/external/videos\"))", "action_parameters": { "cache": true, "edge_ttl": { "mode": "override_origin", "default": 31536000 }, "browser_ttl": { "mode": "bypass" } } }, { "description": "Node External - JavaScript, CSS", "action": "set_cache_settings", "enabled": true, "expression": "(starts_with(http.request.uri, \"/external/resources/javascript\")) or (starts_with(http.request.uri, \"/external/resources/css\"))", "action_parameters": { "cache": true, "edge_ttl": { "mode": "override_origin", "default": 86400 }, "browser_ttl": { "mode": "override_origin", "default": 28800 } } }, { "description": "Node External - Images", "action": "set_cache_settings", "enabled": true, "expression": "(starts_with(http.request.uri, \"/external/resources/images\"))", "action_parameters": { "cache": true, "edge_ttl": { "mode": "override_origin", "default": 86400 }, "browser_ttl": { "mode": "bypass" } } }, { "description": "Node Watch - Watch Page for Displaying a Video", "action": "set_cache_settings", "enabled": true, "expression": "(starts_with(http.request.uri, \"/watch\"))", "action_parameters": { "cache": true, "edge_ttl": { "mode": "respect_origin" }, "browser_ttl": { "mode": "bypass" } } }, { "description": "Node Search - Cache Searches on the Node Page, but bypass if searchTerm is specified", "action": "set_cache_settings", "enabled": true, "expression": "(starts_with(http.request.uri, \"/node/search?searchTerm=&sortTerm\"))", "action_parameters": { "cache": true, "edge_ttl": { "mode": "override_origin", "default": 86400 }, "browser_ttl": { "mode": "bypass" } } }, { "description": "Node Page - Cache for Different Variations of the Node Page, but bypass if searchTerm is specified", "action": "set_cache_settings", "enabled": true, "expression": "(starts_with(http.request.uri, \"/node\")) or (starts_with(http.request.uri, \"/node?searchTerm=&sortTerm\"))", "action_parameters": { "cache": true, "edge_ttl": { "mode": "override_origin", "default": 86400 }, "browser_ttl": { "mode": "bypass" } } }, { "description": "Node External - Cache Bypass for Live (dynamic) HLS stream manifests", "action": "set_cache_settings", "enabled": true, "expression": "(http.request.uri.path contains \"/adaptive/m3u8/dynamic/\") and (http.request.method == \"GET\")", "action_parameters": { "cache": false } }, ] } const response_newZoneRuleSet = await axios.put(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/rulesets/phases/http_request_cache_settings/entrypoint`, newZoneRuleSet, { headers }); if (!response_newZoneRuleSet.data.success) { throw new Error('failed to create zone http_request_cache_settings phase rule set'); } const response_newZoneRuleSet_result = response_newZoneRuleSet.data.result; logDebugMessageToConsole('created zone http_request_cache_settings phase rule set: ' + JSON.stringify(response_newZoneRuleSet_result), null, null); // set Browser Cache TTL to "Respect Existing Headers" logDebugMessageToConsole('setting Browser Cache TTL to Respect Existing Headers', null, null); const browserCacheTtlData = { value: 0 }; const response_BrowserCacheTtl = await axios.patch(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/settings/browser_cache_ttl`, browserCacheTtlData, { headers }); if (!response_BrowserCacheTtl.data.success) { throw new Error('failed to set Browser Cache TTL'); } logDebugMessageToConsole('set Browser Cache TTL to Respect Existing Headers: ' + JSON.stringify(response_BrowserCacheTtl.data), null, null); // enable Always Use HTTPS logDebugMessageToConsole('enabling Always Use HTTPS', null, null); const alwaysUseHttpsData = { value: 'on' }; const response_AlwaysUseHttps = await axios.patch(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/settings/always_use_https`, alwaysUseHttpsData, { headers }); if (!response_AlwaysUseHttps.data.success) { throw new Error('failed to enable Always Use HTTPS'); } logDebugMessageToConsole('enabled Always Use HTTPS: ' + JSON.stringify(alwaysUseHttpsData.data), null, null); // enable Argo Tiered Caching logDebugMessageToConsole('enabling Argo Tiered Caching', null, null); const tieredCachingData = { value: 'on' }; const response_tieredCache = await axios.patch(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/argo/tiered_caching`, tieredCachingData, { headers }); if (!response_tieredCache.data.success) { throw new Error('failed to enable Argo Tiered Caching'); } logDebugMessageToConsole('enabled Argo Tiered Caching: ' + JSON.stringify(response_tieredCache.data), null, null); // enable Tiered Cache Smart Topology logDebugMessageToConsole('enabling Tiered Cache Smart Topology', null, null); const tieredCacheSmartTopologyData = { value: 'on' }; const response_tieredCacheSmartTopology = await axios.patch(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/cache/tiered_cache_smart_topology_enable`, tieredCacheSmartTopologyData, { headers }); if (!response_tieredCacheSmartTopology.data.success) { throw new Error('failed to enable Tiered Cache Smart Topology'); } logDebugMessageToConsole('enabled Tiered Cache Smart Topology: ' + JSON.stringify(response_tieredCacheSmartTopology.data), null, null); logDebugMessageToConsole('successfully set Cloudflare configuration for MoarTube Node', null, null); } async function cloudflare_resetCdn(cloudflareEmailAddress, cloudflareZoneId, cloudflareGlobalApiKey) { const headers = { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey }; // get all rule sets in the zone logDebugMessageToConsole('retrieving zone rule sets', null, null); const response_ruleSets = await axios.get(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/rulesets`, { headers }); if (!response_ruleSets.data.success) { throw new Error('failed to retrieve zone rule sets'); } const response_ruleSets_result = response_ruleSets.data.result; logDebugMessageToConsole('discovered zone rule sets: ' + JSON.stringify(response_ruleSets.data), null, null); // delete all http_request_cache_settings phase rule sets in the zone logDebugMessageToConsole('deleting zone http_request_cache_settings phase rule set if discovered', null, null); for (const ruleSet of response_ruleSets_result) { if (ruleSet.phase === 'http_request_cache_settings') { logDebugMessageToConsole('deleting discovered zone http_request_cache_settings phase rule set: ' + ruleSet.id, null, null); const response_deleteRuleSet = await axios.delete(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/rulesets/${ruleSet.id}`, { headers }); if (response_deleteRuleSet.status === 204) { logDebugMessageToConsole('deleted discovered zone http_request_cache_settings phase rule set: ' + ruleSet.id, null, null); } else { throw new Error('failed to delete zone http_request_cache_settings phase rule set'); } } } // disable Argo Tiered Caching logDebugMessageToConsole('disabling Argo Tiered Caching', null, null); const tieredCachingData = { value: 'off' }; const response_tieredCache = await axios.patch(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/argo/tiered_caching`, tieredCachingData, { headers }); if (!response_tieredCache.data.success) { throw new Error('failed to disable Argo Tiered Caching'); } logDebugMessageToConsole('disabled Argo Tiered Caching: ' + JSON.stringify(response_tieredCache.data), null, null); // disable Tiered Cache Smart Topology logDebugMessageToConsole('disabling Tiered Cache Smart Topology', null, null); const tieredCacheSmartTopologyData = { value: 'off' }; const response_tieredCacheSmartTopology = await axios.patch(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/cache/tiered_cache_smart_topology_enable`, tieredCacheSmartTopologyData, { headers }); if (!response_tieredCacheSmartTopology.data.success) { throw new Error('failed to disable Tiered Cache Smart Topology'); } logDebugMessageToConsole('disabled Tiered Cache Smart Topology: ' + JSON.stringify(response_tieredCacheSmartTopology.data), null, null); // remove CDN related DNS records const dnsRecordGetResponse = await axios.get( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records`, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); if (dnsRecordGetResponse.data.success) { logDebugMessageToConsole('successfully queried DNS records', null, null); const dnsRecords = dnsRecordGetResponse.data.result; for (const dnsRecord of dnsRecords) { if (dnsRecord.name.includes('externalvideos') || dnsRecord.name.includes('testingexternalvideos')) { logDebugMessageToConsole(`removing existing DNS record with name: ${dnsRecord.name}`, null, null); await axios.delete( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records/${dnsRecord.id}`, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); } } } logDebugMessageToConsole('successfully reset Cloudflare configuration for MoarTube Node', null, null); } async function cloudflare_addCdnDnsRecord(cloudflareEmailAddress, cloudflareZoneId, cloudflareGlobalApiKey, storageConfig) { if (storageConfig.storageMode === 'filesystem') { const dnsRecordGetResponse = await axios.get( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records`, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); if (dnsRecordGetResponse.data.success) { logDebugMessageToConsole('successfully queried DNS records', null, null); const dnsRecords = dnsRecordGetResponse.data.result; for (const dnsRecord of dnsRecords) { if (dnsRecord.name.includes('externalvideos') || dnsRecord.name.includes('testingexternalvideos')) { logDebugMessageToConsole(`removing existing DNS record with name: ${dnsRecord.name}`, null, null); await axios.delete( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records/${dnsRecord.id}`, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); } } } else { throw new Error('failed to query DNS records from Cloudflare'); } } else if (storageConfig.storageMode === 's3provider') { let recordName; let recordContent; const bucketName = storageConfig.s3Config.bucketName; recordName = bucketName; const endpoint = storageConfig.s3Config.s3ProviderClientConfig.endpoint; if (endpoint != null) { // assume non-AWS S3 provider const url = new URL(endpoint); const hostname = url.hostname; recordContent = `${bucketName}.${hostname}`; } else { // assume AWS S3 const region = storageConfig.s3Config.s3ProviderClientConfig.region; recordContent = `${bucketName}.s3.${region}.amazonaws.com`; } logDebugMessageToConsole('verifying required DNS record name: ' + recordName + ' Content: ' + recordContent, null, null); logDebugMessageToConsole('querying DNS records...', null, null); const dnsRecordGetResponse = await axios.get( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records`, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); if (dnsRecordGetResponse.data.success) { logDebugMessageToConsole('successfully queried DNS records', null, null); const dnsRecords = dnsRecordGetResponse.data.result; for (const dnsRecord of dnsRecords) { if (dnsRecord.name.includes('externalvideos') || dnsRecord.name.includes('testingexternalvideos')) { logDebugMessageToConsole(`removing existing DNS record with name: ${dnsRecord.name}`, null, null); await axios.delete( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records/${dnsRecord.id}`, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); } } logDebugMessageToConsole('adding DNS record...', null, null); let dnsRecordPostResponse = await axios.post( `https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/dns_records`, { type: 'CNAME', name: recordName, content: recordContent, ttl: 1, proxied: true, }, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } } ); if (dnsRecordPostResponse.data.success) { logDebugMessageToConsole('successfully added DNS record', null, null); } else { throw new Error('failed to add DNS record'); } } else { throw new Error('failed to query DNS records from Cloudflare'); } } } async function cloudflare_validateTurnstileToken(token, cloudflareConnectingIp) { const nodeSettings = getNodeSettings(); const cloudflareTurnstileSecretKey = nodeSettings.cloudflareTurnstileSecretKey; const data = { secret: cloudflareTurnstileSecretKey, response: token }; if (cloudflareConnectingIp != null) { data.remoteip = cloudflareConnectingIp; } const response = await axios.post('https://challenges.cloudflare.com/turnstile/v0/siteverify', data); if (!response.data.success) { throw new Error('human verification was unsuccessful'); } } async function cloudflare_validate(cloudflareEmailAddress, cloudflareZoneId, cloudflareGlobalApiKey) { const response = await axios.get('https://api.cloudflare.com/client/v4/zones/' + cloudflareZoneId, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } }); return response.data; } async function cloudflare_purgeEntireCache(cloudflareEmailAddress, cloudflareZoneId, cloudflareGlobalApiKey) { const response = await axios.post('https://api.cloudflare.com/client/v4/zones/' + cloudflareZoneId + '/purge_cache', { purge_everything: true }, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } }); return response.data; } async function cloudflare_purgeCache(files, source) { if (files.length > 0) { const filesofFiles = formatFilesParameter(files); if (getIsDeveloperMode()) { return Promise.resolve([]); } else { const nodeSettings = getNodeSettings(); if (nodeSettings.isCloudflareCdnEnabled) { const cloudflareEmailAddress = nodeSettings.cloudflareEmailAddress; const cloudflareZoneId = nodeSettings.cloudflareZoneId; const cloudflareGlobalApiKey = nodeSettings.cloudflareGlobalApiKey; const axiosPromises = filesofFiles.map(files => { const filesJson = JSON.stringify(files); return axios.post(`https://api.cloudflare.com/client/v4/zones/${cloudflareZoneId}/purge_cache`, { files: files }, { headers: { 'X-Auth-Email': cloudflareEmailAddress, 'X-Auth-Key': cloudflareGlobalApiKey } }) .then(response => { const data = response.data; if (data.success) { logDebugMessageToConsole(source + ' success', null, null); } else { logDebugMessageToConsole(source + ' failed', null, null); } return { status: 'fulfilled' }; }) .catch(error => { logDebugMessageToConsole(source + ' error: ' + filesJson, error, new Error().stack); return { status: 'rejected' }; }); }); return Promise.allSettled(axiosPromises); } else { return Promise.resolve([]); } } } } function formatFilesParameter(files) { const maxSize = 30; // cloudflare purges are a maximum of 30 files at a time // divide the array of files into an array of arrays of files, each array with a maximum length of maxSize return Array.from({ length: Math.ceil(files.length / maxSize) }, (v, i) => files.slice(i * maxSize, i * maxSize + maxSize)); } module.exports = { cloudflare_validate, cloudflare_purgeEntireCache, cloudflare_purgeWatchPages, cloudflare_purgeVideo, cloudflare_purgeAdaptiveVideos, cloudflare_purgeProgressiveVideos, cloudflare_purgeEmbedVideoPages, cloudflare_purgeNodePage, cloudflare_purgeNodeImages, cloudflare_purgeVideoThumbnailImages, cloudflare_purgeVideoPreviewImages, cloudflare_purgeVideoPosterImages, cloudflare_setCdnConfiguration, cloudflare_resetCdn, cloudflare_validateTurnstileToken, cloudflare_addCdnDnsRecord, cloudflare_purgeAllWatchPages, cloudflare_purgeAllEmbedVideoPages };