Skip to content

Instantly share code, notes, and snippets.

@asheroto
Last active October 14, 2023 04:59
Show Gist options
  • Save asheroto/056000ef898ffd0a84beee784e031aec to your computer and use it in GitHub Desktop.
Save asheroto/056000ef898ffd0a84beee784e031aec to your computer and use it in GitHub Desktop.
Automatically monitor EXE file version updates at a static URL. This is a JavaScript tool that uses Puppeteer/Apify tool to monitor changes, compare versions, and makes GET request to send notifications for updated executable files (but you can change it to anything you'd like of course). Uses anti-bot detection measures such as with Cloudflare.

NOTE: See my new project, Chocolatey-Package-Updater for a PowerShell-only solution.

Monitoring EXE File Version Updates at a Static URL

Tracking version changes in executable files can be challenging, especially when software developers use a static URL for each new release. This issue became apparent to me during my time maintaining Chocolatey packages. New versions would roll out, checksums would differ, and the package would fail, often prompting someone to notify me since there was no other way to be alerted.

Monitoring release notes or changelogs can be useful, and tools like VisualPing.io are generally good for that. However, there are exceptions, such as ClickUp, where this approach falls short. ClickUp maintains a consistent URL for all versions, and their release notes may not necessarily correspond to changes in the desktop client.

For managing version updates in Chocolatey, AU is a commonly used tool. However, AU hasn't been updated since October 2022 and has some functional limitations. For this specific Chocolatey project, I ended up writing a PowerShell script instead of using Puppeteer to monitor filename version changes, however, I am keeping this Gist up in case anyone is interested in the JS/Puppeteer/Apify approach.

The JavaScript code that follows was created before I finished my PowerShell script. The JS/Puppetteer/Apify approach is particularly useful if you don't have your machine running continuously for scheduled scripts. Leveraging Apify's free service with the script below allows you to track changes without needing your computer to be always on.

image

See how the URL will be the same for each version? Argh!

To tackle this, I developed a tool in JavaScript that leverages Puppeteer to initiate a headless Chrome browser. The tool navigates to the download link, examines the Content-Disposition header to find the filename, extracts the version number, and compares it against a version stored in an environment variable. If there's a discrepancy, it triggers a GET request to send an alert.

The notification is handled by a basic script on my web server, which sends an email. The tool can be easily customized to your liking—you could configure it to send Discord messages, text messages, and more. I've built this tool with Apify, which offers some usage for free and supports integrations with platforms like Zapier. This makes it a viable option if you don't have your own web server or hosting solution.

The script is intended to run on a schedule, whether cron, Task Scheduler, or a service like Apify. It makes use of the puppeteer-extra-plugin-stealth package to avoid issues with anti-bot measures such as with Cloudflare.

How it works

  • Launches a new Puppeteer page
  • Navigates to the URL of the file you want to check, which is set as an environment variable
  • Listens for the response event to capture headers
  • Retrieves the filename from the Content-Disposition header
  • Uses a regular expression to extract the version number from the filename
  • Compares the version number to the current version number, which is set as an environment variable
  • If the retrieved version number is greater than the current version number, it navigates to a secret page to send an alert

How to use

  • Set the CURRENT_VERSION environment variable to the current version number you want to compare against
  • Set the FILE_URL environment variable to the URL of the file you want to check
  • Set the SECRET_URL environment variable to the URL of the secret page you want to send an alert to
  • Adjust the line that uses the SECRET_URL variable as needed—the URL provided is just an example method of sending an alert, you can of course use any method you want
  • Set the NOTIFICATION_API_KEY environment variable to the API key for the secret page
  • Update the CURRENT_VERSION environment each time the version changes to prevent it from re-alerting you

Note

  • A net::ERR_ABORTED error will likely occur with Puppeteer on file downloads—it looks like they're still working on fixing this at the time of this writing—until then a try/catch block has been added and an additional console message if it detects that error on checking the file

Hope you find this useful!

/*
This script checks a URL for a new version of an EXE and sends an alert if a new version is available (via GET request). It is intended to run on a schedule,
whether cron, Task Scheduler, or a service like Apify. It uses the puppeteer-extra-plugin-stealth package to avoid issues with Cloudflare and other anti-bot measures.
How to use:
- Set the CURRENT_VERSION environment variable to the current version number you want to compare against
- Set the FILE_URL environment variable to the URL of the file you want to check
- Set the SECRET_URL environment variable to the URL of the secret page you want to send an alert to
- Adjust the full SECRET_URL as needed, the one provided is just an example method of sending an alert, you can of course use any method you want
- Set the NOTIFICATION_API_KEY environment variable to the API key for the secret page
- A net::ERR_ABORTED error will likely occur with Puppeteer on file downloads—it looks like [they're still working on fixing this](https://github.com/puppeteer/puppeteer/issues/299) at the time of this writing—until then a try/catch block has been added and an additional console message if it detects that error on checking the file
*/
import puppeteerVanilla from "puppeteer";
import { addExtra } from "puppeteer-extra";
import semver from "semver";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
const puppeteer = addExtra(puppeteerVanilla);
puppeteer.use(StealthPlugin());
// Environment Variables
const CURRENT_VERSION = process.env.CURRENT_VERSION;
const NOTIFICATION_API_KEY = process.env.NOTIFICATION_API_KEY;
const FILE_URL = process.env.FILE_URL;
const SECRET_URL = process.env.SECRET_URL;
const run = async () => {
const browser = await puppeteer.launch({
args: ['--no-sandbox'],
headless: 'new'
});
const page = await browser.newPage();
// Use this to store whether an alert needs to be sent
let shouldSendAlert = false;
let retrievedVersion = "";
// Listen for response event
page.on('response', async (response) => {
const headers = response.headers();
const contentDisposition = headers['content-disposition'];
if (contentDisposition) {
const filename = contentDisposition.split('filename=')[1];
console.log(`Filename: ${filename}`);
// Use a regular expression to extract the version number
const regex = /(\d+\.\d+\.\d+)/;
const matches = filename.match(regex);
if (matches && matches[1]) {
retrievedVersion = matches[1];
console.log(`Retrieved Version: ${retrievedVersion}`);
if (semver.gt(retrievedVersion, CURRENT_VERSION)) {
console.log("New version is available. Preparing to send alert...");
shouldSendAlert = true;
} else {
console.log("No new version exists. Not sending alert.")
}
}
}
});
// Navigate to the URL of the file to check
try {
await page.goto(FILE_URL);
} catch (error) {
console.log(`Failed to navigate to file URL: ${error}`);
if (error.message.includes('net::ERR_ABORTED')) {
console.log("net::ERR_ABORTED is normal for file downloads and is likely not an error, but is still reported just in case.");
}
}
if (shouldSendAlert) {
// Make a GET request to the secret page to send an alert
try {
await page.goto(`${SECRET_URL}?p=${NOTIFICATION_API_KEY}&v=${retrievedVersion}`);
console.log("Alert sent successfully");
} catch (error) {
console.log(`Failed to send alert: ${error}`);
}
}
// Close the browser
await browser.close();
};
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment