emojme-sync.js

const _ = require('lodash');
const commander = require('commander');

const EmojiAdminList = require('./lib/emoji-admin-list');
const EmojiAdd = require('./lib/emoji-add');

const Cli = require('./lib/util/cli');
const FileUtils = require('./lib/util/file-utils');
const Helpers = require('./lib/util/helpers');
/** @module sync */

/**
 * The sync response object, like other response objects, is organized by input subdomain.
 * @typedef {object} syncResponseObject
 * @property {object} subdomain each subdomain passed in to add will appear as a key in the response
 * @property {emojiList[]} subdomain.emojiList the list of emoji added to `subdomain`, with each element an emoji pulled from either `srcSubdomain` or `subdomains` less the subdomain in question.
 */

/**
 * Sync emoji between slack subdomains
 *
 * Sync can be executed in either a "one way" or "n way" configuration, and both configurations can have a variable number of sources and destinations. In a "one way" configuration, all emoji from all source subdomains will be added to all destination subdomains" and can be set by specifying `srcSubdomains` and `dstSubdomains`. In an "n way" configuration, every subdomain given is treated as the destination for every emoji in every other subdomain.
 *
 * @async
 * @param {string|string[]|null} subdomains Two ore more subdomains that you wish to have the same emoji pool
 * @param {string|string[]|null} tokens cookie tokens corresponding to the given subdomains
 * @param {string|string[]|null} cookies User cookies corresponding to the given subdomains
 * @param {object} options contains src* and dst* information for "one way" sync configuration. Either specify `subdomains` and `tokens`, or `srcSubdomains`, `srcTokens`, `dstSubdomains`, and `dstTokens`, not both.
 * @param {string|string[]} [options.srcSubdomains] slack instances from which to draw emoji. No additions will be made to these subdomains
 * @param {string|string[]} [options.srcTokens] tokens for the slack instances from which to draw emoji
 * @param {string|string[]} [options.srcCookies] cookies auth cookies for the slack instances from which to draw emoji
 * @param {string|string[]} [options.dstSubdomains] slack instances in which all source emoji will be deposited. None of `dstSubdomain`'s emoji will end up in `srcSubdomain`
 * @param {string|string[]} [options.dstTokens] tokens for the slack instances where emoji will be deposited
 * @param {string|string[]} [options.dstCookies] cookies auth cookies for the slack instances from which to draw emoji
 * @param {boolean} [options.bustCache] if `true`, ignore any adminList younger than 24 hours and repull slack instance's existing emoji. Can be useful for making `options.avoidCollisions` more accurate
 * @param {boolean} [options.output] if `false`, no files will be written during execution. Prevents saving of adminList for future use, as well as the writing of log files
 * @param {boolean} [options.verbose] if `true`, all messages will be written to stdout in addition to combined log file.
 *
 * @returns {Promise<syncResponseObject>} syncResponseObject result object
 *
 * @example
var syncOptions = {
  srcSubdomains: ['srcSubdomain'], // copy all emoji from srcSubdomain...
  srcTokens: ['srcToken'],
  srcCookies: ['srcCookie'],
  dstSubdomains: ['dstSubdomain1', 'dstSubdomain2'], // ...to dstSubdomain1 and dstSubdomain2
  dstTokens: ['dstToken1', 'dstToken2'],
  dstCookies: ['dstCookie1', 'dstCookie2'],
  bustCache: true // get fresh lists to make sure we're not doing more lifting than we have to
};
var syncResults = await emojme.sync(null, null, syncOptions);
console.log(syncResults);
// {
//   dstSubdomain1: {
//     emojiList: [
//       { name: emoji-1-from-srcSubdomain ... },
//       { name: emoji-2-from-srcSubdomain ... }
//     ]
//   },
//   dstSubdomain2: {
//     emojiList: [
//       { name: emoji-1-from-srcSubdomain ... },
//       { name: emoji-2-from-srcSubdomain ... }
//     ]
//   }
// }
 */
async function sync(subdomains, tokens, cookies, options) {
  let diffs;
  subdomains = Helpers.arrayify(subdomains);
  tokens = Helpers.arrayify(tokens);
  cookies = Helpers.arrayify(cookies);
  options = options || {};

  const [authTuples, srcPairs, dstPairs] = Helpers.zipAuthTuples(
    subdomains,
    tokens,
    cookies,
    options,
  );

  if (subdomains.length > 0) {
    const emojiLists = await Promise.all(
      authTuples.map(async authTuple => new EmojiAdminList(...authTuple, options.output)
        .get(options.bustCache, options.since)),
    );

    diffs = EmojiAdminList.diff(emojiLists, subdomains);
  } else if (srcPairs && dstPairs) {
    const srcDstPromises = [srcPairs, dstPairs].map(pairs => Promise.all(
      pairs.map(async pair => new EmojiAdminList(...pair, options.output)
        .get(options.bustCache, options.since)),
    ));

    const [srcEmojiLists, dstEmojiLists] = await Promise.all(srcDstPromises);
    diffs = EmojiAdminList.diff(
      srcEmojiLists, options.srcSubdomains, dstEmojiLists, options.dstSubdomains,
    );
  } else {
    throw new Error('Invalid Input');
  }

  const uploadedDiffPromises = diffs.map((diffObj) => {
    const pathSlug = `to-${diffObj.dstSubdomain}.from-${diffObj.srcSubdomains.join('-')}`;
    if (options.output) FileUtils.writeJson(`./build/${pathSlug}.emojiAdminList.json`, diffObj.emojiList);
    if (options.dryRun) return { subdomain: diffObj.dstSubdomain, emojiList: diffObj.emojiList };

    const emojiAdd = new EmojiAdd(diffObj.dstSubdomain, _.find(
      authTuples,
      [0, diffObj.dstSubdomain],
    )[1], options.output);
    return emojiAdd.upload(diffObj.emojiList).then((results) => {
      if (results.errorList && results.errorList.length > 0 && options.output) FileUtils.writeJson(`./build/${pathSlug}.emojiUploadErrors.json`, results.errorList);
      return results;
    });
  });

  return Helpers.formatResultsHash(await Promise.all(uploadedDiffPromises));
}

function syncCli() {
  const program = new commander.Command();

  Cli.requireAuth(program);
  Cli.allowIoControl(program)
    .option('--src-subdomain [value]', 'subdomain from which to draw emoji for one way sync', Cli.list, null)
    .option('--src-token [value]', 'token with which to draw emoji for one way sync', Cli.list, null)
    .option('--src-cookie [value]', 'cookie with which to draw emoji for one way sync', Cli.list, null)
    .option('--dst-subdomain [value]', 'subdomain to which to emoji will be added is one way sync', Cli.list, null)
    .option('--dst-token [value]', 'token with which emoji will be added for one way sync', Cli.list, null)
    .option('--dst-cookie [value]', 'cookie with which emoji will be added for one way sync', Cli.list, null)
    // Notice that this is missing --force and --prefix. These have been
    // deemed TOO POWERFUL for mortal usage. If you _really_ want that
    // power, you can download then upload the adminlist you retrieve.
    .option('--dry-run', 'if set to true, nothing will be uploaded or synced', false)
    .parse(process.argv);

  Cli.unpackAuthJson(program);

  return sync(program.subdomain, program.token, program.cookie, {
    srcSubdomains: program.srcSubdomain,
    srcTokens: program.srcToken,
    srcCookies: program.srcCookie,
    dstSubdomains: program.dstSubdomain,
    dstTokens: program.dstToken,
    dstCookies: program.dstCookie,
    bustCache: program.bustCache,
    output: program.output,
    since: program.since,
    dryRun: program.dryRun,
  });
}

if (require.main === module) {
  syncCli();
}

module.exports = {
  sync,
  syncCli,
};