๐Ÿง™โ€โ™‚๏ธ Resolve Short Links

Open resolve-short-links in Script Kit

๐Ÿง™โ€โ™‚๏ธ Resolve Short Links

Description

  • โŒจ๏ธ Prompts the user for a URL
  • ๐ŸŒ Makes a lightweight request to retrieve the actual URL
  • ๐Ÿงผ Sanitizes unnecessary tracking information
  • ๐Ÿ“‹ Automatically copies the resolved URL to the clipboard.

Explanation

This is useful for removing tracking / other information from URLs before sharing with others.

Many short links embed user / tracking information in the URL on their end.

Every time you share this shortened URL, your information is passed along.

  • TikTok, for example, uses this to display your username on the page when others view the video you shared.

  • Additionally, many short links will append query parameters that include tracking information as well, such as Facebook's fbclid.

This script resolves the short link by sending a HEAD request to the URL.

  • Additionally, any unnecessary query parameters are stripped off

  • The result is a valid URL with the minimum amount of information needed to access the resource

Examples

|Site|Input|Resolved & Sanitized| |-|-|-| |Bitly|https://bit.ly/3IBRlRk?fbclid=csGcfbGQWeVOwKtolljwoSVhwcxZsnDungMTiiycUaTyBkgGlErwsLXEpaVOX|https://www.insider.com/private-caribbean-island-for-sale-photos-2022-8| |TikTok|https://www.tiktok.com/t/kZnATxcP/|https://www.tiktok.com/@the_tim_davidson/video/7148742306085063978| |Facebook Watch|https://fb.watch/ZLce_Hif0_/|https://www.facebook.com/watch/?v=752603792999748|

// Name: ๐Ÿง™โ€โ™‚๏ธ Resolve Short Links
// Description: Resolve short links & removes any tracking info
// Author: Ryan Baer
import "@johnlindquist/kit";
const Resolver = {
requiredParams: {
// youtube.com/watch?v=[videoId] requires the 'v' param to locate the video
"youtube.com": ["v"],
// facebook.com/watch?v=[videoId] requires the 'v' param to locate the video
"facebook.com": ["v"],
},
getURL(input: string) {
let processed = input;
if (!input.match(/^https?:\/\//)) {
processed = `https://${processed}`;
}
return new URL(processed);
},
sanitizeUrl(url: URL) {
function getBaseDomain(hostname: string) {
const [tld, base] = hostname.split(".").reverse();
return `${base}.${tld}`;
}
// Clone so we're not deleting from the list as we're iterating it.
const searchParams = new URLSearchParams(url.searchParams);
searchParams.forEach((_value, key) => {
const baseDomain = getBaseDomain(url.hostname);
const skip = this.requiredParams[baseDomain];
if (skip?.includes(key)) {
return;
}
url.searchParams.delete(key);
});
return url.href;
},
async fetchHead(input: string) {
let url: URL;
try {
url = this.getURL(input);
} catch (error) {
return { resolvedUrl: undefined, error: "Invaid or unreachable URL" };
}
const options = { method: "HEAD", credentials: "omit" } as const;
try {
const response = await fetch(url, options);
const responseUrl = new URL(response.url);
const sanitizedUrl = this.sanitizeUrl(responseUrl);
return { resolvedUrl: sanitizedUrl, error: undefined };
} catch (error) {
return { resolvedUrl: undefined, error: this.getErrorMessage(error) };
}
},
getErrorMessage(error: unknown) {
return error instanceof Error
? error.message
: "Something unexpected happened.";
},
async resolve(url: string) {
return this.fetchHead(url);
},
};
const url = await arg("Enter a shortened URL");
const response = await Resolver.resolve(url);
const { resolvedUrl, error } = response;
const result = error ?? resolvedUrl ?? "(Something went wrong)";
const shouldCopy = !!resolvedUrl;
if (shouldCopy) {
await copy(result);
}
await div(
md(`# ๐Ÿ”ฌ Result${shouldCopy ? " (automatically copied to clipboard)" : ""}
\`\`\`
${result}
\`\`\`
`)
);