meedan/check-bot

View on GitHub
slash-response.js

Summary

Maintainability
D
2 days
Test Coverage
A
100%
// This function adds a ProjectMedia to Check and sends a message back to Slack

const config = require('./config.js'),
      request = require('request'),
      util = require('util'),
      qs = require('querystring'),
      https = require('https'),
      CheckError = require ('./CheckError'),
      imgur = require('imgur');

const { executeMutation, getRedisClient, t, getGraphqlClient, getTeamConfig, saveToRedisAndReplyToSlack, projectMediaCreatedMessage, humanAppName } = require('./helpers.js');

const replyToSlack = function(team, responseUrl, message, callback) {
  request.post({ url: responseUrl, json: true, body: message, headers: { 'Content-type': 'application/json' } }, function(err, res, resjson) {
    console.log('Response from Slack message update: ' + util.inspect(res));
  });
  if (callback) {
    callback(null, message);
  }
};

const getProject = function(teamSlug, projectId, token, done, fail, callback) {
  const client = getGraphqlClient(teamSlug, token, callback);
  const projectQuery = `
  query project($ids: String!) {
    project(ids: $ids) {
      dbid
      title
      description
      team {
        name
        slug
      }
    }
  }
  `;
  client.query(projectQuery, { ids: projectId.toString() })
  .then((resp, errors) => {
    console.log('GraphQL query response: ' + util.inspect(resp));
    done(resp);
  })
  .catch(function(e) {
    fail(e);
    console.log('GraphQL query exception: ' + e.toString());
  });
};

const sendErrorMessage = function(e, vars, text, team_id, responseUrl, callback) {
  const message = { response_type: "ephemeral", text: text };
  if (e.rawError && (error = e.rawError[0])) {
    let error_message = error.message;
    if (error && error.code === CheckError.codes.DUPLICATED ) { error_message += ': ' + error.data.url; };

    message.attachments = [{
      color: 'warning',
      text: error_message,
      footer: vars.url,
      fallback: error_message
    }]
  };
  console.log('Error: ' + util.inspect(e));
  replyToSlack(team_id, responseUrl, message, callback);
};

const createProjectMedia = function(team_id, responseUrl, vars, token, data, callback) {
  const mutationQuery = `($pid: Int!, $url: String!, $clientMutationId: String!) {
    createProjectMedia: createProjectMedia(input: { clientMutationId: $clientMutationId, url: $url, project_id: $pid }) {
      project_media {
        dbid
        oembed_metadata
        url
        quote
      }
    }
  }`;

  const fail = function(callback, thread, channel, link, e) {
    const text = t("sorry,_can't_add_the_URL") + ' ' + link;
    sendErrorMessage(e, vars, text, team_id, responseUrl, callback);
  };

  const done = function(resp) {
    console.log('GraphQL query response: ' + util.inspect(resp));
    const oembedMetadata = JSON.parse(resp.createProjectMedia.project_media.oembed_metadata);
    let message = { response_type: "in_channel", text: projectMediaCreatedMessage() + oembedMetadata.permalink };
    replyToSlack(team_id, responseUrl, message, callback);
    callback(null, message);
  };

  executeMutation(mutationQuery, vars, fail, done, token, callback, {}, data);
};

const addUrl = function(payload, redisKey, callback) {
  const responseUrl = payload.body.response_url;
  const team_id = payload.body.team_id;
  const url = payload.matches[1];
  const vars =  { url: url, clientMutationId: `fromSlackMessage:${payload.body.trigger_id}`};
  const redis = getRedisClient();
  redis.get(redisKey, function(err, reply) {
    if (!reply) {
      console.log('Could not find Redis key for channel' + ' #' + payload.body.channel_name);
      let message = { text: t('default_project_not_defined_for_this_channel'), response_type: 'ephemeral' };
      replyToSlack(team_id, responseUrl, message, callback);
    }
    else {
      const data = JSON.parse(reply.toString());
      data.link = url;
      vars.pid = parseInt(data.project_id);
      createProjectMedia(team_id, responseUrl, vars, payload.user_token, data, callback);
    }
    redis.quit();
  });
  console.log('Add URL to ' + humanAppName() + ': ' + url);
};

const setProject = function(payload, redisKey, callback) {
  const projectUrl = payload.matches[0],
        teamSlug = payload.matches[1],
        projectId = payload.matches[2];

  const fail = function(e) {
    const text = t("sorry,_can't_find_project") + ' ' + projectUrl;
    const vars = { url: projectUrl };
    sendErrorMessage(e, vars, text, payload.body.team_id, payload.body.response_url, callback);
  };

  const done = function(data) {
    const value = { team_slug: teamSlug, project_id: data.project.dbid, project_title: data.project.title, project_url: payload.matches[0]};
    const message = { text: t('project_set') + ': ' + value['project_url'], response_type: 'in_channel' };

    const success = function() {
      replyToSlack(payload.body.team_id, payload.body.response_url, message, callback);
    };

    saveToRedisAndReplyToSlack(redisKey, value, message, success, callback);
  };

  //Handle if project doesn't exist
  getProject(teamSlug, projectId, payload.user_token, done, fail, callback);
  console.log('Set project: ' + projectUrl);
};

const showProject = function(payload, redisKey, callback) {
  let message = ''
  const redis = getRedisClient();
  redis.on('connect', function() {
    redis.get(redisKey, function(err, reply) {
      if (!reply) {
        console.log('Could not find Redis key for channel' + ' #' + payload.body.channel_name);
        message = { text: t('default_project_not_defined_for_this_channel'), response_type: 'ephemeral' };
      }
      else {
        const data = JSON.parse(reply.toString());
        message = { text: t('project_set_to_channel') + ': ' + data.project_url, response_type: 'ephemeral' };
      }
      replyToSlack(payload.body.team_id, payload.body.response_url, message, callback);
    });
    redis.quit();
  });
};

const sendActionToSmoochBot = function(payload, redisKey, callback, action) {
  let message = ''
  const redis = getRedisClient();
  redis.on('connect', function() {
    redis.get(redisKey, function(err, reply) {
      if (!reply) {
        console.log('Could not find Redis key for channel' + ' #' + payload.body.channel_name);
        message = { text: t('this_channel_is_not_related_to_a_bot_conversation'), response_type: 'ephemeral' };
        replyToSlack(payload.body.team_id, payload.body.response_url, message, callback);
      }
      else {
        const data = JSON.parse(reply.toString());
        if (action === 'send') {
          message = { text: t('message_sent_to_the_bot'), response_type: 'in_channel' };

          const mutationQuery = `($action: String!, $id: ID!, $clientMutationId: String!) {
            updateDynamicAnnotationSmoochUser: updateDynamicAnnotationSmoochUser(input: { clientMutationId: $clientMutationId, id: $id, action: $action }) {
              project {
                id
              }
            }
          }`;

          const done = function() {
            console.log(message.text);
            callback(null, message);
          }

          const token = config.checkApi.apiKey;
          executeMutation(mutationQuery, { action: 'send ' + payload.matches[1], id: data.annotation_id, clientMutationId: `fromSlackMessage:${payload.body.trigger_id}` }, null, done, token, callback, {}, {});
          replyToSlack(payload.body.team_id, payload.body.response_url, message, null);
        }
        else {
          if (data.mode === 'human') {
            const newData = Object.assign({}, data);
            newData.mode = 'bot';
            redis.set(redisKey, JSON.stringify(newData), function() {
              message = { text: t('conversation_is_now_in_bot_mode'), response_type: 'in_channel' };

              const mutationQuery = `($action: String!, $id: ID!, $clientMutationId: String!) {
                updateDynamicAnnotationSmoochUser: updateDynamicAnnotationSmoochUser(input: { clientMutationId: $clientMutationId, id: $id, action: $action }) {
                  project {
                    id
                  }
                }
              }`;

              const done = function() {
                console.log(message.text);
                callback(null, message);
              }

              const token = config.checkApi.apiKey;
              executeMutation(mutationQuery, { action, id: data.annotation_id, clientMutationId: `fromSlackMessage:${payload.body.trigger_id}` }, null, done, token, callback, {}, {});
              replyToSlack(payload.body.team_id, payload.body.response_url, message, null);
            });
          }
          else {
            message = { text: t('conversation_is_already_in_bot_mode'), response_type: 'ephemeral' };
            console.log(message.text);
            replyToSlack(payload.body.team_id, payload.body.response_url, message, callback);
          }
        }
      }
      redis.quit();
    });
  });
};

const sendSmoochImage = function(payload, callback) {
  const redis = getRedisClient();
  redis.on('connect', function() {
    const redisKey = 'slack_channel_smooch:' + config.redisPrefix + ':' + payload.body.channel;
    redis.get(redisKey, function(err, reply) {
      if (reply) {
        const teamConfig = getTeamConfig(payload.body.team_id);
        const token = teamConfig.legacyToken;
        payload.body.files.forEach(function(file) {
          request({
            url: file.url_private,
            encoding: null,
            headers: {
              'Authorization': 'Bearer ' + teamConfig.legacyToken
            }
          }, function(err2, res, body) {
            if (!err2) {
              const data = Buffer.from(body).toString('base64');
              imgur.uploadBase64(data)
              .then(function(json) {
                const link = json.link;
                const text = payload.body.text.replace(/^\/sk /, '');
                const message = { token: teamConfig.legacyToken, channel: payload.body.channel, command: '/sk', text: '![' + text + '](' + link + ')' };
                const query = qs.stringify(message);
                https.get('https://slack.com/api/chat.command?' + query, function() {
                  callback(null);
                });
                console.log('Sent image: ' + link);
              });
            }
            else {
              console.log('Could not send image');
            }
          });
        });
      }
      else {
        console.log('Not found in Redis: ' + redisKey);
        callback(null);
      }
      redis.quit();
    });
  });
};

const showTips = function(payload, callback) {
  let message = {
    response_type: 'ephemeral',
    text: ':wave: ' + t('need_some_help_with') + ' `' + payload.body.command + '`?',
    attachments: [
      {
        text: t('define_the_default_project_for_this_channel') + ':\n `' + payload.body.command + ' set ' + config.checkWeb.url + '/[team slug]/project/[project id]`',
        mrkdwn_in: ['text'],
        fallback: t('define_the_default_project_for_this_channel') + ':\n `' + payload.body.command + ' set ' + config.checkWeb.url + '/[team slug]/project/[project id]`'
      },
      {
        text: t('show_the_default_project_for_this_channel') + ':\n `' + payload.body.command + ' show`',
        mrkdwn_in: ['text'],
        fallback: t('show_the_default_project_for_this_channel') + ':\n `' + payload.body.command + ' show`'
      },
      {
        text: t('send_a_URL_to') + ' ' + humanAppName() + '. ' + t('a_default_project_for_this_channel_must_be_already_defined') + ':\n `' + payload.body.command + ' [URL]`',
        mrkdwn_in: ['text'],
        fallback: t('send_the_URL_to') + ' ' + humanAppName() + '. ' + t('a_default_project_for_this_channel_must_be_already_defined') + ':\n `' + payload.body.command + ' [URL]`'
      },
      {
        text: t('reactivate_Smooch_bot_for_this_conversation') + ':\n `' + payload.body.command + ' bot activate`',
        mrkdwn_in: ['text'],
        fallback: t('reactivate_Smooch_bot_for_this_conversation') + ':\n `' + payload.body.command + ' bot activate`',
      },
      {
        text: t('send_message_to_Smooch_bot') + ':\n `' + payload.body.command + ' bot send [message]`',
        mrkdwn_in: ['text'],
        fallback: t('send_message_to_Smooch_bot') + ':\n `' + payload.body.command + ' bot send [message]`',
      },
      {
        text: t('Or_see_our_detailed_user_guide') + ' ' + 'https://medium.com/meedan-user-guides/add-to-check-from-slack-5fee91dadc35',
        mrkdwn_in: ['text'],
        fallback: t('Or_see_our_detailed_user_guide') + ' ' + 'https://medium.com/meedan-user-guides/add-to-check-from-slack-5fee91dadc35'
      }]
  };
  replyToSlack(payload.body.team_id, payload.body.response_url, message, callback);
};

exports.handler = function(event, context, callback) {
  const redisKey = 'slack_channel_project:' + config.redisPrefix + ':' + event.body.channel_id;
  const smoochRedisKey = 'slack_channel_smooch:' + config.redisPrefix + ':' + event.body.channel_id;
  switch (event.type) {
    case 'createProjectMedia':
      addUrl(event, redisKey, callback);
      break;
    case 'setProject':
      setProject(event, redisKey, callback);
      break;
    case 'showProject':
      showProject(event, redisKey, callback);
      break;
    case 'reactivateBot':
      sendActionToSmoochBot(event, smoochRedisKey, callback, 'reactivate');
      break;
    case 'sendBot':
      sendActionToSmoochBot(event, smoochRedisKey, callback, 'send');
      break;
    case 'sendSmoochImage':
      sendSmoochImage(event, callback);
      break;
    default:
      showTips(event, callback);
  }
};