Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Open Slack links in the client #370

ephimoff started this conversation in General
Mar 11, 2020 · 8 comments · 2 replies
Discussion options

Hi,

I was trying to use a handler to open the Slack links in the Slack.app client on mac but it didn't work. The app opens up but not on the right message. Any thoughts on how to achieve that?

Here is the code I used:

{
 // Open these urls in Slack app
 match: finicky.matchHostnames([
 "slack.com",
 ]),
 browser: "com.tinyspeck.slackmacgap"
},
You must be logged in to vote

Replies: 8 comments 2 replies

Comment options

I think we should need to rewrite the https://slack.com/app_redirect to the deeplink slack://open format. I will give it a try during easter.

Ref: https://api.slack.com/reference/deep-linking

You must be logged in to vote
0 replies
Comment options

This at least rewrites the url to the correct one.

rewrite: [
 {
 // Redirect all https://slack.com/app_redirect?team=team=apegroup&channel=random 
 // to slack://channel?team=apegroup&id=random
 match: ({ url }) => url.host.includes("slack.com") && url.pathname.includes("app_redirect"),
 url({ url }) {
 const team = url.search.split('&').filter(part => part.startsWith('team'));
 var channel = "" + url.search.split('&').filter(part => part.startsWith('channel'));
 var id = channel.replace("channel", "id");
 return {
 protocol: "slack",
 username: "",
 password: "",
 host: "channel",
 port: null,
 pathname: "",
 search : team + '&' + id,
 hash: ""
 }
 
 }
 } 
 ]
You must be logged in to vote
0 replies
Comment options

Awesome!
Do I just put it into my .finicky.js?

You must be logged in to vote
0 replies
Comment options

Yes. Still haven't figured out how to get it to actually open Slack in a correct way.

You must be logged in to vote
0 replies
Comment options

Spent some time starting with what @ovelindstrom posted and modified it for my use case. Managed to get most links to work that I use (not sure about others). There didn't appear to be any documentation on how to translate deep linked messages here, but eventually figured out through trial and error how to format the message identifier. Below is the config that I use, but be sure to populate the org map with the proper subdomains and team identifiers:

 handlers: [
 {
 // Redirect all web links
 // from: https://app.slack.com/client/<team id>/<channel>
 // to: slack://channel?team=<team-id>&id=<channel-id>
 //
 // Redirect all deep linked messages
 // from: https://<subdomain>.slack.com/archives/<channel-id>/p<16-digit-timestamp>
 // to: slack://channel?team=<team-id>&id=<channel-id>&message=<10-digit-6-decimal-timestamp> 
 browser: "/Applications/Slack.app",
 match: [
 '*.slack.com/client/*',
 '*.slack.com/archives/*'
 ],
 url({ url }) {
 const parts = url.pathname.split('/')
 // Return input URL if no expected path is found
 if (parts.length < 2) return url
 let team
 switch (parts[1]) {
 // For direct web links
 case 'client':
 team = parts[2]
 parts.splice(2, 1) // Remove team identifier to match archives format
 break
 // For deep links
 case 'archives':
 const org = url.host.split('.')[0]
 switch (org) {
 case '<org subdomain>':
 // Starts with a T and can be found in the web app URL for any channel in your org
 team = '<team id>'
 break
 default:
 // Return input URL if no team lookup available
 return url
 }
 }
 search = `team=${team}`
 let channel = parts[2]
 if (parts.length === 3) {
 // If this is a link to a channel/user
 search = `${search}&id=${channel}`
 // If this is a link to a message
 } else if (parts.length === 4) {
 const message = parts[3].slice(1, 11) + '.' + parts[3].slice(11)
 search = `${search}&channel=${channel}&message=${message}`
 }
 return {
 protocol: "slack",
 username: "",
 password: "",
 host: "channel",
 port: null,
 pathname: "",
 search: search,
 hash: ""
 }
 }
 }
 ]

Also, may be worth linking this response to another related issue: #158

You must be logged in to vote
0 replies
Comment options

I wrote a more robust version of this which supports team hosts, enterprise hosts, and the generic app.slack.com host. It's also just regex matches so if more URL formats are discovered it should be easy to add them to the list. It works in two parts, first doing a rewrite to the slack:// URL then using a handler for the slack protocol to send to the Slack app. This lets us fall back to using the browser if the conversion wasn't successful. I've tested it with all of the following URL formats I could find for our Slack Enterprise and they work as expected:

Note that I did not add support for app_redirect URLs as they can sometimes work but not universally. Any of the following are valid and can be forwarded via the browser:

But deeplinks only support IDs rather than names, so only the first could actually be converted. It's possible a regex could be made to match only IDs, but I couldn't find much information about the endpoint or the ID formats it supports so I opted to ignore it.

Also JavaScript isn't my language of choice, so there may be bugs and the code probably isn't optimal.

module.exports = {
 handlers: [
 {
 match: ({ url }) => url.protocol === "slack",
 browser: "/Applications/Slack.app"
 }
 ],
 rewrite: [
 {
 match: [
 '*.slack.com/*',
 ],
 url: function({ url, urlString }) {
 const subdomain = url.host.slice(0, -10)
 const pathParts = url.pathname.split("/")
 let team, patterns = {}
 if (subdomain != 'app') {
 switch (subdomain) {
 case '<teamname>':
 case '<corpname>.enterprise':
 team = 'T00000000'
 break
 default:
 finicky.notify(
 `No Slack team ID found for ${url.host}`, 
 `Add the team ID to ~/.finicky.js to allow direct linking to Slack.`
 )
 return url
 }
 
 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
 }
 }
 ]
}
You must be logged in to vote
1 reply
Comment options

Many thanks to you! This was very useful to increase slack support. Here's a gist for those that are interested (Finicky v3)

https://gist.github.com/bric3/f1863ed36d6bb57d46724e522c544d75

I'll update this gist with Finicky v4 when it gets released.

Comment options

Not sure if a finicky update caused this to stop working, but I fixed mine by returning outputStr instead of output.
So replace:

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

with

let outputStr = `slack://${host}?${search}`
console.log(`Rewrote Slack URL ${urlString} to deep link ${outputStr}`)
return outputStr

Note

Note that a number of optional URL parameters were removed, and will need to be added to outputStr

You must be logged in to vote
0 replies
Comment options

Final working with v4, solved all warnings, I've updated the wiki.

(Change default browser and browser name)

// 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 (urlObj) {
 const subdomain = urlObj.host.slice(0, -10); // before .slack.com
 const pathParts = urlObj.pathname.split("/");
 let team,
 patterns = {};
 if (subdomain != "app") {
 if (!Object.keys(slackSubdomainMapping).includes(subdomain)) {
 console.log(
 `No Slack team ID found for ${urlObj.host}`,
 `Add a correct team ID to ~/.finicky.js to allow direct linking to Slack.`
 );
 return urlObj;
 }
 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(urlObj.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 outputStr = `slack://${host}?${search}`;
 console.log(
 `Rewrote Slack URL ${urlObj.href} to deep link ${outputStr}`
 );
 
 return new URL(outputStr);
 }
 }
 }
 return urlObj;
 },
};
module.exports = {
 defaultBrowser: "Arc",
 rewrite: [slackRewriter],
 handlers: [
 {
 match: ({ url }) => {
 // Check for both 'slack:' and 'slack' since the property might not include the colon
 return url.protocol === "slack:" || 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: ({ url }) => {
 const workDomains = Object.keys(workSlackTeamMapping).map(subdomain => subdomain + ".slack.com");
 return workDomains.includes(url.host);
 },
 browser: "Arc", // your work browser
 },
 ],
};
You must be logged in to vote
1 reply
Comment options

module.exports = {

should updated with

export default {

Thank you so much for the script. It works perfectly for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help with app someone needs help with opening a specific application
Converted from issue

This discussion was converted from issue #96 on March 16, 2025 14:17.

AltStyle によって変換されたページ (->オリジナル) /