Our ‘Files’ are full of unused files. There doesn’t seem to be a way in HS to sort by usage or a filter for ‘unused’. I was hoping I could write a script using the API to get a list of the unused files and delete them.
However, the V3 API doesn’t seem to offer any meta-data for files. The V2 API does offer a ‘get file metadata’ call, but it doesn’t seem to offer anything that the V3 ‘get file’ call does. I am guessing that this is just a renaming of the endpoint.
So is there any way I can programmatically get information about the usage of files, similar to how I can use the HS file manager on the web? I really don’t want to have to sort through 1000’s of files, and one at a time clicking on details, delete, ok, rinse repeat.
Note that we have HS Marketing Pro, but we do not have/use the HS CMS platform. These are files used in our LP’s, social media posts, or LP templates.
I was able write a tampermonkey script that scraped the information about all the files (including usage) into a json object so that it can be used with a script which deletes the unused files. I’ll post it here hoping that it helps someone else.
- Install the chrome TamperMonkey plugin.
- Add this script below to TM.
- Go to the first page of your files.
- You should see a 'Get File Info button. Click the button.
- Don’t touch anything; wait until a popup shows the JSON, and copy paste it to where you need it.
// ==UserScript==
// @name Hubspot find unused
// @namespace http://tampermonkey.net/
// @version 0.1
// @description crawl the HS file explorer and make a printable list if they are not used.
// @author Shea Martin
// @match https://app.hubspot.com/files/*
// @icon https://www.hubspot.com/hubfs/assets/hubspot.com/style-guide/brand-guidelines/guidelines_approved-sprocket-2.svg
// @grant none
// @require http://code.jquery.com/jquery-latest.js
// ==/UserScript==
var $ = window.jQuery;
const maxPages = 20;
function getFileInfoFromSidePanel(r) {
$('[data-key="FileManagerDashboard.detailPanel.fileUsage"]').click();
$('[data-key="FileManagerDashboard.detailPanel.sections.general"]').click();
const name = $('[data-test-id="panel-file-name"] > span').text();
const url = $('[data-test-id=file-detail-url-link]').attr('href');
const unused = $('[data-key="FileManagerDashboard.fileUsage.unused"]').length === 1;
const size = $('[data-test-id=file-detail-size]').text();
const dimensions = $('[data-key="FileManagerCore.fileDimensions.dimensions"]').text();
const fileId = $('[data-test-id="panel-file-id"]').text();
const fileType = $('[data-key="FileManagerDashboard.detailPanel.fileType"]').parent().next().text();
return {
name,
url,
unused,
size,
dimensions,
fileId,
fileType,
};
}
function processRowsOnPage(rows, fileDetails, pageCompleteCallback) {
const r = rows.pop();
if(!r) {
log(`completed page ${curPage}`);
pageCompleteCallback();
return;
}
r.click();
setTimeout(() => {
const details = getFileInfoFromSidePanel();
fileDetails.push(details);
log(`got details for ${details.name}`);
processRowsOnPage(rows, fileDetails, pageCompleteCallback);
}, 1000);
}
function processPage(fileDetails, allPagesCompleteCallback) {
log(`processing page ${curPage}`);
const rows = $('button[data-test-id=file-row]').toArray();
processRowsOnPage(rows, fileDetails, () => {
const hasNext = nextPage();
if(hasNext) {
processPage(fileDetails, allPagesCompleteCallback);
} else {
allPagesCompleteCallback();
}
});
}
function getUnused() {
curPage = 1;
const fileDetails = [];
processPage(fileDetails, () => {
log('complete');
completeAllPages(fileDetails);
});
}
function completeAllPages(fileDetails) {
const content = JSON.stringify(fileDetails, undefined, 4);
alert(`got information on ${fileDetails.length} files. Copy the results from the results pane.`);
$('#file-results-pane').show();
$('#file-results-data').html(content);
}
let curPage = 1;
function nextPage() {
const nextButton = $('button[data-next-page=true]');
if(curPage >= maxPages) {
log(`page limit of ${maxPages} has been exceeded`);
return false;
}
if(nextButton.length !== 1 || nextButton.attr('aria-disabled') === 'true') {
log('no more pages');
return false;
}
curPage += 1;
nextButton.click();
return true;
}
function setupUi() {
log('ui initiated');
const startButton = $('<button>Get File Info</button>');
startButton.click(() => {
alert('starting to retrieve file information. Do not touch mouse or keyboard until complete dialog shows.');
getUnused();
});
$('[data-test-id=upload-files-button]').parent().append(startButton);
const css = `
border: 4px solid green;
position: absolute;
top: 100px;
left: 20px;
right: 20px;
height: 80vh;
padding: 40px;
overflow: auto;
z-index: 1000;
background-color: white;
font-family: monospace;
display: none;
`;
$('body').prepend(`
<div id="file-results-pane" style="${css}">
<button onclick="$('#file-results-pane').hide();">Close</button>
<h1>Results</h1>
<pre id="file-results-data"></pre>
</div>`);
}
function log(msg) {
console.log('[tm] ' + msg);
}
(function() {
'use strict';
log('loaded');
setTimeout(() => setupUi(), 3000);
// setTimeout(() => completeAllPages([{name: 'shea', age: 43}, { name: 'Denby', age: 42 }]), 4000);
// document.addEventListener('keydown', (e) => {
// if(e.ctrlKey && e.key === 'D') {
// log('shortcut pressed');
// getUnused();
// }
// }, true);
})();
Thanks Sheamus (and no thanks to HubSpot)
the code worked well but I made a few augmentations to it.
Now it highlights and selects those files that are unused.
I kept the json return but left it hidden in case someone really wanted it
// ==UserScript==
// HubSpot -- Select Unused Files
// @namespace http://tampermonkey.net/
// @version 0.2
// @description make HubSpot life easier
// @author erik bloomstrand
// @match https://app.hubspot.com/files/*
// https://www.google.com/s2/favicons?sz=64&domain=hubspot.com
// none
// @require http://code.jquery.com/jquery-latest.js
// ==/UserScript==
var $ = window.jQuery;
const maxPages = 1;
function getFileInfoFromSidePanel(r) {
$('[data-key="FileManagerDashboard.detailPanel.fileUsage"]').click();
$('[data-key="FileManagerDashboard.detailPanel.sections.general"]').click();
const name = $('[data-test-id="panel-file-name"] > span').text();
const url = $('[data-test-id=file-detail-url-link]').attr('href');
const unused = $('[data-key="FileManagerDashboard.fileUsage.unused"]').length === 1;
const size = $('[data-test-id=file-detail-size]').text();
const dimensions = $('[data-key="FileManagerCore.fileDimensions.dimensions"]').text();
const fileId = $('[data-test-id="panel-file-id"]').text();
const fileType = $('[data-key="FileManagerDashboard.detailPanel.fileType"]').parent().next().text();
return {
name,
url,
unused,
size,
dimensions,
fileId,
fileType,
};
}
function processRowsOnPage(rows, fileDetails, pageCompleteCallback) {
const r = rows.pop();
if(!r) {
log(`completed page ${curPage}`);
pageCompleteCallback();
return;
}
r.click();
setTimeout(() => {
const details = getFileInfoFromSidePanel();
fileDetails.push(details);
log(`got details for ${details.name} ${details.fileId} ${details.unused}`);
processRowsOnPage(rows, fileDetails, pageCompleteCallback);
if ( details.unused === true) {
$('tr[data-test-object-id="'+details.fileId+'"]').css('background-color', '#FF000055').find('label').click();
} else {
$('tr[data-test-object-id="'+details.fileId+'"]').css('background-color', '#00FF0055');
}
}, 1000);
}
function processPage(fileDetails, allPagesCompleteCallback) {
log(`processing page ${curPage}`);
const rows = $('button[data-test-id=file-row]').toArray();
processRowsOnPage(rows, fileDetails, () => {
const hasNext = nextPage();
if(hasNext) {
processPage(fileDetails, allPagesCompleteCallback);
} else {
allPagesCompleteCallback();
}
});
}
function getUnused() {
curPage = 1;
const fileDetails = [];
processPage(fileDetails, () => {
log('complete');
completeAllPages(fileDetails);
});
}
function completeAllPages(fileDetails) {
const content = JSON.stringify(fileDetails, undefined, 4);
const unusedFiles = fileDetails.filter(file => file.unused === true);
$('[data-test-id="close-panel-button"]').click();
setTimeout(() => {
alert(`${fileDetails.length} files have been evaluated. ${unusedFiles.length} are selected and not being used`);
}, 200);
// $('#file-results-pane').show();
$('#file-results-data').html(content);
}
let curPage = 1;
function nextPage() {
const nextButton = $('button[data-next-page=true]');
if(curPage >= maxPages) {
log(`page limit of ${maxPages} has been exceeded`);
return false;
}
if(nextButton.length !== 1 || nextButton.attr('aria-disabled') === 'true') {
log('no more pages');
return false;
}
curPage += 1;
nextButton.click();
return true;
}
function setupUi() {
log('ui initiated');
const startButton = $('<button class="uiButton private-button private-button--sm private-button--non-link">Select Unused Files</button>');
startButton.click(() => {
alert('Starting to retrieve file information. Do not touch mouse or keyboard until complete dialog shows.');
getUnused();
});
$('[data-test-id=upload-files-button]').parent().append(startButton);
const css = `
border: 4px solid green;
position: absolute;
top: 100px;
left: 20px;
right: 20px;
height: 80vh;
padding: 40px;
overflow: auto;
z-index: 1000;
background-color: white;
font-family: monospace;
display: none;
`;
$('body').prepend(`
<div id="file-results-pane" style="${css}">
<button onclick="$('#file-results-pane').hide();">Close</button>
<h1>Results</h1>
<pre id="file-results-data"></pre>
</div>`);
}
function log(msg) {
console.log('[tm] ' + msg);
}
(function() {
'use strict';
setTimeout(() => setupUi(), 3000);
})();
here is an example of the output
Hi @schmelik super cool ! I actually be really interested to have the exhaustive code if it is possible please ? 
To be honest, I was under a deadline, so I left out comments
horrible 
I’ll circle back to add the exhaustive code when I find the time. -- no promises