11

Build a Slack App To Know What Your Teammates Are Listening on Spotify

 2 years ago
source link: https://betterprogramming.pub/building-a-slack-app-for-spotify-with-go-64ff71959bd1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Build a Slack App To Know What Your Teammates Are Listening on Spotify

Using Go

1*VG9TmNAI5ogjHOojdEOexg.png
Spotify for Slack

I love Discord’s Spotify feature, where you can see what others are listening to. I’ve used it a lot in my previous company, but now I use Slack.

Unfortunately, Slack doesn’t have a native feature like that. So I’ve decided to build my own slack app for it (https://github.com/o-mago/spotify-status).

And this is Discord the feature I’ve inspired to build my own for Slack.

1*gW-qslQCntHj20DfSwcmTg.jpeg
Discord’s Spotify feature

In this article I will show how to build the app from the ground and deploy it with zero cost using these technologies:

Index

Building the App

1*Pxs5xf9NhfqTnUNYzQjY_g.png
Flux

The idea of the app is to sync the song the user is listening to with his/her Slack status. To do this the app will have a cronjob to run every X seconds. It will verify if the user is listening to something on Spotify and change the status to something like: :spotify-emoji: song_name - singer.

But we must pay attention to some points:

  • The app must only change user’ status if the current status is blank. We don’t want to overwrite another status set by the user. I often change my status to “having lunch” but still I like to listen to Spotify and I don’t want my status to change to my song.
  • The app must clear the status after stopping Spotify player, but only if the current status was set by our Slack app. We don’t want to clear a status set by the user or another Slack App.
  • Slack status text has a maximum limit of 100 characters: https://api.slack.com/docs/presence-and-status#user-presence-and-status__custom-status__reading-statuses
  • We must define our cronjob time to an interval that is not too big to keep it in sync with Spotify, but it doesn’t need to be too small since a song will be played for 3 minutes on average. 10 seconds is a good interval

We also will need a landing page for the app, where the user will add the app to his/her workspace and allow the app to connect to the Spotify account.

After the user allows our app to access his/her data, we need to store the user’s info (from slack and spotify) in a database to let the cronjob work.

Let’s get the party started. We will use Go to build our application. The full app can be found here.

1*G72as1x_ynxAw9g0_gxvww.gif

We will use the default Go net/http to serve our application and some external libraries:

Architecture

I tried to reach some level of clean architecture here, splitting the code between layers (allowing a Separation of Concerns, SoC). Our core app is a 3-tier architecture with handlers , services and repositories :

  • handlers: they are the frontline that will receive the requests, parse parameters and call services when needed.
  • services: they contain our “business” logic (in our case, setting the Slack Status of the user) and call repositories if necessary.
  • repositories: they will do the interface between our logic (services) and the data source (our PostgreSQL DB in this case)
1*LHb3jmdXR3GRQI0Criwilg.png
App structure

We will create two packages for our entities: domain and db_entities .

  • domain: it’s the entities related to our “business” logic, only contains structs with fields that are necessary. The service layer will only receive and send domain entities. It has a more comprehensive language, associated with ou “business” rules
  • db_entitites: it’s a model of our DB tables, with all it’s columns and functions to transform db_entities -> domain or domain -> db_entities.

OBS: In our case, the domain and db_entities will have the same structure. But it’s not something that will always happen. It depends on the domain modeling.

Ok… I know it’s getting too complicated for a simple Slack App and it really is.

My first approach was just using a single file, but it was a mess. So I’ve decided to refactor it, for studying purposes, try to create some order to the chaos. Also, I will consider some improvements at the end of this article, and having a more solid architecture will help if we need to put more features on it.

Server

First we will create a src folder and a file called server.go . It will be our main package, the one that will be compiled to an executable program. Inside of it we will do some stuff:

  • Get our environment variables:

New Relic, Slack, Spotify and Database credentials will be found after completing their setups in this article next sections. The PORT env var is needed with this exactly name since Heroku assigns a random port for our app.

Setup New Relic:

Setup database and run migration. We will use gorm as our ORM:

Create Spotify Authenticator, in order to get user’s spotify player info:

Create handlers, services and repositories:

Create cron for updating user status:

Create server mux (we don’t need to go default handlers), add handlers and start listening. We need 4 routes:

  • /slackAuth: callback route called by Slack after the user allows our app
  • /callback: callback route called by Spotify after the user allows our app
  • /users: route used by New Relic to ping our app and keep it from sleep
  • /: route to serve our static files (landing page)

As you can see, we will serve static files on our server. It’s the landing page on the website’s root path. Instead of creating another project for our frontend, I’ve decided to serve it with our API. It’s not a good practice, but the page is too simple and I didn’t want to add more complexity at this point.

The static website is basically an HTML file and CSS. It’s just a page for our Add to Slack button.

1*ZVlCRM4RVjeFzG3lr69Img.png
Slack App landing page

Handlers

As we saw before, we have 4 routers, one is for serving only static files, and the others will need handlers.

  • New Relic Health Handler:

It’s just a ping handler

  • Slack callback handler:

It will use our slack app credentials and the code generated after the user clicks on Add to Slack button to retrieve user’s data (using a POST request to Slack API): slack user id and access token.

After, the handler will redirect to Spotify Auth URL setting this user info (id and access token) as Cookies. This way, we can retrieve this info inside the Spotify callback handler.

  • Spotify callback handler:

It will retrieve Slack user’s data that were set with Cookies. Also we will exchange the Spotify Auth page (that the user was redirected) code for an access token. This method Token(state string, r *http.Request) (*oauth2.Token, error)from Spotify library also checks the state we’ve generated on our server.gofile. This way we provide protection against attacks such as cross-site request forgery.

With Slack and Spotify users’ data, we can finally add the user to our database. But if you remember our architecture, we need to call the service layer first.

Services

The app has 2 service methods: AddUser and ChangeUserStatus

  • AddUser:

Here we generate a UUID (Universally Unique IDentifier) for the users table primary key. After we call the repository to create the user on our database.

  • ChangeUserStatus:

This is the core feature of our app. It will retrieve all users we have on our database, iterate and then create one goroutine for each.

I know, I know… You are thinking “we might have some problems as the number of users grows”, and you’re right (I will approach this in the final section of this article). But I decided to not deal with it since the idea is to use only this app inside my company, with no more than 100 users.

Inside the goroutine we will check if the user is playing something or not and also check the current slack status. We can only change the status in two situations:

  • Update status to Spotify song: if the user is playing a song and status emoji is empty or is :spotify:
  • Clear status: if the user is not playing a song and the status emoji is :spotify:

Repositories

There are 2 repository methods: CreateUser and SearchUsers

  • CreateUser:

We try to get the user if it exists or create otherwise.

  • SearchUsers:

This repository retrieves all users from the database

Entities

There will be 2 entities: domain.User and db_entities.User

  • domain.User:

Used inside services

  • db_entities.User:

The model of the user's table on our DB. And also contains the transformations from and to domain.User

Running the app

In order to run our app, I’ve created a Dockerfile and docker-compose:

A free tip for the Mac M1 users: add --platform=linux/amd64 on top of your docker file or Heroku will throw an error

So, to run locally, export all the environment variables and run docker compose up .

Deployment

We will deploy our app as a container on Heroku, using Heroku container. So, there are 3 files to be created:

  • Procfile:

It’s a file to inform Heroku of the command to run when starting the dyno

  • heroku.yml:

It defines our build options for our app on Heroku

  • deployHeroku.sh:

A shell script to make it easier the deploy (export the environment vars before running it)

Conclusion

In the end, we have a working, simple, and free slack app that syncs your Slack status with Spotify.

But… we could change some things here. Add features and make it more secure and reliable thinking about scalability. What if our app becomes popular…

New Features

  • Share what you’re listening to with others using commands
  • Start listening what the others from workspace are listening to
  • Control Spotify using commands

Reliability and Security Improvements

  • Allow the user to exclude his data from our database when removing the app
  • Paginate users to update the status instead of retrieving all
  • Create another project for the landing page, with a different deployment for static website. This webpage would contain more info about the app
  • Encrypt user’s data inside the database with, for example, an AES encryption (we need to decrypt our data in order to retrieve user’s info from Spotify and Slack)

And now, but not less important, in the next section I describe about how to setup the services we need to make it work (Heroku, New Relic, Spotify For Developers and Slack App).

Setup services

Heroku

We will create a new free Heroku app. The address it will give us will be like this: <app-name>.herokuapp.com (e.g. spotify-status-slack.herokuapp.com )

1*4ftuI2y5qCi1UxRgu871Lg.png
Heroku panel

And and two resources:

  • Heroku Postgres : it creates an aws postgres free DB with limited resources, but it will do for our purpose
  • New Relic APM : it will be used to ping our app and keep it alive since Heroku, on free plan, puts the app on sleep if it doesn’t receive any request in 30 minutes
1*L_9ZRozpQeE0UL_DNLXgbA.png
Heroku resources
1*UuwJxk1KiBgaQ_wSsP5grQ.png
Database credentials

New Relic

We will create a new Synthetic monitor inside New Relic with the type Availability . This monitor will only ping an endpoint we will build for it every 1 minute (it can be any time less than 30 min)

1*6n8x_YL6EtCGZ5CJO_cNuQ.png
New relic panel
1*M9R6CKoZLvjXS5KhBeuHPg.png
Availability monitor
1*aYA5KKDLF5cGbCErNrVGpA.png
Monitor settings

Spotify for Developers

First of all, we have to create our Spotify developer account, and create our app inside the platform in order to obtain our Client ID and Client Secret .

1*MDWYX90C2ThPZjTN-oy2-Q.png
Spotify for Developers panel
1*LgKcygtRIhGYwpwXLacm0Q.png
Spotify for Developers app screen

We must also provide a Redirect URL for after the user allows our app to access his/her Spotify account info

1*nzM6K-ySvTCMNxrdB_WHNg.png
Settings modal

Slack App

We will start creating a new workspace for development.

1*9q-5WtZn5NmFsPdkRDFVXA.png

Then, let’s create our app from scratch, naming it and selecting the workspace we created before

1*Fh5QFqGIlcvP_Is6xNcWxQ.png
1*8hgjLdQwzEYXVLqiUBRxQQ.png

We need to configure our permissions for the app:

1*wJKjONhA8COx0cTsrGltMg.png

We need to provide a URL to redirect slack permission website after allowing it in the workspace the user is trying to install the app (this will be the final URL after we create our server and deploying it on heroku):

1*VM8lxdcTBPZTCBlhwlkwcg.png

The scopes refer to the permissions we need the user to allow us when installing the app.

  • user.profile.read : it allows us to get user status
  • user.profile.write : it allows us to set user status to the song the person is listening to on Spotify
1*iTkuRQuAOFxjgP3I1O0Xxg.png

To distribute the app, we will add this pre-made Add to Slack button snippet to our website.

1*2Ibn9ZgUEDSoMf9Skp0qYw.png
1*L6vy76-OsZyO_Qj4v4RO2w.png

We also need to provide our URLs for the app (the final url generated by heroku for us)

1*5Bi-a95FyOhfcW4hkc00yQ.png

Since we will have a landing page, we will select Install from your landing page .

1*orgO_kC0-5PL4cmeVZZxEA.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK