Software Development Notions

Botify

April 11, 2021

Introducción

Recientemente he estado trabajando en un proyecto personal que surge por la necesidad de una función específica en spotify, que no podía conseguir a través de la app oficial de la plataforma. Mi objetivo era poder organizar mis playlist por orden de fecha de estreno de la canción. Spotify te permite organizarlas por fecha de agregación (fecha en la que la añadiste a la playlist), pero no por el tipo de organización que yo quería.

Entonces fue cuando descubrí Sort Your Music, que es una aplicación web que te ofrece herramientas para organizar tus playlist de una forma más avanzada.

Gracias a la anterior web me decidí por montar mi propia implementación que tenga lo que me gusta y necesito, y montarlo en algo que de un modo rápido me permita lanzar mi comando de organizar playlist.

El repositorio lo puedes encontrar en:

https://github.com/raulpadilladelgado/botify

Tecnologías usadas

Telegraf

Se trata de una librería que simplifica mucho el como trabajar con los bots de telegram, ya que no interactuas directamente con la API de telegram, si no con unos métodos de la librería que son muy sencillos de entender y de usar. El lenguajes que requiere es JavaScript o TypeScript.

Spotify Web Api Node

Otra librería, pero en este caso nos sirve para simplificar como interactuar con la api de Spotify. Este repositorio en concreto está pensando para usarse con Node JS, que nos viene de maravilla para que combine con Telegraf, pero del mismo owner tenemos otro tipo de wrapper escrito en Java.

NodeJS

Y finalmente nuestro back-end basada en JavaScript.

Desarrollo

Bot de telegram

Lo primero de todo fue crearme un bot en telegram, que se hace de una forma muy sencilla.

Comienza una conversación con @BotFather en telegram usando el comando /start. Tras esto te mostrará información sobre como usar el BotFather para interactuar con los bots que crees. Para crear uno de una forma sencilla aqui tienes el hilo de la conversación para que puedas seguir los pasos:

Raúl Padilla Delgado, [17.01.21 13:18]
/start

BotFather, [17.01.21 13:18]
I can help you create and manage Telegram bots. If you're new to the Bot API, please see the manual (https://core.telegram.org/bots).

You can control me by sending these commands:

/newbot - create a new bot
/mybots - edit your bots [beta]

Edit Bots
/setname - change a bot's name
/setdescription - change bot description
/setabouttext - change bot about info
/setuserpic - change bot profile photo
/setcommands - change the list of commands
/deletebot - delete a bot

Bot Settings
/token - generate authorization token
/revoke - revoke bot access token
/setinline - toggle inline mode (https://core.telegram.org/bots/inline)
/setinlinegeo - toggle inline location requests (https://core.telegram.org/bots/inline#location-based-results)
/setinlinefeedback - change inline feedback (https://core.telegram.org/bots/inline#collecting-feedback) settings
/setjoingroups - can your bot be added to groups?
/setprivacy - toggle privacy mode (https://core.telegram.org/bots#privacy-mode) in groups

Games
/mygames - edit your games (https://core.telegram.org/bots/games) [beta]
/newgame - create a new game (https://core.telegram.org/bots/games)
/listgames - get a list of your games
/editgame - edit a game
/deletegame - delete an existing game

Raúl Padilla Delgado, [17.01.21 13:19]
/newbot

BotFather, [17.01.21 13:19]
Alright, a new bot. How are we going to call it? Please choose a name for your bot.

Raúl Padilla Delgado, [17.01.21 13:20]
botify

BotFather, [17.01.21 13:20]
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.

Raúl Padilla Delgado, [17.01.21 13:20]
Sortifybot

BotFather, [17.01.21 13:20]
Done! Congratulations on your new bot. You will find it at t.me/Sortifybot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:
xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keep your token secure and store it safely, it can be used by anyone to control your bot.

For a description of the Bot API, see this page: https://core.telegram.org/bots/api

Muy importante guardar ese token que nos ha dado, pues es la clave para poder conectar con nuestro bot.

Tras haber realizado lo anterior, nos vamos a nuestro proyecto NodeJS, y vamos a empezar a probar el bot.

const { Telegraf } = require('telegraf');

const bot = new Telegraf(config.bot_id);

bot.start((context)=>{
    context.reply("Hello " + context.from.first_name);
)};

bot.launch();

En el ejemplo anterior hemos instanciado nuestro bot, y hemos programado el comando /start para que nos salude con nuestro nombre.

Para ejecutar con Node un archivo JavaScript puedes usar Node file.js, y así en el ejemplo anterior es como vemos que nuestro bot se mantiene escuchando.

Para más info sobre la librería de Telegraf acerca de los posible métodos a usar y demás mirar:

telegraf.js - v4.3.0

Interactuar con el Wrapper de la API de Spotify

Como he mencionado anteriormente, vamos a utilizar Spotify Web Api Node(disponible en github), para que simplifique nuestro proceso de interactuar con la API de Spotify. Los requisitos para empezar a utilizarlo son:

  • Crearte una cuenta en Spotify Developers
  • Crear una aplicación
  • Localizar tu clientid y clientsecret
  • Guardalos para utilizarlos en el proyecto

También destacar que debemos ir a “Edit Settings”(vease en la imagen anterior), y tener definido lo siguiente:

Que serán las rutas de las que permitimos redigir y que usaremos en el siguiente paso.

images/Untitled.png

images/Untitled1.png

Tras haber realizado todo el proceso anterior, será el momento de poder usar la API en el código. Para instanciar el wrapper simplemente realiza lo siguiente:

const SpotifyWebApi = require('spotify-web-api-node')

var redirect_uri = 'http://localhost:8888/callback'; // Your redirect uri

var spotifyApi = new SpotifyWebApi({
    clientId: client_id, // Your client_id
    clientSecret: client_secret, // Your client_secret
    redirectUri: redirect_uri
});

La ruta de redirección será una que nosotros levantemos localmente integrando Express dentro de NodeJS.

Estaremos definiendo una ruta para login en la que se lleva al usuario a una página que nos crea la librería para que el usuario autorize a nuestra aplicación a acceder al contenido musical de su cuenta; después la ruta de redirección mencionada anteriormente, y que nos permitirá obtener los tokens de acceso; y finalmente la ruta para refrescar los token de acceso.

const express = require('express')
const request = require('request')

var app = express();

var scopes = ['ugc-image-upload',
        'user-read-recently-played',
        'user-top-read',
        'user-read-playback-position',
        'user-read-playback-state',
        'user-modify-playback-state',
        'user-read-currently-playing',
        'app-remote-control',
        'streaming',
        'playlist-modify-public',
        'playlist-modify-private',
        'playlist-read-private',
        'playlist-read-collaborative',
        'user-follow-modify',
        'user-follow-read',
        'user-library-modify',
        'user-library-read',
        'user-read-email',
        'user-read-private'],
    state = 'some-state-of-my-choice';

app.get('/login', function(req, res) {
    res.redirect(spotifyApi.createAuthorizeURL(scopes, state));
});

let code = "";
app.get('/callback', function(req, res) {
    res.sendStatus(200);
    code = req.query.code || null;
    spotifyApi.authorizationCodeGrant(code).then(
        function(data) {
            console.log('The token expires in ' + data.body['expires_in']);
            console.log('The access token is ' + data.body['access_token']);
            console.log('The refresh token is ' + data.body['refresh_token']);
            console.log('The code is' + code);

            spotifyApi.setAccessToken(data.body['access_token']);
            spotifyApi.setRefreshToken(data.body['refresh_token']);
        },
        function(err) {
            console.log('Something went wrong!', err);
        }
    );
});

app.get('/refresh_token', function(req, res) {
    var refresh_token = req.query.refresh_token;
    var authOptions = {
        url: 'https://accounts.spotify.com/api/token',
        headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
        form: {
            grant_type: 'refresh_token',
            refresh_token: refresh_token
        },
        json: true
    };

    request.post(authOptions, function(error, response, body) {
        if (!error && response.statusCode === 200) {
            var access_token = body.access_token;
            res.send({
                'access_token': access_token
            });
        }
    });
});

Los scopes son algo que puedes elegir y que determinaran cuanto acceso tendrá tu aplicación sobre la cuenta spotify del usuario que la use. Puede leer más en: https://developer.spotify.com/documentation/general/guides/scopes/

Y ahora vamos a definir un método de prueba para recibir el nombre de las playlist del usuario que nos ha concedido su permiso de acceso:

async function getUserPlaylists() {
    var result = await spotifyApi.getUserPlaylists().then(
        (data) => {
            return data.body;
        },
        (error) => {
            return error;
        }
    );
    if (result.body && result.body.error.message) return result.body.error.message;
    var playlists = [];
    for (let i = 0; i < result.items.length; i++) {
        playlists.push(result.items[i].name + " - " + result.items[i].id);
    }
    return playlists.toString().replaceAll(",","\n");
}

Para comprobar que recibimos correctamente estas playlists, llamamos al método anterior e imprimimos por consola o cualquier otro método que quieras utilizar. Puedes replicar estos pasos:

  1. Abre tu navegador y ve a localhost:8888/login
  2. Autoriza la app la primera vez, después no te lo pedirá más y cuando vuelvas a iniciar sesión entenderá que ya has dado tu consentimiento.

Llama al método que hemos definido anteriomente para recibir las playlists En mi caso estaba utilizando un bot de telegram para dar una interfaz rápida, asi que el resultado sería algo así:

images/Untitled2.png

Siguientes pasos

En esta primera iteración con el proyecto he ofrecido la posibilidad de ordenar el contenido de una playlist por fecha de estreno de las canciones.

images/404bf091294da200bb142145c748a4c3a68252e2.gif

Entonces surge una pregunta, ¿como explotar el proyecto? Llevo mucho tiempo utilizando spotify en mi día a día y estoy contento con la preocupación que tienen con los usuarios para crear playlist diarios con los gustos personalizados, o también listas de reproduccion con lo que cree que te podría gustar. Para que ocurra esto obviamente spotify va recogiendo tu actividad, tus gustos y preferencias, entonces se me ocurre que quizás nos interesaría conocer dicha información a los usuarios, algo que hasta ahora no ofrece spotify, y tampoco he visto en alguna aplicación externa.

Me gusta mucho crear mis propias playlists, y para mi el orden es fundamental para que cuando esté escuchando música vayan apareciendo las canciones en una secuencia de mi interés, como puede ser la fecha de estreno de las canciones o el BPM de la canción.

En una playlist no todo es ordenar, a veces se duplican canciones o se introducen versiones editadas de las canciones originales (lo que se conoce como Remix) e interesa solo mantener la última versión.

Entre las anteriorer causulistiscas mencionadas y más que puedan surgir, se me ocurre resumir que para hacer más completo botify podríamos añadir:

  • Más formas de ordenar las playlist, ofreciendo métodos alternativos de interés.
  • Sugerir al usuario que borre canciones duplicadas, versiones originales de canciones editadas, canciones que no escucha con frecuencia, etc.
  • Ser capaces de mostrar al usuario sus estadísticas de uso en spotify, sus cantantes más escuchados, entre otros datos de interés.

En el apartado técnico, nos falta algo muy importante como es poder falsear el comportamiento de el wrapper de spotify api que se utiliza en el proyecto para permitir testear todo el comportamiento que tiene nuestra aplicación interactuando con dicho externo. Enlace de interés:

https://dev.to/zaklaughton/the-only-3-steps-you-need-to-mock-an-api-call-in-jest-39mb

Recuerda para clonar el proyecto lo puedes hacer desde el siguiente enlace: https://github.com/raulpadilladelgado/botify


Welcome to my blog about Software Development! I would like to invite you to learning with me 👨‍💻

Search all posts