Showing preview only (489K chars total). Download the full file or copy to clipboard to get everything.
Repository: jackellenberger/emojme
Branch: master
Commit: 03f337b4f9cc
Files: 61
Total size: 467.1 KB
Directory structure:
gitextract_k03dkd1k/
├── .circleci/
│ └── config.yml
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .jsdoc.json
├── CHANGELOG.md
├── README.md
├── USAGE.md
├── create-json.sh
├── docs/
│ ├── emojme-add.js.html
│ ├── emojme-download.js.html
│ ├── emojme-favorites.js.html
│ ├── emojme-sync.js.html
│ ├── emojme-upload.js.html
│ ├── emojme-user-stats.js.html
│ ├── index.html
│ ├── module-add.html
│ ├── module-download.html
│ ├── module-favorites.html
│ ├── module-sync.html
│ ├── module-upload.html
│ ├── module-userStats.html
│ ├── scripts/
│ │ ├── linenumber.js
│ │ └── pagelocation.js
│ └── styles/
│ ├── jsdoc-default.css
│ ├── prettify-jsdoc.css
│ └── prettify-tomorrow.css
├── emojme-add.js
├── emojme-download.js
├── emojme-favorites.js
├── emojme-sync.js
├── emojme-upload.js
├── emojme-user-stats.js
├── emojme.js
├── lib/
│ ├── client-boot.js
│ ├── emoji-add.js
│ ├── emoji-admin-list.js
│ ├── logger.js
│ ├── slack-client.js
│ └── util/
│ ├── cli.js
│ ├── file-utils.js
│ └── helpers.js
├── package.json
├── scripts/
│ └── usage.sh
└── spec/
├── e2e/
│ └── emojme-download.js
├── fixtures/
│ ├── clientBoot.json
│ ├── emojiList.json
│ └── emojiList.yaml
├── integration/
│ ├── emojme-add-spec.js
│ ├── emojme-download-spec.js
│ ├── emojme-favorites-spec.js
│ ├── emojme-sync-spec.js
│ ├── emojme-upload-spec.js
│ └── emojme-user-stats-spec.js
├── spec-helper.js
└── unit/
└── lib/
├── emoji-add-spec.js
├── emoji-admin-list-spec.js
├── file-utils-spec.js
├── slack-client-spec.js
└── util/
├── cli-spec.js
└── helpers-spec.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:10.13.0-jessie
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
working_directory: ~/emojme
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: npm install
- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}
# run tests!
- run: npm test
================================================
FILE: .eslintignore
================================================
docs/
================================================
FILE: .eslintrc.json
================================================
{
"extends": "airbnb-base",
"rules": {
"no-console": "off",
"no-plusplus": "off",
"no-param-reassign": "off",
"linebreak-style": "off",
"prefer-destructuring": "off",
"guard-for-in": "off",
"no-restricted-syntax": "off",
"no-cond-assign": "off",
"no-multi-assign": "off",
"max-len": [
"error", 100, 2, {
"ignoreUrls": true,
"ignoreComments": true,
"ignoreRegExpLiterals": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}
]
},
"env": {
"commonjs": true,
"node": true,
"mocha": true
}
}
================================================
FILE: .gitignore
================================================
# Script results
build/
log/*
# Secret inputs
.env
.config
# node projects amirite
node_modules/
package-lock.json
emojme@*
# Moving files around
*.old
# OSX Garb
.DS_Store
================================================
FILE: .jsdoc.json
================================================
{
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc"]
},
"source": {
"include": [".", "lib", "package.json", "README.md"],
"includePattern": ".js$",
"excludePattern": "(node_modules/|docs|spec)"
},
"plugins": [
"plugins/markdown"
],
"templates": {
"referenceTitle": "emojme",
"disableSort": false,
"collapse": true
},
"opts": {
"destination": "./docs/",
"encoding": "utf8",
"private": true,
"recurse": true,
"template": "./node_modules/jsdoc-template"
}
}
================================================
FILE: CHANGELOG.md
================================================
# 2.0.0
* Require cookie tokens and cookies >:[
* All operations that previously required a (subdomain, token) tuple now require a (subdomain, token, cookie) tuple.
* This means the addition of a `--cookie` command line argument.
* cookie is also now the third ordered argument in emojme module methods.
* Check the readme for how to collect a cookie.
* alias --subdomain to --domain for kicks
* Reduce adminList request rate slightly to dodge rate limiting.
* AuthPairs are now AuthTuples as they represent subdomain, token, and cookie.
# 1.9.1
* Add Emojme chrome extension to README
* Resolve (#59), sanitizing user names for disk interaction
# 1.9.0
* Clean up README readablility
* Add `--since` option to download, user-stats, and sync
* Add `--dry-run` option to emojme sync
# 1.8.1
* Add `--lite` option to emojme favorites.
* Does not download complete adminList
* returns only emoji name and usage count in `favoriteEmojiAdminList`
* adds a little more documentation around `allowCollisions`
# 1.8.0
* Add confusingly named `allowCollisions` to `add` and `upload` endpoints alongside existing `avoidCollisions` param
* When set, no adminList will be pre-fetched to prevent collisions. This allows uploads to execute much faster, but with "untrusted" uploads it could cause many more errors and therefore rate limiting.
* In a future major version: `avoidCollisions` will be renamed to more-accurate `preventCollisions` and `allowCollisions` will be negated and renamed to `avoidCollisison`. For the time being, we don't need a 2.0 / breaking change.
# 1.7.0
* Add /client.boot endpoint accessor
* Add emojme favorites function to find a user's favorite emoji
* This comprises the content of the `Frequently Used` emoji box
* Also includes personal emoji usage counts (!?)
# 1.6.3
* Update README to reflect slack's new api_token location
* Fix rate limiting for good this time
* Resolve npm audit vulnerability
# 1.6.0
* Implement rate limiting
* rate varies depending on endpoint
* Can be overridden but new environment variables
* SLACK_REQUEST_CONCURRENCY
* SLACK_REQUEST_RATE
* SLACK_REQUEST_WINDOW
* Add naive backoff logic
* Add timestamps to logs
# 1.5.1
* Resolve npm audit vulnerability
# 1.5.0
* Rework logging to be less noisy and more organized.
* Use Winston
* log warning and worse to the console
* log everything to log/combined.log
* Add verbosity control
* Fix bug related to incorrect upload summary output
# 1.4.0
* Revamp download
* `--save` can no longer be called with 'all' (but that never worked)
* `--save-all-by-user` added to save all emoji by all users into build/$subdomain/$user
* `--save-all` added to save all emoji to build/$subdomain
* Add jsdoc documentation, available at https://jackellenberger.github.io/emojme
* Configure circle ci
* Clarify what a user token should look like
# 1.3.3
* Fix bug preventing correct package contents from being uploaded to npm
* Fix bug preventing empty slack instances from adding and syncing emoji
# 1.3.2
* Add keywords, bin, etc to package.json
* Add module usage instructions to readme
# 1.3.1
* Create CHANGELOG.md
* Allow certain required `Add` params to be nulled out by providing an empty string
* For example, `add --src 'source.jpg' --name ''` will act identically to `add --src 'source.jpg'`
* This resolves an issue where adding multiple emoji of different shapes (i.e. new vs alias vs default named new) could become misaligned
* Add emojiList to output of `user-stats` for consistency and ease of use
* Add `<action>Cli` methods to ease testing.
* Resolve issue where repeated invocation of cli from a single process could pollute commander args
================================================
FILE: README.md
================================================
# [emojme](https://github.com/jackellenberger/emojme) - [Documentation](https://jackellenberger.github.io/emojme)
## Table of Contents
* [Project Overview](#what-it-is)
* [Breaking Changes](#breaking-changes)
* [2.0.0](#2-0-0)
* [Requirements](#requirements)
* [Installation](#installation)
* [Getting a slack token](#finding-a-slack-token)
* [Getting a slack cookie](#finding-a-slack-cookie)
* [Usage](#usage)
* [Command Line](#usage)
* [Module](#module)
* [Build directory output](#build-directory-output)
* [A closer look at options](#a-closer-look-at-options)
* [Add vs Upload](#whats-the-difference-between-add-and-upload)
* [CLI Examples](#cli-examples)
* [Download](#emojme-download)
* [Add](#emojme-add)
* [Upload](#emojme-upload)
* [Sync](#emojme-sync)
* [User Stats](#emojme-user-stats)
* [Favorites](#emojme-favorites)
* [Pro Moves](#pro-moves-promoves)
* [Rate Limiting](#rate-limiting-and-you)
* [FAQ](#faq)
* [Other Projects of Note](#inspirations)
## What it is
Emojme is a set of tools to manage your Slack emoji, either directly from the command line or from within your own Javascript project.
Primary features are:
* Uploading new emoji
* Individually, by passing a file or url
* In bulk, by passing a json "adminList" or a yaml "emojipack" file
* To one or many slack instances at once
* Download existing emoji
* From one or many slack instances
* Download all emoji
* Download some emoji
* Sync emoji between mulitple slack instance
* One to one, one to many, many to one, or many to many
* Analyze emoji authorship
* Who makes the most emoji in your slack instance?
* Analyze emoji usage
* Which emoji do you use most?
jsdocs are available at [https://jackellenberger.github.io/emojme](https://jackellenberger.github.io/emojme). Read em.
## Breaking Changes
### 2.0.0
Removes support for easy breazy beautiful user token auth, adds support for grumble grumble cookie token + cookie auth. Slack made me do it I swear. What does it mean for you?
- Whenever you wrote or used an emojme method with a signature like `method(domain, token, options)`, you will now need `method(domain, token, cookie, options)`.
- Whenever you were calling the CLI with a pattern like `emojme command --subdomain $SUBDOMAIN --token $TOKEN`, you will now need `emojme command --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE`.
- Read on for examples and instructions on how to collect your cookie from the jar.
## Requirements
To use emojme you don't need a bot or a workspace admin account. In fact, ~only regular [**user tokens**](https://api.slack.com/docs/token-types#user) work~ only *cookie* tokens work, in combination with shortlived browser tokens, and getting both isn't _quite_ as easy as getting other types of tokens. Limitations are:
* Cookie tokens can be grabbed from any logged in slack webpage by following [these instructions](#finding-a-slack-token).
* Auth Cookies are grabbed with even more difficulty, again from logged in slack pages, following [these instructions](#finding-a-slack-cookie).
* All actions taken through Emojme can be linked back to your user account. That might be bad, but no one has yelled at me yet.
* Cookie tokens are cycled at inditerminate times, and cannot (to my knowledge) be cycled manually. Ditto for the cookies themselves. **DO NOT LOSE CONTROL OF YOUR COOKIES**. Any project that uses emojme should have tokens passed in through environment variables and should not store them in source control.
* Update July 2021: If you are have been using an automated system to scrape User Tokens, you are pretty much hosed. The cookies now required are [Http Only](https://owasp.org/www-community/HttpOnly) and can't be easily (or at all?) accessed via javascript.
## Installation
### Command Line
Via npm
```bash
$ (nvm use 10 || nvm install 10) && npm install emojme
$ npx emojme [command] [options]
```
Via github
```bash
$ git clone https://github.com/jackellenberger/emojme.git
$ cd emojme
$ node ./emojme [command] [options]
```
In order to use either feature, you will need both a Token and a Cookie each for every target subdomain (e.g. my-subdomain.slack.com). You can of course use your own methods for achieving this, but (and I will repeat this), the [Emojme: Emoji Anywhere](https://chrome.google.com/webstore/detail/emojme-emoji-anywhere/nbnaglaclijdfidbinlcnfdbikpbdkog?hl=en-US) Chrome Extension makes it very much easier than anything else, at only minor risk to your personal security. But hey if I were gonna steal your slack creds I'd do it in an alley with a knife or something, not in broad daylight. Its source is also [available on github](https://github.com/jackellenberger/emojme-emoji-anywhere) if you don't enjoy pre-rolls.
### Finding a slack token
Update July 2021: Slack has switched away from using questionably rotated user tokens to using "cookie tokens" and an associated short lived cookie. Smart, but we're smarter. User Tokens were of the format `xox[sp]-(\w{12}|\w{10})-(\w{12}|\w{11})-\w{12}-\w{64}` but *will no longer work* in most cases (and i'm too lazy to determine which). If use see an auth error, this is probably the reason. Cookie tokens follow a similar form, but note the `c`: `xoxc-(\w{12}|\w{10})-(\w{12}|\w{11})-\w{12}-\w{64}`.
As of emojme@2.0.0, support for cookie tokens has been added and is the recommended way of interacting with this dumb tool. The [emojme chrome extension](https://chrome.google.com/webstore/detail/emojme-emoji-anywhere/nbnaglaclijdfidbinlcnfdbikpbdkog?hl=en-US) provides a relatively ergonomic way to capture these along with a matching cookie.
Update August 2022: ~It doesn't appear that the boot data that previously persisted on the page sticks around, and with it getting cleaned up there's no longer a "just run this js one liner" to my knowledge - if you know one, submit a PR! Put it right here -> [here](#cookie-token-one-liner) <-, with your name (@mootari), and feel free to take this wedge of cheese as payment 💨.
#### Cookie Token One-liner
To extract the Slack cookie token, run the following script in your devtools console while being logged into your Slack team:
```js
JSON.parse(localStorage.localConfig_v2).teams[document.location.pathname.match(/^\/client\/([TE][A-Z0-9]+)/)[1]].token
```
Thanks again to @mootari for finding this (and all of `localStorage.localConfig_v2`!)
#### Finding a slack cookie
As cookies are now required, so too is this section. Slack's auth cookie, as far as I can tell, is the `d` cookie, which is unfortunately HttpOnly meaning it cannot be accessed via javascript. It can, however, be accessed with a little creativity.
Chrome's (and presumably any modern browser's) cookies API does allow for HttpConly cookies to be accessed, but require the user's explicit approval but way of an extension. [Emojme: Emoji Anywhere](https://github.com/jackellenberger/emojme-emoji-anywhere) is such an extension, and is [available in the chrome web store](https://chrome.google.com/webstore/detail/emojme-emoji-anywhere/nbnaglaclijdfidbinlcnfdbikpbdkog?hl=en-US) (or of course can be loaded from source if you want to take your life in your own hands). Clicking the extension icon > `Get Slack Token and Cookie` will land you with what I am calling a "auth blob", which you can then pass to emojme via the `--auth-json` argument.

You may also pull the `d` cookie with your fleshy human hands, if you so desire. Open up your browser's developer tools, then Application menu > Cookies > d, and copy the string out for yourself. With this method, it will be easier to specify individual `--subdomain --token --cookie` flags.

## Usage
Emojme can be used either as a command line tool or as a node module to be mixed in with your existing projects.
Complete CLI flags can be found in [USAGe.md](USAGE.md), but each command takes the `--help` option.
### Module
In your project's directory
```bash
npm install --save emojme
```
In your project
```node
var emojme = require('emojme');
// emojme-download
var downloadOptions = {
save: ['username_1', 'username_2'], // Download the emoji source files for these two users
bustCache: true, // make sure this data is fresh
output: true // download the adminList to ./build
};
var downloadResults = await emojme.download('mySubdomain', 'myToken', 'myCookie', downloadOptions);
console.log(downloadResults);
/*
{
mySubdomain: {
emojiList: [
{ name: 'emoji-from-mySubdomain', ... },
...
],
saveResults: [
'./build/mySubdomain/username_1/an_emoji.jpg',
'./build/mySubdomain/username_1/another_emoji.gif',
... all of username_1's emoji
'./build/mySubdomain/username_2/some_emoji.jpg',
'./build/mySubdomain/username_2/some_other_emoji.gif',
... all of username_2's emoji
]
}
}
*/
// emojme-upload
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 ... },
...
]
}
}
*/
// emojme-add
var addOptions = {
src: ['./emoji1.jpg', 'http://example.com/emoji2.png'], // upload these two images
name: ['myLocalEmoji', 'myOnlineEmoji'], // call them these two names
bustCache: false, // don't bother redownloading existing emoji
avoidCollisions: true, // if there are similarly named emoji, change my new emoji names
output: false // don't write any files
};
var subdomains = ['mySubdomain1', 'mySubdomain2'] // can add one or multiple
var tokens = ['myToken1', 'myToken2'] // can add one or multiple
var addResults = await emojme.add(subdomains, tokens, addOptions);
console.log(addResults);
/*
{
mySubomain1: {
collisions: [], // only defined if avoidCollisons = false
emojiList: [
{ name: 'myLocalEmoji', ... },
{ name: 'myOnlineEmoji', ... },
]
},
mySubomain2: {
collisions: [], // only defined if avoidCollisons = false
emojiList: [
{ name: 'myLocalEmoji', ... },
{ name: 'myOnlineEmoji', ... },
]
}
}
*/
// emojme-sync
var syncOptions = {
srcSubdomains: ['srcSubdomain'], // copy all emoji from srcSubdomain...
srcTokens: ['srcToken'],
dstSubdomains: ['dstSubdomain1', 'dstSubdomain2'], // ...to dstSubdomain1 and dstSubdomain2
dstTokens: ['dstToken1', 'dstToken2'],
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 ... }
]
}
}
*/
//emojme-user-stats
var userStatsOptions = {
user: ['username_1', 'username_2'] // get me some info on these two users
};
var userStatsResults = await emojme.userStats('mySubdomain', 'myToken', 'myCookie', userStatsOptions);
console.log(userStatsResults);
/*
{
mySubdomain: {
userStatsResults: [
{
user: 'username_1',
userEmoji: [{ all username_1's emoji }],
subdomain: mySubdomain,
originalCount: x,
aliasCount: y,
totalCount: x + y,
percentage: (x + y) / mySubdomain's total emoji count
},
{
user: 'username_2',
userEmoji: [{ all username_2's emoji }],
subdomain: mySubdomain,
originalCount: x,
aliasCount: y,
totalCount: x + y,
percentage: (x + y) / mySubdomain's total emoji count
}
]
}
}
*/
//emojme-favorites
var favoritesResult = await emojme.favorites('mySubdomain', 'myToken', 'myCookie', {});
console.log(favoritesResult);
/*
{
mySubdomain: {
favoritesResult: {
user: '{myToken's user}',
favoriteEmoji: [
emojiName,
...
],
favoriteEmojiAdminList: [
{emojiName}: {adminList-style emoji object, with additional `usage` value}
...
],
}
}
}
*/
```
## Build directory output
Okay you've run it, now what? Where are my dang emoji?
* Diagnostic info and intermediate results are written to the build directory. Some might come in handy!
* `build/$SUBDOMAIN.emojiUploadErrors.json` will give you a json of emoji that failed to upload and why. Use it to reattempt an upload! Generated from `upload` and `sync` calls.
* `build/$SUBDOMAIN.adminList.json` is the "master list" of a subdomain's emoji. Generated from `download` and `sync` calls.
* `build/$USER.$SUBDOMAIN.adminList.json` is all the emoji created by a user. Generated from `user-stats` calls.
* `build/diff.to-$SUBDOMAIN.from-$SUBDOMAINLIST.adminList.json` contains all emoji present in $SUBDOMAINLIST but not in $SUBDOMAIN. Generated from `sync` calls.
## A closer look at options
* Universal options:
* **requires** at least one `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept multiple auth tuples.
* exception: sync can use a source/destination pattern, see below.
* _optional_: `--bust-cache` will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
* `download`
* **requires** at least one `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept multiple auth tuples.
* _optional_: `--save $user` will save actual emoji data for the specified user, rather than just adminList json. Find the emoji in ./build/subdomain/user/
* _optional_: `--bust-cache` will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
* _optional_: `--since timestamp` will only download or save emoji created after the epoch time timestamp given, e.g. `1572064302751`
* `upload`
* **requires** at least one `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept multiple auth tuples.
* **requires** at least one `--src` source json file.
* Src json should contain a list of objects where each object contains a "name" and "url" for image source
* Src yaml should contain an `emojis` key whose value is a list of emoji objects. Each object should contain `name` and `src` if an original emoji, or `name`, `is_alias: 1`, and `alias_for` if an alias.
* If adding an alias, url will be ignored and "is_alias" should be set to "1", and "alias_for" should be the name of the emoji to be aliased.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
* `add`
* **requires** at least one `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept multiple auth tuples.
* **requires** one of the following:
1. `--src` path of local emoji file.
* _optional_: `--name` name of the emoji being uploaded. If not provided, the file name will be used.
1. `--name` and `--alias-for` to create an alias called `$NAME` with the same image as `$ALIAS-FOR`
* Multiple `--src`'s or `--name`/`--alias-for` pairs may be provided, but don't mix the patterns. You'll confuse yourself.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
* `user-stats`
* **requires** at least one `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept multiple auth tuples.
* With no optional parameters given, this will print the top 10 emoji contributors
* _optional_: one of the following:
1. `--top` will show the top $TOP emoji contributors
1. `--user` will show statistics for $USER. Can accept multiple `--user` calls.
* _optional_: `--bust-cache` will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
* _optional_: `--since timestamp` will count the author statistics of only those emoji created after the epoch time timestamp given, e.g. `1572064302751`
* `sync`
* **requires** one of the following:
1. at least **two** `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept more than two auth tuples.
1. at least **one** `--src-subdomain`/`--src-token` auth tuple and at least **one** `--dst-subdomain`/`--dst-token` auth tuples for "one way" syncing.
* _optional_: `--bust-cache` will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
* _optional_: `--since timestamp` will count the author statistics of only those emoji created after the epoch time timestamp given, e.g. `1572064302751`
* _optional_: `--dry-run` download adminLists for all requested subdomains and diff them, but don't upload any new emoji. Find the diffs in `./output/to-$DST_SUBDOMAIN.from-$SRC_SUBDOMAIN.adminList.json`
* `favorites`
* **requires** at least one `--subdomain`/`--token`/`--cookie` **auth tuple**. Can accept multiple auth tuples.
* With no optional parameters given, this will print the token's user's 10 most used emoji
* _optional_: `--top` _verbose cli usage only_ limits stdout to top N most used emoji
* _optional_: `--usage` _verbose cli usage only_ prints not only the user's favorite emoji, but also the usage numbers.
* _optional_: `--bust-cache` will force a redownload of emoji adminlist and boot data. If not supplied, a redownload is forced every 24 hours.
* _optional_: `--no-output` will prevent writing of files in the ./build directory. It does not currently suppres stdout.
## What's the difference between `Add` and `Upload`?
Input type and use case! Technically (and behind the scenes) these commands do the same thing, which is post emoji to Slack.
The difference is that `Upload` is designed to take an `adminList` (what Slack calls a list of emoji and their related metadata) in the form of a json file. You can create this json file yourself, or use the `download` command to get it from an existing slack instance. It should be a Json array of objects, where each object represents an emoji and has attributes:
* `name` (the name of the emoji duh)
* `url` (the source content of the emoji. either a url, a file path, or a raw `data:` string)
* `is_alias` (either 0 for non-aliases or 1 for aliases)
* `alias_for` (name of the emoji to alias if the emoji being uploaded is an alias)
There are other fields in an adminList, but no others are used at the current time.
`Add` is designed to allow users to upload a single or few emoji, directly from the command line, without having to craft a json file before hand. You can create either new emojis or new aliases (but not both, for now). Each new emoji needs a `--src`, and can take a `--name`, otherwise the file name will be used. Each new alias takes a `--name` and the name of the original emoji to alias as `--alias-for`.
## CLI Examples
It should be noted that there are many ways to run this project. `npx emojme add` will work when emojme is present in `node_modules` (such as when downloaded via `npm`). `node ./emojme add` and `node ./emojme-add` will work if you have cloned the repo. These examples will use the former construction, but feel free to do whatever.
### emojme download
* Download all emoji from subdomain
* `npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE`
* creates `./build/$SUBDOMAIN.adminList.json` containing url references to all emoji, but not the files themselves.
* Download all emoji from subdomain using an authjson
* `npx emojme download --auth-json '{"token":"$TOKEN","domain":"$SUBDOMAIN","cookie":"$COOKIE"}'`
* creates `./build/$SUBDOMAIN.adminList.json` containing url references to all emoji, but not the files themselves.
* Download all emoji from multiple subdomains
* `npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2`
* creates `./build/$SUBDOMAIN1.adminList.json` and `./build/$SUBDOMAIN2.adminList.json`
* download source content for emoji made by $USER1 and $USER2 in $SUBDOMAIN
* `npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --save $USER1 --save $USER2`
* This will create directories `./build/$SUBDOMAIN/$USER1/` and `./build/$SUBDOMAIN/$USER2/`, each containing that user's raw emoji image files
* download source content for all emoji in $SUBDOMAIN, grouping by user
* `npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --save-all`
* This will create directories `./build/$SUBDOMAIN/$USER/` for each user in $SUBDOMAIN that has created an emoji
### emojme add
* add $FILE as :$NAME: and $URL as :$NAME2: to subdomain
* `npx emojme add --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src $FILE --name $NAME --src $URL --name $NAME2`
* in $SUBDOMAIN1 and $SUBDOMAIN2, alias $ORIGINAL to $NAME
* `npx emojme add --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 ---subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2 --alias-for '$ORIGINAL' --name '$NAME'`
* Alias :$ORIGINAL: as :$NAME:, and if :$NAME: exists, alias as :$NAME-1: instead
* `npx emojme add --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --name $NAME --alias_for $ORIGINAL --avoid-collisions`
* This has some amount of intelligence to it - if $ORIGINAL uses `_`'s, the alias will be `$ORIGINAL_1`, if the original has hyphens it will use hyphens, and if `-1` already exists it will use `-2`, etc.
### emojme upload
* upload emoji from source json to subdomain
* `npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './myfile.json'`
* upload emoji from source emojipacks yaml to subdomain
* `npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './emojipacks.yaml'`
* upload emoji from source json to multiple subdomains
* `npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2 --src './myfile.json'`
* upload emoji from source json to subdomain, with each emoji being prefixed by $PREFIX
* `npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './myfile.json' --prefix '$PREFIX'`
* upload emoji from source json to subdomain, with each emoji being suffixed if it conficts with an existing emoji
* `npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './myfile.json' --avoid-collisions`
### emojme-sync
* sync emoji so that $SUBDOMAIN1 and $SUBDOMAIN2 have the same emoji*
* <sup>*the same emoji names, that is. If :hi: is different on the two subdomains they will remain different</sup>
* `npx emojme sync --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2`
* sync emoji so that $SUBDOMAIN1, $SUBDOMAIN2, and $SUBDOMAIN3 have the same emoji
* `npx emojme sync --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2 --subdomain $SUBDOMAIN3 --token $TOKEN3 --cookie $COOKIE3`
* sync emoji from $SUBDOMAIN1 to $SUBDOMAIN2, so that $SUBDOMAIN1's emoji are a subset of $SUBDOMAIN2's emoji
* `npx emojme sync --src-subdomain $SUBDOMAIN1 --src-token $TOKEN1 --dst-subdomain $SUBDOMAIN2 --dst-token $TOKEN2`
* sync emoji from $SUBDOMAIN1 to $SUBDOMAIN2 and $SUBDOMAIN3
* `npx emojme sync --src-subdomain $SUBDOMAIN1 --src-token $TOKEN1 --dst-subdomain $SUBDOMAIN2 --dst-token $TOKEN2 --dst-subdomain $SUBDOMAIN3 --dst-token $TOKEN3`
* sync emoji from $SUBDOMAIN1 and $SUBDOMAIN2 to $SUBDOMAIN3
* `npx emojme sync --src-subdomain $SUBDOMAIN1 --src-token $TOKEN1 --src-subdomain $SUBDOMAIN2 --src-token $TOKEN2 --dst-subdomain $SUBDOMAIN3 --dst-token $TOKEN3`
### emojme user stats
These commands all write files to the build directory, but become more immediately useful with the `--verbose` flag.
* get author statistics for user $USER (emoji upload count, etc)
* `npx emojme user-stats --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --user $USER --verbose`
* This will create json file `./build/$USER.$SUBDOMAIN.adminList.json`
* get user statistics for multiple users
* `npx emojme user-stats --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --user $USER --user $USER2 --user $USER3`
* This will create json files `./build/$USERX.$SUBDOMAIN.adminList.json` for each user passed
* get user statistics for top $N contributors
* `npx emojme user-stats --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --top $N`
* Defaults to top 10 users.
### emojme-favorites
* Print the token's user's top 20 most used emoji
* `npx emojme favorites --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --top 20 --verbose`
* Print the usage numbers for the user's top 10 most used emoji
* `npx emojme favorites --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --usage --verbose`
## Pro Moves :promoves:
### Creating a json file from a directory of images
You can use the script below to create a json file that will include all images in a directory. Make sure your directory only has files that end in gif, png, jpg, or jpeg. It will output a file called `emoji.json`.
```
brew install jq
./create-json.sh $PATH
```
### Getting a list of single attributes from an adminList json:
Hey try this with $ATTRIBUTE of "url". You might need all those urls!
```
cat $ADMINLIST.json | jq '.[] | .["$ATTRIBUTE"]'
```
### Rate limiting and you
Slack [threatened to release](https://api.slack.com/changelog/2018-03-great-rate-limits) then [released](https://api.slack.com/docs/rate-limits) rate limiting rules across its new api endpoints, and the rollout has included their undocumented endpoints now as well. As such, Emojme is going to slow down :capysad: Another nail in the coffin of making this a useful slackbot.
Though it is unpublished, I have on good authority that `/emoji.adminList` is Tier 3 (when paginated) and `/emoji.add` is Tier 2, so emojme now has a "fast part" and a "slow part" respectively.
I'm not one to judge how a person uses their own credentials, so there is a work around for those looking to get a bit more personal with the Slack networking infra team; Use the following environment variables to override my conservative defaults:
```sh
# How many requests to make at a time. Higher numbers are faster (as long as the other two params allow) and more prone to trip Slack's "hey that's not a burst that's a malicous user" alarm
SLACK_REQUEST_CONCURRENCY
# How many requests are to be sent per unit time. This is the real control of speed, the higher the more likely you are to be rate limited.
SLACK_REQUEST_RATE
# The unit of time, in ms. The lower the number the faster.
SLACK_REQUEST_WINDOW
# So, an example that has 10 in-flight requests at a time at a maximum rate of 200 requests per minute would be:
SLACK_REQUEST_CONCURRENCY=10 \
SLACK_REQUEST_RATE=200 \
SLACK_REQUEST_WINDOW=60000 \
node emojme-download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --save-all --bust-cache
```
I have tried my darndest to make the slack client in this project 429 tolerant, but after a few ignored 429's Slack gets mean and says you can't try again, so have fun dealing with that.
### FAQ
* I'm getting `invalid_auth` errors? huh???
* See #60. Essentially, Slack has gotten wise to our whole "you can use a token for arbitrary lengths of time because Slack doesn't want to rotate them often and log us out of active sessions, or deal with zombie sessions that are authed with out of date tokens". They've switched from using User Tokens (xoxs-) to Cookie Tokens (xoxc-), in combination with a cookie that is shortlived. Very clever, but we are more cleverer. We'll just rip off that cookie and pass it through the same way we were doing the token. It'll be a pain, but only as insecure as it was before.
* I don't see any progress when I run a cli command
* Do you have `--verbose` in your command? that's pretty useful.
* My network requests are slow and jerky
* That's how we gotta live under [rate limiting](#rate-limiting-and-you). To speed things up, try the env vars that are listed, but things might not go well. To make things less jerkey, knock down the concurrency so requests are more serial and there is no down time between bursts.
* I just want to upload this thing fast, but I have to download 20k emoji to upload one?
* Nope! That is the normal behavior to not anger slack - we do more easy GET's to avoid some troublesome POSTs, but you can turn that off. Just add `--allow-collisions` (or `{collsions: true}`) to your upload request.
## Contributing
Contribute! I'm garbo at js (and it's js's fault), so feel free to jump inand clean up, add features, and make the project live. I would recommend:
* Add tests
* Make your change
* Run tests `npm run test` or `npm run test:unit && npm run test:integration`
* pro move: add a `debugger;` and use `it.only`, then `npm inspect node_modules/mocha/bin/_mocha spec/...` to debug a failing test.
* Run end to end tests (requires a real slack instance) `npm run test:e2e -- --subdomain $YOUR_REAL_SUBDOMAIN --token $YOUR_REAL_TOKEN`
* Lint
* Regenerate docs, if necessary
## Inspirations
* [emojipacks](https://github.com/lambtron/emojipacks) is my OG. It mostly worked but seems rather undermaintained.
* [neutral-face-emoji-tools](https://github.com/Fauntleroy/neutral-face-emoji-tools) is a fantastic tool that has enabled me to make enough emoji that this tool became necessary.
## Stupid ways to use this stupid library!
* https://github.com/jackellenberger/allmyemojichildren
* https://github.com/guyfedwards/emoji
* https://github.com/jackellenberger/emojme-hubot-plugin
* https://github.com/jackellenberger/emojme-emoji-anywhere
* https://github.com/jackellenberger/infinite-emoji-discord-bot
================================================
FILE: USAGE.md
================================================
# Commands
```
Usage: emojme [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
download download all emoji from given subdomain
upload upload source emoji to given subdomain
add upload source emoji to given subdomain
user-stats get emoji statistics for given user on given subdomain
sync get emoji statistics for given user on given subdomain
favorites get favorite emoji and personal emoji usage statistics
help [cmd] display help for [cmd]
```
## emojme download
```
Usage: emojme-download [options]
Options:
-s, --subdomain <value> slack subdomain. Can be specified multiple times, paired with respective token. (default: [])
-d, --domain <value> alias for --subdomain (default: [])
-t, --token <value> slack cookie token. ususaly starts xoxc-... Can be specified multiple times, paired with respective subdomains. User tokens (xoxs-...) are no longer supported. :( (default: [])
-c, --cookie <value> slack cookie. paired with respective subdomains and tokens. (default: [])
-a --auth-json <value> A json-string containing keys "domain", "token", and "cookie", as generated by the Emojme: Emoji Anywhere Chrome Extension. Can be used as a replacement for or in leu of subdomain, token, and cookie options. (default: [])
--bust-cache force a redownload of all cached info.
--no-output prevent writing of files in build/ and log/
--since <value> only consider emoji since the given epoch timestamp
--verbose log debug messages to console
--save <user> save all of <user>'s emoji to disk at build/$subdomain/$user (default: [])
--save-all save all emoji from all users to disk at build/$subdomain
--save-all-by-user save all emoji from all users to disk at build/$subdomain/$user
-h, --help output usage information
```
## emojme upload
```
Usage: emojme-upload [options]
Options:
-s, --subdomain <value> slack subdomain. Can be specified multiple times, paired with respective token. (default: [])
-d, --domain <value> alias for --subdomain (default: [])
-t, --token <value> slack cookie token. ususaly starts xoxc-... Can be specified multiple times, paired with respective subdomains. User tokens (xoxs-...) are no longer supported. :( (default: [])
-c, --cookie <value> slack cookie. paired with respective subdomains and tokens. (default: [])
-a --auth-json <value> A json-string containing keys "domain", "token", and "cookie", as generated by the Emojme: Emoji Anywhere Chrome Extension. Can be used as a replacement for or in leu of subdomain, token, and cookie options. (default: [])
--bust-cache force a redownload of all cached info.
--no-output prevent writing of files in build/ and log/
--since <value> only consider emoji since the given epoch timestamp
--verbose log debug messages to console
--allow-collisions do not cull collisions ever, upload everything just as it is and accept the collisions. This will be faster for known-good uploads, more rate-limiting prone for untrusted uploads.
--avoid-collisions instead of culling collisions, rename the emoji to be uploaded "intelligently"
--prefix <value> prefix all emoji to be uploaded with <value>
--src <value> source file(s) for emoji json or yaml you'd like to upload
-h, --help output usage information
```
## emojme add
```
Usage: emojme-add [options]
Options:
-s, --subdomain <value> slack subdomain. Can be specified multiple times, paired with respective token. (default: [])
-d, --domain <value> alias for --subdomain (default: [])
-t, --token <value> slack cookie token. ususaly starts xoxc-... Can be specified multiple times, paired with respective subdomains. User tokens (xoxs-...) are no longer supported. :( (default: [])
-c, --cookie <value> slack cookie. paired with respective subdomains and tokens. (default: [])
-a --auth-json <value> A json-string containing keys "domain", "token", and "cookie", as generated by the Emojme: Emoji Anywhere Chrome Extension. Can be used as a replacement for or in leu of subdomain, token, and cookie options. (default: [])
--bust-cache force a redownload of all cached info.
--no-output prevent writing of files in build/ and log/
--since <value> only consider emoji since the given epoch timestamp
--verbose log debug messages to console
--allow-collisions do not cull collisions ever, upload everything just as it is and accept the collisions. This will be faster for known-good uploads, more rate-limiting prone for untrusted uploads.
--avoid-collisions instead of culling collisions, rename the emoji to be uploaded "intelligently"
--prefix <value> prefix all emoji to be uploaded with <value>
--src <value> source image/gif/#content for emoji you'd like to upload (default: null)
--name <value> name of the emoji from --src that you'd like to upload (default: null)
--alias-for <value> name of the emoji you'd like --name to be an alias of. Specifying this will negate --src (default: null)
-h, --help output usage information
```
## emojme user-stats
```
Usage: emojme-user-stats [options]
Options:
-s, --subdomain <value> slack subdomain. Can be specified multiple times, paired with respective token. (default: [])
-d, --domain <value> alias for --subdomain (default: [])
-t, --token <value> slack cookie token. ususaly starts xoxc-... Can be specified multiple times, paired with respective subdomains. User tokens (xoxs-...) are no longer supported. :( (default: [])
-c, --cookie <value> slack cookie. paired with respective subdomains and tokens. (default: [])
-a --auth-json <value> A json-string containing keys "domain", "token", and "cookie", as generated by the Emojme: Emoji Anywhere Chrome Extension. Can be used as a replacement for or in leu of subdomain, token, and cookie options. (default: [])
--bust-cache force a redownload of all cached info.
--no-output prevent writing of files in build/ and log/
--since <value> only consider emoji since the given epoch timestamp
--verbose log debug messages to console
--user <value> slack user you'd like to get stats on. Can be specified multiple times for multiple users. (default: null)
--top <value> the top n users you'd like user emoji statistics on (default: 10)
-h, --help output usage information
```
## emojme sync
```
Usage: emojme-sync [options]
Options:
-s, --subdomain <value> slack subdomain. Can be specified multiple times, paired with respective token. (default: [])
-d, --domain <value> alias for --subdomain (default: [])
-t, --token <value> slack cookie token. ususaly starts xoxc-... Can be specified multiple times, paired with respective subdomains. User tokens (xoxs-...) are no longer supported. :( (default: [])
-c, --cookie <value> slack cookie. paired with respective subdomains and tokens. (default: [])
-a --auth-json <value> A json-string containing keys "domain", "token", and "cookie", as generated by the Emojme: Emoji Anywhere Chrome Extension. Can be used as a replacement for or in leu of subdomain, token, and cookie options. (default: [])
--bust-cache force a redownload of all cached info.
--no-output prevent writing of files in build/ and log/
--since <value> only consider emoji since the given epoch timestamp
--verbose log debug messages to console
--src-subdomain [value] subdomain from which to draw emoji for one way sync (default: null)
--src-token [value] token with which to draw emoji for one way sync (default: null)
--src-cookie [value] cookie with which to draw emoji for one way sync (default: null)
--dst-subdomain [value] subdomain to which to emoji will be added is one way sync (default: null)
--dst-token [value] token with which emoji will be added for one way sync (default: null)
--dst-cookie [value] cookie with which emoji will be added for one way sync (default: null)
--dry-run if set to true, nothing will be uploaded or synced
-h, --help output usage information
```
## emojme favorites
```
Usage: emojme-favorites [options]
Options:
-s, --subdomain <value> slack subdomain. Can be specified multiple times, paired with respective token. (default: [])
-d, --domain <value> alias for --subdomain (default: [])
-t, --token <value> slack cookie token. ususaly starts xoxc-... Can be specified multiple times, paired with respective subdomains. User tokens (xoxs-...) are no longer supported. :( (default: [])
-c, --cookie <value> slack cookie. paired with respective subdomains and tokens. (default: [])
-a --auth-json <value> A json-string containing keys "domain", "token", and "cookie", as generated by the Emojme: Emoji Anywhere Chrome Extension. Can be used as a replacement for or in leu of subdomain, token, and cookie options. (default: [])
--bust-cache force a redownload of all cached info.
--no-output prevent writing of files in build/ and log/
--since <value> only consider emoji since the given epoch timestamp
--verbose log debug messages to console
--top <value> (verbose cli only) the top n favorites you'd like to see (default: 10)
--usage (verbose cli only) print emoji usage of favorites in addition to their names
--lite do not attempt to marry favorites with complete adminlist content. Results will contain only emoji name and usage count.
-h, --help output usage information
```
================================================
FILE: create-json.sh
================================================
ls $1 | jq -R "reduce . as \$i ({}; {\"src\": (\"$1/\" + \$i), \"name\": (\$i | sub(\".png\"; \"\") | sub(\".gif\"; \"\") | sub(\".jpg\"; \"\") | sub(\".jpeg\"; \"\"))})" | jq -s '.' > emoji.json
================================================
FILE: docs/emojme-add.js.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
emojme-add.js - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
emojme-add.js
</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const _ = require('lodash');
const commander = require('commander');
const EmojiAdminList = require('./lib/emoji-admin-list');
const EmojiAdd = require('./lib/emoji-add');
const FileUtils = require('./lib/util/file-utils');
const Helpers = require('./lib/util/helpers');
const Cli = require('./lib/util/cli');
/** @module add */
/**
* The add response object, like other response objects, is organized by input subdomain.
* @typedef {object} addResponseObject
* @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 reflecting the parameters passed in to `add`
* @property {emojiList[]} subdomain.collisions if `options.avoidCollisions` is `false`, emoji that cannot be uploaded due to existing conflicting emoji names will exist here
*/
/**
* Add emoji described by parameters within options to the specified subdomain(s).
*
* Note that options can accept both aliases and original emoji at the same time, but ordering can get complicated and honestly I'd skip it if I were you. For each emoji, make sure that every descriptor (src, name, aliasFor) has a value, using `null`s for fields that are not relevant to the current emoji.
*
* @async
* @param {string|string[]} subdomains a single or list of subdomains to add emoji to. Must match respectively to `token`s and `cookie`s.
* @param {string|string[]} tokens a single or list of tokens to add emoji to. 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 image files for the emoji to be added. If no corresponding `options.name` is given, the filename will be used
* @param {string|string[]} [options.name] names of the emoji to be added, overriding filenames if given, and becoming the alias name if an `options.aliasFor` is given
* @param {string|string[]} [options.aliasFor] names of emoji to be aliased to `options.name`
* @param {boolean} [options.allowCollisions] if `true`, emoji being uploaded will not be checked against existing emoji. This will take less time up front but may cause more errors.
* @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
*
* @returns {Promise<addResponseObject>} addResponseObject result object
*
* @example
var addOptions = {
src: ['./emoji1.jpg', 'http://example.com/emoji2.png'], // upload these two images
name: ['myLocalEmoji', 'myOnlineEmoji'], // call them these two names
bustCache: false, // don't bother redownloading existing emoji
avoidCollisions: true, // if there are similarly named emoji, change my new emoji names
output: false // don't write any files
};
var subdomains = ['mySubdomain1', 'mySubdomain2'] // can add one or multiple
var tokens = ['myToken1', 'myToken2'] // can add one or multiple
var cookies = ['myCookie1', 'myCookie2'] // can add one or multiple
var addResults = await emojme.add(subdomains, tokens, cookies, addOptions);
console.log(userStatsResults);
// {
// mySubomain1: {
// collisions: [], // only defined if avoidCollisons = false
// emojiList: [
// { name: 'myLocalEmoji', ... },
// { name: 'myOnlineEmoji', ... },
// ]
// },
// mySubomain2: {
// collisions: [], // only defined if avoidCollisons = false
// emojiList: [
// { name: 'myLocalEmoji', ... },
// { name: 'myOnlineEmoji', ... },
// ]
// }
// }
*/
async function add(subdomains, tokens, cookies, options) {
subdomains = Helpers.arrayify(subdomains);
tokens = Helpers.arrayify(tokens);
cookies = Helpers.arrayify(cookies);
options = options || {};
const aliases = Helpers.arrayify(options.aliasFor);
const names = Helpers.arrayify(options.name);
const sources = Helpers.arrayify(options.src);
let inputEmoji = []; let name; let alias; let
source;
while (aliases.length || sources.length) {
name = names.shift();
if (source = sources.shift()) {
inputEmoji.push({
is_alias: 0,
url: source,
name: name || source.match(/(?:.*\/)?(.*).(jpg|jpeg|png|gif)/)[1],
});
} else {
alias = aliases.shift();
inputEmoji.push({
is_alias: 1,
alias_for: alias,
name,
});
}
}
if (names.length || _.find(inputEmoji, ['name', undefined])) {
return Promise.reject(new Error('Invalid input. Either not all inputs have been consumed, or not all emoji are well formed. Consider simplifying input, or padding input with `null` values.'));
}
const [authTuples] = Helpers.zipAuthTuples(subdomains, tokens, cookies);
const addPromises = 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);
return emojiAdd.upload(emojiToUpload).then((uploadResult) => {
if (uploadResult.errorList && uploadResult.errorList.length > 1 && options.output) {
FileUtils.writeJson(`./build/${this.subdomain}.emojiUploadErrors.json`, uploadResult.errorList);
}
return Object.assign({}, uploadResult, { collisions });
});
});
return Helpers.formatResultsHash(await Promise.all(addPromises));
}
function addCli() {
const program = new commander.Command();
Cli.requireAuth(program);
Cli.allowIoControl(program);
Cli.allowEmojiAlterations(program)
.option('--src <value>', 'source image/gif/#content for emoji you\'d like to upload', Cli.list, null)
.option('--name <value>', 'name of the emoji from --src that you\'d like to upload', Cli.list, null)
.option('--alias-for <value>', 'name of the emoji you\'d like --name to be an alias of. Specifying this will negate --src', Cli.list, null)
.parse(process.argv);
Cli.unpackAuthJson(program);
return add(program.subdomain, program.token, program.cookie, {
src: program.src,
name: program.name,
aliasFor: program.aliasFor,
bustCache: program.bustCache,
allowCollisions: program.allowCollisions,
avoidCollisions: program.avoidCollisions,
prefix: program.prefix,
output: program.output,
});
}
if (require.main === module) {
addCli();
}
module.exports = {
add,
addCli,
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/emojme-download.js.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
emojme-download.js - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
emojme-download.js
</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const commander = require('commander');
const EmojiAdminList = require('./lib/emoji-admin-list');
const Cli = require('./lib/util/cli');
const Helpers = require('./lib/util/helpers');
/** @module download */
/**
* The download response object, like other response objects, is organized by input subdomain.
* @typedef {object} downloadResponseObject
* @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 downloaded from `subdomain`
* @property {string[]} subdomain.saveResults an array of paths for emoji that have been downloaded. note that all users that have been passed with `options.save` will be grouped together here.
*/
/**
* Download the list of custom emoji that have been added to the given slack instances, by default saving a json of all available relevant data. Optionally save the source images for a given user.
*
* @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.save] A user name or array of user names whose emoji source images will be saved. All emoji source images are linked to in the default adminList, but passing a user name here will save that user's emoji to build/<subdomain>/<username>
* @param {boolean} [options.saveAll] if `true`, download all emoji on slack instance from all users to disk in a single location.
* @param {boolean} [options.saveAllByUser] if `true`, download all emoji on slack instance from all users to disk, organized into directories by user.
* @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<downloadResponseObject>} downloadResponseObject result object
*
* @example
var downloadOptions = {
save: ['username_1', 'username_2'], // Download the emoji source files for these two users
bustCache: true, // make sure this data is fresh
output: true // download the adminList to ./build
};
var downloadResults = await emojme.download('mySubdomain', 'myToken', 'myCookie', downloadOptions);
console.log(downloadResults);
// {
// mySubdomain: {
// emojiList: [
// { name: 'emoji-from-mySubdomain', ... },
// ...
// ],
// saveResults: [
// './build/mySubdomain/username_1/an_emoji.jpg',
// './build/mySubdomain/username_1/another_emoji.gif',
// ... all of username_1's emoji
// './build/mySubdomain/username_2/some_emoji.jpg',
// './build/mySubdomain/username_2/some_other_emoji.gif',
// ... all of username_2's emoji
// ]
// }
// }
*/
async function download(subdomains, tokens, cookies, options) {
subdomains = Helpers.arrayify(subdomains);
tokens = Helpers.arrayify(tokens);
cookies = Helpers.arrayify(cookies);
options = options || {};
const [authTuples] = Helpers.zipAuthTuples(subdomains, tokens, cookies);
const downloadPromises = authTuples.map(async (authTuple) => {
const subdomain = authTuple[0];
let saveResults = [];
const adminList = new EmojiAdminList(...authTuple, options.output);
const emojiList = await adminList.get(options.bustCache, options.since);
if ((options.save && options.save.length) || options.saveAll || options.saveAllByUser) {
saveResults = saveResults.concat(await EmojiAdminList.save(emojiList, subdomain, {
save: options.save, saveAll: options.saveAll, saveAllByUser: options.saveAllByUser,
}));
}
return { emojiList, subdomain, saveResults };
});
return Helpers.formatResultsHash(await Promise.all(downloadPromises));
}
function downloadCli() {
const program = new commander.Command();
Cli.requireAuth(program);
Cli.allowIoControl(program)
.option('--save <user>', 'save all of <user>\'s emoji to disk at build/$subdomain/$user', Cli.list, [])
.option('--save-all', 'save all emoji from all users to disk at build/$subdomain')
.option('--save-all-by-user', 'save all emoji from all users to disk at build/$subdomain/$user')
.parse(process.argv);
Cli.unpackAuthJson(program);
return download(program.subdomain, program.token, program.cookie, {
save: program.save,
saveAll: program.saveAll,
saveAllByUser: program.saveAllByUser,
bustCache: program.bustCache,
output: program.output,
since: program.since,
});
}
if (require.main === module) {
downloadCli();
}
module.exports = {
download,
downloadCli,
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/emojme-favorites.js.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
emojme-favorites.js - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
emojme-favorites.js
</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const _ = require('lodash');
const commander = require('commander');
const util = require('util');
const ClientBoot = require('./lib/client-boot');
const EmojiAdminList = require('./lib/emoji-admin-list');
const logger = require('./lib/logger');
const Cli = require('./lib/util/cli');
const FileUtils = require('./lib/util/file-utils');
const Helpers = require('./lib/util/helpers');
/** @module favorites */
/**
* The user-specific favorites response object, like other response objects, is organized by input subdomain.
* @typedef {object} favoritesResponseObject
* @property {object} subdomain each subdomain passed in to add will appear as a key in the response
* @property {string} subdomain.favoritesResult.user the username associated with the given cookie token
* @property {string[]} subdomain.favoritesResult.favoriteEmoji the list of 'favorite' emoji as deemed by slack, in desc sorted order
* @property {object[]} subdomain.favoritesResult.favoriteEmojiAdminList an array of emoji objects, as organized by emojiAdminList
*/
/**
* Get the contents of the "Frequenly Used" box for your specified user
*
* @async
* @param {string|string[]} subdomains a single or list of subdomains from which to analyze emoji. Must match respectively to `token`s and `cookie`s.
* @param {string|string[]} tokens a single or list of tokens to add emoji to. 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 options on what to present
* @param {Number} [options.lite] do not attempt to marry favorites with complete adminlist content. Results will contain only emoji name and usage count.
* @param {Number} [options.top] (verbose cli only) count of top n emoji contriubtors you would like to retrieve user statistics on
* @param {Number} [options.usage] (verbose cli only) print not just the list of favorite emoji, but their usage count
* @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<favoritesResponseObject>} fovoritesResponseObject result object
*
* @example
var favoritesResult = await emojme.favorites('mySubdomain', 'myToken', 'myCookie', {});
console.log(favoritesResult);
// {
// mySubdomain: {
// favoritesResult: {
// user: '{myToken's user}',
// favoriteEmoji: [
// emojiName,
// ...
// ],
// favoriteEmojiAdminList: [
// {emojiName}: {adminList-style emoji object, with additional `usage` value}
// ...
// ],
// }
// }
// }
*/
async function favorites(subdomains, tokens, cookies, options) {
subdomains = Helpers.arrayify(subdomains);
tokens = Helpers.arrayify(tokens);
cookies = Helpers.arrayify(cookies);
options = options || {};
const [authTuples] = Helpers.zipAuthTuples(subdomains, tokens, cookies);
const favoritesPromises = authTuples.map(async (authTuple) => {
let emojiList = [];
if (!options.lite) {
const emojiAdminList = new EmojiAdminList(...authTuple, options.output);
emojiList = await emojiAdminList.get(options.bustCache);
}
const bootClient = new ClientBoot(...authTuple, options.output);
const bootData = await bootClient.get(options.bustCache);
const user = ClientBoot.extractName(bootData);
const favoriteEmojiUsage = ClientBoot.extractEmojiUse(bootData);
const favoriteEmojiList = favoriteEmojiUsage.map(e => e.name);
const favoriteEmojiAdminList = _.reduce(favoriteEmojiUsage, (acc, usageObj) => {
acc.push({
[usageObj.name]: {
...EmojiAdminList.find(emojiList, usageObj.name),
usage: usageObj.usage,
},
});
return acc;
}, []);
const result = {
user,
subdomain: bootClient.subdomain,
favoriteEmoji: favoriteEmojiList,
favoriteEmojiAdminList,
};
const safeUserName = FileUtils.sanitize(result.user);
if (options.output) FileUtils.writeJson(`./build/${safeUserName}.${bootClient.subdomain}.favorites.json`, result.favoriteEmojiAdminList, null, 3);
const topNFavorites = util.inspect(
(options.usage ? favoriteEmojiList : favoriteEmojiUsage)
.slice(0, options.top),
);
logger.info(`[${bootClient.subdomain}] Favorite emoji for ${result.user}: ${topNFavorites}`);
return { subdomain: bootClient.subdomain, favoritesResult: result };
});
return Helpers.formatResultsHash(_.flatten(await Promise.all(favoritesPromises)));
}
function favoritesCli() {
const program = new commander.Command();
Cli.requireAuth(program);
Cli.allowIoControl(program);
program
.option('--top <value>', '(verbose cli only) the top n favorites you\'d like to see', 10)
.option('--usage', '(verbose cli only) print emoji usage of favorites in addition to their names', false)
.option('--lite', 'do not attempt to marry favorites with complete adminlist content. Results will contain only emoji name and usage count.', false)
.parse(process.argv);
Cli.unpackAuthJson(program);
return favorites(program.subdomain, program.token, program.cookie, {
top: program.top,
usage: program.usage,
lite: program.lite,
bustCache: program.bustCache,
output: program.output,
}).catch((err) => {
console.error('An error occurred: ', err);
});
}
if (require.main === module) {
favoritesCli();
}
module.exports = {
favorites,
favoritesCli,
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/emojme-sync.js.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
emojme-sync.js - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
emojme-sync.js
</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>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,
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/emojme-upload.js.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
emojme-upload.js - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
emojme-upload.js
</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>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,
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/emojme-user-stats.js.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
emojme-user-stats.js - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
emojme-user-stats.js
</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const _ = require('lodash');
const commander = require('commander');
const EmojiAdminList = require('./lib/emoji-admin-list');
const Cli = require('./lib/util/cli');
const FileUtils = require('./lib/util/file-utils');
const Helpers = require('./lib/util/helpers');
/** @module userStats */
/**
* The user-specific userStats response object, like other response objects, is organized by input subdomain.
* @typedef {object} userStatsResponseObject
* @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 downloaded from `subdomain`
* @property {object[]} subdomain.userStatsResults an array of user stats objects
* @property {object} subdomain.userStatsResults.userStatsObject An object containing several maybe-useful statistics, separated by user
* @property {string} subdomain.userStatsResults.userStatsObject.user the name of the user in question
* @property {emojiList[]} subdomain.userStatsResults.userStatsObject.userEmoji the emojiList the user authored
* @property {string} subdomain.userStatsResults.userStatsObject.subdomain redundant :shrug:
* @property {Number} subdomain.userStatsResults.userStatsObject.originalCount the number of original emoji the user has created
* @property {Number} subdomain.userStatsResults.userStatsObject.aliasCount the number of emoji aliases the user has defined
* @property {Number} subdomain.userStatsResults.userStatsObject.totalCount the number of original and aliases the user has created
* @property {Number} subdomain.userStatsResults.userStatsObject.percentage the percentage of emoji in the given subdomain that the user is responsible for
*/
/**
* Get a few useful-ish statistics for either specific users, or the top-n emoji creators
*
* @async
* @param {string|string[]} subdomains a single or list of subdomains to analyze. Must match respectively to `token`s and `cookie`s.
* @param {string|string[]} tokens a single or list of tokens to add emoji to. 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 options for what stats to present
* @param {string|string[]} [options.user] user name or array of user names you would like to retrieve user statistics on. If specified, ignores `top`
* @param {Number} [options.top] count of top n emoji contriubtors you would like to retrieve user statistics on
* @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<userStatsResponseObject>} userStatsResponseObject result object
*
* @example
var userStatsOptions = {
user: ['username_1', 'username_2'] // get me some info on these two users
};
var userStatsResults = await emojme.userStats('mySubdomain', 'myToken', 'myCookie', userStatsOptions);
console.log(userStatsResults);
// {
// mySubdomain: {
// userStatsResults: [
// {
// user: 'username_1',
// userEmoji: [{ all username_1's emoji }],
// subdomain: mySubdomain,
// originalCount: x,
// aliasCount: y,
// totalCount: x + y,
// percentage: (x + y) / mySubdomain's total emoji count
// },
// {
// user: 'username_2',
// userEmoji: [{ all username_2's emoji }],
// subdomain: mySubdomain,
// originalCount: x,
// aliasCount: y,
// totalCount: x + y,
// percentage: (x + y) / mySubdomain's total emoji count
// }
// ]
// }
// }
*/
async function userStats(subdomains, tokens, cookies, options) {
subdomains = Helpers.arrayify(subdomains);
tokens = Helpers.arrayify(tokens);
cookies = Helpers.arrayify(cookies);
const users = Helpers.arrayify(options.user);
options = options || {};
const [authTuples] = Helpers.zipAuthTuples(subdomains, tokens, cookies);
const userStatsPromises = authTuples.map(async (authTuple) => {
const emojiAdminList = new EmojiAdminList(...authTuple, options.output);
const emojiList = await emojiAdminList.get(options.bustCache, options.since);
if (users && users.length > 0) {
const results = EmojiAdminList.summarizeUser(emojiList, authTuple[0], users);
return results.map((result) => {
const safeUserName = FileUtils.sanitize(result.user);
FileUtils.writeJson(`./build/${safeUserName}.${result.subdomain}.adminList.json`, result.userEmoji, null, 3);
return { subdomain: authTuple[0], userStatsResults: results, emojiList };
});
}
const results = EmojiAdminList.summarizeSubdomain(emojiList, authTuple[0], options.top);
results.forEach((result) => {
const safeUserName = FileUtils.sanitize(result.user);
FileUtils.writeJson(`./build/${safeUserName}.${result.subdomain}.adminList.json`, result.userEmoji, null, 3);
});
return { subdomain: authTuple[0], userStatsResults: results, emojiList };
});
return Helpers.formatResultsHash(_.flatten(await Promise.all(userStatsPromises)));
}
function userStatsCli() {
const program = new commander.Command();
Cli.requireAuth(program);
Cli.allowIoControl(program)
.option('--user <value>', 'slack user you\'d like to get stats on. Can be specified multiple times for multiple users.', Cli.list, null)
.option('--top <value>', 'the top n users you\'d like user emoji statistics on', 10)
.parse(process.argv);
Cli.unpackAuthJson(program);
return userStats(program.subdomain, program.token, program.cookie, {
user: program.user,
top: program.top,
bustCache: program.bustCache,
output: program.output,
since: program.since,
});
}
if (require.main === module) {
userStatsCli();
}
module.exports = {
userStats,
userStatsCli,
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Home - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li class="active" >
<a href="index.html">
<svg fill="#0095dd" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<section class="readme">
<article>
<h1><a href="https://github.com/jackellenberger/emojme">emojme</a> - <a href="https://jackellenberger.github.io/emojme">Documentation</a></h1><h2>Table of Contents</h2><ul>
<li><a href="#what-it-is">Project Overview</a></li>
<li><a href="#breaking-changes">Breaking Changes</a><ul>
<li><a href="#2-0-0">2.0.0</a></li>
</ul>
</li>
<li><a href="#requirements">Requirements</a></li>
<li><a href="#installation">Installation</a><ul>
<li><a href="#finding-a-slack-token">Getting a slack token</a></li>
<li><a href="#finding-a-slack-cookie">Getting a slack cookie</a></li>
</ul>
</li>
<li><a href="#usage">Usage</a><ul>
<li><a href="#usage">Command Line</a></li>
<li><a href="#module">Module</a></li>
</ul>
</li>
<li><a href="#build-directory-output">Build directory output</a></li>
<li><a href="#a-closer-look-at-options">A closer look at options</a></li>
<li><a href="#whats-the-difference-between-add-and-upload">Add vs Upload</a></li>
<li><a href="#cli-examples">CLI Examples</a><ul>
<li><a href="#emojme-download">Download</a></li>
<li><a href="#emojme-add">Add</a></li>
<li><a href="#emojme-upload">Upload</a></li>
<li><a href="#emojme-sync">Sync</a></li>
<li><a href="#emojme-user-stats">User Stats</a></li>
<li><a href="#emojme-favorites">Favorites</a></li>
</ul>
</li>
<li><a href="#pro-moves-promoves">Pro Moves</a><ul>
<li><a href="#rate-limiting-and-you">Rate Limiting</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</li>
<li><a href="#inspirations">Other Projects of Note</a></li>
</ul>
<h2>What it is</h2><p>Emojme is a set of tools to manage your Slack emoji, either directly from the command line or from within your own Javascript project.</p>
<p>Primary features are:</p>
<ul>
<li>Uploading new emoji<ul>
<li>Individually, by passing a file or url</li>
<li>In bulk, by passing a json "adminList" or a yaml "emojipack" file</li>
<li>To one or many slack instances at once</li>
</ul>
</li>
<li>Download existing emoji<ul>
<li>From one or many slack instances</li>
<li>Download all emoji</li>
<li>Download some emoji</li>
</ul>
</li>
<li>Sync emoji between mulitple slack instance<ul>
<li>One to one, one to many, many to one, or many to many</li>
</ul>
</li>
<li>Analyze emoji authorship<ul>
<li>Who makes the most emoji in your slack instance?</li>
</ul>
</li>
<li>Analyze emoji usage<ul>
<li>Which emoji do you use most?</li>
</ul>
</li>
</ul>
<p>jsdocs are available at <a href="https://jackellenberger.github.io/emojme">https://jackellenberger.github.io/emojme</a>. Read em.</p>
<h2>Breaking Changes</h2><h3>2.0.0</h3><p>Removes support for easy breazy beautiful user token auth, adds support for grumble grumble cookie token + cookie auth. Slack made me do it I swear. What does it mean for you?</p>
<ul>
<li>Whenever you wrote or used an emojme method with a signature like <code>method(domain, token, options)</code>, you will now need <code>method(domain, token, cookie, options)</code>.</li>
<li>Whenever you were calling the CLI with a pattern like <code>emojme command --subdomain $SUBDOMAIN --token $TOKEN</code>, you will now need <code>emojme command --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE</code>.</li>
<li>Read on for examples and instructions on how to collect your cookie from the jar.</li>
</ul>
<h2>Requirements</h2><p>To use emojme you don't need a bot or a workspace admin account. In fact, ~only regular <a href="https://api.slack.com/docs/token-types#user"><strong>user tokens</strong></a> work~ only <em>cookie</em> tokens work, in combination with shortlived browser tokens, and getting both isn't <em>quite</em> as easy as getting other types of tokens. Limitations are:</p>
<ul>
<li>Cookie tokens can be grabbed from any logged in slack webpage by following <a href="#finding-a-slack-token">these instructions</a>.</li>
<li>Auth Cookies are grabbed with even more difficulty, again from logged in slack pages, following <a href="#finding-a-slack-cookie">these instructions</a>.</li>
<li>All actions taken through Emojme can be linked back to your user account. That might be bad, but no one has yelled at me yet.</li>
<li>Cookie tokens are cycled at inditerminate times, and cannot (to my knowledge) be cycled manually. Ditto for the cookies themselves. <strong>DO NOT LOSE CONTROL OF YOUR COOKIES</strong>. Any project that uses emojme should have tokens passed in through environment variables and should not store them in source control.<ul>
<li>Update July 2021: If you are have been using an automated system to scrape User Tokens, you are pretty much hosed. The cookies now required are <a href="https://owasp.org/www-community/HttpOnly">Http Only</a> and can't be easily (or at all?) accessed via javascript.</li>
</ul>
</li>
</ul>
<h2>Installation</h2><h3>Command Line</h3><p>Via npm</p>
<pre class="prettyprint source lang-bash"><code>$ (nvm use 10 || nvm install 10) && npm install emojme
$ npx emojme [command] [options]</code></pre><p>Via github</p>
<pre class="prettyprint source lang-bash"><code>$ git clone https://github.com/jackellenberger/emojme.git
$ cd emojme
$ node ./emojme [command] [options]</code></pre><p>In order to use either feature, you will need both a Token and a Cookie each for every target subdomain (e.g. my-subdomain.slack.com). You can of course use your own methods for achieving this, but (and I will repeat this), the <a href="https://chrome.google.com/webstore/detail/emojme-emoji-anywhere/nbnaglaclijdfidbinlcnfdbikpbdkog?hl=en-US">Emojme: Emoji Anywhere</a> Chrome Extension makes it very much easier than anything else, at only minor risk to your personal security. But hey if I were gonna steal your slack creds I'd do it in an alley with a knife or something, not in broad daylight. Its source is also <a href="https://github.com/jackellenberger/emojme-emoji-anywhere">available on github</a> if you don't enjoy pre-rolls.</p>
<h3>Finding a slack token</h3><p>Update July 2021: Slack has switched away from using questionably rotated user tokens to using "cookie tokens" and an associated short lived cookie. Smart, but we're smarter. User Tokens were of the format <code>xox[sp]-(\w{12}|\w{10})-(\w{12}|\w{11})-\w{12}-\w{64}</code> but <em>will no longer work</em>. If use see an auth error, this is probably the reason. Cookie tokens follow a similar form, but not the <code>c</code>: <code>xoxc-(\w{12}|\w{10})-(\w{12}|\w{11})-\w{12}-\w{64}</code>.</p>
<h4>Slack for Web</h4><p>It's easyish! Open and sign into the slack customization page, e.g. https://my.slack.com/customize, right click anywhere > inspect element. Open the console and paste:</p>
<pre class="prettyprint source lang-javascript"><code>window.prompt("your api token is: ", TS.boot_data.api_token)</code></pre><p>You will be prompted with your api token! This can be sped up if you find yourself doing it often by adding the following bookmarklet, becase who doesn't love a good bookmarklet?:</p>
<pre class="prettyprint source"><code>javascript:(function()%7Bwindow.prompt(%22your%20api%20token%20is%3A%20%22%2C%20TS.boot_data.api_token)%7D)()</code></pre><p>(Slack has invalidated @curtisgibby's sage advice here, but we still appreciate them)</p>
<h4>Slack for Desktop (Has anyone tried this?)</h4><p>This is a similar process, but requires an extra step depending on your platform.</p>
<ul>
<li>OSX: run or add to your .bashrc: <code>export SLACK_DEVELOPER_MENU=true; open -a /Applications/Slack.app</code></li>
<li>Windows: create a shortcut: <code>C:\Windows\System32\cmd.exe /c " SET SLACK_DEVELOPER_MENU=TRUE && start C:\existing\path\to\slack.exe"</code></li>
<li>Linux: honestly probably the same as OSX :shrug:</li>
</ul>
<p>With that done and slack open, open View > Developer > Toggle Webapp DevTools (shortcut <code>super+option+i</code>). This will give you a chromium inspector into which you can paste</p>
<pre class="prettyprint source"><code>console.log(window.boot_data.api_token)</code></pre><h3>Finding a slack cookie</h3><p>As cookies are now required, and so too is this section. Slack's auth cookie, as far as I can tell, is the <code>d</code> cookie, which is unfortunately HttpOnly meaning it cannot be accessed via javascript. It can, however, be accessed with a little creativity.</p>
<p>Chrome's (and presumably any modern browser's) cookies API does allow for HttpConly cookies to be accessed, but require the user's explicit approval but way of an extension. <a href="https://github.com/jackellenberger/emojme-emoji-anywhere">Emojme: Emoji Anywhere</a> is such an extension, and is <a href="https://chrome.google.com/webstore/detail/emojme-emoji-anywhere/nbnaglaclijdfidbinlcnfdbikpbdkog?hl=en-US">available in the chrome web store</a> (or of course can be loaded from source if you want to take your life in your own hands). Clicking the extension icon > <code>Get Slack Token and Cookie</code> will land you with what I am calling a "auth blob", which you can then pass to emojme via the <code>--auth-json</code> argument.</p>
<p><img src="/images/emojme-chrome-extension.jpg" alt="So easy! So Fun! With just one chrome extension!"></p>
<p>You may also pull the <code>d</code> cookie with your fleshy human hands, if you so desire. Open up your browser's developer tools, then Application menu > Cookies > d, and copy the string out for yourself. With this method, it will be easier to specify individual <code>--subdomain --token --cookie</code> flags.</p>
<p><img src="/images/how-to-get-a-cookie.jpg" alt="I have an MFA in drawing with a mouse"></p>
<h2>Usage</h2><p>Emojme can be used either as a command line tool or as a node module to be mixed in with your existing projects.</p>
<p>Complete CLI flags can be found in <a href="USAGE.md">USAGe.md</a>, but each command takes the <code>--help</code> option.</p>
<h3>Module</h3><p>In your project's directory</p>
<pre class="prettyprint source lang-bash"><code>npm install --save emojme</code></pre><p>In your project</p>
<pre class="prettyprint source lang-node"><code>var emojme = require('emojme');
// emojme-download
var downloadOptions = {
save: ['username_1', 'username_2'], // Download the emoji source files for these two users
bustCache: true, // make sure this data is fresh
output: true // download the adminList to ./build
};
var downloadResults = await emojme.download('mySubdomain', 'myToken', 'myCookie', downloadOptions);
console.log(downloadResults);
/*
{
mySubdomain: {
emojiList: [
{ name: 'emoji-from-mySubdomain', ... },
...
],
saveResults: [
'./build/mySubdomain/username_1/an_emoji.jpg',
'./build/mySubdomain/username_1/another_emoji.gif',
... all of username_1's emoji
'./build/mySubdomain/username_2/some_emoji.jpg',
'./build/mySubdomain/username_2/some_other_emoji.gif',
... all of username_2's emoji
]
}
}
*/
// emojme-upload
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 ... },
...
]
}
}
*/
// emojme-add
var addOptions = {
src: ['./emoji1.jpg', 'http://example.com/emoji2.png'], // upload these two images
name: ['myLocalEmoji', 'myOnlineEmoji'], // call them these two names
bustCache: false, // don't bother redownloading existing emoji
avoidCollisions: true, // if there are similarly named emoji, change my new emoji names
output: false // don't write any files
};
var subdomains = ['mySubdomain1', 'mySubdomain2'] // can add one or multiple
var tokens = ['myToken1', 'myToken2'] // can add one or multiple
var addResults = await emojme.add(subdomains, tokens, addOptions);
console.log(addResults);
/*
{
mySubomain1: {
collisions: [], // only defined if avoidCollisons = false
emojiList: [
{ name: 'myLocalEmoji', ... },
{ name: 'myOnlineEmoji', ... },
]
},
mySubomain2: {
collisions: [], // only defined if avoidCollisons = false
emojiList: [
{ name: 'myLocalEmoji', ... },
{ name: 'myOnlineEmoji', ... },
]
}
}
*/
// emojme-sync
var syncOptions = {
srcSubdomains: ['srcSubdomain'], // copy all emoji from srcSubdomain...
srcTokens: ['srcToken'],
dstSubdomains: ['dstSubdomain1', 'dstSubdomain2'], // ...to dstSubdomain1 and dstSubdomain2
dstTokens: ['dstToken1', 'dstToken2'],
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 ... }
]
}
}
*/
//emojme-user-stats
var userStatsOptions = {
user: ['username_1', 'username_2'] // get me some info on these two users
};
var userStatsResults = await emojme.userStats('mySubdomain', 'myToken', 'myCookie', userStatsOptions);
console.log(userStatsResults);
/*
{
mySubdomain: {
userStatsResults: [
{
user: 'username_1',
userEmoji: [{ all username_1's emoji }],
subdomain: mySubdomain,
originalCount: x,
aliasCount: y,
totalCount: x + y,
percentage: (x + y) / mySubdomain's total emoji count
},
{
user: 'username_2',
userEmoji: [{ all username_2's emoji }],
subdomain: mySubdomain,
originalCount: x,
aliasCount: y,
totalCount: x + y,
percentage: (x + y) / mySubdomain's total emoji count
}
]
}
}
*/
//emojme-favorites
var favoritesResult = await emojme.favorites('mySubdomain', 'myToken', 'myCookie', {});
console.log(favoritesResult);
/*
{
mySubdomain: {
favoritesResult: {
user: '{myToken's user}',
favoriteEmoji: [
emojiName,
...
],
favoriteEmojiAdminList: [
{emojiName}: {adminList-style emoji object, with additional `usage` value}
...
],
}
}
}
*/</code></pre><h2>Build directory output</h2><p>Okay you've run it, now what? Where are my dang emoji?</p>
<ul>
<li>Diagnostic info and intermediate results are written to the build directory. Some might come in handy!</li>
<li><code>build/$SUBDOMAIN.emojiUploadErrors.json</code> will give you a json of emoji that failed to upload and why. Use it to reattempt an upload! Generated from <code>upload</code> and <code>sync</code> calls.</li>
<li><code>build/$SUBDOMAIN.adminList.json</code> is the "master list" of a subdomain's emoji. Generated from <code>download</code> and <code>sync</code> calls.</li>
<li><code>build/$USER.$SUBDOMAIN.adminList.json</code> is all the emoji created by a user. Generated from <code>user-stats</code> calls.</li>
<li><code>build/diff.to-$SUBDOMAIN.from-$SUBDOMAINLIST.adminList.json</code> contains all emoji present in $SUBDOMAINLIST but not in $SUBDOMAIN. Generated from <code>sync</code> calls.</li>
</ul>
<h2>A closer look at options</h2><ul>
<li><p>Universal options:</p>
<ul>
<li><strong>requires</strong> at least one <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept multiple auth tuples.<ul>
<li>exception: sync can use a source/destination pattern, see below.</li>
</ul>
</li>
<li><em>optional</em>: <code>--bust-cache</code> will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
</ul>
</li>
<li><p><code>download</code></p>
<ul>
<li><strong>requires</strong> at least one <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept multiple auth tuples.</li>
<li><em>optional</em>: <code>--save $user</code> will save actual emoji data for the specified user, rather than just adminList json. Find the emoji in ./build/subdomain/user/</li>
<li><em>optional</em>: <code>--bust-cache</code> will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
<li><em>optional</em>: <code>--since timestamp</code> will only download or save emoji created after the epoch time timestamp given, e.g. <code>1572064302751</code></li>
</ul>
</li>
<li><code>upload</code><ul>
<li><strong>requires</strong> at least one <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept multiple auth tuples.</li>
<li><strong>requires</strong> at least one <code>--src</code> source json file.<ul>
<li>Src json should contain a list of objects where each object contains a "name" and "url" for image source</li>
<li>Src yaml should contain an <code>emojis</code> key whose value is a list of emoji objects. Each object should contain <code>name</code> and <code>src</code> if an original emoji, or <code>name</code>, <code>is_alias: 1</code>, and <code>alias_for</code> if an alias.</li>
<li>If adding an alias, url will be ignored and "is_alias" should be set to "1", and "alias_for" should be the name of the emoji to be aliased.</li>
</ul>
</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
</ul>
</li>
<li><code>add</code><ul>
<li><strong>requires</strong> at least one <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept multiple auth tuples.</li>
<li><strong>requires</strong> one of the following:<ol>
<li><code>--src</code> path of local emoji file.<ul>
<li><em>optional</em>: <code>--name</code> name of the emoji being uploaded. If not provided, the file name will be used.</li>
</ul>
</li>
<li><code>--name</code> and <code>--alias-for</code> to create an alias called <code>$NAME</code> with the same image as <code>$ALIAS-FOR</code></li>
</ol>
</li>
<li>Multiple <code>--src</code>'s or <code>--name</code>/<code>--alias-for</code> pairs may be provided, but don't mix the patterns. You'll confuse yourself.</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
</ul>
</li>
<li><code>user-stats</code><ul>
<li><strong>requires</strong> at least one <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept multiple auth tuples.</li>
<li>With no optional parameters given, this will print the top 10 emoji contributors</li>
<li><em>optional</em>: one of the following:<ol>
<li><code>--top</code> will show the top $TOP emoji contributors</li>
<li><code>--user</code> will show statistics for $USER. Can accept multiple <code>--user</code> calls.</li>
</ol>
</li>
<li><em>optional</em>: <code>--bust-cache</code> will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
<li><em>optional</em>: <code>--since timestamp</code> will count the author statistics of only those emoji created after the epoch time timestamp given, e.g. <code>1572064302751</code></li>
</ul>
</li>
<li><code>sync</code><ul>
<li><strong>requires</strong> one of the following:<ol>
<li>at least <strong>two</strong> <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept more than two auth tuples.</li>
<li>at least <strong>one</strong> <code>--src-subdomain</code>/<code>--src-token</code> auth tuple and at least <strong>one</strong> <code>--dst-subdomain</code>/<code>--dst-token</code> auth tuples for "one way" syncing.</li>
</ol>
</li>
<li><em>optional</em>: <code>--bust-cache</code> will force a redownload of emoji adminlist. If not supplied, a redownload is forced every 24 hours.</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
<li><em>optional</em>: <code>--since timestamp</code> will count the author statistics of only those emoji created after the epoch time timestamp given, e.g. <code>1572064302751</code></li>
<li><em>optional</em>: <code>--dry-run</code> download adminLists for all requested subdomains and diff them, but don't upload any new emoji. Find the diffs in <code>./output/to-$DST_SUBDOMAIN.from-$SRC_SUBDOMAIN.adminList.json</code></li>
</ul>
</li>
<li><code>favorites</code><ul>
<li><strong>requires</strong> at least one <code>--subdomain</code>/<code>--token</code>/<code>--cookie</code> <strong>auth tuple</strong>. Can accept multiple auth tuples.</li>
<li>With no optional parameters given, this will print the token's user's 10 most used emoji</li>
<li><em>optional</em>: <code>--top</code> <em>verbose cli usage only</em> limits stdout to top N most used emoji</li>
<li><em>optional</em>: <code>--usage</code> <em>verbose cli usage only</em> prints not only the user's favorite emoji, but also the usage numbers.</li>
<li><em>optional</em>: <code>--bust-cache</code> will force a redownload of emoji adminlist and boot data. If not supplied, a redownload is forced every 24 hours.</li>
<li><em>optional</em>: <code>--no-output</code> will prevent writing of files in the ./build directory. It does not currently suppres stdout.</li>
</ul>
</li>
</ul>
<h2>What's the difference between <code>Add</code> and <code>Upload</code>?</h2><p>Input type and use case! Technically (and behind the scenes) these commands do the same thing, which is post emoji to Slack.</p>
<p>The difference is that <code>Upload</code> is designed to take an <code>adminList</code> (what Slack calls a list of emoji and their related metadata) in the form of a json file. You can create this json file yourself, or use the <code>download</code> command to get it from an existing slack instance. It should be a Json array of objects, where each object represents an emoji and has attributes:</p>
<ul>
<li><code>name</code> (the name of the emoji duh)</li>
<li><code>url</code> (the source content of the emoji. either a url, a file path, or a raw <code>data:</code> string)</li>
<li><code>is_alias</code> (either 0 for non-aliases or 1 for aliases)</li>
<li><code>alias_for</code> (name of the emoji to alias if the emoji being uploaded is an alias)
There are other fields in an adminList, but no others are used at the current time.</li>
</ul>
<p><code>Add</code> is designed to allow users to upload a single or few emoji, directly from the command line, without having to craft a json file before hand. You can create either new emojis or new aliases (but not both, for now). Each new emoji needs a <code>--src</code>, and can take a <code>--name</code>, otherwise the file name will be used. Each new alias takes a <code>--name</code> and the name of the original emoji to alias as <code>--alias-for</code>.</p>
<h2>CLI Examples</h2><p>It should be noted that there are many ways to run this project. <code>npx emojme add</code> will work when emojme is present in <code>node_modules</code> (such as when downloaded via <code>npm</code>). <code>node ./emojme add</code> and <code>node ./emojme-add</code> will work if you have cloned the repo. These examples will use the former construction, but feel free to do whatever.</p>
<h3>emojme download</h3><ul>
<li><p>Download all emoji from subdomain</p>
<ul>
<li><code>npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE</code></li>
<li>creates <code>./build/$SUBDOMAIN.adminList.json</code> containing url references to all emoji, but not the files themselves.</li>
</ul>
</li>
<li><p>Download all emoji from subdomain using an authjson</p>
<ul>
<li><code>npx emojme download --auth-json '{"token":"$TOKEN","domain":"$SUBDOMAIN","cookie":"$COOKIE"}'</code></li>
<li>creates <code>./build/$SUBDOMAIN.adminList.json</code> containing url references to all emoji, but not the files themselves.</li>
</ul>
</li>
<li><p>Download all emoji from multiple subdomains</p>
<ul>
<li><code>npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2</code></li>
<li>creates <code>./build/$SUBDOMAIN1.adminList.json</code> and <code>./build/$SUBDOMAIN2.adminList.json</code></li>
</ul>
</li>
<li><p>download source content for emoji made by $USER1 and $USER2 in $SUBDOMAIN</p>
<ul>
<li><code>npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --save $USER1 --save $USER2</code></li>
<li>This will create directories <code>./build/$SUBDOMAIN/$USER1/</code> and <code>./build/$SUBDOMAIN/$USER2/</code>, each containing that user's raw emoji image files</li>
</ul>
</li>
<li><p>download source content for all emoji in $SUBDOMAIN, grouping by user</p>
<ul>
<li><code>npx emojme download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --save-all</code></li>
<li>This will create directories <code>./build/$SUBDOMAIN/$USER/</code> for each user in $SUBDOMAIN that has created an emoji</li>
</ul>
</li>
</ul>
<h3>emojme add</h3><ul>
<li><p>add $FILE as :$NAME: and $URL as :$NAME2: to subdomain</p>
<ul>
<li><code>npx emojme add --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src $FILE --name $NAME --src $URL --name $NAME2</code></li>
</ul>
</li>
<li><p>in $SUBDOMAIN1 and $SUBDOMAIN2, alias $ORIGINAL to $NAME</p>
<ul>
<li><code>npx emojme add --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 ---subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2 --alias-for '$ORIGINAL' --name '$NAME'</code></li>
</ul>
</li>
<li><p>Alias :$ORIGINAL: as :$NAME:, and if :$NAME: exists, alias as :$NAME-1: instead</p>
<ul>
<li><code>npx emojme add --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --name $NAME --alias_for $ORIGINAL --avoid-collisions</code></li>
<li>This has some amount of intelligence to it - if $ORIGINAL uses <code>_</code>'s, the alias will be <code>$ORIGINAL_1</code>, if the original has hyphens it will use hyphens, and if <code>-1</code> already exists it will use <code>-2</code>, etc.</li>
</ul>
</li>
</ul>
<h3>emojme upload</h3><ul>
<li><p>upload emoji from source json to subdomain</p>
<ul>
<li><code>npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './myfile.json'</code></li>
</ul>
</li>
<li><p>upload emoji from source emojipacks yaml to subdomain</p>
<ul>
<li><code>npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './emojipacks.yaml'</code></li>
</ul>
</li>
<li><p>upload emoji from source json to multiple subdomains</p>
<ul>
<li><code>npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2 --src './myfile.json'</code></li>
</ul>
</li>
<li><p>upload emoji from source json to subdomain, with each emoji being prefixed by $PREFIX</p>
<ul>
<li><code>npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './myfile.json' --prefix '$PREFIX'</code></li>
</ul>
</li>
<li><p>upload emoji from source json to subdomain, with each emoji being suffixed if it conficts with an existing emoji</p>
<ul>
<li><code>npx emojme upload --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --src './myfile.json' --avoid-collisions</code></li>
</ul>
</li>
</ul>
<h3>emojme-sync</h3><ul>
<li><p>sync emoji so that $SUBDOMAIN1 and $SUBDOMAIN2 have the same emoji*</p>
<ul>
<li><sup>*the same emoji names, that is. If :hi: is different on the two subdomains they will remain different</sup></li>
<li><code>npx emojme sync --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2</code></li>
</ul>
</li>
<li><p>sync emoji so that $SUBDOMAIN1, $SUBDOMAIN2, and $SUBDOMAIN3 have the same emoji</p>
<ul>
<li><code>npx emojme sync --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --subdomain $SUBDOMAIN2 --token $TOKEN2 --cookie $COOKIE2 --subdomain $SUBDOMAIN3 --token $TOKEN3 --cookie $COOKIE3</code></li>
</ul>
</li>
<li><p>sync emoji from $SUBDOMAIN1 to $SUBDOMAIN2, so that $SUBDOMAIN1's emoji are a subset of $SUBDOMAIN2's emoji</p>
<ul>
<li><code>npx emojme sync --src-subdomain $SUBDOMAIN1 --src-token $TOKEN1 --dst-subdomain $SUBDOMAIN2 --dst-token $TOKEN2</code></li>
</ul>
</li>
<li><p>sync emoji from $SUBDOMAIN1 to $SUBDOMAIN2 and $SUBDOMAIN3</p>
<ul>
<li><code>npx emojme sync --src-subdomain $SUBDOMAIN1 --src-token $TOKEN1 --dst-subdomain $SUBDOMAIN2 --dst-token $TOKEN2 --dst-subdomain $SUBDOMAIN3 --dst-token $TOKEN3</code></li>
</ul>
</li>
<li><p>sync emoji from $SUBDOMAIN1 and $SUBDOMAIN2 to $SUBDOMAIN3</p>
<ul>
<li><code>npx emojme sync --src-subdomain $SUBDOMAIN1 --src-token $TOKEN1 --src-subdomain $SUBDOMAIN2 --src-token $TOKEN2 --dst-subdomain $SUBDOMAIN3 --dst-token $TOKEN3</code></li>
</ul>
</li>
</ul>
<h3>emojme user stats</h3><p>These commands all write files to the build directory, but become more immediately useful with the <code>--verbose</code> flag.</p>
<ul>
<li><p>get author statistics for user $USER (emoji upload count, etc)</p>
<ul>
<li><code>npx emojme user-stats --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --user $USER --verbose</code></li>
<li>This will create json file <code>./build/$USER.$SUBDOMAIN.adminList.json</code></li>
</ul>
</li>
<li><p>get user statistics for multiple users</p>
<ul>
<li><code>npx emojme user-stats --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --user $USER --user $USER2 --user $USER3</code></li>
<li>This will create json files <code>./build/$USERX.$SUBDOMAIN.adminList.json</code> for each user passed</li>
</ul>
</li>
<li><p>get user statistics for top $N contributors</p>
<ul>
<li><code>npx emojme user-stats --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --top $N</code></li>
<li>Defaults to top 10 users.</li>
</ul>
</li>
</ul>
<h3>emojme-favorites</h3><ul>
<li><p>Print the token's user's top 20 most used emoji</p>
<ul>
<li><code>npx emojme favorites --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --top 20 --verbose</code></li>
</ul>
</li>
<li><p>Print the usage numbers for the user's top 10 most used emoji</p>
<ul>
<li><code>npx emojme favorites --subdomain $SUBDOMAIN1 --token $TOKEN1 --cookie $COOKIE1 --usage --verbose</code></li>
</ul>
</li>
</ul>
<h2>Pro Moves :promoves:</h2><h3>Getting a list of single attributes from an adminList json:</h3><p>Hey try this with $ATTRIBUTE of "url". You might need all those urls!</p>
<pre class="prettyprint source"><code>cat $ADMINLIST.json | jq '.[] | .["$ATTRIBUTE"]'</code></pre><h3>Rate limiting and you</h3><p>Slack <a href="https://api.slack.com/changelog/2018-03-great-rate-limits">threatened to release</a> then <a href="https://api.slack.com/docs/rate-limits">released</a> rate limiting rules across its new api endpoints, and the rollout has included their undocumented endpoints now as well. As such, Emojme is going to slow down :capysad: Another nail in the coffin of making this a useful slackbot.</p>
<p>Though it is unpublished, I have on good authority that <code>/emoji.adminList</code> is Tier 3 (when paginated) and <code>/emoji.add</code> is Tier 2, so emojme now has a "fast part" and a "slow part" respectively.</p>
<p>I'm not one to judge how a person uses their own credentials, so there is a work around for those looking to get a bit more personal with the Slack networking infra team; Use the following environment variables to override my conservative defaults:</p>
<pre class="prettyprint source lang-sh"><code># How many requests to make at a time. Higher numbers are faster (as long as the other two params allow) and more prone to trip Slack's "hey that's not a burst that's a malicous user" alarm
SLACK_REQUEST_CONCURRENCY
# How many requests are to be sent per unit time. This is the real control of speed, the higher the more likely you are to be rate limited.
SLACK_REQUEST_RATE
# The unit of time, in ms. The lower the number the faster.
SLACK_REQUEST_WINDOW
# So, an example that has 10 in-flight requests at a time at a maximum rate of 200 requests per minute would be:
SLACK_REQUEST_CONCURRENCY=10 \
SLACK_REQUEST_RATE=200 \
SLACK_REQUEST_WINDOW=60000 \
node emojme-download --subdomain $SUBDOMAIN --token $TOKEN --cookie $COOKIE --save-all --bust-cache</code></pre><p>I have tried my darndest to make the slack client in this project 429 tolerant, but after a few ignored 429's Slack gets mean and says you can't try again, so have fun dealing with that.</p>
<h3>FAQ</h3><ul>
<li><p>I'm getting <code>invalid_auth</code> errors? huh???</p>
<ul>
<li>See #60. Essentially, Slack has gotten wise to our whole "you can use a token for arbitrary lengths of time because Slack doesn't want to rotate them often and log us out of active sessions, or deal with zombie sessions that are authed with out of date tokens". They've switched from using User Tokens (xoxs-) to Cookie Tokens (xoxc-), in combination with a cookie that is shortlived. Very clever, but we are more cleverer. We'll just rip off that cookie and pass it through the same way we were doing the token. It'll be a pain, but only as insecure as it was before.</li>
</ul>
</li>
<li><p>I don't see any progress when I run a cli command</p>
<ul>
<li>Do you have <code>--verbose</code> in your command? that's pretty useful.</li>
</ul>
</li>
<li><p>My network requests are slow and jerky</p>
<ul>
<li>That's how we gotta live under <a href="#rate-limiting-and-you">rate limiting</a>. To speed things up, try the env vars that are listed, but things might not go well. To make things less jerkey, knock down the concurrency so requests are more serial and there is no down time between bursts.</li>
</ul>
</li>
<li><p>I just want to upload this thing fast, but I have to download 20k emoji to upload one?</p>
<ul>
<li>Nope! That is the normal behavior to not anger slack - we do more easy GET's to avoid some troublesome POSTs, but you can turn that off. Just add <code>--allow-collisions</code> (or <code>{collsions: true}</code>) to your upload request.</li>
</ul>
</li>
</ul>
<h2>Contributing</h2><p>Contribute! I'm garbo at js (and it's js's fault), so feel free to jump inand clean up, add features, and make the project live. I would recommend:</p>
<ul>
<li>Add tests</li>
<li>Make your change</li>
<li>Run tests <code>npm run test</code> or <code>npm run test:unit && npm run test:integration</code><ul>
<li>pro move: add a <code>debugger;</code> and use <code>it.only</code>, then <code>npm inspect node_modules/mocha/bin/_mocha spec/...</code> to debug a failing test.</li>
</ul>
</li>
<li>Run end to end tests (requires a real slack instance) <code>npm run test:e2e -- --subdomain $YOUR_REAL_SUBDOMAIN --token $YOUR_REAL_TOKEN</code></li>
<li>Lint</li>
<li>Regenerate docs, if necessary</li>
</ul>
<h2>Inspirations</h2><ul>
<li><a href="https://github.com/lambtron/emojipacks">emojipacks</a> is my OG. It mostly worked but seems rather undermaintained.</li>
<li><a href="https://github.com/Fauntleroy/neutral-face-emoji-tools">neutral-face-emoji-tools</a> is a fantastic tool that has enabled me to make enough emoji that this tool became necessary.</li>
</ul>
<h2>Stupid ways to use this stupid library!</h2><ul>
<li>https://github.com/jackellenberger/allmyemojichildren</li>
<li>https://github.com/guyfedwards/emoji</li>
<li>https://github.com/jackellenberger/emojme-hubot-plugin</li>
<li>https://github.com/jackellenberger/emojme-emoji-anywhere</li>
<li>https://github.com/jackellenberger/infinite-emoji-discord-bot</li>
</ul>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/module-add.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
add - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
add
</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
</div>
<h3 class="subsection-title">Methods</h3>
<span class='name-container'>
<a class="link-icon" href="#~add">
<svg height="20" width="20" style="fill: black;">
<use xlink:href="#linkIcon"></use>
</svg>
</a>
<h4 class="name" id="~add">
<span class="type-signature">(async, inner) </span>add<span class="signature">(subdomains, tokens, cookies, options)</span><span class="type-signature"> → {Promise.<addResponseObject>}</span>
</h4>
</span>
<div class="description">
<p>Add emoji described by parameters within options to the specified subdomain(s).</p>
<p>Note that options can accept both aliases and original emoji at the same time, but ordering can get complicated and honestly I'd skip it if I were you. For each emoji, make sure that every descriptor (src, name, aliasFor) has a value, using <code>null</code>s for fields that are not relevant to the current emoji.</p>
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>subdomains</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>a single or list of subdomains to add emoji to. Must match respectively to <code>token</code>s and <code>cookie</code>s.</p>
</td>
</tr>
<tr>
<td class="name"><code>tokens</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>a single or list of tokens to add emoji to. Must match respectively to <code>subdomain</code>s and <code>cookie</code>s.</p>
</td>
</tr>
<tr>
<td class="name"><code>cookies</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>a single or list of cookies used to authenticate access to the given subdomain. Must match respectively to <code>subdomain</code>s and <code>token</code>s.</p>
</td>
</tr>
<tr>
<td class="name"><code>options</code></td>
<td class="type">
<span class="param-type">
object
</span>
</td>
<td class="description last">
<p>contains singleton or arrays of emoji descriptors.</p>
<h6>Properties</h6>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>src</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>source image files for the emoji to be added. If no corresponding <code>options.name</code> is given, the filename will be used</p>
</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>names of the emoji to be added, overriding filenames if given, and becoming the alias name if an <code>options.aliasFor</code> is given</p>
</td>
</tr>
<tr>
<td class="name"><code>aliasFor</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>names of emoji to be aliased to <code>options.name</code></p>
</td>
</tr>
<tr>
<td class="name"><code>allowCollisions</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, emoji being uploaded will not be checked against existing emoji. This will take less time up front but may cause more errors.</p>
</td>
</tr>
<tr>
<td class="name"><code>avoidCollisions</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, emoji being added will be renamed to not collide with existing emoji. See lib/util/helpers.avoidCollisions for logic and details // TODO: fix this link, maybe link to tests which has better examples</p>
</td>
</tr>
<tr>
<td class="name"><code>prefix</code></td>
<td class="type">
<span class="param-type">
string
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>string to prefix all emoji being uploaded</p>
</td>
</tr>
<tr>
<td class="name"><code>bustCache</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, ignore any adminList younger than 24 hours and repull slack instance's existing emoji. Can be useful for making <code>options.avoidCollisions</code> more accurate</p>
</td>
</tr>
<tr>
<td class="name"><code>output</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>false</code>, no files will be written during execution. Prevents saving of adminList for future use</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source">
<ul class="dummy">
<li>
<a href="emojme-add.js.html">emojme-add.js</a>, <a href="emojme-add.js.html#line71">line 71</a>
</li>
</ul>
</dd>
</dl>
<div class="example-container">
<a class="link-icon" href="#add-examples">
<svg height="20" width="20" style="fill: black;">
<use xlink:href="#linkIcon"></use>
</svg>
</a>
<h5 id="add-examples">Example</h5>
<pre class="prettyprint"><code>var addOptions = {
src: ['./emoji1.jpg', 'http://example.com/emoji2.png'], // upload these two images
name: ['myLocalEmoji', 'myOnlineEmoji'], // call them these two names
bustCache: false, // don't bother redownloading existing emoji
avoidCollisions: true, // if there are similarly named emoji, change my new emoji names
output: false // don't write any files
};
var subdomains = ['mySubdomain1', 'mySubdomain2'] // can add one or multiple
var tokens = ['myToken1', 'myToken2'] // can add one or multiple
var cookies = ['myCookie1', 'myCookie2'] // can add one or multiple
var addResults = await emojme.add(subdomains, tokens, cookies, addOptions);
console.log(userStatsResults);
// {
// mySubomain1: {
// collisions: [], // only defined if avoidCollisons = false
// emojiList: [
// { name: 'myLocalEmoji', ... },
// { name: 'myOnlineEmoji', ... },
// ]
// },
// mySubomain2: {
// collisions: [], // only defined if avoidCollisons = false
// emojiList: [
// { name: 'myLocalEmoji', ... },
// { name: 'myOnlineEmoji', ... },
// ]
// }
// }</code></pre>
</div>
<h3 class="subsection-title">Type Definitions</h3>
<span class='name-container'>
<a class="link-icon" href="#~addResponseObject">
<svg height="20" width="20" style="fill: black;">
<use xlink:href="#linkIcon"></use>
</svg>
</a>
<h4 class="name" id="~addResponseObject">
<span class="type-signature"></span>addResponseObject<span class="type-signature"> :object</span>
</h4>
</span>
<div class="description">
<p>The add response object, like other response objects, is organized by input subdomain.</p>
</div>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>subdomain</code></td>
<td class="type">
<span class="param-type">
object
</span>
</td>
<td class="description last">
<p>each subdomain passed in to add will appear as a key in the response</p>
<h6>Properties</h6>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>emojiList</code></td>
<td class="type">
<span class="param-type">
Array.<emojiList>
</span>
</td>
<td class="description last">
<p>the list of emoji added to <code>subdomain</code>, with each element reflecting the parameters passed in to <code>add</code></p>
</td>
</tr>
<tr>
<td class="name"><code>collisions</code></td>
<td class="type">
<span class="param-type">
Array.<emojiList>
</span>
</td>
<td class="description last">
<p>if <code>options.avoidCollisions</code> is <code>false</code>, emoji that cannot be uploaded due to existing conflicting emoji names will exist here</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source">
<ul class="dummy">
<li>
<a href="emojme-add.js.html">emojme-add.js</a>, <a href="emojme-add.js.html#line12">line 12</a>
</li>
</ul>
</dd>
</dl>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a>
</footer>
<script src="scripts/linenumber.js"></script>
<script src="scripts/pagelocation.js"></script>
</body>
</html>
================================================
FILE: docs/module-download.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
download - Documentation
</title>
<link href="https://www.braintreepayments.com/images/favicon-ccda0b14.png" rel="icon" type="image/png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<!-- start Mixpanel -->
<script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("1919205b2da72e4da3b9b6639b444d59");</script>
<!-- end Mixpanel -->
</head>
<body>
<svg style="display: none;">
<defs>
<symbol id="linkIcon" fill="#706d77" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
</symbol>
</defs>
</svg>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<div class="top-nav-wrapper">
<ul>
<li >
<a href="index.html">
<svg fill="#6D6D6D" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>
</a>
</li>
</ul>
</div>
<nav>
<h3 class="reference-title">
emojme
</h3>
<h3>Modules</h3><ul><li id="add-nav"><a href="module-add.html">add</a><ul class='methods'><li data-type="method" id="add-add-nav"><a href="module-add.html#~add">add</a></li></ul></li><li id="download-nav"><a href="module-download.html">download</a><ul class='methods'><li data-type="method" id="download-download-nav"><a href="module-download.html#~download">download</a></li></ul></li><li id="favorites-nav"><a href="module-favorites.html">favorites</a><ul class='methods'><li data-type="method" id="favorites-favorites-nav"><a href="module-favorites.html#~favorites">favorites</a></li></ul></li><li id="sync-nav"><a href="module-sync.html">sync</a><ul class='methods'><li data-type="method" id="sync-sync-nav"><a href="module-sync.html#~sync">sync</a></li></ul></li><li id="upload-nav"><a href="module-upload.html">upload</a><ul class='methods'><li data-type="method" id="upload-upload-nav"><a href="module-upload.html#~upload">upload</a></li></ul></li><li id="userStats-nav"><a href="module-userStats.html">userStats</a><ul class='methods'><li data-type="method" id="userStats-userStats-nav"><a href="module-userStats.html#~userStats">userStats</a></li></ul></li></ul>
</nav>
<div id="main">
<h1 class="page-title">
download
</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
</div>
<h3 class="subsection-title">Methods</h3>
<span class='name-container'>
<a class="link-icon" href="#~download">
<svg height="20" width="20" style="fill: black;">
<use xlink:href="#linkIcon"></use>
</svg>
</a>
<h4 class="name" id="~download">
<span class="type-signature">(async, inner) </span>download<span class="signature">(subdomains, tokens, cookies, options)</span><span class="type-signature"> → {Promise.<downloadResponseObject>}</span>
</h4>
</span>
<div class="description">
<p>Download the list of custom emoji that have been added to the given slack instances, by default saving a json of all available relevant data. Optionally save the source images for a given user.</p>
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>subdomains</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>a single or list of subdomains from which to download emoji. Must match respectively to <code>token</code>s and <code>cookie</code>s.</p>
</td>
</tr>
<tr>
<td class="name"><code>tokens</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>a single or list of tokens with which to authenticate. Must match respectively to <code>subdomain</code>s and <code>cookie</code>s.</p>
</td>
</tr>
<tr>
<td class="name"><code>cookies</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>a single or list of cookies used to authenticate access to the given subdomain. Must match respectively to <code>subdomain</code>s and <code>token</code>s.</p>
</td>
</tr>
<tr>
<td class="name"><code>options</code></td>
<td class="type">
<span class="param-type">
object
</span>
</td>
<td class="description last">
<p>contains singleton or arrays of emoji descriptors.</p>
<h6>Properties</h6>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>save</code></td>
<td class="type">
<span class="param-type">
string
</span>
|
<span class="param-type">
Array.<string>
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>A user name or array of user names whose emoji source images will be saved. All emoji source images are linked to in the default adminList, but passing a user name here will save that user's emoji to build/<subdomain>/<username></p>
</td>
</tr>
<tr>
<td class="name"><code>saveAll</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, download all emoji on slack instance from all users to disk in a single location.</p>
</td>
</tr>
<tr>
<td class="name"><code>saveAllByUser</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, download all emoji on slack instance from all users to disk, organized into directories by user.</p>
</td>
</tr>
<tr>
<td class="name"><code>bustCache</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, ignore any adminList younger than 24 hours and repull slack instance's existing emoji. Can be useful for making <code>options.avoidCollisions</code> more accurate</p>
</td>
</tr>
<tr>
<td class="name"><code>output</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>false</code>, no files will be written during execution. Prevents saving of adminList for future use, as well as the writing of log files</p>
</td>
</tr>
<tr>
<td class="name"><code>verbose</code></td>
<td class="type">
<span class="param-type">
boolean
</span>
</td>
<td class="attributes">
<optional><br>
</td>
<td class="description last">
<p>if <code>true</code>, all messages will be written to stdout in addition to combined log file.</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source">
<ul class="dummy">
<li>
<a href="emojme-download.js.html">emojme-download.js</a>, <a href="emojme-download.js.html#line59">line 59</a>
</li>
</ul>
</dd>
</dl>
<div class="example-container">
<a class="link-icon" href="#download-examples">
<svg height="20" width="20" style="fill: black;">
<use xlink:href="#linkIcon"></use>
</svg>
</a>
<h5 id="download-examples">Example</h5>
<pre class="prettyprint"><code>var downloadOptions = {
save: ['username_1', 'username_2'], // Download the emoji source files for these two users
bustCache: true, // make sure this data is fresh
output: true // download the adminList to ./build
};
var downloadResults = await emojme.download('mySubdomain', 'myToken', 'myCookie', downloadOptions);
console.log(downloadResults);
// {
// mySubdomain: {
// emojiList: [
// { name: 'emoji-from-mySubdomain', ... },
// ...
// ],
// saveResults: [
// './build/mySubdomain/username_1/an_emoji.jpg',
// './build/mySubdomain/username_1/another_emoji.gif',
// ... all of username_1's emoji
// './build/mySubdomain/username_2/some_emoji.jpg',
// './build/mySubdomain/username_2/some_other_emoji.gif',
// ... all of username_2's emoji
// ]
// }
// }</code></pre>
</div>
<h3 class="subsection-title">Type Definitions</h3>
<span class='name-container'>
<a class="link-icon" href="#~downloadResponseObject">
<svg height="20" width="20" style="fill: black;">
<use xlink:href="#linkIcon"></use>
</svg>
</a>
<h4 class="name" id="~downloadResponseObject">
<span class="type-signature"></span>downloadResponseObject<span class="type-signature"> :object</span>
</h4>
</span>
<div class="description">
<p>The download response object, like other response objects, is organized by input subdomain.</p>
</div>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>subdomain</code></td>
<td class="type">
<span class="param-type">
object
</span>
</td>
<td class="description last">
<p>each subdomain passed in to add will appear as a key in the response</p>
<h6>Properties</h6>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>emojiList</code></td>
<td class="type">
<span class="param-type">
Array.<emojiList>
</span>
</td>
<td class="description last">
<p>the list of emoji downloaded from <code>subdomain</code></p>
</td>
</tr>
<tr>
<td class="name"><code>saveResults</code></td>
<td class="type">
<span class="param-type">
Array.<string>
</span>
</td>
<td class="description last">
<p>an array of paths for emoji that have been downloaded. note that all users that have been passed with <code>options.save</code> will be grouped together here.</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source">
<ul class="dummy">
<li>
gitextract_k03dkd1k/
├── .circleci/
│ └── config.yml
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .jsdoc.json
├── CHANGELOG.md
├── README.md
├── USAGE.md
├── create-json.sh
├── docs/
│ ├── emojme-add.js.html
│ ├── emojme-download.js.html
│ ├── emojme-favorites.js.html
│ ├── emojme-sync.js.html
│ ├── emojme-upload.js.html
│ ├── emojme-user-stats.js.html
│ ├── index.html
│ ├── module-add.html
│ ├── module-download.html
│ ├── module-favorites.html
│ ├── module-sync.html
│ ├── module-upload.html
│ ├── module-userStats.html
│ ├── scripts/
│ │ ├── linenumber.js
│ │ └── pagelocation.js
│ └── styles/
│ ├── jsdoc-default.css
│ ├── prettify-jsdoc.css
│ └── prettify-tomorrow.css
├── emojme-add.js
├── emojme-download.js
├── emojme-favorites.js
├── emojme-sync.js
├── emojme-upload.js
├── emojme-user-stats.js
├── emojme.js
├── lib/
│ ├── client-boot.js
│ ├── emoji-add.js
│ ├── emoji-admin-list.js
│ ├── logger.js
│ ├── slack-client.js
│ └── util/
│ ├── cli.js
│ ├── file-utils.js
│ └── helpers.js
├── package.json
├── scripts/
│ └── usage.sh
└── spec/
├── e2e/
│ └── emojme-download.js
├── fixtures/
│ ├── clientBoot.json
│ ├── emojiList.json
│ └── emojiList.yaml
├── integration/
│ ├── emojme-add-spec.js
│ ├── emojme-download-spec.js
│ ├── emojme-favorites-spec.js
│ ├── emojme-sync-spec.js
│ ├── emojme-upload-spec.js
│ └── emojme-user-stats-spec.js
├── spec-helper.js
└── unit/
└── lib/
├── emoji-add-spec.js
├── emoji-admin-list-spec.js
├── file-utils-spec.js
├── slack-client-spec.js
└── util/
├── cli-spec.js
└── helpers-spec.js
SYMBOL INDEX (84 symbols across 15 files)
FILE: docs/scripts/pagelocation.js
function highlightActiveHash (line 39) | function highlightActiveHash(event) {
function highlightActiveSection (line 60) | function highlightActiveSection() {
function getCurrentSectionName (line 66) | function getCurrentSectionName() {
function getCurrentHashName (line 78) | function getCurrentHashName() {
FILE: emojme-add.js
function add (line 71) | async function add(subdomains, tokens, cookies, options) {
function addCli (line 141) | function addCli() {
FILE: emojme-download.js
function download (line 59) | async function download(subdomains, tokens, cookies, options) {
function downloadCli (line 84) | function downloadCli() {
FILE: emojme-favorites.js
function favorites (line 59) | async function favorites(subdomains, tokens, cookies, options) {
function favoritesCli (line 111) | function favoritesCli() {
FILE: emojme-sync.js
function sync (line 68) | async function sync(subdomains, tokens, cookies, options) {
function syncCli (line 121) | function syncCli() {
FILE: emojme-upload.js
function upload (line 111) | async function upload(subdomains, tokens, cookies, options) {
function uploadCli (line 167) | function uploadCli() {
FILE: emojme-user-stats.js
function userStats (line 74) | async function userStats(subdomains, tokens, cookies, options) {
function userStatsCli (line 106) | function userStatsCli() {
FILE: lib/client-boot.js
constant ENDPOINT (line 6) | const ENDPOINT = '/client.boot';
constant FLANNEL_API_VER (line 7) | const FLANNEL_API_VER = '4';
class ClientBoot (line 12) | class ClientBoot {
method constructor (line 13) | constructor(subdomain, token, cookie, output) {
method get (line 23) | async get(bustCache) {
method createMultipart (line 34) | createMultipart() {
method extractEmojiUse (line 41) | static extractEmojiUse(data) {
method extractName (line 51) | static extractName(data) {
FILE: lib/emoji-add.js
constant ENDPOINT (line 8) | const ENDPOINT = '/emoji.add';
class EmojiAdd (line 13) | class EmojiAdd {
method constructor (line 14) | constructor(subdomain, token, cookie, output) {
method uploadSingle (line 23) | async uploadSingle(emoji) {
method upload (line 39) | async upload(src) {
method createMultipart (line 69) | static async createMultipart(emoji, token) {
FILE: lib/emoji-admin-list.js
constant ENDPOINT (line 9) | const ENDPOINT = '/emoji.adminList';
constant PAGE_SIZE (line 10) | const PAGE_SIZE = 500;
class EmojiAdminList (line 17) | class EmojiAdminList {
method constructor (line 18) | constructor(subdomain, token, cookie, output) {
method setPageSize (line 28) | setPageSize(pageSize) {
method createMultipart (line 32) | createMultipart(page) {
method get (line 41) | async get(bustCache, sinceTimestamp) {
method getAdminListPages (line 57) | async getAdminListPages() {
method find (line 88) | static find(emojiList, emojiName) {
method summarizeUser (line 93) | static summarizeUser(emojiList, subdomain, users) {
method summarizeSubdomain (line 123) | static summarizeSubdomain(emojiList, subdomain, n) {
method diff (line 135) | static diff(srcLists, srcSubdomains, dstLists, dstSubdomains) {
method since (line 154) | static since(emojiList, sinceTimestamp) {
method save (line 162) | static async save(emojiList, subdomain, options) {
FILE: lib/slack-client.js
constant RATE_LIMIT_TIER (line 7) | const RATE_LIMIT_TIER = {
class SlackClient (line 38) | class SlackClient {
method constructor (line 39) | constructor(subdomain, cookie, options) {
method url (line 60) | url(endpoint) {
method attachParts (line 64) | attachParts(req, hash, action) {
method request (line 80) | async request(endpoint, parts) {
method rateLimitTier (line 143) | static rateLimitTier(tier) {
FILE: lib/util/cli.js
function list (line 5) | function list(val, memo) {
function verbosity (line 11) | function verbosity() {
function requireAuth (line 15) | function requireAuth(program) {
function allowEmojiAlterations (line 24) | function allowEmojiAlterations(program) {
function allowIoControl (line 31) | function allowIoControl(program) {
function unpackAuthJson (line 39) | function unpackAuthJson(program) {
FILE: lib/util/file-utils.js
constant MAX_AGE (line 7) | const MAX_AGE = (1000 * 60 * 60 * 24);
constant VALID_FILENAME_CHARS (line 8) | const VALID_FILENAME_CHARS = /[\w\-. ]+/;
function mkdirp (line 10) | function mkdirp(path) {
function writeJson (line 18) | function writeJson(path, data) {
function readJson (line 26) | function readJson(path) {
function readYaml (line 34) | function readYaml(path) {
function isExpired (line 44) | function isExpired(path, expirationTimestamp) {
function saveData (line 59) | function saveData(data, path) {
function getData (line 66) | async function getData(path, options) {
function sanitize (line 92) | function sanitize(str) {
FILE: lib/util/helpers.js
function validAuthTuples (line 3) | function validAuthTuples(subdomains, tokens, cookies) {
function validSrcDstPairs (line 11) | function validSrcDstPairs(options) {
function atLeastOneValidInputType (line 24) | function atLeastOneValidInputType(subdomains, tokens, cookies, options) {
function zipAuthTuples (line 34) | function zipAuthTuples(subdomains, tokens, cookies, options) {
function applyPrefix (line 54) | function applyPrefix(emojiList, prefix) {
function slugName (line 59) | function slugName(emoji, returnIdDelim) {
function groupEmoji (line 89) | function groupEmoji(emojiList) {
function avoidCollisions (line 104) | function avoidCollisions(newEmoji, existingEmoji) {
function formatResultsHash (line 130) | function formatResultsHash(resultsArray) {
function arrayify (line 141) | function arrayify(elem) {
FILE: spec/spec-helper.js
method authTuples (line 5) | authTuples(n) {
method emojiName (line 8) | emojiName(i) {
method userName (line 11) | userName(i) {
method testEmoji (line 14) | testEmoji(i) {
method testEmojiList (line 24) | testEmojiList(n) {
method mockedSlackResponse (line 27) | mockedSlackResponse(emojiCount, pageSize, page, ok) {
method mockedBootData (line 40) | mockedBootData() {
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (505K chars).
[
{
"path": ".circleci/config.yml",
"chars": 973,
"preview": "# Javascript Node CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-javascript/ for more "
},
{
"path": ".eslintignore",
"chars": 6,
"preview": "docs/\n"
},
{
"path": ".eslintrc.json",
"chars": 668,
"preview": "{\n \"extends\": \"airbnb-base\",\n \"rules\": {\n \"no-console\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-param-re"
},
{
"path": ".gitignore",
"chars": 177,
"preview": "# Script results\nbuild/\nlog/*\n\n# Secret inputs\n.env\n.config\n\n# node projects amirite\nnode_modules/\npackage-lock.json\nemo"
},
{
"path": ".jsdoc.json",
"chars": 517,
"preview": "{\n\t\"tags\": {\n\t\t\"allowUnknownTags\": true,\n\t\t\"dictionaries\": [\"jsdoc\"]\n\t},\n\t\"source\": {\n\t\t\"include\": [\".\", \"lib\", \"package"
},
{
"path": "CHANGELOG.md",
"chars": 3725,
"preview": "# 2.0.0\n* Require cookie tokens and cookies >:[\n * All operations that previously required a (subdomain, token) tuple n"
},
{
"path": "README.md",
"chars": 31155,
"preview": "# [emojme](https://github.com/jackellenberger/emojme) - [Documentation](https://jackellenberger.github.io/emojme)\n\n## Ta"
},
{
"path": "USAGE.md",
"chars": 10065,
"preview": "# Commands\n```\nUsage: emojme [options] [command]\n\nOptions:\n -V, --version output the version number\n -h, --help o"
},
{
"path": "create-json.sh",
"chars": 195,
"preview": "ls $1 | jq -R \"reduce . as \\$i ({}; {\\\"src\\\": (\\\"$1/\\\" + \\$i), \\\"name\\\": (\\$i | sub(\\\".png\\\"; \\\"\\\") | sub(\\\".gif\\\"; \\\"\\\""
},
{
"path": "docs/emojme-add.js.html",
"chars": 13016,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "docs/emojme-download.js.html",
"chars": 10670,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "docs/emojme-favorites.js.html",
"chars": 11316,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "docs/emojme-sync.js.html",
"chars": 12923,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "docs/emojme-upload.js.html",
"chars": 13530,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "docs/emojme-user-stats.js.html",
"chars": 11654,
"preview": "\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "docs/index.html",
"chars": 41963,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/module-add.html",
"chars": 18697,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/module-download.html",
"chars": 16993,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/module-favorites.html",
"chars": 16619,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/module-sync.html",
"chars": 18968,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/module-upload.html",
"chars": 27133,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/module-userStats.html",
"chars": 19525,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, i"
},
{
"path": "docs/scripts/linenumber.js",
"chars": 597,
"preview": "'use strict';\n\n/* global document */\n(function () {\n var lineId, lines, totalLines, anchorHash;\n var source = document"
},
{
"path": "docs/scripts/pagelocation.js",
"chars": 2545,
"preview": "'use strict';\n\n$(document).ready(function () {\n var currentSectionNav, target;\n\n // If an anchor hash is in the URL hi"
},
{
"path": "docs/styles/jsdoc-default.css",
"chars": 19405,
"preview": "@font-face {\n font-family: \"Avenir Next W01\";\n font-style: normal;\n font-weight: 600;\n src: url(\"https://fas"
},
{
"path": "docs/styles/prettify-jsdoc.css",
"chars": 1547,
"preview": "/* JSDoc prettify.js theme */\n\n/* plain text */\n.pln {\n color: #000000;\n font-weight: normal;\n font-style: normal;\n}\n"
},
{
"path": "docs/styles/prettify-tomorrow.css",
"chars": 2213,
"preview": "/* Tomorrow Theme */\n/* Original theme - https://github.com/chriskempson/tomorrow-theme */\n/* Pretty printing styles. Us"
},
{
"path": "emojme-add.js",
"chars": 7679,
"preview": "const _ = require('lodash');\nconst commander = require('commander');\n\nconst EmojiAdminList = require('./lib/emoji-admin-"
},
{
"path": "emojme-download.js",
"chars": 5328,
"preview": "const commander = require('commander');\n\nconst EmojiAdminList = require('./lib/emoji-admin-list');\n\nconst Cli = require("
},
{
"path": "emojme-favorites.js",
"chars": 5989,
"preview": "const _ = require('lodash');\nconst commander = require('commander');\nconst util = require('util');\n\nconst ClientBoot = r"
},
{
"path": "emojme-sync.js",
"chars": 7585,
"preview": "const _ = require('lodash');\nconst commander = require('commander');\n\nconst EmojiAdminList = require('./lib/emoji-admin-"
},
{
"path": "emojme-upload.js",
"chars": 8209,
"preview": "const _ = require('lodash');\nconst fs = require('graceful-fs');\nconst commander = require('commander');\n\nconst EmojiAdmi"
},
{
"path": "emojme-user-stats.js",
"chars": 6314,
"preview": "const _ = require('lodash');\nconst commander = require('commander');\n\nconst EmojiAdminList = require('./lib/emoji-admin-"
},
{
"path": "emojme.js",
"chars": 978,
"preview": "#!/usr/bin/env node\n/* eslint-disable global-require */\n\nconst program = require('commander');\n\nif (require.main === mod"
},
{
"path": "lib/client-boot.js",
"chars": 1520,
"preview": "const _ = require('lodash');\n\nconst SlackClient = require('./slack-client');\nconst FileUtils = require('./util/file-util"
},
{
"path": "lib/emoji-add.js",
"chars": 2776,
"preview": "const fs = require('graceful-fs');\nconst _ = require('lodash');\n\nconst logger = require('./logger');\nconst SlackClient ="
},
{
"path": "lib/emoji-admin-list.js",
"chars": 7854,
"preview": "const _ = require('lodash');\n\nconst Throttle = require('superagent-throttle');\nconst logger = require('./logger');\nconst"
},
{
"path": "lib/logger.js",
"chars": 767,
"preview": "const winston = require('winston');\n\nconst logger = winston.createLogger({\n levels: winston.config.syslog.levels,\n tra"
},
{
"path": "lib/slack-client.js",
"chars": 4698,
"preview": "const _ = require('lodash');\nconst superagent = require('superagent');\nconst Throttle = require('superagent-throttle');\n"
},
{
"path": "lib/util/cli.js",
"chars": 2574,
"preview": "const _ = require('lodash');\n\nconst logger = require('../logger');\n\nfunction list(val, memo) {\n memo = memo || [];\n me"
},
{
"path": "lib/util/file-utils.js",
"chars": 2503,
"preview": "const yaml = require('js-yaml');\nconst superagent = require('superagent');\nconst fs = require('graceful-fs');\n\nconst log"
},
{
"path": "lib/util/helpers.js",
"chars": 4672,
"preview": "const _ = require('lodash');\n\nfunction validAuthTuples(subdomains, tokens, cookies) {\n return subdomains.length === tok"
},
{
"path": "package.json",
"chars": 1655,
"preview": "{\n \"name\": \"emojme\",\n \"description\": \"The Emojartist's toolbox for spreading their work across the slackosphere\",\n \"v"
},
{
"path": "scripts/usage.sh",
"chars": 630,
"preview": "#!/bin/bash\n\n# Don't lose all our progress if we can avoid it\nmv USAGE.md USAGE.md.old\n\n# Get top level commands into US"
},
{
"path": "spec/e2e/emojme-download.js",
"chars": 1636,
"preview": "const chai = require('chai');\nchai.use(require('chai-shallow-deep-equal'));\n\nconst assert = chai.assert;\n\nconst commande"
},
{
"path": "spec/fixtures/clientBoot.json",
"chars": 22613,
"preview": "{\n \"ok\": true,\n \"self\": {\n \"id\": \"UUSERID\",\n \"name\": \"username\",\n \"prefs\": {\n \"user_colors\": \"\",\n \""
},
{
"path": "spec/fixtures/emojiList.json",
"chars": 465,
"preview": "[\n {\n \"name\": \"emoji-1\",\n \"url\": \"./spec/fixtures/Example.jpg\",\n \"user_display_name\": \"test-user-0\"\n },\n {\n "
},
{
"path": "spec/fixtures/emojiList.yaml",
"chars": 384,
"preview": "---\ntitle: 'some title it doesnt matter'\nanIgnoredKey: 'all keys outside \"emojis\" are ignored'\nemojis:\n - name: 'emoji-"
},
{
"path": "spec/integration/emojme-add-spec.js",
"chars": 14406,
"preview": "const chai = require('chai');\nchai.use(require('chai-shallow-deep-equal'));\n\nconst assert = chai.assert;\nconst sinon = r"
},
{
"path": "spec/integration/emojme-download-spec.js",
"chars": 5700,
"preview": "const chai = require('chai');\nchai.use(require('chai-shallow-deep-equal'));\n\nconst assert = chai.assert;\nconst sinon = r"
},
{
"path": "spec/integration/emojme-favorites-spec.js",
"chars": 2784,
"preview": "const chai = require('chai');\nchai.use(require('chai-shallow-deep-equal'));\n\nconst assert = chai.assert;\nconst sinon = r"
},
{
"path": "spec/integration/emojme-sync-spec.js",
"chars": 8505,
"preview": "const chai = require('chai');\nchai.use(require('chai-shallow-deep-equal'));\n\nconst assert = chai.assert;\nconst sinon = r"
},
{
"path": "spec/integration/emojme-upload-spec.js",
"chars": 6963,
"preview": "const chai = require('chai');\n\nconst assert = chai.assert;\n\nconst sinon = require('sinon');\nconst fs = require('graceful"
},
{
"path": "spec/integration/emojme-user-stats-spec.js",
"chars": 6111,
"preview": "const chai = require('chai');\nchai.use(require('chai-shallow-deep-equal'));\n\nconst assert = chai.assert;\nconst sinon = r"
},
{
"path": "spec/spec-helper.js",
"chars": 1057,
"preview": "const fs = require('fs');\n\nmodule.exports = {\n authTuple: ['subdomain1', 'token1'],\n authTuples(n) {\n return Array("
},
{
"path": "spec/unit/lib/emoji-add-spec.js",
"chars": 4576,
"preview": "const assert = require('chai').assert;\nconst sinon = require('sinon');\nconst fs = require('graceful-fs');\n\nconst EmojiAd"
},
{
"path": "spec/unit/lib/emoji-admin-list-spec.js",
"chars": 10550,
"preview": "const assert = require('chai').assert;\nconst sinon = require('sinon');\nconst _ = require('lodash');\nconst fs = require('"
},
{
"path": "spec/unit/lib/file-utils-spec.js",
"chars": 2030,
"preview": "const assert = require('chai').assert;\nconst fs = require('graceful-fs');\n\nconst FileUtils = require('../../../lib/util/"
},
{
"path": "spec/unit/lib/slack-client-spec.js",
"chars": 1306,
"preview": "const assert = require('chai').assert;\nconst SlackClient = require('../../../lib/slack-client');\n\ndescribe('SlackClient'"
},
{
"path": "spec/unit/lib/util/cli-spec.js",
"chars": 2687,
"preview": "const chai = require('chai');\n\nconst assert = chai.assert;\nconst commander = require('commander');\nconst Cli = require('"
},
{
"path": "spec/unit/lib/util/helpers-spec.js",
"chars": 8040,
"preview": "const chai = require('chai');\n\nconst assert = chai.assert;\nconst commander = require('commander');\nconst Helpers = requi"
}
]
About this extraction
This page contains the full source code of the jackellenberger/emojme GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (467.1 KB), approximately 129.9k tokens, and a symbol index with 84 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.