267 lines
12 KiB
Markdown
267 lines
12 KiB
Markdown
![]() |
# Setting Slack Status with PomoDoneApp using Google App Scripts
|
|||
|
|
|||
|
[PomoDoneApp](https://pomodoneapp.com/) is an app that I use on a regular basis to keep myself on task, and monitor my productivity. The thing that really sets it apart from other similar apps is all of the built-in integrations with many of the popular productivity tools like Trello, Todoist, Slack, JIRA and so on.
|
|||
|
|
|||
|
The built-in slack integration uses a public Slack bot, which has the ability to post in slack channels, but is unable to set a user's status. Personally, when I am working, I like to set my status to let my co-workers know not to bother me, or at least let them know why I am not replying to their messages. So, when I originally started using the app, I used their integration with Zapier to make zaps that would change my status automatically. This is very easy to set up and works great, but if, like me, you don't want to spend 25 dollars a month for a Zapier membership, then you are limited to 100 "tasks" per month, which really isn't enough if you are changing your status many times throughout the day.
|
|||
|
|
|||
|
So I went searching for free solutions. I figured that Zapier must be using some API to do what it is doing, so I should be able to use that same API with something that I have developed myself. The PomoDoneApp API has basically know documentation, so I had to reach out to the PomoDoneApp team to ask them how to use their API. I am using [Google API scripts](https://script.google.com/) in this tutorial, but if you don't want to use Google, this can easily be adapted to use something home-grown. I am planning on doing that in the future, but the Google solution works well enough for the time being.
|
|||
|
|
|||
|
The Google scripts that I am using here are adapted from [this google doc](http://pmdn.co/webhooks4todoist) that was sent to me by Alex Mauzon of the PomoDoneApp Team, so kudos to him for helping me get this up and running.
|
|||
|
|
|||
|
## Creating a slack bot
|
|||
|
The first thing that needs to be done, is you need to create a slack bot that is capable of changing your status. You can do this by going to the [Apps dashboard on api.slack.com](https://api.slack.com/apps). There you can create a new app. You then just need to go to the `OAuth & Permissions` section in the left sidebar and add `users.profile:write` scope in the `User Token Scopes` section under `Scopes`. Then you can scroll up to the `OAuth Tokens for Your Workscape` section, install the app, and get the `User OAuth Token`. This token should begin with `xoxp`. Once you have that token, you are ready to create your scripts.
|
|||
|
|
|||
|
## Creating the Google App Scripts scripts
|
|||
|
Go to [script.google.com](https://script.google.com/) and make sure to sign in with your Google Account. Then create a new project. The project will be created with a blank `.gs` script called `Code.gs`:
|
|||
|
``` javascript
|
|||
|
function myFunction() {
|
|||
|
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### The slack status script
|
|||
|
|
|||
|
Change `myFunction` to `testSetStatus` and create another function called `setStatus` that looks like the one below.
|
|||
|
``` javascript
|
|||
|
const APITOKEN = 'xoxp-my-token';
|
|||
|
|
|||
|
function testSetStatus() {
|
|||
|
setStatus('test status', ':man-shrugging', 0);
|
|||
|
}
|
|||
|
|
|||
|
function setStatus(status, emoji, timeEnd) {
|
|||
|
var header = {
|
|||
|
"Content-Type": "application/json; charset=utf-8",
|
|||
|
"Authorization": "Bearer " + APITOKEN,
|
|||
|
};
|
|||
|
|
|||
|
var payload = {
|
|||
|
"profile": {
|
|||
|
"status_text": status,
|
|||
|
"status_emoji": emoji,
|
|||
|
"status_expiration": timeEnd
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var options = {
|
|||
|
'method' : 'post',
|
|||
|
'headers' : header,
|
|||
|
'muteHttpExceptions': false,
|
|||
|
'payload': JSON.stringify(payload)
|
|||
|
};
|
|||
|
|
|||
|
var url2fetch = "https://cyclicarx.slack.com/api/users.profile.set";
|
|||
|
response = UrlFetchApp.fetch(url2fetch, options);
|
|||
|
console.log(response.getContentText())
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
You can then open the `Execution log`, select `testSetStatus` from the menu bar and hit `Run`. If you set up your slack bot correctly, you should see your slack status change to "test status". If the request is unsuccessful, there will be a console message showing the response data which should give you some idea why it didn't work.
|
|||
|
|
|||
|
If this was successful, it's time to set up the PomoDoneApp side of things. First add another function to `Code.gs` called `doPost`:
|
|||
|
``` javascript
|
|||
|
/*
|
|||
|
* Function run when pomodoneapp posts event to google script
|
|||
|
*/
|
|||
|
function doPost(e) {
|
|||
|
if(e.postData){
|
|||
|
var myData = JSON.parse(e.postData.contents);
|
|||
|
}
|
|||
|
|
|||
|
if(myData){
|
|||
|
var pdEvent = myData.eventType; // timerStart, timerStop, cardDone
|
|||
|
var pdTaskName = myData.name; // "Your Task Name",
|
|||
|
var pdTimerSize = myData.minutes; // Duration of the timer, e.g. 5
|
|||
|
}
|
|||
|
|
|||
|
if(pdEvent.toLowerCase() == 'timerstop'){
|
|||
|
// Run on timerstop event
|
|||
|
var response = setStatus('', '', 0);
|
|||
|
}
|
|||
|
|
|||
|
if(pdEvent.toLowerCase() == 'timerstart'){
|
|||
|
// Run on timerstart event
|
|||
|
var timeEnd = Math.floor(Date.now()/1000) + (pdTimerSize*60);
|
|||
|
var response = setStatus(
|
|||
|
'Working on ' + pdTaskName + ' for the next ' + pdTimerSize + ' minutes.',
|
|||
|
':thinking_face:', timeEnd
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This is the function that will be run by the PomoDoneApp. When the timer is stopped, it sends a blank status update, effectively clearing the status, and when the timer starts, it uses the name of the event and the time in minutes to generate an expiry for the status.
|
|||
|
|
|||
|
### The PomoDoneApp Registration Script
|
|||
|
Now that we have a scipt that can change our slack status, we need to tell PomoDoneApp to use it. Press the `+` symbol in the `Files` section of the left sidebar and select `Script`. Call it whatever you want. Mine is called `pgHookReg.gs`. You will be presented with a new file with the same `myFunction` function in it.
|
|||
|
|
|||
|
We'll need 4 constants for this file:
|
|||
|
``` javascript
|
|||
|
const MYAPIKEY = 'my-pomodoneapp-API-key';
|
|||
|
const MYINTEGRATIONNAME = 'gsheet';
|
|||
|
const EVENTTOADD = 'timerStart';
|
|||
|
const DEPLOYMENTID = "my-deploy-id";
|
|||
|
```
|
|||
|
The `DEPLOYMENTID` can be filled in after we deploy the script. `MYAPIKEY` can be found in your PomoDoneApp account. You have to go to `My Settings` and look for `Your API Key` section.
|
|||
|
|
|||
|
Next we need a function for registering the webhook with pomodoneapp:
|
|||
|
``` javascript
|
|||
|
function registerWebHook() {
|
|||
|
var header = {"Content-Type": "application/json"};
|
|||
|
|
|||
|
var payload = {
|
|||
|
"subscription_url": "registered By Google Script",
|
|||
|
"target_url": "https://script.google.com/macros/s/" + DEPLOYMENTID + "/exec",
|
|||
|
"event": EVENTTOADD
|
|||
|
};
|
|||
|
|
|||
|
var options = {
|
|||
|
'method' : 'POST',
|
|||
|
'header' : header,
|
|||
|
'payload': JSON.stringify(payload)
|
|||
|
};
|
|||
|
|
|||
|
var url2fetch = 'https://my.pomodoneapp.com/integration/authorize/?integration=' +
|
|||
|
MYINTEGRATIONNAME + '¶ms[api_key]=' + MYAPIKEY;
|
|||
|
response = UrlFetchApp.fetch(url2fetch, options);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
We'll also want a function to remove the web hook if we want to change something in the future:
|
|||
|
``` javascript
|
|||
|
function removeAllWebHooks() {
|
|||
|
var header = {"Content-Type": "application/json"};
|
|||
|
var payload = {
|
|||
|
"target_url": "https://script.google.com/macros/s/" + DEPLOYMENTID + "/exec"
|
|||
|
}
|
|||
|
|
|||
|
var options = {
|
|||
|
'method' : 'POST',
|
|||
|
'header' : header,
|
|||
|
'payload': JSON.stringify(payload)
|
|||
|
};
|
|||
|
|
|||
|
var url2fetch = 'https://my.pomodoneapp.com/integration/remove/?integration=' +
|
|||
|
MYINTEGRATIONNAME + '¶ms[api_key]='+MYAPIKEY;
|
|||
|
response = UrlFetchApp.fetch(url2fetch, options);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Deploying the scripts
|
|||
|
At this point your scripts should look like this:
|
|||
|
`Code.gs`
|
|||
|
``` javascript
|
|||
|
const APITOKEN = 'xoxp-my-token';
|
|||
|
|
|||
|
function testSetStatus() {
|
|||
|
setStatus('test status', ':man-shrugging', 0);
|
|||
|
}
|
|||
|
|
|||
|
function setStatus(status, emoji, timeEnd) {
|
|||
|
var header = {
|
|||
|
"Content-Type": "application/json; charset=utf-8",
|
|||
|
"Authorization": "Bearer " + APITOKEN,
|
|||
|
};
|
|||
|
|
|||
|
var payload = {
|
|||
|
"profile": {
|
|||
|
"status_text": status,
|
|||
|
"status_emoji": emoji,
|
|||
|
"status_expiration": timeEnd
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
var options = {
|
|||
|
'method' : 'post',
|
|||
|
'headers' : header,
|
|||
|
'muteHttpExceptions': false,
|
|||
|
'payload': JSON.stringify(payload)
|
|||
|
};
|
|||
|
|
|||
|
var url2fetch = "https://cyclicarx.slack.com/api/users.profile.set";
|
|||
|
response = UrlFetchApp.fetch(url2fetch, options);
|
|||
|
console.log(response.getContentText())
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Function run when pomodoneapp posts event to google script
|
|||
|
*/
|
|||
|
function doPost(e) {
|
|||
|
if(e.postData){
|
|||
|
var myData = JSON.parse(e.postData.contents);
|
|||
|
}
|
|||
|
|
|||
|
if(myData){
|
|||
|
var pdEvent = myData.eventType; // timerStart, timerStop, cardDone
|
|||
|
var pdTaskName = myData.name; // "Your Task Name",
|
|||
|
var pdTimerSize = myData.minutes; // Duration of the timer, e.g. 5
|
|||
|
}
|
|||
|
|
|||
|
if(pdEvent.toLowerCase() == 'timerstop'){
|
|||
|
// Run on timerstop event
|
|||
|
var response = setStatus('', '', 0);
|
|||
|
}
|
|||
|
|
|||
|
if(pdEvent.toLowerCase() == 'timerstart'){
|
|||
|
// Run on timerstart event
|
|||
|
var timeEnd = Math.floor(Date.now()/1000) + (pdTimerSize*60);
|
|||
|
var response = setStatus(
|
|||
|
'Working on ' + pdTaskName + ' for the next ' + pdTimerSize + ' minutes.',
|
|||
|
':thinking_face:', timeEnd
|
|||
|
54);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
`pgHookReg.gs`
|
|||
|
``` javascript
|
|||
|
const MYAPIKEY = 'my-pomodoneapp-API-key';
|
|||
|
const MYINTEGRATIONNAME = 'gsheet';
|
|||
|
const EVENTTOADD = 'timerStart';
|
|||
|
const DEPLOYMENTID = "my-deploy-id";
|
|||
|
|
|||
|
function registerWebHook() {
|
|||
|
var header = {"Content-Type": "application/json"};
|
|||
|
|
|||
|
var payload = {
|
|||
|
"subscription_url": "registered By Google Script",
|
|||
|
"target_url": "https://script.google.com/macros/s/" + DEPLOYMENTID + "/exec",
|
|||
|
"event": EVENTTOADD
|
|||
|
};
|
|||
|
|
|||
|
var options = {
|
|||
|
'method' : 'POST',
|
|||
|
'header' : header,
|
|||
|
'payload': JSON.stringify(payload)
|
|||
|
};
|
|||
|
|
|||
|
var url2fetch = 'https://my.pomodoneapp.com/integration/authorize/?integration=' +
|
|||
|
MYINTEGRATIONNAME + '¶ms[api_key]=' + MYAPIKEY;
|
|||
|
response = UrlFetchApp.fetch(url2fetch, options);
|
|||
|
}
|
|||
|
|
|||
|
function removeAllWebHooks() {
|
|||
|
var header = {"Content-Type": "application/json"};
|
|||
|
var payload = {
|
|||
|
"target_url": "https://script.google.com/macros/s/" + DEPLOYMENTID + "/exec"
|
|||
|
}
|
|||
|
|
|||
|
var options = {
|
|||
|
'method' : 'POST',
|
|||
|
'header' : header,
|
|||
|
'payload': JSON.stringify(payload)
|
|||
|
};
|
|||
|
|
|||
|
var url2fetch = 'https://my.pomodoneapp.com/integration/remove/?integration=' +
|
|||
|
MYINTEGRATIONNAME + '¶ms[api_key]='+MYAPIKEY;
|
|||
|
response = UrlFetchApp.fetch(url2fetch, options);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Now we need to deploy. In the top right of the editor window, you should see a big blue `Deploy` button. Click that and select `New deployment`. The type should be `Web app`. Give it any description that you'd like and hit `Deploy`. Once it is deployed you will be given a deployment ID that you can put into the `DEPLOYMENTID` parameter in `pgHookReg.gs`. All you need to do now is open `Execution log`, select `registerWebHook` from the dropdown at the top bar and hit `Run`. If there is no error, then the webhook should now be registered. Test that its working by opening the pomodoneapp and running a timer. You should then see your status change.
|
|||
|
|
|||
|
Now we have the scripts deployed and the `'timerStart'` event registered, but we still need to add a `'timerStop'` hook. Change `EVENTTOADD` to `'timerStop'` and run `registerWebHook` again. Then stop the timer in your pomodone app and you should see your status clear.
|
|||
|
|
|||
|
## Edit: Home-grown Solution
|
|||
|
### Date: 20220313
|
|||
|
Since creating the above scripts, I set out to find a solution that I could host at home instead of relying on Google servers to run it. I ended up creating [this Flask app](https://gitlab.com/fizzizist/pomodone-slack-bridge), which is essentially a python rendition of the above scripts. I run this at home on a Raspi 4 now. It can easily be run on any cloud VM as well, but in that case, it's probably cheaper to just continue using the Google script. If you'd like to use this app instead, you can follow the README to get it up and running.
|