Skip to content

Instantly share code, notes, and snippets.

@servel333
Last active July 14, 2024 21:11
Show Gist options
  • Save servel333/ea13c6ce921c2f281878bbc4ee0afc5d to your computer and use it in GitHub Desktop.
Save servel333/ea13c6ce921c2f281878bbc4ee0afc5d to your computer and use it in GitHub Desktop.
OpenGameArt Asset Downloader and Markdown Generator

OpenGameArt Asset Downloader and Markdown Generator

This Tampermonkey script adds a convenient "Download Files & Markdown" button to OpenGameArt.org content pages, streamlining the process of downloading assets and their associated information.

Features

  • Adds a download button to https://opengameart.org/content/* pages
  • Generates a markdown file containing:
    • Asset title with link to the original page
    • Author name with link to their profile
    • Submission information (if applicable)
    • Publication date
    • License(s)
    • Tags with links
    • Full description
  • Creates a zip file including:
    • The generated markdown file
    • All downloadable asset files
    • All preview images
  • Automatically triggers the download of the zip file

Usage

  1. Install the Tampermonkey browser extension
  2. Create a new script in Tampermonkey and paste the provided code
  3. Save the script and ensure it's enabled
  4. Navigate to any OpenGameArt.org content page
  5. Click the "Download Files & Markdown" button that appears next to the asset title
  6. Wait for the zip file to be generated and downloaded (this may take a few seconds depending on the number and size of files)

Notes

  • The script handles URL-encoded characters in filenames, ensuring proper naming in the zip file
  • In case of duplicate filenames, the script automatically renames files to avoid overwriting
  • Preview images are prefixed with "preview_" in the zip file for easy identification
  • The script provides visual feedback during the download process and in case of errors

Compatibility

This script is designed to work with modern browsers that support ES6+ JavaScript features. It has been tested with Firefox 126.0.1 (64-bit).

Contributing

Feel free to comment if you encounter any problems or have suggestions for improvements.

License

OpenGameArt Asset Downloader and Markdown Generator by Nathaniel Perry is marked with CC0 1.0 Universal CC Logo Zero Logo

// ==UserScript==
// @name OpenGameArt Asset Downloader and Markdown Generator
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Download assets and create markdown with one click, compressed into a single zip file
// @author Nathaniel Perry and Claude 3.5 Sonnet
// @match https://opengameart.org/content/*
// @grant GM_xmlhttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @license CC0 1.0 Universal https://creativecommons.org/publicdomain/zero/1.0/?ref=chooser-v1
// ==/UserScript==
(function() {
'use strict';
console.log("OpenGameArt Asset Downloader and Markdown Generator: loading");
function log_debug(...args) {
// console.log(...args);
}
function decodeURLCharacters(str) {
return decodeURIComponent(str.replace(/\+/g, ' '));
}
// Extracts the slug from the current URL
function getSlugFromURL() {
const path = window.location.pathname;
const parts = path.split('/');
const slug = parts[parts.length - 1];
log_debug(`getSlugFromURL slug "${slug}"`);
return slug;
}
// Sanitizes a filename to ensure it's valid across different operating systems
function sanitizeFilename(name, preserveSlashes = false) {
log_debug(`sanitizeFilename name "${name}"`);
name = decodeURLCharacters(name);
log_debug(`sanitizeFilename decodeURLCharacters(name) "${name}"`);
if (!preserveSlashes) {
name = name.replace(/\//g, '_');
}
log_debug(`sanitizeFilename name.replace(/\//g, '_') "${name}"`);
// Check if there's an extension
const lastDotIndex = name.lastIndexOf('.');
let base, ext;
if (lastDotIndex > 0 && lastDotIndex < name.length - 1) {
base = name.slice(0, lastDotIndex);
ext = name.slice(lastDotIndex + 1);
} else {
base = name;
ext = '';
}
log_debug(`sanitizeFilename base "${base}", ext "${ext}"`);
const sanitizedBase = base
.replace(/[\\\/:*?"<>\|]/gi, '_')
.replace(/_+/g, '_')
.toLowerCase();
log_debug(`sanitizeFilename sanitizedBase "${sanitizedBase}"`);
const sanitizedName = (sanitizedBase + (ext ? '.' + ext : ''))
.replace(/^_+|_+$/g, '');
log_debug(`sanitizeFilename RETURN sanitizedName "${sanitizedName}"`);
return sanitizedName;
}
// Displays a loading spinner on the download button
function showSpinner(button) {
button.disabled = true;
button.innerHTML = 'Downloading... <span class="spinner"></span>';
button.style.position = 'relative';
const spinner = button.querySelector('.spinner');
spinner.style.display = 'inline-block';
spinner.style.width = '20px';
spinner.style.height = '20px';
spinner.style.border = '3px solid rgba(255,255,255,.3)';
spinner.style.borderRadius = '50%';
spinner.style.borderTopColor = '#fff';
spinner.style.animation = 'spin 1s linear infinite';
spinner.style.marginLeft = '10px';
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
}
// Hides the spinner and updates the button text
function hideSpinner(button, text = 'Download Files & Markdown') {
button.disabled = false;
button.innerHTML = text;
}
// Displays an error message to the user
function showError(button, message) {
hideSpinner(button, 'Error');
button.style.backgroundColor = 'red';
const errorMessage = document.createElement('div');
errorMessage.textContent = message;
errorMessage.style.color = 'red';
errorMessage.style.marginTop = '5px';
button.parentNode.insertBefore(errorMessage, button.nextSibling);
setTimeout(() => {
button.style.backgroundColor = '';
errorMessage.remove();
hideSpinner(button);
}, 5000);
}
// Main function to handle the download process
function handleDownload(event) {
const button = event.target;
showSpinner(button);
try {
const info = getInfo();
log_debug("Info gathered:", info);
const markdownContent = generateMarkdown(info);
log_debug("Markdown generated");
const slug = getSlugFromURL();
log_debug(`handleDownload slug "${slug}"`);
const template = `OpenGameArt_${sanitizeFilename(slug)}_by_${sanitizeFilename(info.author)}`;
log_debug(`handleDownload template "${template}"`);
createAndDownloadZip(template, markdownContent, info, button);
} catch (error) {
console.error('Error in handleDownload:', error);
showError(button, 'An error occurred while preparing the download.');
}
}
// Creates a zip file containing all downloadable content and metadata
function createAndDownloadZip(template, markdownContent, info, button) {
const zip = new JSZip();
zip.file(`${sanitizeFilename(info.title)}.md`, markdownContent);
const downloadLinks = document.querySelectorAll('.field-name-field-art-files a[href^="https://opengameart.org/sites/default/files"]');
const totalFiles = downloadLinks.length + info.previewImages.length;
let completedFiles = 0;
const usedFilenames = new Set();
function getUniqueFilename(filename) {
if (!usedFilenames.has(filename)) {
usedFilenames.add(filename);
return filename;
}
let uniqueFilename = filename;
let counter = 1;
const lastDotIndex = filename.lastIndexOf('.');
const name = lastDotIndex > 0 ? filename.substring(0, lastDotIndex) : filename;
const ext = lastDotIndex > 0 ? filename.substring(lastDotIndex) : '';
while (usedFilenames.has(uniqueFilename)) {
uniqueFilename = `${name}_${counter}${ext}`;
counter++;
}
usedFilenames.add(uniqueFilename);
return uniqueFilename;
}
function checkCompletion() {
completedFiles++;
if (completedFiles === totalFiles) {
finalizeZip(zip, template, button);
}
}
downloadLinks.forEach((link) => {
// Use the displayed filename instead of the URL
const displayedFilename = link.textContent.trim();
const filename = sanitizeFilename(displayedFilename, true);
const uniqueFilename = getUniqueFilename(filename);
downloadFile(link.href, (content) => {
if (content) {
zip.file(uniqueFilename, content);
checkCompletion();
} else {
console.error(`Failed to download: ${uniqueFilename}`);
checkCompletion();
}
});
});
info.previewImages.forEach((img) => {
const previewFilename = `preview_${sanitizeFilename(img.filename)}`;
const uniqueFilename = getUniqueFilename(previewFilename);
downloadFile(img.src, (content) => {
if (content) {
zip.file(uniqueFilename, content);
checkCompletion();
} else {
console.error(`Failed to download preview: ${uniqueFilename}`);
checkCompletion();
}
});
});
// Fallback for cases where there are no files to download
// This ensures the zip is created even if the page has no downloadable content
if (totalFiles === 0) {
finalizeZip(zip, template, button);
}
}
// Downloads a file from a given URL
function downloadFile(url, callback) {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'arraybuffer',
onload: function(response) {
callback(response.response);
},
onerror: function(error) {
console.error(`Error downloading file: ${url}`, error);
callback(null);
}
});
}
// Generates the final zip file and initiates the download
function finalizeZip(zip, template, button) {
zip.generateAsync({type:"blob"})
.then(function(content) {
hideSpinner(button, 'Download Ready');
saveAs(content, `${template}.zip`);
console.log(`Download complete: ${template}.zip`);
})
.catch(function(error) {
console.error('Error generating zip:', error);
showError(button, 'Failed to generate zip file.');
});
}
// Extracts relevant information from the webpage
function getInfo() {
const info = {};
info.title = document.querySelector('div.group-header div.field-name-title h2').innerText;
const authorElement = document.querySelector('span.username > a');
info.author = authorElement ? authorElement.innerText : '';
info.submitter = document.querySelector('.field-name-author-submitter .field-item strong > a')?.innerText;
info.url = window.location.href;
info.publishDate = document.querySelector('.field-name-post-date').innerText;
info.licenses = Array.from(document.querySelectorAll('.field-name-field-art-licenses .license-name')).map(el => el.innerText);
info.description = document.querySelector('.field-name-body .field-item').innerText;
info.tags = Array.from(document.querySelectorAll('.field-name-field-art-tags a')).map(el => ({
name: el.innerText,
url: el.href
}));
info.previewImages = Array.from(document.querySelectorAll('.field-name-field-art-preview img')).map(img => ({
src: img.src,
filename: img.src.split('/').pop()
}));
return info;
}
// Generates markdown content based on the extracted information
function generateMarkdown(info) {
let md = `# [${info.title}](${info.url})\n\n`;
md += `Author: [${info.author}](https://opengameart.org/users/${info.author.toLowerCase()})\n\n`;
if (info.submitter) {
md += `(Submitted by **${info.submitter}**)\n\n`;
}
md += `Published: ${info.publishDate}\n\n`;
md += `License(s):\n${info.licenses.map(license => `- ${license}`).join('\n')}\n\n`;
md += `Tags:\n${info.tags.map(tag => `- [${tag.name}](${tag.url})`).join('\n')}\n\n`;
md += `## Description\n\n${info.description}\n`;
return md;
}
// Adds the download button to the webpage
function addDownloadButton() {
const titleElement = document.querySelector('div.group-header div.field-name-title h2');
if (!titleElement) return;
const button = document.createElement('button');
button.innerHTML = 'Download Files & Markdown';
button.style.marginLeft = '10px';
titleElement.parentNode.insertBefore(button, titleElement.nextSibling);
button.addEventListener('click', handleDownload);
log_debug("Download button added successfully");
}
addDownloadButton();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment