-
-
Notifications
You must be signed in to change notification settings - Fork 142
Configuration ideas
Feel free to add your own ideas for how to use Finicky.
rewrite: [{
match: ({url}) => url.protocol === "http",
url: {
protocol: "https"
}
}]
handlers: [{
// Open any link clicked in Mail & Outlook in Google Chrome
match: ({opener}) =>
["com.apple.mail", "com.microsoft.Outlook"].includes(opener.bundleId),
browser: "Google Chrome"
}]
or
handlers: [{
// Open any link clicked in Mail & Outlook in Google Chrome
match: ({opener}) =>
["Mail", "Microsoft Outlook"].includes(opener.name),
browser: "Google Chrome"
}]
handlers: [{
match: ({url}) =>
url.host.includes("example.com") && url.pathname.includes("Finicky"),
browser: "Firefox"
}]
rewrite: [{
match: "amazon.com/*",
url: {
host: "smile.amazon.com"
}
}]
rewrite: [{
match: () => true, // Execute rewrite on all incoming urls to make this example easier to understand
url: ({url}) => {
const removeKeysStartingWith = ["utm_", "uta_"]; // Remove all query parameters beginning with these strings
const removeKeys = ["fbclid", "gclid"]; // Remove all query parameters matching these keys
const search = url.search
.split("&")
.map((parameter) => parameter.split("="))
.filter(([key]) => !removeKeysStartingWith.some((startingWith) => key.startsWith(startingWith)))
.filter(([key]) => !removeKeys.some((removeKey) => key === removeKey));
return {
...url,
search: search.map((parameter) => parameter.join("=")).join("&"),
};
},
}]
rewrite: [{
match: finicky.matchDomains(["google.com"]),
url: "https://duckduckgo.com"
}]
handlers: [{
match: finicky.matchDomains(["trello.com"]),
url: ({url}) =>
({...url, protocol: "trello"}),
browser: "Trello"
}]
handlers: [{
match: finicky.matchDomains("open.spotify.com"),
browser: "Spotify"
}]
handlers: [{
match: /zoom\.us\/join/,
browser: "us.zoom.xos"
}],
rewrite: [{
match: ({
url
}) => url.host.includes("zoom.us") && url.pathname.includes("/j/"),
url({
url
}) {
try {
var pass = '&pwd=' + url.search.match(/pwd=(\w*)/)[1];
} catch {
var pass = ""
}
var conf = 'confno=' + url.pathname.match(/\/j\/(\d+)/)[1];
return {
search: conf + pass,
pathname: '/join',
protocol: "zoommtg"
}
}
}]
handlers: [{
// Open Apple Music links directly in Music.app
match: [
"music.apple.com*",
"geo.music.apple.com*",
],
url: {
protocol: "itmss"
},
browser: "Music",
}]
handlers: [{
match: finicky.matchHostnames(['teams.microsoft.com']),
browser: 'com.microsoft.teams',
url: ({url}) =>
({...url, protocol: 'msteams'}),
}]
handlers: [{
match: "https://www.figma.com/file/*",
browser: "Figma",
}]
rewrite: [{
match: /vk\.com\/away.php/,
url: ({url}) => {
const match = url.search.match(/to=(.+)/)
return !match ? url : decodeURIComponent(decodeURIComponent(match[1]));
}
}]
For example, you may want to only open matching URLs in a specific browser when opening those URLs from within a
particular application. In that case, you need to match on both the opener
and the url
:
handlers: [{
// Open Google Drive in Firefox if opened from Slack
match: ({opener, url}) =>
opener.bundleId === "com.tinyspeck.slackmacgap" && url.host.includes("drive.google.com"),
browser: "Firefox"
}]
Open Jitsi links in Jitsi desktop app for MacOS
handlers: [{
match: ({url}) => url.host.includes("jitsi.your-selfhosted-server.com") ||
url.host.includes("meet.jit.si"),
url: ({url}) => {
return {
...url,
protocol: "jitsi-meet",
host: url.host,
pathname: url.pathname
};
},
browser: "/Applications/Jitsi\ Meet.app"
}]
Since 3.2.0, you can specify the browser profile as an option.
{
match: ({opener}) => ["Telegram"].includes(opener.name),
browser: {
name: "Microsoft Edge",
profile: "Profile 1",
};
}
This example assumes you have installed Figma, Linear, and Slite desktop apps. But you can make it work with any other app.
- install the targeted apps you want to support,
- check their URL schemes with:
defaults read /Applications/THE_APP.app/Contents/Info.plist CFBundleURLTypes
handlers: [{
match: ({url}) => !url.protocol.match(/^https?$/),
browser: "Finder"
}],
rewrite: [{
match: ({url}) => url.host.split('.').some((c) => ['figma', 'linear', 'slite'].includes(c)),
url: ({url}) => {
const protocol = url.host.match(/\.?(\w+)\.\w\w\w?$/)[1];
const exceptions = { 'linear': ['uploads.linear.app'] };
if (!(protocol in exceptions) || !exceptions[protocol].includes(url.host)) {
return {...url, protocol };
}
return url;
}
}]
handlers: [{
match: "https://discord.com/*",
url: { protocol: "discord" },
browser: "Discord",
}]
rewrite: [{
match: finicky.matchHostnames("www.spamcop.net"),
url: { hash: "report" }
}]
rewrite: [{
// Redirect Tiktok video links to use Proxitok public proxies
match: ({ url }) => (url.host.endsWith("tiktok.com") && url.pathname.startsWith('/@')) || url.host.endsWith("vm.tiktok.com"),
url: ({ url }) => {
// See more https://github.com/pablouser1/ProxiTok/wiki/Public-instances
const selectRandomTikTokProxy = () => {
const TIKTOK_PROXIES = [
"proxitok.pabloferreiro.es", // Official
"proxitok.pussthecat.org",
"tok.habedieeh.re",
"proxitok.esmailelbob.xyz",
"proxitok.privacydev.net",
"tok.artemislena.eu",
"tok.adminforge.de",
"tt.vern.cc",
"cringe.whatever.social",
"proxitok.lunar.icu",
"proxitok.privacy.com.de",
"cringe.seitan-ayoub.lol",
"cringe.datura.network",
"tt.opnxng.com",
"tiktok.wpme.pl",
"proxitok.r4fo.com",
"proxitok.belloworld.it",
]
return TIKTOK_PROXIES[Math.floor(Math.random() * TIKTOK_PROXIES.length)]
}
return {
protocol: "https",
host: selectRandomTikTokProxy(),
// Prepend pathname with /@placeholder/video to match ProkiTok urls
pathname: (url.pathname.startsWith('/@') ? url.pathname : `/@placeholder/video${url.pathname}`)
}
}
]}
// Based on @opalelement's answer https://github.com/johnste/finicky/issues/96#issuecomment-844571182
// Team ID can be found in the browser URL : https://slack.com/help/articles/221769328-Locate-your-Slack-URL-or-ID
// Free, Pro, and Business+ plans => Team or workspace ID starts with a T in https://app.slack.com/client/TXXXXXXX/CXXXXXXX
// Enterprise grid plans => Org ID starts with an E in https://app.slack.com/client/EXXXXXXX/CXXXXXXX
const workSlackTeamMapping = {
// 'subdomain': 'TXXXXXXX',
// 'acmecorp.enterprise': 'EXXXXXXX',
// 'acmecorp': 'EXXXXXXX',
}
const personalSlackMapping = {
// personal slacks
}
const slackSubdomainMapping = {
...workSlackTeamMapping,
...personalSlackMapping,
}
const slackRewriter = {
match: [
'*.slack.com/*',
],
url: function({ url, urlString }) {
const subdomain = url.host.slice(0, -10) // before .slack.com
const pathParts = url.pathname.split("/")
let team, patterns = {}
if (subdomain != 'app') {
if (!Object.keys(slackSubdomainMapping).includes(subdomain)) {
finicky.log(
`No Slack team ID found for ${url.host}`,
`Add a correct team ID to ~/.finicky.js to allow direct linking to Slack.`
)
return url
}
team = slackSubdomainMapping[subdomain]
if (subdomain.slice(-11) == '.enterprise') {
patterns = {
'file': [/\/files\/\w+\/(?<id>\w+)/]
}
} else {
patterns = {
'file': [/\/messages\/\w+\/files\/(?<id>\w+)/],
'team': [/(?:\/messages\/\w+)?\/team\/(?<id>\w+)/],
'channel': [/\/(?:messages|archives)\/(?<id>\w+)(?:\/(?<message>p\d+))?/]
}
}
} else {
patterns = {
'file': [
/\/client\/(?<team>\w+)\/\w+\/files\/(?<id>\w+)/,
/\/docs\/(?<team>\w+)\/(?<id>\w+)/
],
'team': [/\/client\/(?<team>\w+)\/\w+\/user_profile\/(?<id>\w+)/],
'channel': [/\/client\/(?<team>\w+)\/(?<id>\w+)(?:\/(?<message>[\d.]+))?/]
}
}
for (let [host, host_patterns] of Object.entries(patterns)) {
for (let pattern of host_patterns) {
let match = pattern.exec(url.pathname)
if (match) {
let search = `team=${team || match.groups.team}`
if (match.groups.id) {
search += `&id=${match.groups.id}`
}
if (match.groups.message) {
let message = match.groups.message
if (message.charAt(0) == 'p') {
message = message.slice(1, 11) + '.' + message.slice(11)
}
search += `&message=${message}`
}
let output = {
protocol: "slack",
username: "",
password: "",
host: host,
port: null,
pathname: "",
search: search,
hash: ""
}
let outputStr = `${output.protocol}://${output.host}?${output.search}`
finicky.log(`Rewrote Slack URL ${urlString} to deep link ${outputStr}`)
return output
}
}
}
return url
}
}
module.exports = {
rewrite: [
slackRewriter,
],
handlers: [
{
match: ({ url }) => url.protocol === "slack",
browser: 'Slack',
},
{
// Optional. If these work url cannot be converted, open them is work browser
// Login in work workspace unfortunately lands on the personal browser, just copy the link to the work browser
match: Object.keys(workSlackTeamMapping).map(subdomain =>
finicky.matchDomains(subdomain + ".slack.com")
),
browser: 'Chrome' // you work browser
},
],
}
On occasions, it's not possible to select a browser programmatically, it's useful to open a browser selector. Since there's none yet in Finicky, it's possible to use Browserosaurus.
Example with slack login URLs, e.g. when adding a Workspace from the app :
const browsers = {
personal: {
name: 'org.mozilla.firefox',
openInBackground: false,
},
work: {
name: 'com.google.Chrome',
openInBackground: false,
},
selector: {
name: 'com.browserosaurus',
openInBackground: false,
}
}
module.exports = {
handlers = [
{
// Select browser when slack wants to login
// https://app.slack.com/ssb/add?s=1&v=4.41.90&ssb_vid=.<hex id>&ssb_instance_id=<UUID>
match: [
"app.slack.com/ssb/*"
],
browser: browsers.selector,
},
]
}
Browserosaurus lists all apps that can handle links Slack, Spotify, Zoom, etc. however they can be sorted in the preferences.