const _ = require('lodash');
const fs = require('graceful-fs');
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 upload */
/**
* The upload 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.
* @property {emojiList[]} subdomain.collisions if `options.avoidCollisions` is `false`, emoji that cannot be uploaded due to existing conflicting emoji names will exist here
*/
/**
* The required format of a json file that can be used as the `options.src` for {@link upload}
*
* To see an example, use {@link download}, then look at `buidl/*.adminList.json`
*
* @typedef {Array} jsonEmojiListFormat
* @property {Array} emojiList
* @property {object} emojiList.emojiObject
* @property {string} emojiList.emojiObject.name the name of the emoji
* @property {1|0} emojiList.emojiObject.is_alias whether or not the emoji is an alias. If `1`, `alias_for` is require and `url` is ignored. If `0` vice versa
* @property {string} emojiList.emojiObject.alias_for the name of the emoji this emoji is apeing
* @property {string} emojiList.emojiObject.url the remote url or local path of the emoji
* @property {string} emojiList.emojiObject.user_display_name the name of the emoji creator
*
* @example
* [
* {
* "name": "a_giving_lovely_generous_individual",
* "is_alias": 1,
* "alias_for": "caleb"
* },
* {
* "name": "gooddoggy",
* "is_alias": 0,
* "alias_for": null,
* "url": "https://emoji.slack-edge.com/T3T9KQULR/gooddoggy/849f53cf1de25f97.png"
* }
* ]
*/
/**
* The required format of a yaml file that can be used as the `options.src` for {@link upload}
* @typedef {object} yamlEmojiListFormat
* @property {object} topLevelYaml all keys execpt for `emojis` are ignored
* @property {Array} emojis the array of emoji objects
* @property {object} emojis.emojiObject
* @property {string} emojis.emojiObject.name the name of the emoji
* @property {string} emojis.emojiObject.src alias for `name`
* @property {1|0} emojis.emojiObject.is_alias whether or not the emoji is an alias. If `1`, `alias_for` is require and `url` is ignored. If `0` vice versa
* @property {string} emojis.emojiObject.alias_for the name of the emoji this emoji is apeing
* @property {string} emojis.emojiObject.url the remote url or local path of the emoji
* @property {string} emojis.emojiObject.user_display_name the name of the emoji creator
*
* @example
* title: animals
* emojis:
* - name: llama
* src: http://i.imgur.com/6bKXKUP.gif
* - name: alpaca
* src: http://i.imgur.com/c6QxTbM.gif
*/
/**
* Upload multiple emoji described by an existing list on disk, either as a json emoji admin list or emojipacks-like yaml.
*
* @async
* @param {string|string[]} subdomains a single or list of subdomains from which to download emoji. Must match respectively to `token`s and `cookie`s.
* @param {string|string[]} tokens a single or list of tokens with which to authenticate. Must match respectively to `subdomain`s and `cookie`s.
* @param {string|string[]} cookies a single or list of cookies used to authenticate access to the given subdomain. Must match respectively to `subdomain`s and `token`s.
* @param {object} options contains singleton or arrays of emoji descriptors.
* @param {string|string[]} options.src source emoji list files for the emoji to be added. Can either be in {@link jsonEmojiListFormat} or {@link yamlEmojiListFormat}
* @param {boolean} [options.avoidCollisions] if `true`, emoji being added will be renamed to not collide with existing emoji. See {@link lib/util/helpers.avoidCollisions} for logic and details // TODO: fix this link, maybe link to tests which has better examples
* @param {string} [options.prefix] string to prefix all emoji being uploaded
* @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<uploadResponseObject>} uploadResponseObject result object
*
* @example
var uploadOptions = {
src: './emoji-list.json', // upload all the emoji in this json array of objects
avoidCollisions: true, // append '-1' or similar if we try to upload a dupe
prefix: 'new-' // prepend every emoji in src with "new-", e.g. "emoji" becomes "new-emoji"
};
var uploadResults = await emojme.upload('mySubdomain', 'myToken', 'myCookie', uploadOptions);
console.log(uploadResults);
// {
// mySubdomain: {
// collisions: [
// { name: an-emoji-that-already-exists-in-mySubdomain ... }
// ],
// emojiList: [
// { name: emoji-from-emoji-list-json ... },
// { name: emoji-from-emoji-list-json ... },
// ...
// ]
// }
// }
*/
async function upload(subdomains, tokens, cookies, options) {
subdomains = Helpers.arrayify(subdomains);
tokens = Helpers.arrayify(tokens);
cookies = Helpers.arrayify(cookies);
options = options || {};
let inputEmoji;
// TODO this isn't handling --src file --src file correctly
if (Array.isArray(options.src)) {
inputEmoji = options.src;
} else if (!fs.existsSync(options.src)) {
throw new Error(`Emoji source file ${options.src} does not exist`);
} else {
const fileType = options.src.split('.').slice(-1)[0];
if (fileType.match(/yaml|yml/)) {
inputEmoji = FileUtils.readYaml(options.src);
} else if (fileType.match(/json/)) {
inputEmoji = FileUtils.readJson(options.src);
} else {
throw new Error(`Filetype ${fileType} is not supported`);
}
}
const [authTuples] = Helpers.zipAuthTuples(subdomains, tokens, cookies);
const uploadPromises = authTuples.map(async (authTuple) => {
let emojiToUpload = []; let
collisions = [];
if (options.prefix) {
inputEmoji = Helpers.applyPrefix(inputEmoji, options.prefix);
}
if (options.allowCollisions) {
emojiToUpload = inputEmoji;
} else {
const existingEmojiList = await new EmojiAdminList(...authTuple, options.output)
.get(options.bustCache);
const existingNameList = existingEmojiList.map(e => e.name);
if (options.avoidCollisions) {
inputEmoji = Helpers.avoidCollisions(inputEmoji, existingEmojiList);
}
[collisions, emojiToUpload] = _.partition(inputEmoji,
emoji => existingNameList.includes(emoji.name));
}
const emojiAdd = new EmojiAdd(...authTuple);
const uploadResult = await emojiAdd.upload(emojiToUpload);
return Object.assign({}, uploadResult, { collisions });
});
return Helpers.formatResultsHash(await Promise.all(uploadPromises));
}
function uploadCli() {
const program = new commander.Command();
Cli.requireAuth(program);
Cli.allowIoControl(program);
Cli.allowEmojiAlterations(program)
.option('--src <value>', 'source file(s) for emoji json or yaml you\'d like to upload')
.parse(process.argv);
Cli.unpackAuthJson(program);
return upload(program.subdomain, program.token, program.cookie, {
src: program.src,
bustCache: program.bustCache,
allowCollisions: program.allowCollisions,
avoidCollisions: program.avoidCollisions,
prefix: program.prefix,
output: program.output,
});
}
if (require.main === module) {
uploadCli();
}
module.exports = {
upload,
uploadCli,
};