Exporting Custom Slack Emoji
Published: ....
Last modified: ....
Share this post on BlueskySee discussion on Bluesky
This workflow has been proven to work at the end of March 2024, it may break in the future!
At work we recently bypassed over 60k custom slack emoji in our workspace, and I'd been meaning to download them locally so I could reference them in other discussions outside of slack with past and present coworkers.
I did some scavenging on Google (actually Perplexity) and found a fairly active Gist that seems to have captured a few different routes to accomplish the use case, but some of the API has changed since then.
So I figured I'd document what I did to accomplish this in case others are looking for a similar solution!
Steps:
Download emoji metadata:
- Visit your
/customize/emoji
page in a browser of your choosing a. This should look roughly like:https://<organization>.slack.com/customize/emoji
- Open the network panel and look for a request to the
emoji.adminList
endpoint - Copy that request as
fetch
(right click the request, copy as ->fetch
in Chrome/Arc) - Copy the below snippet and paste it into the console a. This snippet only defines a few helper functions, it won't do anything malicious, but validate it for yourself if you'd like!
// Function to fetch a page of emoji
async function fetchPage(pageNum) {
let res = TODO;
let json = await res.json();
return json;
}
// function to download a JSON file automatically from the console in devtools
function downloadFile(content, fileName, contentType) {
let anchor = document.createElement("a");
document.body.appendChild(anchor);
let file = new Blob([content], { type: contentType });
anchor.href = URL.createObjectURL(file);
anchor.download = fileName;
anchor.click();
}
// Function to batch download emoji
async function downloadAllEmoji(start, stop) {
for (let pageNum = start; pageNum <= stop; pageNum++) {
let json = await fetchPage(pageNum);
let content = JSON.stringify(json);
downloadFile(content, `page-${pageNum}.json`, "application/json");
}
}
- Replace the
TODO
withinfetchPage
with the copiedfetch
request from the network panel - Important! Look in the request body of the
fetch
you copied for apage: 1
entry, and replace the1
with${pageNum}
- you'll also need to change the body to a template string! - Run
downloadAllEmoji
with a start value >= 1 and a stop value <= the max number of pages (you can look at theemoji.adminList
request for page 1 to see the maximum number of pages)
Download images:
To download the actual images, you'll want to:
- Move the downloaded metadata files into a shared directory
- Initialize a new Bun project within that directory by running
bun init
- Paste in the following snippet into
index.ts
:
import fg from "fast-glob";
async function loadAllEmoji() {
const files = await fg("page-*.json");
const emojis = [];
for (const file of files) {
const json = await Bun.file(file).json();
if (!json.emoji) {
throw new Error(
"Emoji not found in " +
file +
", did the response get rate limited? Check the file and re-download if needed!"
);
}
emojis.push(...json.emoji);
}
return emojis;
}
async function downloadEmoji(emojiList) {
for (let emoji of emojiList) {
let res = await fetch(emoji.url);
console.log('Downloading: ' + emoji.name + ' (' + idx + 'of ' + emojiList.length + ')');
await Bun.write(
"./emojis/" + emoji.name + "." + emoji.url.split(".").at(-1),
res
);
}
}
let emoji = await loadAllEmoji();
await downloadEmoji(emoji);
- Install
fast-glob
viabun add fast-glob
- Run the above script via
bun ./index.ts
Note! If you have a lot of emoji, this will take a while! It downloads the images synchronously to avoid being rate limited.
Recommendations:
I recommend not trying to run through all pages at once when fetching the emoji metadata, as sometimes Slack may not be able to handle the requests.
In my case, since we had ~600 pages to go through, I started with chunks of 25 then went up to 100 pages at a time.
Tags:
Related Posts
Tip
Published: ....
A quick tip to implementing CSS theming that's compatible with Server Side Rendered applications!
Published: ....
Recently Dropbox announced that it was shutting down the Capture app/service, so I sought out an alternative that provided a similar user experience!
Published: ....
A quick tip outlining how to provide specific TypeScript type definitions for a local module!
Published: ....
A (running) collection of Bluesky tips, tools, packages, and other misc things!
Published: ....
A quick look at a small but powerful pattern I've been leveraging as of late!
Published: ....
React components have a fundamental contract that is often unstated in their implementation, and you should know about it!
Published: ....
Replace that old useState and useEffect combo for a new and better option!
Published: ....
There's a common gotcha when creating Web Request and Response instances with Headers!
Published: ....
Zed language server quick tip; fixing Zed's language server
Guides
Published: ....
I've started to use Cloudflare to manage my domains for several side projects, have had to jump through the same hooks multiple times that I figured I should document them here!
Published: ....
Revising my previous blog post on React Error Boundaries and my preferred go-to implementation!