feat(download): show notice download url leads to another website #198

Merged
enes merged 1 commits from extra/161-download-link-notice into staging 2025-01-16 14:03:33 +00:00
2 changed files with 120 additions and 1 deletions

View File

@ -11,7 +11,7 @@ import {
import { toast } from 'react-toastify'
import { BlogCard } from '../../components/BlogCard'
import { ProfileSection } from '../../components/ProfileSection'
import { useAppSelector, useBodyScrollDisable } from '../../hooks'
import { useAppSelector, useBodyScrollDisable, useDidMount } from '../../hooks'
import { getGamePageRoute, getModsEditPageRoute } from '../../routes'
import '../../styles/comments.css'
import '../../styles/downloads.css'
@ -26,6 +26,7 @@ import '../../styles/write.css'
import { DownloadUrl, ModPageLoaderResult } from '../../types'
import {
capitalizeEachWord,
checkUrlForFile,
copyTextToClipboard,
downloadFile,
getFilenameFromUrl
@ -613,6 +614,12 @@ const Download = ({
customNote
}: DownloadUrl) => {
const [showAuthDetails, setShowAuthDetails] = useState(false)
const [showNotice, setShowNotice] = useState(false)
useDidMount(async () => {
const isFile = await checkUrlForFile(url)
setShowNotice(!isFile)
})
const handleDownload = () => {
// Get the filename from the URL
@ -632,6 +639,16 @@ const Download = ({
Download
</button>
</div>
{showNotice && (
<div className='IBMSMSMBSSNote'>
<p>
Notice: The creator has provided a download link that doesn&#39;t
download the files immediately, but rather redirects you to a
different site.
<br />
</p>
</div>
)}
{/*temporarily commented out the WoT rating for download links within a mod post
<div className='IBMSMSMBSSDownloadsElementInside'>
<p>Ratings (WIP):</p>

View File

@ -119,3 +119,105 @@ export const downloadFile = (url: string, filename: string) => {
// Remove the anchor from the document
document.body.removeChild(a)
}
/**
* Checks if the url endpoint returns a file
* @param url
* @returns true if matches a possible file download
*/
export const checkUrlForFile = async (url: string) => {
try {
// HTTP HEAD request to get headers without downloading the full content
const response = await fetch(url, { method: 'HEAD' })
// Check Content-Disposition header
const contentDisposition = response.headers.get('content-disposition')
if (contentDisposition && contentDisposition.includes('attachment')) {
return true
}
//Check Content-Type header
const contentType = response.headers.get('content-type')
if (
contentType &&
(contentType.includes('application/') ||
contentType.includes('image/') ||
contentType.includes('audio/') ||
contentType.includes('video/'))
) {
return true
}
} catch {
// Ignore
}
// Check if blossom file (link includes sha256 string in the url)
// Most likely it's a file that users would directly download
const regex = /\/[a-fA-F0-9]{64}(\.[a-zA-Z0-9]+)?$/
if (regex.test(url)) {
return true
}
// Common mod file extensions
const fileExtensions = [
'.zip',
'.rar',
'.7z',
'.tar',
'.gz',
'.bz2',
'.xz',
'.cab',
'.iso', // (can also be considered a disk image)
'.tgz', // (tar.gz)
'.z', // (compress)
'.lz', // (Lempel-Ziv)
'.mp3',
'.wav',
'.aac',
'.flac',
'.ogg',
'.wma',
'.m4a',
'.opus',
'.mp4',
'.avi',
'.mkv',
'.mov',
'.wmv',
'.flv',
'.webm',
'.mpeg',
'.3gp',
'.jpg',
'.jpeg',
'.png',
'.gif',
'.bmp',
'.tiff',
'.tif',
'.svg',
'.raw',
'.heic',
'.exe', // (executable files for Windows games)
'.apk', // (Android Package)
'.ipa', // (iOS App Store Package)
'.bin', // (binary file, often used for game data)
'.iso', // (disk image, often for console games)
'.sav', // (save game file)
'.cfg', // (configuration file)
'.mod', // (modification file)
'.pak', // (package file, often used in games)
'.unity', // (Unity game project file)
'.game', // (generic game file)
'.dmg' // (disk image for macOS applications)
]
for (const ext of fileExtensions) {
if (url.endsWith(ext)) {
return true
}
}
return false
}