Skip to content

Instantly share code, notes, and snippets.

@chrisdone-artificial
Last active March 3, 2026 13:11
Show Gist options
  • Select an option

  • Save chrisdone-artificial/2485c695fa02e0527f13270ea65bb528 to your computer and use it in GitHub Desktop.

Select an option

Save chrisdone-artificial/2485c695fa02e0527f13270ea65bb528 to your computer and use it in GitHub Desktop.
Slack->Todoist GreaseMonkey script
// ==UserScript==
// @name Slack->Todoist
// @version 1
// @match https://app.slack.com/*
// ==/UserScript==
const timer = setInterval(() => {
const containers = Array.from(document.querySelectorAll(`div[data-qa="message_container"]:not([${TAG}])`));
if (containers.length == 0) {
} else {
containers.forEach((container) => {
// Skip messages with a Todoist emoji on it
if (container.querySelectorAll("img[aria-label='todoist emoji']").length != 0)
return;
container.setAttribute(TAG, 'true');
let div = document.createElement("div");
div.style = 'position: absolute; top: 5px; left: 5px; cursor: pointer; width: 16px; height: 16px; opacity: 0.5';
div.innerHTML = ICON;
div.addEventListener('click', async (event) => {
event.stopPropagation();
event.preventDefault();
const text = container.querySelector('[data-qa="message-text"]')?.innerText || container.querySelector('[data-qa="message_content"]')?.innerText || '';
const sender = container.querySelector('[data-qa="message_sender_name"]')?.innerText || 'Slack';
const ts = container.querySelector('a.c-timestamp[href]');
const summary = await summarizeAsk(sender, text);
const params = new URLSearchParams();
params.set('content', '[slack] p2 ' + sender + ': ' + summary + ' today');
params.set('description', ts?.href + '\n' + text);
const url = "todoist://openquickadd?" + params.toString();
window.location.href = url;
});
div.addEventListener('mouseover', function() {
div.style.opacity = '1';
});
div.addEventListener('mouseout', function() {
div.style.opacity = '0.5';
});
container.append(div);
});
}
}, 500);
// Summarise an "IRC message".
const summarizeAsk = async (author, text) => {
try {
const response = await fetch("http://localhost:8080/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"model": "local-model",
"messages": [
{
"role": "system",
"content": "You produce punchy titles of IRC messages for the user with no preamble or commentary."
},
{
"role": "user", "content": "In my next message I will send you an IRC message. Summarise it into a concise title. OK?"
},
{
"role": "assistant", "content": "OK, I'll wait for the message."},
{
"role": "user",
"content": '<' + author + '> ' + text
}
],
"temperature": 0.1
})
});
const data = await response.json();
return data.choices[0].message.content.trim().split(/\n/g)[0].replace(/"/g,'');
} catch (error) {
alert("Fetch error (check if server is running):", error);
}
};
const TAG = 'slack-todoist-tagged';
const ICON = `<svg fill="none" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4 0h24c2.2 0 4 1.8 4 4v24c0 2.2-1.8 4-4 4H4c-2.2 0-4-1.8-4-4v-5.374l.057.033c1.365.794 4.592 2.674 5.382 3.12.478.27.936.263 1.397-.003l6.328-3.641.012-.007.035-.02 6.575-3.784c.277-.16.291-.652-.02-.829l-.217-.124c-.316-.18-.726-.413-.901-.516a1.02 1.02 0 0 0-.998.012c-.154.09-10.5 6.038-10.847 6.235a1.349 1.349 0 0 1-1.338 0L0 19.927v-2.693l.057.033c1.365.795 4.592 2.674 5.382 3.12.478.27.936.264 1.397-.002.276-.16 3.307-1.903 6.367-3.664l.024-.014 6.559-3.775c.277-.16.291-.651-.02-.828l-.217-.124c-.316-.18-.726-.414-.901-.517a1.02 1.02 0 0 0-.998.012c-.154.09-10.5 6.039-10.847 6.235a1.349 1.349 0 0 1-1.338 0L0 14.535v-2.693l.056.033a943.17 943.17 0 0 0 5.383 3.12c.478.27.936.264 1.397-.002l6.374-3.668.008-.005c3.2-1.84 6.426-3.698 6.568-3.78.277-.16.291-.65-.02-.828l-.217-.124c-.317-.18-.727-.414-.901-.516a1.02 1.02 0 0 0-.998.012c-.154.089-10.5 6.038-10.847 6.235a1.349 1.349 0 0 1-1.338 0L0 9.143V4c0-2.2 1.8-4 4-4Z"
fill="#E44232"
/>
<path
d="M6.836 14.993c.276-.16 3.311-1.906 6.374-3.668l.008-.005c3.2-1.84 6.426-3.698 6.568-3.78.277-.16.29-.65-.02-.828l-.217-.124c-.316-.18-.727-.414-.902-.516a1.02 1.02 0 0 0-.997.012c-.155.089-10.501 6.038-10.847 6.235a1.349 1.349 0 0 1-1.339 0L0 9.143v2.698l.056.033c1.364.795 4.592 2.674 5.383 3.12.478.27.936.264 1.397-.002Z"
fill="#fff"
/>
<path
d="m6.836 20.385 6.366-3.664.024-.014 6.56-3.775c.277-.16.29-.651-.02-.828l-.217-.124c-.316-.18-.727-.414-.902-.517a1.02 1.02 0 0 0-.997.012c-.155.09-10.501 6.039-10.847 6.235a1.349 1.349 0 0 1-1.339 0L0 14.535v2.699l.057.033c1.365.795 4.591 2.674 5.382 3.12.478.27.936.264 1.397-.002Z"
fill="#fff"
/>
<path
d="m13.21 22.108-6.374 3.668c-.461.266-.919.273-1.397.003-.79-.446-4.018-2.326-5.382-3.12L0 22.626v-2.699l5.464 3.175c.413.238.925.235 1.339 0 .346-.197 10.692-6.146 10.847-6.235a1.02 1.02 0 0 1 .997-.012c.175.103.586.336.902.516l.218.124c.31.177.296.669.019.829l-6.575 3.784Z"
fill="#fff"
/>
</svg>`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment