Em's Site

Hide Giphies in Slack

Going through how to hide giphy posts in Slack

TL;DR: if you just want the script, it's here. Works with TamperMonkey (Chrome) and GreaseMonkey (Firefox).

Slack has a somewhat funny /giphy command, where you can type /giphy whatever and it will display a random GIF of whatever. As cute and funny as they can be, I have discovered that this might get annoying when your entire channel is full of nothing but GIFs. Now, Slack does have preferences to not show the image by default, but it still wastes space. So, I decided to find a way to remove them.

Note: This only works if you access Slack through a browser, not the app, as there's no way (that I know of) to insert a script into the app

Easy Firefox way

My first idea was to use the userContent.css in Firefox. If you aren't familiar with userContent.css, it's a css file that lives [in your Firefox profile][5], and allows you to add CSS that will be inserted into every page. AFAIK, there isn't an equivalent to this in any other browser (Chrome had something similar, but it was removed) In slack, the wrapper of giphy images has a data-real-src attribute, which I'm assuming is for lazy loading images. So the easy method is to put [data-real-src*="giphy.com"] {display: none} in the userContent.css in your profile.

That sort of works, it hides the image, but doesn't hide the command to show it. Looking at the DOM, there was no way to differentiate between giphy commands and normal commands. So, JavaScript is necessary if we want to truly hide the giphy posts.

TamperMonkey script (part 1)

Slack has a interesting DOM structure, where the image is inside a <span> inside a <div>. So from the image, we would need to get the ancestor two levels up from the image. The idea is simple, use [document.querySelectorAll][6] to find all the giphy image elements and loop through them. For each element, go two levels up the tree by calling [parentElement][7] twice, and then hide that element. Or, in code:

var giphies = document.querySelectorAll('div[data-real-src*="giphy.com"]');
for (var i = 0; i < giphies.length; i++) {
  giphies[i].parentElement.parentElement.style.display = "none";
}

But Slack gets all the messages using AJAX, so they aren't in the DOM when the load event fires. Poking around with the inspector, I found that the <body> element has a class of loading to start, and then that class is removed when all the messages are shown. Using that, I could rig up a check using setInterval to wait until the class is removed:

var timeout;

function hideGiphies() {
  if (!document.body.classList.contains('loading')) {
    var giphies = document.querySelectorAll('div[data-real-src*="giphy.com"]');
    for (var i = 0; i < giphies.length; i++) {
      giphies[i].parentElement.parentElement.style.display = "none";
    }
      window.clearInterval(timeout);
  }
}

document.addEventListener('load', function() {
  timeout = window.setInterval(hideGiphies, 200);
});

Now we have a script that reliably removes all giphy images on page load.

Except it doesn't (part 2)

Slack doesn't load all the messages for a channel to start. If you scroll up, it will load more messages, which is a problem for two reasons: 1) The new messages will have the giphy images, because they weren't in the DOM to begin with, and 2) All the GIFs that were hidden will be shown again, because for some reason all the message divs are deleted and then reinserted into the DOM. Not feeling like digging into Slack's JavaScript, I opted to use mutation observers instead.

Mutation observers are a newer addition to the DOM API. They are similar to the Event object in that they bind a function to be called on a change, or mutation, of an element. These mutations can observe a number of different properties, including child node changes. Stepping through the "loading more" function, I found a wrapper div where all the nodes got deleted and then added back.

With a mutation observer attached to that wrapper div, we get the following script:

var timeout;

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (mutation.addedNodes.length !== 0) hideGiphies();
  });
});

function hideGiphies() {
  if (!document.body.classList.contains('loading')) {
    var giphies = document.querySelectorAll('div[data-real-src*="giphy.com"]');
    for (var i = 0; i < giphies.length; i++) {
      giphies[i].parentElement.parentElement.style.display = "none";
    }
    var target = document.querySelector('#msgs_div');
    observer.observe(target, {childList: true});
    window.clearInterval(timeout);
  }
}

document.addEventListener('DOMContentLoaded', function() {
  timeout = window.setInterval(hideGiphies, 200);
});

Note that I switched to DOMContentLoaded instead of load, because load was taking a while to fire, depending on the amount of images. The difference between the two is DOMContentLoaded will fire when the HTML is finished parsing, while load will fire only after all the external resources have finished downloading.

Conclusion

So, there it is. A simple script to hide giphies in Slack. If you missed the link at the top, here it is again. It's released into the public domain with the CC0 license, so you can take it and do with it as you please. Maybe port it to hipchat or gitter, or hide Slackbot's auto-responses. If you do make something cool and want me to link it here, shoot me an email (see footer for address) with the link and what you did, and maybe we can start a collection of Slack scripts!