jesustorresdev / slack-badges-bot

2 stars 1 forks source link

Implementa la funcionalidad de listar las medallas en slack #11

Closed mbdaso closed 5 years ago

mbdaso commented 5 years ago

Al principio tuve un problema con la peticion que llega desde slack al servidor en

async def slash_command_handler(self, request):
        # TODO: Comprobar argumentos en request y añadir manejo de errores y excepciones
        # TODO: Usar signed secrets para comprobar que quien hace la petición es Slack.
        #       Ver https://api.slack.com/docs/verifying-requests-from-slack
        try:
            request.query['text']

request.query['text'] produce "KeyError: 'text'" Así que escribí un método para pasarlo todo a json con urllib:

    async def urlstring_to_json(self, request: web.Request):
        request_bytes = await request.read()
        request_json = urllib.parse.parse_qs(request_bytes.decode('utf-8'))
        request_json = {key:request_json[key][0] for key in request_json}
        return request_json

De resto, todo bien. Así se ve en slack:

Peek 2019-08-09 02-02

mbdaso commented 5 years ago

https://github.com/aplatanado/slack-badges-bot/pull/11/commits/e8960f04b4c7c4f9e3d0d77e67c84cb5774d47cc Corregidos fallos en verificación

jesustorresdev commented 5 years ago

Al principio tuve un problema con la peticion que llega desde slack al servidor en

async def slash_command_handler(self, request):
        # TODO: Comprobar argumentos en request y añadir manejo de errores y excepciones
        # TODO: Usar signed secrets para comprobar que quien hace la petición es Slack.
        #       Ver https://api.slack.com/docs/verifying-requests-from-slack
        try:
            request.query['text']

request.query['text'] produce "KeyError: 'text'"

Ok. No necesitas esa función. Te explico el error.

Slack te hace a ti una petición HTTP para pasarte los datos. En ese sentido el servidor de Slack hace de cliente tu de navegador. Para tratar la petición hay que mirar la documentación porque hay distintos tipos de peticiones y según el tipo hay varias formas de pasar parámetros.

Todos los tipos admiten mandar información en el cuerpo de la petición. Así que por ahí se mueden mandar los parámetros codificados como se prefiera. Está de moda usar JSON pero podría ser XML, texto plano o un formato que inventes. En resumen, un petición GET se manda así por la red.

GET /index.html HTTP/1.1
 Host: www.example.com
 Referer: www.google.com
 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
 Connection: keep-alive
 [Línea en blanco]
{
  "id": "hola",
  "text": "hoaoalala"
}

Y lo mismo pasa con peticiones POST, DELETE, UPDATE y otras.

Pero esta no la única forma de pasar parámetros. Otra forma es en a URL. Simplemente se añade a la URL ? seguido de los parámetros separados por &. Así una petición a:

http://www.example.com/index.html?opcion1=foo&opcion2=bar

Se convierte en una petición así:

GET /index.htmll?opcion1=foo&opcion2=bar HTTP/1.1
 Host: www.example.com
 Referer: www.google.com
 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
 Connection: keep-alive
 [Línea en blanco]

Eso es lo que suelen hacer los navegadores cuando pones una URL y le das a enter. Ahora bien, si la petición se crea con código, usando una librería como requests, la petición puede tener cuerpo y parámetros en la query:

GET /index.htmll?opcion1=foo&opcion2=bar HTTP/1.1
 Host: www.example.com
 Referer: www.google.com
 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
 Connection: keep-alive
 [Línea en blanco]
{
  "id": "hola",
  "text": "hoaoalala"
}

Y ahi está la cosa. En el request de AIOHttp query es un diccionario con los parámetros de la URL:

https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.BaseRequest.query

Mientras que read() sirve para leer el body (si existe).

https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.BaseRequest.read

Claro, slack no te pasa text por la URL sino por el cuerpo. No necesitas esa función que has hecho porque si miras un poco más abajo verás que request tiene el método json():

https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.BaseRequest.json

Que lee el cuerpo y lo decodifica considerando es un JSON.

jesustorresdev commented 5 years ago

De resto, todo bien. Así se ve en slack:

Peek 2019-08-09 02-02

Está chulo :)

mbdaso commented 5 years ago

Tampoco funciona con json()

Traceback (most recent call last):
  File "/home/mbelda/repos/slack-badges-bot/slack_badges_bot/adapters/slack.py", line 76, in slash_command_handler
    request_json = await request.json()
  File "/home/mbelda/.local/share/virtualenvs/slack-badges-bot-5-YPmOZL/lib/python3.7/site-packages/aiohttp/web_request.py", line 584, in json
    return loads(body)
  File "/usr/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.7/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Este es el paquete que manda slack:

POST /slack/slash-command HTTP/1.0
Host: localhost:5000
Connection: close
Content-Length: 365
User-Agent: Slackbot 1.0 (+https://api.slack.com/robots)
Accept-Encoding: gzip,deflate
Accept: application/json,*/*
X-Slack-Signature: v0=d0c98b410ef172e5c774aa1c3647b01dc23b8ff894d7ffc73a27f2611b071a22
X-Slack-Request-Timestamp: 1565308514
Content-Type: application/x-www-form-urlencoded
Cache-Control: max-age=259200

token=41S425kCtbTlfDvhNhbkRyCL&team_id=TJ4FFFPQC&team_domain=ullespacio&channel_id=CJ4FFG21J&channel_name=general&user_id=UHYNH5UL9&user_name=alu0100832211&command=%2Fbadges&text=list+all&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FTJ4FFFPQC%2F721129123488%2FAhjK0m1F4WWOQTQeEo3furdu&trigger_id=722999692167.616525533828.511ae45d3cfeecc1bdfb44a7820d4c29% 
jesustorresdev commented 5 years ago

Vale. Es que lo llamaste request_json pero no era un json. Es el formato típico con el que se mandan los formularios con un post. Se llaman application/x-www-form-urlencoded y es similar a la query en la URL.

Usa https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.BaseRequest.post

mbdaso commented 5 years ago

Ahora sí