diff --git a/.dockerignore b/.dockerignore index 633824c1..a5cf508e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,220 +1,15 @@ -static/tmp/ - -# Created by .ignore support plugin (hsz.mobi) -### Node template -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Typescript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -#*.log -.static_storage/ -.media/ -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -#.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/dictionaries - -# Sensitive or high-churn files: -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.xml -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml - -# Gradle: -.idea/**/gradle.xml -.idea/**/libraries - -# CMake -cmake-build-debug/ - -# Mongo Explorer plugin: -.idea/**/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - +* + +!bin/ +!config/ +!db/ +!scripts/ +!src/ +!static/ +!vendor/ + +!alembic.ini +!requirements.in +!requirements.txt +!setup.cfg +!setup.py \ No newline at end of file diff --git a/.env.example b/.env.example index 629f7f8f..29970837 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,21 @@ -SLACK_API_TOKEN=xoxb-BOGUS +KIZUNA_ENV=development + +# used by the web interface for authentication FERNET_KEY=HYgZOdpD7D3mpX0aXB4hfzQNwdFcllMZqSmOANSmABg= -SECRET_KEY=cftvCmKSjcgrMU7p920kue+ZxWzoQ3Os +SECRET_KEY=cftvCmKSjcgrMU7p920kue+ZxWzoQ3Os +KIZUNA_API_KEY=BOGUSBOGUSBOGUS + +# slack related +## https://api.slack.com/web#authentication +SLACK_API_TOKEN=xoxb-BOGUS +## https://api.slack.com/docs/verifying-requests-from-slack +SLACK_SIGNING_SECRET=BOGUS +SLACK_VERIFICATION_TOKEN=BOGUS + +# uploading images to s3 +AWS_ACCESS_KEY_ID=BOGUS +AWS_SECRET_ACCESS_KEY=BOGUS + +# some debugging settings +PYTHONUNBUFFERED=TRUE +FLASK_DEBUG=1 diff --git a/.travis.yml b/.travis.yml index 2a70f3ab..9923a280 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,11 +24,11 @@ before_install: --project kizuna-188702 - make dev_info install: -- make pull +- make registry_pull - make build script: -- make pep8 -- make test +#- make pep8 +#- make test deploy: provider: script skip_cleanup: true diff --git a/Dockerfile.api b/Dockerfile.api deleted file mode 100644 index 33ca5823..00000000 --- a/Dockerfile.api +++ /dev/null @@ -1,5 +0,0 @@ -FROM austinpray/kizuna/base - -CMD ["gunicorn", "--config", "python:config.gunicorn_api", "api:app"] - -COPY . ${workdir} diff --git a/Dockerfile.base b/Dockerfile.base deleted file mode 100644 index 3549e8a3..00000000 --- a/Dockerfile.base +++ /dev/null @@ -1,20 +0,0 @@ -FROM python:3.6-stretch - -ENV workdir /kizuna - -WORKDIR ${workdir} -ENV PYTHONPATH="${PYTHONPATH}:${workdir}" - -RUN apt-get update \ - && apt-get install -y vim graphviz - -# maint deps -RUN pip install pip-tools - -# heavy deps -RUN pip install spacy -RUN python -m spacy download en - -# regular deps -COPY requirements.in requirements.txt ${workdir}/ -RUN pip install -r requirements.txt diff --git a/Dockerfile.web b/Dockerfile.web deleted file mode 100644 index 350ccbbb..00000000 --- a/Dockerfile.web +++ /dev/null @@ -1,5 +0,0 @@ -FROM austinpray/kizuna/base - -CMD ["gunicorn", "--config", "python:config.gunicorn_web", "web:app"] - -COPY . ${workdir} diff --git a/Dockerfile.worker b/Dockerfile.worker deleted file mode 100644 index d4a076fe..00000000 --- a/Dockerfile.worker +++ /dev/null @@ -1,5 +0,0 @@ -FROM austinpray/kizuna/base - -CMD ["dramatiq-gevent", "worker", "-p", "2", "-t", "8"] - -COPY . ${workdir} diff --git a/Makefile b/Makefile index 097c18fb..13c91639 100644 --- a/Makefile +++ b/Makefile @@ -1,158 +1,155 @@ -.PHONY: kub_deploy web api build pull perm dev_info base migrate_dev -.PHONY: test ngrok repl -.PHONY: dev dev_worker -.PHONY: pep8 autopep8 -.PHONY: \ - registry_push \ - registry_push_base \ - registry_push_api \ - registry_push_web \ - registry_push_worker +#.PHONY: build api worker web +.PHONY: build api worker +.PHONY: clean ci_% -# simulate CI environment -TRAVIS_COMMIT ?= $(shell git rev-parse HEAD) +#build: api worker web +build: api worker -# tags used locally -base_tag = austinpray/kizuna/base +api: image_api -api_tag = austinpray/kizuna/api -web_tag = austinpray/kizuna/web -worker_tag = austinpray/kizuna/worker +worker: image_worker -# remote registry prefix for pushing to gcloud -registry_prefix = us.gcr.io/kizuna-188702 +#web: image_web -# base tags for different images in project -registry_base_tag = $(registry_prefix)/base +clean: perms docker_clean + git clean -fdx -e .idea -e .env -registry_api_tag = $(registry_prefix)/api -registry_web_tag = $(registry_prefix)/web -registry_worker_tag = $(registry_prefix)/worker +ci_%: + $(MAKE) registry_pull_$(*) + $(MAKE) image_$(*) + $(MAKE) registry_push_$(*) -# commit level tag -registry_base_tag_commit = $(registry_base_tag):$(TRAVIS_COMMIT) +TRAVIS_COMMIT ?= $(shell git rev-parse HEAD)-WIP -registry_api_tag_commit = $(registry_api_tag):$(TRAVIS_COMMIT) -registry_web_tag_commit = $(registry_web_tag):$(TRAVIS_COMMIT) -registry_worker_tag_commit = $(registry_worker_tag):$(TRAVIS_COMMIT) +local_prefix ?= austinpray/kizuna +registry_prefix ?= us.gcr.io/kizuna-188702 -# latest tags -registry_base_tag_latest = $(registry_base_tag):latest +DRUN = docker run -it --rm -v $(shell pwd):/kizuna -w /kizuna +NODE = $(DRUN) --name kizuna-node-$$(uuidgen) node:10 +PYTHON = $(DRUN) --name kizuna-py-$$(uuidgen) python:3.6 +KIZ = $(DRUN) --name kizuna-$$(uuidgen) $(local_prefix)/base -registry_api_tag_latest = $(registry_api_tag):latest -registry_web_tag_latest = $(registry_web_tag):latest -registry_worker_tag_latest = $(registry_worker_tag):latest +.PHONY: test dev_info -# build everything -build: dev_info api worker web +test: + $(KIZ) pytest -# check the project for pep8 compliance -pep8: - docker run --rm -v $(shell pwd):/code omercnet/pycodestyle --show-source /code +# make a dev-info file so kizuna knows what commit she's on +dev_info: + bin/generate-dev-info.py --revision $(TRAVIS_COMMIT) > .dev-info.json -autopep8: - find . -name '*.py' | xargs autopep8 --in-place --aggressive --aggressive -test: - docker run --rm -v $(shell pwd):/kizuna $(base_tag) pytest +# push all the images to gcloud registry +.PHONY: registry_pull_% registry_pull image_% registry_push_% kube_deploy_% docker_clean + +# pull images from registry prolly for caching reasons +registry_pull_%: + docker pull $(registry_prefix)/$(*) + docker tag $(registry_prefix)/$(*) $(local_prefix)/$(*) + +#registry_pull: registry_pull_base registry_pull_api registry_pull_web registry_pull_worker +registry_pull: registry_pull_base registry_pull_api registry_pull_worker + +DOCKER_BUILD = docker build +ifeq ($(KIZ_CACHE),true) +DOCKER_BUILD += --cache-from $(registry_prefix)/{{T}} +endif +DOCKER_BUILD += --file docker/{{T}}/Dockerfile +DOCKER_BUILD += -t $(local_prefix)/{{T}} +DOCKER_BUILD += . + +#.PHONY: image_base image_% image_web image_tag_% image_tag +.PHONY: image_base image_% image_tag_% image_tag + +image_base: + $(subst {{T}},base,$(DOCKER_BUILD)) + +#image_web: image_base static/dist +# $(subst {{T}},web,$(DOCKER_BUILD)) + +image_%: image_base + $(subst {{T}},$(*),$(DOCKER_BUILD)) + +image_tag_%: + docker tag $(local_prefix)/$(*) $(registry_prefix)/$(*):$(TRAVIS_COMMIT) + docker tag $(local_prefix)/$(*) $(registry_prefix)/$(*):latest + +#image_tag: image_tag_api image_tag_web image_tag_worker +image_tag: image_tag_api image_tag_worker + +.PHONY: registry_push_% registry_push + +registry_push_%: image_tag_% + docker push $(registry_prefix)/$(*):$(TRAVIS_COMMIT) + docker push $(registry_prefix)/$(*):latest + +#registry_push: registry_push_api registry_push_web registry_push_worker +registry_push: registry_push_api registry_push_worker + +.PHONY: kube_deploy_% kube_deploy + +kube_deploy_%: registry_push_% + kubectl set image deployment/$(*) $(*)=$(registry_prefix)/$(*) + +#kube_deploy: kube_deploy_api kube_deploy_web kube_deploy_worker +kube_deploy: registry_push + kubectl set image deployment/api api=$(registry_prefix)/api:$(TRAVIS_COMMIT) + kubectl set image deployment/worker worker=$(registry_prefix)/worker:$(TRAVIS_COMMIT) + +.PHONY: docker_clean + +docker_clean: + docker rmi -f $(local_prefix)/api + docker rmi -f $(local_prefix)/base + #docker rmi -f $(local_prefix)/web + docker rmi -f $(local_prefix)/worker + + docker rmi -f $(registry_prefix)/api + docker rmi -f $(registry_prefix)/base + #docker rmi -f $(registry_prefix)/web + docker rmi -f $(registry_prefix)/worker + +# assets + +.PHONY: assets-watch assets-dev + +node_modules: package-lock.json + ${NODE} npm ci + +#static/dist: node_modules webpack.config.js src/kizuna/web/js +# ${NODE} npm run build -- -p + +assets-watch: node_modules + ${NODE} npm run build -- -d --watch + +assets-dev: + ${NODE} bash + +# dev commands +## watch for file changes and restart accordingly +.PHONY: repl ngrok pep8 autopep8 perms dev dev_worker migrate_dev repl: - docker run --rm -it -v $(shell pwd):/kizuna $(base_tag) python + $(KIZ) python ngrok: ngrok http -subdomain=kizuna 8001 -# make a dev-info file so kizuna knows what commit she's on -dev_info: - bin/generate-dev-info.py --revision $(TRAVIS_COMMIT) > .dev-info.json +# check the project for pep8 compliance +pep8: + docker run --rm -v $(shell pwd):/code omercnet/pycodestyle --show-source /code/src -# push all the images to gcloud registry -registry_push_base: - docker tag $(base_tag) $(registry_base_tag_commit) - docker tag $(base_tag) $(registry_base_tag_latest) - gcloud docker -- push $(registry_base_tag_commit) - gcloud docker -- push $(registry_base_tag_latest) - -registry_push_api: - docker tag $(api_tag) $(registry_api_tag_commit) - docker tag $(api_tag) $(registry_api_tag_latest) - gcloud docker -- push $(registry_api_tag_commit) - gcloud docker -- push $(registry_api_tag_latest) - -registry_push_web: - docker tag $(web_tag) $(registry_web_tag_commit) - docker tag $(web_tag) $(registry_web_tag_latest) - gcloud docker -- push $(registry_web_tag_commit) - gcloud docker -- push $(registry_web_tag_latest) - -registry_push_worker: - docker tag $(worker_tag) $(registry_worker_tag_commit) - docker tag $(worker_tag) $(registry_worker_tag_latest) - gcloud docker -- push $(registry_worker_tag_commit) - gcloud docker -- push $(registry_worker_tag_latest) - - -registry_push: \ - registry_push_base \ - registry_push_api \ - registry_push_web \ - registry_push_worker - -# release the current commit to kube -kube_deploy: registry_push - kubectl set image deployment/api api=$(registry_api_tag_commit) - kubectl set image deployment/web web=$(registry_web_tag_commit) - kubectl set image deployment/worker worker=$(registry_worker_tag_commit) +autopep8: + find ./src -name '*.py' | xargs autopep8 --in-place --aggressive --aggressive -# pull images from registry prolly for caching reasons -pull: - gcloud docker -- pull $(registry_base_tag) - gcloud docker -- pull $(registry_api_tag) - gcloud docker -- pull $(registry_web_tag) - gcloud docker -- pull $(registry_worker_tag) - docker tag $(registry_base_tag) $(base_tag) - docker tag $(registry_api_tag) $(api_tag) - docker tag $(registry_web_tag) $(web_tag) - docker tag $(registry_worker_tag) $(worker_tag) - -# image building -base: - docker build \ - --file Dockerfile.base \ - --cache-from $(base_tag) \ - -t $(base_tag) \ - . - -api: base - docker build \ - --file Dockerfile.api \ - --cache-from $(api_tag) \ - -t $(api_tag) \ - . - -web: api - docker run -it --rm --name build-web-assets -v $(shell pwd):/kizuna -w /kizuna node:10 npm run build - docker build \ - --file Dockerfile.web \ - --cache-from $(web_tag) \ - -t $(web_tag) \ - . - -worker: base - docker build \ - --file Dockerfile.worker \ - --cache-from $(worker_tag) \ - -t $(worker_tag) \ - . # docker permissions helper -perm: +perms: sudo chown -R $(shell whoami):$(shell whoami) . -# dev commands -## watch for file changes and restart accordingly dev: - nodemon -e 'py' --exec docker-compose restart api web worker + #nodemon -e 'py' --exec docker-compose restart api web worker + nodemon -e 'py' --exec docker-compose restart api worker dev_worker: nodemon -e 'py' --exec docker-compose restart worker @@ -160,3 +157,12 @@ dev_worker: ## run dev migrations migrate_dev: docker-compose run api alembic upgrade head + +## slacktools +.PHONY: pull-slacktools push-slacktools + +pull-slacktools: + git subtree pull --prefix vendor/python-slacktools python-slacktools develop --squash + +push-slacktools: + git subtree push --prefix vendor/python-slacktools python-slacktools develop diff --git a/README.md b/README.md index d20311a0..b2abe759 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,10 @@ I am a slack bot with functionality that ranges from "useless" to "pretty neat". ### Mentions Graph -I can draw a directed graph of the mentions between all the people in your slack. The vertices are people and the edges are the the mentions between two people. The weight a particular edge represents how many times the head vertex has mentioned the tail vertex. +I can draw a directed graph of the mentions between all the people in your +slack. The vertices are people and the edges are the the mentions between two +people. The weight a particular edge represents how many times the head vertex +has mentioned the tail vertex. ![mentions demo](static/images/kizuna_mentions_demo.gif) @@ -35,15 +38,44 @@ optional arguments: ## Development -I'm pretty easy to get running. Development and production deployment is all done with Docker. Make sure you have Docker installed and running on your system. +I'm kinda easy to get running. Development and production deployment is all +done with Docker. Make sure you have Docker installed and running on your +system. -1. You should probably create your own bot testbed slack if you don't already have one. [https://slack.com/create]() -2. You need create a `.env` file that contains your `SLACK_API_TOKEN`, `SECRET_KEY`, and `FERNET_KEY`. -Easiest way to do this is to `cp .env.example .env` and then replace the bogus slack token with your real token you get from your bot's slack app config. -`FERNET_KEY` can be generated with `bin/generate-fernet-key.py` but the one in `.env.example` is probably fine for dev. -`SECRET_KEY` can be generated with `bin/generate-secret-key.py` but the one in `.env.example` is probably fine for dev. -3. Run `make build` to build all the containers -4. `docker-compose run --rm bot alembic upgrade head` will create the database for you -5. `docker-compose up` will start me up and connect me to slack! +The most difficult part of this whole thing is literally just filling in the +environment variables. Please PR this document or the `.env.example` file to +make this easier for people who come after you. + +### Run me + +1. You should probably create your own bot testbed slack if you don't already + have one. [https://slack.com/create]() +1. [Create a slack app](https://api.slack.com/apps) and deploy it to your testbed slack +2. You need create a `.env` file with all the necessary values. Easiest way to + do this is to `cp .env.example .env` and then replace the bogus values with + real values. You can ignore the AWS creds if you are not working on the + image upload feature. `FERNET_KEY` can be generated with + `bin/generate-fernet-key.py` but the one in `.env.example` is probably fine + for dev. `SECRET_KEY` can be generated with `bin/generate-secret-key.py` but + the one in `.env.example` is probably fine for dev. +3. Run `make` to build all the containers +4. `docker-compose run --rm worker alembic upgrade head` will create the + database for you +5. `docker-compose up` will start me up! My web interface runs at + [http://localhost:8000]() and my API runs at [http://localhost:8001]() + + +We aren't out of the woods yet. Now we have to let slack make requests to our API. + +1. Install [ngrok][] or do something that will allow you to expose your local + API endpoint to the internet. +2. Go to [Your Apps](https://api.slack.com/apps) and under your bot under + "Event Subscriptions" enter your api url. The path should be: + `/slack/events` +3. If you are using [ngrok][] you can inspect the requests from slack at + [http://127.0.0.1:4040]() + +NOW you should be up and running. + +[ngrok]: https://ngrok.com/ -That's it! diff --git a/api.py b/api.py deleted file mode 100644 index 63730aa3..00000000 --- a/api.py +++ /dev/null @@ -1,192 +0,0 @@ -import falcon -import json -import config -import logging -import arrow -from dramatiq.brokers.rabbitmq import RabbitmqBroker -from dramatiq.message import Message -from sqlalchemy import create_engine, asc -from sqlalchemy.orm import sessionmaker -from kizuna.models.FaxMessage import FaxMessage -from kizuna.utils import db_session_scope - -db_engine = create_engine(config.DATABASE_URL) -make_session = sessionmaker(bind=db_engine) - -rabbitmq_broker = RabbitmqBroker(url=config.RABBITMQ_URL) - - -class HealthCheckResource(object): - def on_get(self, req, resp): - resp.body = '{"ok": true}' - - -class EventsResource(object): - - def __init__(self): - self.logger = logging.getLogger('kizuna_api.' + __name__) - - def on_post(self, req, resp): - if not req.content_length: - resp.status = falcon.HTTP_400 - resp.body = 'go away' - return - - doc = json.load(req.stream) - print(doc) - - callback_type = doc['type'] - - if doc['token'] != config.SLACK_VERIFICATION_TOKEN: - resp.status = falcon.HTTP_401 - resp.body = 'go away' - return - - if callback_type == 'url_verification': - resp.body = doc['challenge'] - return - - if callback_type == 'event_callback': - event = doc['event'] - event_type = event['type'] - if event_type == 'message': - self.logger.debug(event) - rabbitmq_broker.enqueue(Message(queue_name='default', - actor_name='worker', - args=(event,), - options={}, - kwargs={})) - - resp.body = 'thanks!' - - -class SlashCommandsResource(object): - - def __init__(self): - self.logger = logging.getLogger('kizuna_api.' + __name__) - - def on_post(self, req, resp): - if not req.content_length: - resp.status = falcon.HTTP_400 - resp.body = 'go away' - return - - verification_token = req.get_param('token') - if verification_token not in config.SLACK_WEBHOOK_TOKENS: - resp.status = falcon.HTTP_401 - resp.body = 'this slack not allowed to send slash commands to kizuna :/' - return - - command = req.get_param('command') - if command not in ['/faxaustin']: - resp.status = falcon.HTTP_400 - resp.body = 'unknown command' - return - - text = req.get_param('text') - if not text: - resp.status = falcon.HTTP_200 - resp.body = 'blank fax, you gotta type some text :^(' - return - if len(text) > 280: - resp.status = falcon.HTTP_200 - resp.body = 'faxes have to be 280 chars or less' - return - - team_id = req.get_param('team_id', required=True) - trigger_id = req.get_param('trigger_id', required=True) - user_id = req.get_param('user_id', required=True) - user_name = req.get_param('user_name', required=True) - - with db_session_scope(make_session) as session: - message = FaxMessage(team_id=team_id, - text=text, - trigger_id=trigger_id, - user_id=user_id, - user_name=user_name) - - session.add(message) - - resp.body = 'sent fax to austin :^)' - - -def valid_auth_header(auth_header) -> bool: - if not auth_header: - return False - - auth_token = auth_header.split(' ')[1] - - if not auth_token or auth_token != config.API_KEY: - return False - - return True - - -class FaxMessagesResource(object): - def __init__(self): - self.logger = logging.getLogger('kizuna_api.' + __name__) - - # todo: put me in a middleware - def on_get(self, req, resp): - if not valid_auth_header(req.auth): - resp.status = falcon.HTTP_401 - return - - with db_session_scope(make_session) as session: - messages = session\ - .query(FaxMessage)\ - .filter(FaxMessage.printed_at.is_(None))\ - .order_by(asc(FaxMessage.created_at))\ - .all() - - resp.body = json.dumps({'messages': [m.to_json_serializable() for m in messages]}) - - -class FaxMessageResource(object): - def on_put(self, req, resp, message_id): - # todo: put me in a middleware - if not valid_auth_header(req.auth): - resp.status = falcon.HTTP_401 - return - - message_id = int(message_id) - - data = json.load(req.stream) - - with db_session_scope(make_session) as session: - message = session.query(FaxMessage).filter(FaxMessage.id == message_id).first() - - if not message: - resp.status = falcon.HTTP_404 - return - - if 'printed' not in data: - resp.status = falcon.HTTP_400 - return - - printed = data['printed'] - message.printed_at = arrow.get().datetime if printed else None - - session.add(message) - - resp.body = '{"ayy": "lmao"}' - - -app = falcon.API() - -app.req_options.auto_parse_form_urlencoded = True - -events = EventsResource() -app.add_route('/slack/events', events) - -slash_commands = SlashCommandsResource() -app.add_route('/slack/slash_commands', slash_commands) - -fax_messages = FaxMessagesResource() -app.add_route('/fax_messages', fax_messages) - -fax_message = FaxMessageResource() -app.add_route('/fax_messages/{message_id}', fax_message) - -healthChecks = HealthCheckResource() -app.add_route('/', healthChecks) diff --git a/config/gunicorn_api.py b/config/gunicorn_api.py index d0d2c399..7fdff540 100644 --- a/config/gunicorn_api.py +++ b/config/gunicorn_api.py @@ -1,4 +1,4 @@ -from . import KIZUNA_ENV +from .kizuna import KIZUNA_ENV if KIZUNA_ENV == 'development': bind = '0.0.0.0:8001' diff --git a/config/gunicorn_web.py b/config/gunicorn_web.py index 37a04fcd..1d0c0cbc 100644 --- a/config/gunicorn_web.py +++ b/config/gunicorn_web.py @@ -1,4 +1,4 @@ -from . import KIZUNA_ENV +from .kizuna import KIZUNA_ENV if KIZUNA_ENV == 'development': bind = '0.0.0.0:8000' diff --git a/config/__init__.py b/config/kizuna.py similarity index 90% rename from config/__init__.py rename to config/kizuna.py index f3df1f98..762c5695 100644 --- a/config/__init__.py +++ b/config/kizuna.py @@ -1,4 +1,6 @@ import os +from os import path + from .constants import DAY_IN_SECONDS API_KEY = os.environ.get('KIZUNA_API_KEY', None) @@ -16,6 +18,8 @@ SENTRY_URL = os.environ.get('SENTRY_URL', None) SLACK_API_TOKEN = os.environ.get('SLACK_API_TOKEN', None) SLACK_VERIFICATION_TOKEN = os.environ.get('SLACK_VERIFICATION_TOKEN', None) +SLACK_SIGNING_SECRET = os.environ.get('SLACK_SIGNING_SECRET', None) +STATIC_DIR = os.environ.get('STATIC_DIR', path.abspath(path.join(path.dirname(__file__), '../static'))) RABBITMQ_URL = os.environ.get('RABBITMQ_URL', 'amqp://guest:guest@rabbitmq:5672/%2F') diff --git a/db/env.py b/db/env.py index a7a4e6b9..34bb9f9e 100644 --- a/db/env.py +++ b/db/env.py @@ -2,7 +2,12 @@ from alembic import context from sqlalchemy import create_engine from logging.config import fileConfig -import config as kizuna_config +import os +from types import SimpleNamespace + +kizuna_config = SimpleNamespace( + DATABASE_URL=os.environ.get('DATABASE_URL') +) # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/db/versions/0fbc66fe58b8_add_api_key_to_users.py b/db/versions/0fbc66fe58b8_add_api_key_to_users.py index 8959fee0..7a030e20 100644 --- a/db/versions/0fbc66fe58b8_add_api_key_to_users.py +++ b/db/versions/0fbc66fe58b8_add_api_key_to_users.py @@ -8,9 +8,13 @@ from alembic import op import sqlalchemy as sa from sqlalchemy.sql import select +from secrets import token_hex -from kizuna.models.User import user_generate_api_key +def user_generate_api_key(): + token = token_hex(32) + return 'kiz-{}'.format(token) + # revision identifiers, used by Alembic. revision = '0fbc66fe58b8' diff --git a/docker-compose.yml b/docker-compose.yml index c77db2d0..b3c5e199 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,22 +10,22 @@ services: POSTGRES_DB: kizuna POSTGRES_PASSWORD: kizuna POSTGRES_USER: kizuna + rabbitmq: image: rabbitmq + worker: - build: - context: . - dockerfile: Dockerfile.worker + image: austinpray/kizuna/worker env_file: .env + command: ["dramatiq-gevent", "kizuna.worker", "-p", "2", "-t", "8", "--watch", "./src"] volumes: - .:/kizuna depends_on: - db - rabbitmq + api: - build: - context: . - dockerfile: Dockerfile.api + image: austinpray/kizuna/api env_file: .env volumes: - .:/kizuna @@ -34,20 +34,13 @@ services: - rabbitmq ports: - 8001:8001 - web: - build: - context: . - dockerfile: Dockerfile.web - env_file: .env - volumes: - - .:/kizuna - depends_on: - - db - ports: - - 8000:8000 - js: - image: "node:9" - working_dir: /kizuna - volumes: - - ./:/kizuna - command: npm run watch + + #web: + # image: austinpray/kizuna/web + # env_file: .env + # volumes: + # - .:/kizuna + # depends_on: + # - db + # ports: + # - 8000:8000 diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile new file mode 100644 index 00000000..5b18c814 --- /dev/null +++ b/docker/api/Dockerfile @@ -0,0 +1,6 @@ +FROM austinpray/kizuna/base + +CMD ["gunicorn", "--config", "python:config.gunicorn_api", "kizuna.api:app"] + +COPY . . +RUN pip install -e .[api] diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile new file mode 100644 index 00000000..65295a7b --- /dev/null +++ b/docker/base/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7 + +ENV KIZ_DOCKER_VERSION 1 +WORKDIR /kizuna + +RUN apt-get update \ + && apt-get install -y --no-install-recommends vim \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --upgrade pip + +COPY vendor ./vendor +RUN pip install -e ./vendor/python-slacktools diff --git a/docker/bundle/Dockerfile b/docker/bundle/Dockerfile new file mode 100644 index 00000000..9830d13d --- /dev/null +++ b/docker/bundle/Dockerfile @@ -0,0 +1,4 @@ +FROM austinpray/kizuna/base + +COPY . . +RUN pip install -e '.[api,web,worker]' diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile new file mode 100644 index 00000000..9ad7a936 --- /dev/null +++ b/docker/web/Dockerfile @@ -0,0 +1,6 @@ +FROM austinpray/kizuna/base + +CMD ["gunicorn", "--config", "python:config.gunicorn_web", "kizuna.web:app"] + +COPY . . +RUN pip install -e .[web] diff --git a/docker/worker/Dockerfile b/docker/worker/Dockerfile new file mode 100644 index 00000000..6727a2aa --- /dev/null +++ b/docker/worker/Dockerfile @@ -0,0 +1,11 @@ +FROM austinpray/kizuna/base + +CMD ["dramatiq-gevent", "kizuna.worker", "-p", "2", "-t", "8"] + +RUN apt-get update \ + && apt-get install -y --no-install-recommends graphviz \ + && rm -rf /var/lib/apt/lists/* + +# heavy deps +COPY . . +RUN pip install -e '.[worker]' diff --git a/kizuna/AtGraph.py b/kizuna/AtGraph.py deleted file mode 100644 index 57483dcf..00000000 --- a/kizuna/AtGraph.py +++ /dev/null @@ -1,5 +0,0 @@ -import re - - -def extract_ats(text): - return set(re.findall(r"<@(.*?)>", text, re.DOTALL)) diff --git a/kizuna/Kizuna.py b/kizuna/Kizuna.py deleted file mode 100644 index f301dab7..00000000 --- a/kizuna/Kizuna.py +++ /dev/null @@ -1,161 +0,0 @@ -from kizuna.strings import \ - JAP_DOT, LQUO, RQUO, YOSHI, HAI_DOMO, KIZUNA, VERSION_TRANSITION_TEMPLATE, VERSION_UPDATE_TEMPLATE, VERSION_UP - -from os import path -import json -from pprint import pprint - -from kizuna.models.Meta import Meta - - -class Kizuna: - def __init__(self, bot_id, slack_client, main_channel, home_channel) -> None: - self.bot_id = bot_id - self.sc = slack_client - self.respond_tokens = ( - 'kiz', - 'Kiz', - 'kizuna', - 'Kizuna', - '@kizuna', - '@Kizuna', - '<@{}>'.format(bot_id), - KIZUNA - ) - self.main_channel = main_channel - self.home_channel = home_channel - self.registered_commands = [] - - def is_at(self, text): - return text.startswith(self.respond_tokens) - - def register_command(self, command): - self.registered_commands.append(command) - - @staticmethod - def read_dev_info(dev_info_path): - if not path.isfile(dev_info_path): - print('startup: no dev file at "{}"'.format(dev_info_path)) - return {} - - dev_info = json.load(open(dev_info_path)) - print('startup: dev info loaded from "{}"'.format(dev_info_path)) - pprint(dev_info) - return dev_info - - def handle_startup(self, dev_info, db_session): - def send(text, - title=None, - footer=None, - footer_icon=None): - attachment = { - 'title': title, - 'color': 'good', - 'text': text, - 'footer': footer, - 'footer_icon': footer_icon - } - return self.sc.api_call("chat.postMessage", - channel=self.home_channel, - attachments=[attachment], - as_user=True) - - new_revision = dev_info.get('revision') - if not new_revision: - return - - current_revision = db_session.query(Meta).filter(Meta.key == 'current_revision').first() - previous_revision = db_session.query(Meta).filter(Meta.key == 'previous_revision').first() - - if not current_revision: - current_revision = Meta(key='current_revision', value=new_revision) - db_session.add(current_revision) - db_session.commit() - out = YOSHI + JAP_DOT - revision_link = self.slack_link(current_revision.value[:7], - self.get_revision_github_url(current_revision.value)) - out += VERSION_UPDATE_TEMPLATE.replace('{{VERSION}}', LQUO + revision_link + RQUO) - return send(out, title=VERSION_UP) - - if new_revision == current_revision.value: - return - - if not previous_revision: - previous_revision = Meta(key='previous_revision', value=current_revision.value) - db_session.add(previous_revision) - - previous_revision.value = current_revision.value - current_revision.value = new_revision - db_session.commit() - - out = YOSHI + JAP_DOT - out += VERSION_TRANSITION_TEMPLATE - previous_revision_link = self.slack_link(previous_revision.value[:7], - self.get_revision_github_url(previous_revision.value)) - current_revision_link = self.slack_link(current_revision.value[:7], - self.get_revision_github_url(current_revision.value)) - out = out \ - .replace('{{FROM_VERSION}}', LQUO + previous_revision_link + RQUO) \ - .replace('{{TO_VERSION}}', LQUO + current_revision_link + RQUO) - compare_link = self.slack_link('Compare Changes on GitHub', - self.get_revision_range_github_url(previous_revision.value, - current_revision.value)) - return send(out, title=VERSION_UP, footer=compare_link) - - @staticmethod - def slack_link(text, url): - return '<{}|{}>'.format(url, text) - - @staticmethod - def get_revision_github_url(revision): - return 'https://github.com/austinpray/kizuna/commit/{}'.format(revision) - - @staticmethod - def get_revision_range_github_url(old_revision, new_revision): - return 'https://github.com/austinpray/kizuna/compare/{}...{}'.format(old_revision, new_revision) - - def handle_message(self, message): - if 'user' in message and message['user'] == self.bot_id: - return - - if 'text' not in message or not message['text']: - return - - message['text'] = message['text'].strip() - text = message['text'] - channel = message['channel'] - - registered_always_commands = filter(lambda c: c.always, self.registered_commands) - for command in registered_always_commands: - command.maybe_respond(self.sc, message) - - if self.is_at(text): - parts = text.split(' ', 1) - if len(parts) < 2: - return - - at, command_text = parts - message['text'] = command_text - - if command_text.lower() in ['-h', '--help', 'help']: - help_header = HAI_DOMO - help_text_list = map(lambda c: c.help(self.respond_tokens[0]), self.registered_commands) - help_text_list = list(text for text in help_text_list if text) - - separator = '\n' + ':sparkles:' * 5 + '\n' - - help_commands = help_header + '\n\n' + separator.join(help_text_list) - return self.sc.api_call("chat.postMessage", - channel=channel, - text=help_commands, - as_user=True) - - registered_at_commands = filter(lambda c: c.is_at and not c.always, self.registered_commands) - for command in registered_at_commands: - command.maybe_respond(self.sc, message) - return - - registered_general_commands = filter(lambda c: not c.is_at and not c.always, self.registered_commands) - - for command in registered_general_commands: - command.maybe_respond(self.sc, message) diff --git a/kizuna/SlackArgumentParser.py b/kizuna/SlackArgumentParser.py deleted file mode 100644 index 5dcab32c..00000000 --- a/kizuna/SlackArgumentParser.py +++ /dev/null @@ -1,10 +0,0 @@ -from argparse import ArgumentParser - - -class SlackArgumentParserException(Exception): - pass - - -class SlackArgumentParser(ArgumentParser): - def error(self, message): - raise SlackArgumentParserException(message) diff --git a/kizuna/commands/AtGraphCommand.py b/kizuna/commands/AtGraphCommand.py deleted file mode 100644 index 3f823d6c..00000000 --- a/kizuna/commands/AtGraphCommand.py +++ /dev/null @@ -1,196 +0,0 @@ -from kizuna.models.AtGraphEdge import AtGraphEdge -from kizuna.models.User import User -from kizuna.commands.Command import Command -from palettable import tableau - -from subprocess import CalledProcessError - -from graphviz import Digraph - -from kizuna.strings import WAIT_A_SEC, JAP_DOT - -from threading import Thread -from time import sleep - -from kizuna.SlackArgumentParser import SlackArgumentParser, SlackArgumentParserException - - -class AtGraphCommand(Command): - def __init__(self, db_session) -> None: - - self.available_layouts = ['dot', 'neato', 'fdp', 'twopi', 'circo'] - - parser = SlackArgumentParser(prog='kizuna mentions', description='Generate a mentions graph', add_help=False) - self.add_help_command(parser) - - markdown_available_layouts = list(map(lambda s: '`{}`'.format(s), self.available_layouts)) - parser.add_argument('--layout', - '-l', - dest='layout', - default='dot', - help='Defaults to `dot`. Can be any of ' + ', '.join(markdown_available_layouts)) - - parser.add_argument('--raster', - '-r', - dest='raster', - action='store_true', - help='Set to render a png instead of a pdf') - - self.set_help_text(parser) - self.parser = parser - - pattern = "mentions(?: (.*))?$" - super().__init__('mention-graph', pattern, self.help_text, True, db_session_maker=db_session) - - @staticmethod - def linear_scale(old_max, old_min, new_max, new_min, value): - old_range = (old_max - old_min) - if old_range == 0: - return value - new_range = (new_max - new_min) - return (((value - old_min) * new_range) / old_range) + new_min - - def respond(self, slack_client, message, matches): - channel = message['channel'] - send = self.send_factory(slack_client, message['channel']) - - user_args = matches[0].split(' ') if matches[0] else [] - try: - args = self.parser.parse_args(user_args) - except SlackArgumentParserException as err: - return send(str(err)) - - user_layout = args.layout - output_format = 'png' if args.raster else 'pdf' - - def send_message(text): - return slack_client.api_call("chat.postMessage", - channel=channel, - text=text, - as_user=True) - - if user_layout not in self.available_layouts: - layout_error_message = ("Oops! --user_layout needs to be one of '{}'. " - "You gave me '{}'").format(', '.join(self.available_layouts), user_layout) - return send_message(layout_error_message) - - if args.help: - return send_message(self.help_text) - - with self.db_session_scope() as session: - edges = session.query(AtGraphEdge).order_by(AtGraphEdge.weight.asc()).all() - users = session.query(User).order_by(User.name.asc()).all() - - if not edges or len(edges) < 1: - send_message("Uhh...Could not find any edges in the db. Something is probably wrong.") - return - - loading_message = slack_client.api_call("chat.postMessage", - channel=channel, - text=WAIT_A_SEC + JAP_DOT, - as_user=True) - - loaded = False - - def continiously_update_loading_message(): - cycle_count = 0 - dot_count = 1 - while not loaded and cycle_count < 20: - sleep(1) - if loaded: - break - dot_count = dot_count + 1 if dot_count < 3 else 1 - new_text = WAIT_A_SEC + (dot_count * JAP_DOT) - slack_client.api_call("chat.update", - ts=loading_message['ts'], - channel=channel, - text=new_text, - as_user=True) - cycle_count += 1 - - thread = Thread(target=continiously_update_loading_message) - - if loading_message['ok']: - thread.start() - - graph = Digraph(comment='Mentions', format=output_format) - color_index = 0 - user_color_map = {} - colors = tableau.get_map('Tableau_20').hex_colors - - for user in users: - color = colors[color_index] - color_index = color_index + 1 if color_index < (len(colors) - 1) else 0 - user_color_map[user.name] = color - graph.node(user.name, color=color) - - max_weight = edges[len(edges) - 1].weight - min_weight = edges[0].weight - - max_penwidth = 5 - min_penwidth = 0.10 - - max_fontsize = 20 - min_fontsize = 7 - - def scale_penwidth_by(value): - return self.linear_scale(max_weight, min_weight, max_penwidth, min_penwidth, value) - - def scale_fontsize_by(value): - return self.linear_scale(max_weight, min_weight, max_fontsize, min_fontsize, value) - - for edge in edges: - graph.edge(edge.head_user.name, - edge.tail_user.name, - penwidth=str(scale_penwidth_by(edge.weight)), - label=str(edge.weight), - weight=str(edge.weight), - fontsize=str(scale_fontsize_by(edge.weight)), - fontcolor=str(user_color_map[edge.head_user.name]), - color=user_color_map[edge.head_user.name]) - - def dot(): - graph.engine = 'dot' - - def neato(): - graph.engine = 'neato' - - def fdp(): - graph.engine = 'fdp' - - def twopi(): - graph.engine = 'twopi' - - def circo(): - graph.engine = 'circo' - - def layout_graph(layout): - return { - "dot": dot, - "neato": neato, - "fdp": fdp, - "twopi": twopi, - "circo": circo - }.get(layout, dot) - - try: - layout_graph(user_layout)() - - slack_client.api_call('files.upload', - as_user=True, - channels=message['channel'], - filename=f'graph.{output_format}', - file=graph.pipe()) - except CalledProcessError as err: - send_message('Encountered a problem while rendering the graph :monkas:') - raise err - except TypeError as err: - send_message('Encountered a problem while trying to write the graph to the file system :monkas:') - raise err - finally: - loaded = True - thread.join() - slack_client.api_call("chat.delete", - ts=loading_message['ts'], - channel=channel, - as_user=True) diff --git a/kizuna/commands/AtGraphDataCollector.py b/kizuna/commands/AtGraphDataCollector.py deleted file mode 100644 index 5adcc577..00000000 --- a/kizuna/commands/AtGraphDataCollector.py +++ /dev/null @@ -1,33 +0,0 @@ -from kizuna.AtGraph import extract_ats -from kizuna.models.AtGraphEdge import AtGraphEdge -from kizuna.commands.Command import Command - -from kizuna.models.User import User - - -class AtGraphDataCollector(Command): - def __init__(self, db_session, slack_client) -> None: - super().__init__('at-graph-data-collector', '.*', '', False, db_session_maker=db_session) - self.sc = slack_client - - def maybe_respond(self, slack_client, message): - mentioned_user_ids = extract_ats(message['text']) - - if not mentioned_user_ids or len(mentioned_user_ids) < 1: - return - - with self.db_session_scope() as session: - # make sure all users exist in db before we do graph operations - try: - def id_to_user(mentioned_user_id): - return User.maybe_create_user_from_slack_id(mentioned_user_id, self.sc, session) - - from_user = id_to_user(message['user']) - mentioned_users = list(map(id_to_user, mentioned_user_ids)) - except ValueError as err: - print('Error: probably had trouble finding user by id') - print(err) - return - - for mentioned_user in mentioned_users: - AtGraphEdge.increment_edge(from_user, mentioned_user, session) diff --git a/kizuna/commands/ClapCommand.py b/kizuna/commands/ClapCommand.py deleted file mode 100644 index de27a8b1..00000000 --- a/kizuna/commands/ClapCommand.py +++ /dev/null @@ -1,54 +0,0 @@ -from kizuna.commands.Command import Command -from kizuna.strings import random_insult -from kizuna.SlackArgumentParser import SlackArgumentParser, SlackArgumentParserException - -from argparse import REMAINDER - - -class ClapCommand(Command): - def __init__(self) -> None: - parser = SlackArgumentParser(prog='kizuna clap', description='obnoxiously clap', add_help=False) - self.add_help_command(parser) - - parser.add_argument('-s', - '--separator', - dest='separator', - default=':clap:', - help='defaults to :clap:') - - parser.add_argument('-a', - '--at', - dest='at', - metavar='PERSON', - help='a user to send this message to') - - parser.add_argument('message', - metavar='MESSAGE', - nargs=REMAINDER, - help='the message to clappify') - - self.set_help_text(parser) - self.parser = parser - super().__init__('clap', 'clap (.*)', help_text=self.help_text, is_at=True) - - def respond(self, slack_client, message, matches): - send = self.send_factory(slack_client, message['channel']) - try: - args = self.parser.parse_args(matches[0].split(' ')) - except SlackArgumentParserException as err: - # lol commented out for max sass - # send(str(err)) - return send(random_insult()) - - if args.help: - return send(self.help_text) - - if not args.message: - return send(random_insult()) - - new_message = ' {} '.format(args.separator).join(args.message) - - if args.at: - new_message = '{} {}'.format(args.at, new_message) - - send(new_message) diff --git a/kizuna/commands/Command.py b/kizuna/commands/Command.py deleted file mode 100644 index 35987de1..00000000 --- a/kizuna/commands/Command.py +++ /dev/null @@ -1,94 +0,0 @@ -import re - -from io import StringIO - -from functools import partial - -from kizuna.models.User import User -from kizuna.slack import format_slack_mention - -from contextlib import contextmanager - - -class Command: - def __init__(self, name, pattern, help_text='', is_at=True, always=False, db_session_maker=None) -> None: - self.name = name - self.is_at = is_at - self.help_text = help_text - self.pattern = re.compile(pattern, re.IGNORECASE) if isinstance(pattern, str) else pattern - self.always = always - self.db_session_maker = db_session_maker - - def help(self, bot_name): - return self.help_text.replace("{bot}", bot_name) - - @contextmanager - def db_session_scope(self): - if not self.db_session_maker: - raise RuntimeError('no db_session_maker specified for this command') - - session = self.db_session_maker() - try: - yield session - session.commit() - except Exception: - session.rollback() - raise - finally: - session.close() - - @staticmethod - def add_help_command(parser): - parser.add_argument('-h', - '--help', - action='store_true', - dest='help', - help='show this help message and exit') - - def set_help_text(self, parser): - help_text_capture = StringIO() - parser.print_help(file=help_text_capture) - help_text = help_text_capture.getvalue() - help_text = help_text.replace('usage: ', '') - self.help_text = help_text - - @staticmethod - def send(slack_client, channel, text): - return slack_client.api_call("chat.postMessage", - channel=channel, - text=text, - as_user=True) - - @staticmethod - def reply(slack_client, message, text): - return slack_client.api_call("chat.postMessage", - channel=message['channel'], - text=f"{format_slack_mention(message['user'])} {text}", - as_user=True) - - @staticmethod - def send_factory(slack_client, channel): - return partial(Command.send, slack_client, channel) - - @staticmethod - def send_ephemeral(slack_client, channel, user, text): - if isinstance(user, User): - user = user.slack_id - - return slack_client.api_call("chat.postEphemeral", - channel=channel, - user=user, - text=text, - as_user=True) - - @staticmethod - def send_ephemeral_factory(slack_client, channel, user): - return partial(Command.send_ephemeral, slack_client, channel, user) - - def respond(self, slack_client, message, matches): - return None - - def maybe_respond(self, slack_client, message): - match = self.pattern.match(message['text']) - if bool(match): - return self.respond(slack_client, message, match.groups()) diff --git a/kizuna/commands/KKredsBalanceCommand.py b/kizuna/commands/KKredsBalanceCommand.py deleted file mode 100644 index 27d595d4..00000000 --- a/kizuna/commands/KKredsBalanceCommand.py +++ /dev/null @@ -1,27 +0,0 @@ -from kizuna.commands.Command import Command -from kizuna.models.User import User -from kizuna.Kizuna import Kizuna - - -class KKredsBalanceCommand(Command): - def __init__(self, make_session, kizuna: Kizuna) -> None: - help_text = 'kizuna balance - show your kkreds balance' - - kizuna_resp_token_regex = '|'.join(kizuna.respond_tokens) - triggers = [ - "(?:(?:m|M)olly|<@U04EQPCC8>) ?balance$", - f"(?:{kizuna_resp_token_regex}) ?balance$" - ] - - pattern = "|".join(triggers) - super().__init__('ping$', pattern, help_text, is_at=False, always=True, db_session_maker=make_session) - - def respond(self, slack_client, message, matches): - user_id = message['user'] - - with self.db_session_scope() as session: - user = User.get_by_slack_id(session, user_id) - balance = user.get_kkred_balance(session) - - pluralized_kkreds = 'kkred' if balance == 1 else 'kkreds' - return self.reply(slack_client, message, f'your balance is {balance} {pluralized_kkreds}') diff --git a/kizuna/commands/KKredsMiningCommand.py b/kizuna/commands/KKredsMiningCommand.py deleted file mode 100644 index 2452806d..00000000 --- a/kizuna/commands/KKredsMiningCommand.py +++ /dev/null @@ -1,62 +0,0 @@ -from kizuna.Kizuna import Kizuna -from kizuna.commands.Command import Command -from kizuna.kkreds import is_payable -from kizuna.kkreds import strip_date -from kizuna.models.KKredsTransaction import KKredsTransaction -from kizuna.models.User import User -import arrow - - -class KKredsMiningCommand(Command): - def __init__(self, make_session, kizuna: Kizuna) -> None: - self.kizuna = kizuna - - help_text = 'kizuna pay me - at 4:20 in the America/Chicago time zone on both meridians you can say "pay me" ' \ - 'and you will be awarded a kkred ' - - triggers = [ - ".*(?:gibbe|give) money.*", - ".*pay me.*", - ".*:watermelon:.*" - ] - - pattern = "|".join(triggers) - super().__init__('mine_kkred', pattern, help_text, is_at=False, always=True, db_session_maker=make_session) - - def respond(self, slack_client, message, matches): - message_ts = arrow.get(message['event_ts']) - - if not is_payable(message_ts): - return - - user_id = message['user'] - - with self.db_session_scope() as session: - user = User.get_by_slack_id(session, user_id) - - if not user: - return - - latest_mine = session\ - .query(KKredsTransaction)\ - .filter(KKredsTransaction.to_user_id == user.id)\ - .filter(KKredsTransaction.is_mined)\ - .order_by(KKredsTransaction.created_at.desc())\ - .first() - - if latest_mine and latest_mine.created_at: - message_ts_stripped = strip_date(message_ts) - latest_mine_time_stripped = strip_date(latest_mine.created_at) - if latest_mine_time_stripped >= message_ts_stripped: - return - - kizuna_user = User.get_by_slack_id(session, self.kizuna.bot_id) - mined_kkred = KKredsTransaction(from_user=kizuna_user, - to_user=user, - amount=1, - is_mined=True, - created_at=message_ts.datetime) - - session.add(mined_kkred) - - self.reply(slack_client, message, 'successfully mined 1 kkred') diff --git a/kizuna/commands/KKredsTransactionCommand.py b/kizuna/commands/KKredsTransactionCommand.py deleted file mode 100644 index ed598ceb..00000000 --- a/kizuna/commands/KKredsTransactionCommand.py +++ /dev/null @@ -1,75 +0,0 @@ -from kizuna.Kizuna import Kizuna -from kizuna.commands.Command import Command -from kizuna.models.KKredsTransaction import KKredsTransaction -from kizuna.models.User import User -import arrow -from kizuna.slack import is_user_mention, get_user_id_from_mention -from decimal import Decimal, InvalidOperation - - -class KKredsTransactionCommand(Command): - def __init__(self, make_session, kizuna: Kizuna) -> None: - self.kizuna = kizuna - - help_text = 'kizuna pay - pay the user that amount of kkreds' - - pattern = "(?:pay|tip|give|send) (\S*) (\S*)" - - super().__init__('send_kkred', pattern, help_text, is_at=True, db_session_maker=make_session) - - def respond(self, slack_client, message, matches): - message_ts = arrow.get(message['event_ts']) - - sending_user_id = message['user'] - - with self.db_session_scope() as session: - sending_user = User.get_by_slack_id(session, sending_user_id) - - if not sending_user: - return - - receiving_user_raw = matches[0] - - if not is_user_mention(receiving_user_raw): - return self.reply(slack_client, - message, - 'User has to be an `@` mention. Like it has to be a real blue `@` mention.') - - receiving_user = User.get_by_slack_id(session, - get_user_id_from_mention(receiving_user_raw)) - - if not receiving_user: - return self.reply(slack_client, message, 'Could not find that user') - - if sending_user.id == receiving_user.id: - return self.reply(slack_client, - message, - 'You can’t send money to yourself.') - - amount_raw = matches[1] - - try: - amount = Decimal(amount_raw) - except InvalidOperation: - return self.reply(slack_client, - message, - 'That amount is invalid. Try a decimal or integer value') - - if amount <= 0: - return self.reply(slack_client, - message, - 'Amount has to be non-zero') - - if amount > sending_user.get_kkred_balance(session): - return self.reply(slack_client, - message, - 'You don’t have enough kkreds') - - transaction = KKredsTransaction(from_user=sending_user, - to_user=receiving_user, - amount=amount, - created_at=message_ts.datetime) - - session.add(transaction) - - self.reply(slack_client, message, f'successfully sent {amount} to {receiving_user.name}') diff --git a/kizuna/commands/LoginCommand.py b/kizuna/commands/LoginCommand.py deleted file mode 100644 index ecfb4c5f..00000000 --- a/kizuna/commands/LoginCommand.py +++ /dev/null @@ -1,22 +0,0 @@ -from kizuna.commands.Command import Command -from kizuna.utils import build_url -import config -from kizuna.models.User import User - - -class LoginCommand(Command): - def __init__(self, make_session) -> None: - help_text = "kizuna login - login to the web interface" - - super().__init__('login', pattern='login', help_text=help_text, is_at=True, db_session_maker=make_session) - - def respond(self, slack_client, message, matches): - send = self.send_ephemeral_factory(slack_client, message['channel'], message['user']) - with self.db_session_scope() as session: - user = User.get_by_slack_id(session, message['user']) - - if not user: - return send("I don't have your users in the db. Prolly run 'kizuna refresh users' and if that still " - "doesn't fix it: Austin fucked up somewhere :^(") - - send(build_url(config.KIZUNA_WEB_URL, '/login', {'auth': user.get_token()})) diff --git a/kizuna/commands/PingCommand.py b/kizuna/commands/PingCommand.py deleted file mode 100644 index caccc288..00000000 --- a/kizuna/commands/PingCommand.py +++ /dev/null @@ -1,11 +0,0 @@ -from kizuna.commands.Command import Command - - -class PingCommand(Command): - def __init__(self) -> None: - help_text = "{bot} ping - respond with pong" - - super().__init__('ping$', "ping", help_text, True) - - def respond(self, slack_client, message, matches): - self.send(slack_client, message['channel'], 'pong') diff --git a/kizuna/commands/ReactCommand.py b/kizuna/commands/ReactCommand.py deleted file mode 100644 index 4c9b85c2..00000000 --- a/kizuna/commands/ReactCommand.py +++ /dev/null @@ -1,83 +0,0 @@ -from .Command import Command - -from config import KIZUNA_WEB_URL -from kizuna.models.User import User -from kizuna.utils import build_url, slack_link -from kizuna.models import ReactionImageTag -from random import choice -from kizuna.nlp import extract_possible_tags -import itertools -import sqlalchemy.orm as orm - - -class ReactCommand(Command): - def __init__(self, make_session, nlp) -> None: - help_text = 'kizuna react - view available reaction images and tags\n' \ - 'kizuna react add - upload some reaction images\n' \ - 'kizuna tfw - Send a reaction related to the text' - self.nlp = nlp - super().__init__(name='react', - pattern='(react(?:ion)?|tfw)(?: (.*))?$', - help_text=help_text, - is_at=True, - db_session_maker=make_session) - - def respond(self, slack_client, message, matches): - send = self.send_ephemeral_factory(slack_client, message['channel'], message['user']) - send_public = self.send_factory(slack_client, message['channel']) - - query = matches[1] - if query: - query = query.strip() - - with self.db_session_scope() as session: - user = User.get_by_slack_id(session, message['user']) - - if not user: - return send("I don't have your users in the db. Prolly run 'kizuna refresh users' and if that still " - "doesn't fix it: Austin fucked up somewhere :^(") - - def authenticated_path(path): - return build_url(KIZUNA_WEB_URL, path, {'auth': user.get_token()}) - - react_homepage_url = authenticated_path('/react') - react_add_image_url = authenticated_path('/react/images/new') - - if not query: - # kizuna react$ - return send(react_homepage_url) - - if query == 'add': - # kizuna react add - return send(react_add_image_url) - - # kizuna react - def add_images_nag(): - add_images = slack_link('(Add Images)', react_add_image_url) - view_images = slack_link('(View Images)', react_homepage_url) - send("You can add some images though! {} {}".format(add_images, view_images)) - - possible_tags = [token.text for token in extract_possible_tags(self.nlp, query)] - - tags = session\ - .query(ReactionImageTag)\ - .options(orm.joinedload("images"))\ - .filter(ReactionImageTag.name.in_(possible_tags))\ - .all() - - if tags: - images = list(itertools.chain.from_iterable([tag.images for tag in tags])) - - if len(images) > 0: - for image in images: - image.tags_text = [tag.name for tag in image.tags if tag.name in possible_tags] - images.sort(key=lambda t: len(t.tags_text), reverse=True) - best_match_length = len(images[0].tags_text) - best_matches = [image for image in images if len(image.tags_text) == best_match_length] - return send_public(choice(best_matches).url) - - send_public('I don\'t have any images for "{}" :^('.format(query)) - return add_images_nag() - - send_public('I don\'t have anything for "{}" :^('.format(query)) - return add_images_nag() diff --git a/kizuna/commands/UserRefreshCommand.py b/kizuna/commands/UserRefreshCommand.py deleted file mode 100644 index a9c9ad42..00000000 --- a/kizuna/commands/UserRefreshCommand.py +++ /dev/null @@ -1,41 +0,0 @@ -from kizuna.commands.Command import Command - -from kizuna.models.User import User - - -class UserRefreshCommand(Command): - def __init__(self, db_session) -> None: - help_text = "kizuna refresh users - Make sure my users database is up-to-date with slack. You probably want " \ - "to run this if someone has changed their name or a new user has joined slack." - super().__init__(name="refresh-user", - pattern='refresh users$', - help_text=help_text, - is_at=True, - db_session_maker=db_session) - - def respond(self, slack_client, message, matches): - - res = slack_client.api_call('users.list') - - if not res['ok']: - raise RuntimeError('call to users.list failed') - - slack_members = [member for member in res['members'] if not member['deleted']] - - with self.db_session_scope() as session: - def maybe_create(slack_id): - return User.maybe_create_user_from_slack_id(slack_id, slack_client, session) - - kizuna_members = [maybe_create(member['id']) for member in slack_members] - - for member in kizuna_members: - el = [x for x in slack_members if x['id'] == member.slack_id] - if not el: - continue - - el = el[0] - - if member.name != el['name']: - member.name = el['name'] - - self.send(slack_client, message['channel'], 'Refreshed users. :^)') diff --git a/kizuna/kkreds.py b/kizuna/kkreds.py deleted file mode 100644 index 288f7c9d..00000000 --- a/kizuna/kkreds.py +++ /dev/null @@ -1,30 +0,0 @@ -from arrow import Arrow -from datetime import datetime -import arrow -from sqlalchemy.orm import sessionmaker -from kizuna.models.User import User -from decimal import localcontext as decimal_localcontext, Decimal - - -def is_payable(utc: Arrow) -> bool: - """Should return True if time is 4:20AM or 4:20PM in Texas""" - central = utc.to('America/Chicago') - - hour = central.hour - minute = central.minute - - if minute != 20: - return False - - if hour not in [4, 16]: - return False - - return True - - -def strip_date(target_date): - return arrow.get(datetime(year=target_date.year, - month=target_date.month, - day=target_date.day, - hour=target_date.hour, - minute=target_date.minute)) diff --git a/kizuna/models/AtGraphEdge.py b/kizuna/models/AtGraphEdge.py deleted file mode 100644 index 90308b6b..00000000 --- a/kizuna/models/AtGraphEdge.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy import Column, Integer, ForeignKey -from sqlalchemy.orm import relationship -from kizuna.models.Models import Base - - -class AtGraphEdge(Base): - __tablename__ = 'at_graph_edges' - - id = Column('id', Integer, primary_key=True) - head_user_id = Column('head_user_id', Integer, ForeignKey('users.id')) - head_user = relationship("User", foreign_keys=[head_user_id]) - tail_user_id = Column('tail_user_id', Integer, ForeignKey('users.id')) - tail_user = relationship("User", foreign_keys=[tail_user_id]) - weight = Column('weight', Integer, nullable=False) - - def __repr__(self): - return "".format(self.id, - self.head_user.name, - self.tail_user.name) - - @staticmethod - def increment_edge(head_user, tail_user, session): - edge = session \ - .query(AtGraphEdge) \ - .filter(AtGraphEdge.head_user_id == head_user.id) \ - .filter(AtGraphEdge.tail_user_id == tail_user.id) \ - .first() - - if not edge: - edge = AtGraphEdge(head_user_id=head_user.id, - tail_user_id=tail_user.id, - weight=1) - return session.add(edge) - - edge.weight = AtGraphEdge.weight + 1 - session.add(edge) diff --git a/kizuna/models/ReactionImage.py b/kizuna/models/ReactionImage.py deleted file mode 100644 index 39d1dac3..00000000 --- a/kizuna/models/ReactionImage.py +++ /dev/null @@ -1,23 +0,0 @@ -from sqlalchemy import Column, Integer, String, DateTime, func, Text -from sqlalchemy.orm import relationship -from kizuna.models.Models import Base, reaction_images_tags_join_table - - -class ReactionImage(Base): - __tablename__ = 'reaction_images' - - id = Column(Integer, primary_key=True) - url = Column(String, index=True, unique=True) - name = Column(String) - type = Column(String) - description = Column(Text) - - created_at = Column(DateTime, default=func.current_timestamp()) - updated_at = Column(DateTime, default=func.current_timestamp(), onupdate=func.current_timestamp()) - - tags = relationship("ReactionImageTag", - secondary=reaction_images_tags_join_table, - backref="images") - - def __repr__(self): - return "".format(self.id, self.url, self.name) diff --git a/kizuna/models/ReactionImageTag.py b/kizuna/models/ReactionImageTag.py deleted file mode 100644 index 63a99767..00000000 --- a/kizuna/models/ReactionImageTag.py +++ /dev/null @@ -1,36 +0,0 @@ -from sqlalchemy import Column, Integer, String, DateTime, func, Text -from kizuna.models.Models import Base -import arrow - - -class ReactionImageTag(Base): - __tablename__ = 'reaction_image_tags' - - id = Column(Integer, primary_key=True) - name = Column(String, index=True, unique=True) - description = Column(Text) - - created_at = Column(DateTime, default=func.current_timestamp()) - updated_at = Column(DateTime, default=func.current_timestamp(), onupdate=func.current_timestamp()) - - @staticmethod - def maybe_create_tag(session, name): - tag = session.query(ReactionImageTag).filter(ReactionImageTag.name == name).first() - if tag: - return tag - - tag = ReactionImageTag(name=name) - session.add(tag) - return tag - - def to_dict(self): - return { - 'id': self.id, - 'name': self.name, - 'description': self.description, - 'created_at': arrow.get(self.created_at).timestamp, - 'updated_at': arrow.get(self.updated_at).timestamp - } - - def __repr__(self): - return "".format(self.id, self.name) diff --git a/kizuna/models/__init__.py b/kizuna/models/__init__.py deleted file mode 100644 index 0538193f..00000000 --- a/kizuna/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .ReactionImage import ReactionImage -from .ReactionImageTag import ReactionImageTag diff --git a/kizuna/nlp.py b/kizuna/nlp.py deleted file mode 100644 index 7c79236a..00000000 --- a/kizuna/nlp.py +++ /dev/null @@ -1,17 +0,0 @@ - -# https://spacy.io/api/annotation -taggable_pos = [ - 'ADJ', - 'ADV', - 'INTJ', - 'NOUN', - 'NUM', - 'PROPN', - 'VERB', - 'X' -] - - -def extract_possible_tags(nlp, text): - doc = nlp(text) - return [token for token in doc if token.pos_ in taggable_pos] diff --git a/kizuna/slack.py b/kizuna/slack.py deleted file mode 100644 index c79ab663..00000000 --- a/kizuna/slack.py +++ /dev/null @@ -1,25 +0,0 @@ -import re - - -def format_slack_mention(slack_id: str): - return f"<@{slack_id}>" - - -user_id_regex = '^<@(U[A-Za-z0-9]{8,})>$' - - -def get_user_id_from_mention(m: str): - match = re.match(user_id_regex, m) - if not match: - return None - - return match.group(1) - - -def is_user_mention(s: str) -> bool: - """user mentions should be in format <@UXXXXXXXX>""" - - if not s or not re.match(user_id_regex, s): - return False - - return True diff --git a/kizuna/strings.py b/kizuna/strings.py deleted file mode 100644 index 6eaea4e3..00000000 --- a/kizuna/strings.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding=utf-8 -import random - -HAI_DOMO = 'はいども!キズナです!' -KIZUNA = '\u30AD\u30BA\u30CA' -WAIT_A_SEC = '\u3061\u3087\u3063\u3068\u5F85\u3063\u3066\u304F\u3060\u3055\u3044' -JAP_DOT = '\u3002' - -AHO = '\u3042\u307B' -BAKA = '\u3070\u304B' -INSULTS = [AHO, BAKA] - - -def random_insult(): - return random.choice(INSULTS) - - -YOSHI = '\u3088\u3057' - -VERSION_UPDATE_TEMPLATE = '\u79C1\u306F{{VERSION}}\u306B\u66F4\u65B0\u3057\u307E\u3057\u305F' -VERSION_TRANSITION_TEMPLATE = '{{FROM_VERSION}}から{{TO_VERSION}}にバージョンアップしました。' - - -LQUO = '\u300C' -RQUO = '\u300D' - -VERSION_UP = '\u30D0\u30FC\u30B8\u30E7\u30F3\u30A2\u30C3\u30D7' - -GOODBYE = '\u3055\u3088\u306A\u3089' diff --git a/kizuna_js/containers/NewReactionImage.css b/kizuna_js/containers/NewReactionImage.css deleted file mode 100644 index 7dda5f40..00000000 --- a/kizuna_js/containers/NewReactionImage.css +++ /dev/null @@ -1,60 +0,0 @@ -.kiz-upload-menu { - display: flex; - justify-content: space-between; -} - -.kiz-upload-menu__reset { - -} - -.kiz-upload-menu__upload { - -} - - -.react-tagsinput-input { - width: 100%; -} - -.react-dropzone:hover { - cursor: pointer; -} - -.react-autosuggest__suggestions-list { - margin: 0; - padding: 0; -} - -.react-autosuggest__suggestion { - color: dimgray; - background: #ccc; - border-color: dimgray; -} -.react-autosuggest__suggestion:hover { - cursor: pointer; -} - -.react-tagsinput .react-autosuggest__suggestions-container { - display: none; -} - -.react-tagsinput--focused .react-autosuggest__suggestions-container { - animation: grow 300ms ease-out; - display: block; -} - -@keyframes grow { - 0% { - display: none; - opacity: 0; - } - 1% { - display: block; - opacity: 0; - transform: scaleY(0); - } - 100% { - opacity: 1; - transform: scaleY(1); - } -} diff --git a/kizuna_js/containers/NewReactionImage.js b/kizuna_js/containers/NewReactionImage.js deleted file mode 100644 index 5afad122..00000000 --- a/kizuna_js/containers/NewReactionImage.js +++ /dev/null @@ -1,353 +0,0 @@ -import React, {Component} from 'react'; -import {ReactionImage} from "../models/ReactionImage"; -import Dropzone from "react-dropzone" -import _set from "lodash/set"; -import _uniq from "lodash/uniq"; -import _forEach from "lodash/forEach"; -import _filter from "lodash/filter"; -import _difference from "lodash/difference"; -import _map from "lodash/map"; -import _union from "lodash/union"; -import TagsInput from 'react-tagsinput'; -import Autosuggest from 'react-autosuggest'; -import Sticky from 'react-stickynode'; - -import 'react-tagsinput/react-tagsinput.css' -import "./NewReactionImage.css" - -import {GoCloudUpload, GoChevronLeft} from 'react-icons/lib/go'; - -export class NewReactionImageContainer extends Component { - static style = { - margin: '0 auto', - maxWidth: '60em', - width: '100%', - background: '#f1f1f1', - padding: '0.5em', - border: '1px solid #ccc', - marginTop: '1em' - }; - - static imageRowStyle = { - margin: '5px 5px 10px', - padding: '1em' - }; - - static inputStyle = { - display: 'block' - }; - - - constructor(props) { - super(props); - this.defaultState = Object.freeze({ - images: {}, - suggestions: [], - suggestionsLoading: true, - suggestionsLoadingError: false, - uploading: false - }); - - this.state = { - ...this.defaultState - }; - } - - doSetup() { - this.setState({ - ...this.defaultState - }); - fetch('/api/react/tags') - .then(res => res.json()) - .then((json) => { - this.setState({suggestions: _map(json.tags, 'name'), suggestionsLoading: false}) - }) - .catch(() => { - this.setState({suggestionsLoading: false, suggestionsLoadingError: true}) - }); - } - - componentWillMount() { - this.doSetup(); - } - - autocompleteRenderInput ({alreadyUsedTags}, {addTag, ...props}) { - const handleOnChange = (e, {newValue, method}) => { - if (method === 'enter') { - e.preventDefault() - } else { - props.onChange(e) - } - }; - - const inputValue = (props.value && props.value.trim().toLowerCase()) || ''; - const inputLength = inputValue.length; - - let {suggestions: allSuggestions} = this.state; - - let suggestions = _difference(allSuggestions, alreadyUsedTags).filter((state) => { - return state.toLowerCase().slice(0, inputLength) === inputValue - }); - - const defaultTheme = { - container: 'react-autosuggest__container', - containerOpen: 'react-autosuggest__container--open', - input: 'react-autosuggest__input', - inputOpen: 'react-autosuggest__input--open', - inputFocused: 'react-autosuggest__input--focused', - suggestionsContainer: 'react-autosuggest__suggestions-container', - suggestionsContainerOpen: 'react-autosuggest__suggestions-container--open', - suggestionsList: 'react-autosuggest__suggestions-list', - suggestion: 'react-autosuggest__suggestion', - suggestionFirst: 'react-autosuggest__suggestion--first', - suggestionHighlighted: 'react-autosuggest__suggestion--highlighted', - sectionContainer: 'react-autosuggest__section-container', - sectionContainerFirst: 'react-autosuggest__section-container--first', - sectionTitle: 'react-autosuggest__section-title' - }; - - const theme = { - ...defaultTheme, - suggestion: 'react-tagsinput-tag react-autosuggest__suggestion' - }; - - return ( - value && value.trim().length > 0} - getSuggestionValue={(suggestion) => suggestion} - renderSuggestion={(suggestion) => {suggestion}} - inputProps={{...props, onChange: handleOnChange}} - alwaysRenderSuggestions={true} - onSuggestionSelected={(e, {suggestion}) => { - addTag(suggestion) - }} - onSuggestionsClearRequested={() => {}} - onSuggestionsFetchRequested={() => {}} - /> - ) - } - - onDrop(accepted, rejected) { - const images = accepted.reduce(function (acc, file) { - const image = new ReactionImage({ - id: `${file.name}/${file.size}/${+new Date()}`, - file: file, - tags: [], - title: file.name, - type: file.type - }); - acc[image.id] = image; - return acc; - }, {}); - this.setState({images}); - } - - changeImageValue(id, attribute, event) { - const images = this.state.images; - _set(images, [id, attribute], event.target.value); - this.setState({images}); - } - - changeImageTags(id, tags) { - const {images, suggestions} = this.state; - const imageTags = _map(tags, tag => tag.toLowerCase()); - _set(images, [id, 'tags'], imageTags); - const newSuggestions = _union(suggestions, imageTags); - this.setState({images, suggestions: newSuggestions}); - } - - uploadImages(event) { - const {auth} = this.props; - event.preventDefault(); - const {images} = this.state; - this.setState({uploading: true}); - const requests = []; - _forEach(_filter(images, {uploaded: false}), (image, id) => { - const formData = new FormData(); - formData.append('auth', auth); - formData.append('file', image.file); - formData.append('title', image.title); - formData.append('tags', JSON.stringify(image.tags)); - const req = fetch('/api/react/images', { - method: 'POST', - body: formData - }) - .then(res => res.json()) - .then((json) => { - if (json.ok) { - image.uploaded = true; - this.setState({images}) - } else { - alert('upload error for ' + id) - } - }) - .catch(() => { - image.uploaded = false; - image.uploadFailure = true; - this.setState({images}) - }); - requests.push(req) - }); - const setUploadingFalse = () => this.setState({uploading: false}); - Promise.all(requests).then(setUploadingFalse, setUploadingFalse); - } - - render() { - const self = NewReactionImageContainer; - const images = Object.keys(this.state.images).map((image_id) => { - return this.state.images[image_id]; - }); - - const {uploading} = this.state; - const hasImages = images.length > 0; - const hasUnuploadedImages = _filter(images, {uploaded: false}).length > 0; - const textColor = '#2C3E50'; - const bgColor = '#A3B5CB'; - const modal = { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - }; - const dropzoneStyles = { - ...modal, - width: '100%', - background: bgColor, - border: `5px ${textColor} dashed` - }; - - const innerModal = { - position: 'absolute', - top: '50%', - right: '10%', - left: '10%', - transform: 'translateY(-50%)', - textAlign: 'center' - }; - - const dropzoneTextStyles = { - ...innerModal, - color: textColor, - }; - - let body = (() => { - if (hasImages) { - if (uploading) { - return
-
-

uploading your images...

-
- {images.map((image) => { - return 10 ? '50px' : '100px'}} /> - })} -
-
-
- } - if (!hasUnuploadedImages) { - return
-

Got it! Thanks :^)

-
-

- Image Gallery - - -

-
-
- {images.map((image) => { - return 10 ? '100px' : '150px'}} /> - })} -
-
- } - return
- -
- - {hasUnuploadedImages && ( - - )} -
-
- {_filter(images, {uploaded: false}).map((image) => { - const rowStyle = { - ...self.imageRowStyle, - background: image.uploaded ? 'white' : '#f1f1f1' - }; - return
- {image.uploaded && Uploaded successfully} - {image.uploadFailure && This Image Did Not Upload Successfully} - -
- -
-
- -
-
- })} -
- } - - return
- -
-

please feed me reaction images (✿╹◡╹)

-
- -
- -
-
- - or drag images here. - -
-
-
; - })(); - - - return
- {body} -
- } -} \ No newline at end of file diff --git a/kizuna_js/index.js b/kizuna_js/index.js deleted file mode 100644 index 9e0d659d..00000000 --- a/kizuna_js/index.js +++ /dev/null @@ -1,59 +0,0 @@ -import ReactDOM from 'react-dom'; -import React from 'react'; -import URI from 'urijs'; -import localforage from 'localforage'; -import _every from "lodash/every"; - -const STORE_NAME = 'kizuna'; - -function initStorage() { - const store = localforage.createInstance({ - name: STORE_NAME - }); - const result = { - store, - auth: null - }; - const uri = new URI(window.location.href); - const {auth} = uri.search(true); - return new Promise(function (resolve, reject) { - if (auth) { - return store.setItem('auth', auth) - .then(() => resolve({...result, auth})) - .catch(() => resolve(result)) - } - return store.getItem('auth') - .then(function (auth) { - if (auth) { - return resolve({...result, auth}) - } - - return resolve(result); - }) - .catch(() => resolve(result)) - }); -} - -// below is not DRY but IDGAF the perf benefits are worth it - -const new_reaction_image_entry_el = document.getElementById('new-reaction-image'); -if (new_reaction_image_entry_el) { - import( - /* webpackChunkName: "kizuna-new-reaction-image" */ - "./containers/NewReactionImage" - ).then(module => { - const {NewReactionImageContainer} = module; - initStorage().then(function (storage) { - ReactDOM.render(, new_reaction_image_entry_el); - }); - }) -} - -// just a regular page with no react components -// still grab the auth key from url and save it -const entry_elements = [ - new_reaction_image_entry_el -]; -if (_every(entry_elements, element => !element)) { - initStorage(); -} diff --git a/kizuna_js/models/ReactionImage.js b/kizuna_js/models/ReactionImage.js deleted file mode 100644 index 0eb24d5a..00000000 --- a/kizuna_js/models/ReactionImage.js +++ /dev/null @@ -1,11 +0,0 @@ -export class ReactionImage { - constructor({id, description='', file, tags=[], title, type}) { - this.description = description; - this.id = id; - this.file = file; - this.tags = tags; - this.title = title; - this.type = type; - this.uploaded = false; - } -} diff --git a/kizuna_web/__init__.py b/kizuna_web/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/kizuna_web/app.py b/kizuna_web/app.py deleted file mode 100644 index 19a723b2..00000000 --- a/kizuna_web/app.py +++ /dev/null @@ -1,23 +0,0 @@ -from flask import Flask -from raven.contrib.flask import Sentry - -import config - -from kizuna.Kizuna import Kizuna -from kizuna_web.views import blueprint as views_blueprint - -DEV_INFO = Kizuna.read_dev_info('./.dev-info.json') - -app = Flask(__name__, static_folder='../static') - -app.secret_key = config.SECRET_KEY - -app.config['SENTRY_CONFIG'] = { - 'dsn': config.SENTRY_URL, - 'release': DEV_INFO.get('revision'), - 'environment': config.KIZUNA_ENV -} - -sentry = Sentry(app) if config.SENTRY_URL else None - -app.register_blueprint(views_blueprint) diff --git a/kizuna_web/extentions.py b/kizuna_web/extentions.py deleted file mode 100644 index e6a8cac3..00000000 --- a/kizuna_web/extentions.py +++ /dev/null @@ -1,16 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -import boto3 -import config - -db_engine = create_engine(config.DATABASE_URL) -make_db_session = sessionmaker(bind=db_engine) - -if not config.AWS_ACCESS_KEY_ID or not config.AWS_SECRET_ACCESS_KEY: - raise Warning('You have not configured your AWS credentials. Reaction image feature will not work') - -aws_client = boto3.client( - 's3', - aws_access_key_id=config.AWS_ACCESS_KEY_ID, - aws_secret_access_key=config.AWS_SECRET_ACCESS_KEY -) diff --git a/kizuna_web/middleware.py b/kizuna_web/middleware.py deleted file mode 100644 index adac1f9e..00000000 --- a/kizuna_web/middleware.py +++ /dev/null @@ -1,38 +0,0 @@ -from functools import wraps -from flask import request, Response, session, render_template -from kizuna.models.User import User, InvalidToken - - -def check_auth(token): - """This function is called to check if a username / - password combination is valid. - """ - try: - token = User.decrypt_token(token) - return token and len(token) > 0 - except InvalidToken: - return False - - -def authenticate(): - """Sends a 401 response that enables basic auth""" - return render_template('not_authorized.jinja2'), 401 - - -def requires_auth_factory(app): - def requires_auth(f): - @wraps(f) - def decorated(*args, **kwargs): - auth = request.values.get('auth') - if not auth or not check_auth(auth): - if 'auth' not in session: - return authenticate() - - if not check_auth(session['auth']): - return authenticate() - - if auth: - session['auth'] = auth - return f(*args, **kwargs) - return decorated - return requires_auth diff --git a/kizuna_web/templates/gallery.jinja2 b/kizuna_web/templates/gallery.jinja2 deleted file mode 100644 index 3b178a9d..00000000 --- a/kizuna_web/templates/gallery.jinja2 +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "parents/base.jinja2" %} -{% block content %} -
-

here's what we have in our gallery so far.

- {% for image in images %} -

{{ image.name }}

-
- -
-
- Tags:
    {% for tag in image.tags %}
  • {{ tag.name }}
  • {% endfor %}
-
- {% endfor %} -
-{% endblock %} \ No newline at end of file diff --git a/kizuna_web/templates/inclues/buttons.css b/kizuna_web/templates/inclues/buttons.css deleted file mode 100644 index 6470bf8f..00000000 --- a/kizuna_web/templates/inclues/buttons.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! -Pure v1.0.0 -Copyright 2013 Yahoo! -Licensed under the BSD License. -https://github.com/yahoo/pure/blob/master/LICENSE.md -*/ -.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:transparent;background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{filter:alpha(opacity=90);background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;margin:0;border-radius:0;border-right:1px solid #111;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none} \ No newline at end of file diff --git a/kizuna_web/templates/index.jinja2 b/kizuna_web/templates/index.jinja2 deleted file mode 100644 index 9093633f..00000000 --- a/kizuna_web/templates/index.jinja2 +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "parents/base.jinja2" %} -{% block content %} -
-

Hi!

-
-

Reaction Image Database

-

I'm trying to build a reaction image database. Will you help me?

-

- - View Gallery - - - Upload Images - -

-
-{% endblock %} \ No newline at end of file diff --git a/kizuna_web/templates/login.jinja2 b/kizuna_web/templates/login.jinja2 deleted file mode 100644 index 25a08e98..00000000 --- a/kizuna_web/templates/login.jinja2 +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'parents/base.jinja2' %} -{% block content %} -
-

I logged you in!

-
-

- - go do some stuff - -

-
-{% endblock %} diff --git a/kizuna_web/templates/not_authorized.jinja2 b/kizuna_web/templates/not_authorized.jinja2 deleted file mode 100644 index 41ae528e..00000000 --- a/kizuna_web/templates/not_authorized.jinja2 +++ /dev/null @@ -1,13 +0,0 @@ -{% extends 'parents/base.jinja2' %} -{% block content %} -
-

Not Authorized

-
-

You either:

-
    -
  • Did not give me an authorization token
  • -
  • Your session expired and you need to login again
  • -
-

Just run "kizuna login" to give your browser a fresh token.

-
-{% endblock %} \ No newline at end of file diff --git a/kizuna_web/templates/parents/base.jinja2 b/kizuna_web/templates/parents/base.jinja2 deleted file mode 100644 index 62232b96..00000000 --- a/kizuna_web/templates/parents/base.jinja2 +++ /dev/null @@ -1,68 +0,0 @@ - - - - Kizuna - {{ title }} - - - - - -{% block content %}{% endblock %} - - - - \ No newline at end of file diff --git a/kizuna_web/templates/react_component.jinja2 b/kizuna_web/templates/react_component.jinja2 deleted file mode 100644 index df24a28b..00000000 --- a/kizuna_web/templates/react_component.jinja2 +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "parents/base.jinja2" %} -{% block content %} - -
-
- Loading -
-
-{% endblock %} diff --git a/kizuna_web/utils.py b/kizuna_web/utils.py deleted file mode 100644 index b6c4de3a..00000000 --- a/kizuna_web/utils.py +++ /dev/null @@ -1,16 +0,0 @@ -import pathlib - -content_type_map = { - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', -} - - -def image_path_to_content_type(path): - ext = pathlib.Path(path).suffix - if ext not in content_type_map: - raise ValueError('image type "{}" is not recognized'.format(ext)) - - return content_type_map[ext] diff --git a/kizuna_web/views.py b/kizuna_web/views.py deleted file mode 100644 index 50857502..00000000 --- a/kizuna_web/views.py +++ /dev/null @@ -1,92 +0,0 @@ -import json -import os -from uuid import uuid4 - -from flask import render_template -from flask import request, Response, Blueprint -from werkzeug.utils import secure_filename -from kizuna_web.utils import image_path_to_content_type - -from config import \ - IMAGE_UPLOAD_DIR, \ - S3_BUCKET, \ - S3_BUCKET_URL -from kizuna.models.ReactionImage import ReactionImage -from kizuna.models.ReactionImageTag import ReactionImageTag -from .extentions import aws_client, make_db_session -from .middleware import requires_auth_factory - -blueprint = Blueprint('views', __name__) - -requires_auth = requires_auth_factory(blueprint) - - -@blueprint.route("/") -def hello(): - return render_template('index.jinja2', title='Welcome') - - -@blueprint.route("/login") -@requires_auth -def login(): - return render_template('login.jinja2') - - -@blueprint.route("/react/images/new") -@requires_auth -def new_reaction_image(): - return render_template('react_component.jinja2', - title='Upload New Reaction Image', - component_entry='new-reaction-image') - - -def allowed_image_file(type): - return type in ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'] - - -@blueprint.route("/react") -def gallery(): - session = make_db_session() - images = session.query(ReactionImage).all() - return render_template('gallery.jinja2', - images=images) - - -@blueprint.route("/api/react/images", methods=['POST']) -@requires_auth -def handle_image_upload(): - session = make_db_session() - res = Response(json.dumps({'ok': True}), status=200, mimetype='application/json') - - title = request.values.get('title') - tags = json.loads(request.values.get('tags')) - file = request.files.get('file') - if file.filename == '': - return Response(json.dumps({'ok': False}), status=400, mimetype='application/json') - if file and allowed_image_file(type=file.content_type): - ext = os.path.splitext(file.filename)[1] - filename = secure_filename(uuid4().hex + ext) - content_type = image_path_to_content_type(filename) - image_path = os.path.join(IMAGE_UPLOAD_DIR, filename) - aws_client.put_object(Bucket=S3_BUCKET, - Key=image_path, - ACL='public-read', - ContentType=content_type, - StorageClass='REDUCED_REDUNDANCY', - Body=file) - image_url = S3_BUCKET_URL + '/' + image_path - image = ReactionImage(name=title, url=image_url) - session.add(image) - tags = [ReactionImageTag.maybe_create_tag(session, tag.lower()) for tag in tags] - for tag in tags: - image.tags.append(tag) - session.commit() - return Response(json.dumps({'ok': True, 'file': 'file'}), mimetype='application/json') - return res - - -@blueprint.route("/api/react/tags") -def get_tags(): - tags = [tag.to_dict() for tag in make_db_session().query(ReactionImageTag).all()] - return Response(json.dumps({'tags': tags}), - mimetype='application/json') diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 08afa027..00000000 --- a/package-lock.json +++ /dev/null @@ -1,4739 +0,0 @@ -{ - "name": "kizuna", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.34.tgz", - "integrity": "sha512-CAEMoiuya1pvissuqLntIRkVFmFA54mvovKvdHmsdWETut9qJ+bASY8fT7yxSwHCwhAHj2Ed6Als87BSaS7nhg==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "@babel/core": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.34.tgz", - "integrity": "sha512-A14rafWuxdqGFVNCYksQsY/pJX01lhWpdvXxwNSu18E7DrKK0wkXRCTNman43oDc7yR+BSxFkrdaQkTvNrxfuA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.34", - "@babel/generator": "7.0.0-beta.34", - "@babel/helpers": "7.0.0-beta.34", - "@babel/template": "7.0.0-beta.34", - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34", - "babylon": "7.0.0-beta.34", - "convert-source-map": "1.5.1", - "debug": "3.1.0", - "json5": "0.5.1", - "lodash": "4.17.4", - "micromatch": "2.3.11", - "resolve": "1.5.0", - "source-map": "0.5.7" - } - }, - "@babel/generator": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.34.tgz", - "integrity": "sha512-2ymX/4tB4JzuLIpv2iYzWlMGz55Ixs/YjMVjtZ49qXaKvVhkJ0+a2p3rg0Nq98/1ngI1mTRtOzGnR6Or01Qc8A==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34", - "jsesc": "2.5.1", - "lodash": "4.17.4", - "source-map": "0.5.7", - "trim-right": "1.0.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.34.tgz", - "integrity": "sha512-Sbf/3iLVGtq0SEI5qDV2uGKGLWsqMKFl/jKVWXomp0SYYuLtWxvMacvVaYa5EQ7U17y4yf9iao9GQrBJ6MOzXg==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.34.tgz", - "integrity": "sha512-tqtviffy4ftWyJoCl/tajjsEcO3qHDYeUKHj/JMFZxBK2NNpYToq6a5GIQ0v87xlDXFI1/MAjvW4XRIeJDBJBw==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.0.0-beta.34.tgz", - "integrity": "sha512-WiVAkxuaLxigXd8bdOLhbBIdN1gcf8NT+DWMQ8phAcF6njl8wxK7q7WQhLVBeAvRwloFXFtGdDOrJUNWsl5Zbg==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34", - "esutils": "2.0.2" - } - }, - "@babel/helper-call-delegate": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.34.tgz", - "integrity": "sha512-R0cjbmHfzNDILlRUC6GyuMieGqwcxcjmCwoOZU42pVz1NTrbE9pUYS9hAGBBdH2PANzXjOs5OzkM8xnBVetjLQ==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "7.0.0-beta.34", - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-define-map": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.0.0-beta.34.tgz", - "integrity": "sha512-MG/xndS6/jLoZMm11i/YXVLgvDFo5gH4hKm9+2HW5qmiCW0v9T2k930yJoPhwjeOb0O38gxmy1vE9/35nmX1Qw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34", - "lodash": "4.17.4" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.34.tgz", - "integrity": "sha512-1xxcIc9JUpm+E3ZEa8yV5+vkMwDd0Cel2fV7UzsWFG0IcRY3dC+tJ9c3auf3oCVd3jn+4zTbwws5KRbiKb0XIg==", - "dev": true, - "requires": { - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-function-name": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.34.tgz", - "integrity": "sha512-2nkFlb5/sAeptgEtmAxxK+zELRlfExUymxncNcN996GARKFjn0dNYSnsYwS952nwG9fRpl2lAWLt3Ygk8LyF+Q==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.34", - "@babel/template": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.34.tgz", - "integrity": "sha512-e9WktWH2PyOzp2lNbIVx2NyiGR/pIc9j28vUnqoDvg9cD1bOqQKVxsDffDl76M0+cPr11C1SV5bN2Y5r24sTIQ==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.34.tgz", - "integrity": "sha512-8CHKEDXEW+4zFeBKuszkP5hr7vxuz7UBQ4QmrLEb5OaZp9djjcG7PIJ9+RQDVIVyL/GdQN2OqKNR/hE4Pb04IQ==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-module-imports": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.34.tgz", - "integrity": "sha512-wlVrm72pIv+Vw4GF3KdQnklzCkL8nDOEyjnTtmeYpSAfawQwXXE/tvXw7uUW2Ut8PCde+nGuqfATMaD4hIUSRw==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34", - "lodash": "4.17.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.34.tgz", - "integrity": "sha512-wkKYPBH8eTwKDBDm24SdBHNh4E+4epY74pKd/+jBaGy/1nxOgPtq7wNVd+8XCHVpxbilvRWFSdDoG/sR/5PDJg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "7.0.0-beta.34", - "@babel/helper-simple-access": "7.0.0-beta.34", - "@babel/template": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34", - "lodash": "4.17.4" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.34.tgz", - "integrity": "sha512-amewaBxr7Sv/O+CtdQB1kjewMNOwl5C54W0nNuDsvj7Ria1xsyj5UHuA9Z432Hn9pAC6+BJwh0/dxqcBVOC3AQ==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-regex": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.34.tgz", - "integrity": "sha512-IQss/f04tvVEvs7XyEyuQO9HerP6Sa/ZoaqV4E6BXa023s1OhNg0TqRjepRU80Evn+L4zdVy/m8K6xe0V5lnzw==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.34.tgz", - "integrity": "sha512-Jf/YLf55UH/llbozjmUfzDZV40WO7eUuiTrgqqqBq5Yl/ARuGDAk4RnQhUaKj7IRsSvzb6IV6qObj/boI9bD0g==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.34", - "@babel/helper-wrap-function": "7.0.0-beta.34", - "@babel/template": "7.0.0-beta.34", - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-replace-supers": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.34.tgz", - "integrity": "sha512-eCp5EyVYppTdY3OK9RK0p7GZH1x7PuXQzazS7sM3y2qwdrqOEEspMChV9aDub4yMFewhgqMCUkF+SClLjq8yJQ==", - "dev": true, - "requires": { - "@babel/helper-optimise-call-expression": "7.0.0-beta.34", - "@babel/template": "7.0.0-beta.34", - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helper-simple-access": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.34.tgz", - "integrity": "sha512-xetjlCPV6rrnuW3Q0mim9VEXZKsQHCu90UQO5AD3i79IYNVZcVNByTOJNP7Lcs4DIbKki4aPbaCA7Gt/GhMMvQ==", - "dev": true, - "requires": { - "@babel/template": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34", - "lodash": "4.17.4" - } - }, - "@babel/helper-wrap-function": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.34.tgz", - "integrity": "sha512-hXE7ob10HNumhoVMHyBH9skoL6LTTkP/GUTMd8MB93KwXyezqvM/2dfIzGySI5YzfVqjg4Rij1jhfhV8wPAg/Q==", - "dev": true, - "requires": { - "@babel/helper-function-name": "7.0.0-beta.34", - "@babel/template": "7.0.0-beta.34", - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/helpers": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.34.tgz", - "integrity": "sha512-2slsGVna3h6CWXsMm5nKdovDBr/6njwHUxInosttdBiV1WVjOj/pT5YJZ1VbXHcbfqq5TwMfOPU+Mhp+ws61gA==", - "dev": true, - "requires": { - "@babel/template": "7.0.0-beta.34", - "@babel/traverse": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34" - } - }, - "@babel/plugin-check-constants": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-check-constants/-/plugin-check-constants-7.0.0-beta.34.tgz", - "integrity": "sha512-H68lHIRXAD9OH8AMtfq9g7bnn2fEjuh68rlyLzUnaj443GxN2O8XLqhGJc2hFwFizqM6QfLLsafPzIelS7NnMQ==", - "dev": true - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.34.tgz", - "integrity": "sha512-fyhp4cF2IR1I4gHkTtQdDshoF7qOSr7xiOwffnuqSU40sEjF9EdmRbGPNjn/KaINnLM0zZfuKEUCWntzl0pa7Q==", - "dev": true, - "requires": { - "@babel/helper-remap-async-to-generator": "7.0.0-beta.34", - "@babel/plugin-syntax-async-generators": "7.0.0-beta.34" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.0.0-beta.34.tgz", - "integrity": "sha512-KLIqA5aHR1Uz6phe2Mare8l5Zfogz6H/m+ukCEuAVpcCHR/2LZSqZCM24qYC2dnAwxvMRAl/QyUe8MclHhU3Dg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "7.0.0-beta.34", - "@babel/plugin-syntax-class-properties": "7.0.0-beta.34" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.34.tgz", - "integrity": "sha512-yW9xsvPsXWwwDnOWzUm+Q8cIf+gkCOdsIXF51NTaLgX5TdOBmErLBFUrlbY3oveqsiEa26qlA6nwM9/Wkh4gog==", - "dev": true, - "requires": { - "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.34" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0-beta.34.tgz", - "integrity": "sha512-Tyn/lappcQOloRto93ys5dpG0oIFRbTAUYjGwrsKr3kOAcG8mTwg0ZARohgD3UN9Q6/MvkXNurmwooTAVVsQbA==", - "dev": true, - "requires": { - "@babel/plugin-syntax-optional-catch-binding": "7.0.0-beta.34" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0-beta.34.tgz", - "integrity": "sha512-Mgy3leJpvooJrCEt43DprrEI9DM5K/isO26cn3Yzj9xcRMblU9FG0JBRMuujCTWnuFWATQ60yvPdfGXaqXoUyQ==", - "dev": true, - "requires": { - "@babel/helper-regex": "7.0.0-beta.34", - "regexpu-core": "4.1.3" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.34.tgz", - "integrity": "sha512-4y/+dUbzOgWE5gOgK1KKTd/3bSH1Tc97Ba5xlvZk+ufTBiHsG+WSvdaK0aNtPmBcA3mvfHFDYiLPRslU/X88ww==", - "dev": true - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0-beta.34.tgz", - "integrity": "sha512-XuTrpRivb04Pd8Knmduuo1ejgrmpK2dX5fftVmgVT+noX9/MC1s+A0lAqmAISRy029uth5hM34oappUYz0LrFw==", - "dev": true - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-beta.34.tgz", - "integrity": "sha512-uw+hEEeSl9PCNR//d3pGEwLHXSrZSPmlXZND9N/IsZfY36f7i+Pe140UWVxwzOcNCr4IKWyTlsu3FxisFsxfzw==", - "dev": true - }, - "@babel/plugin-syntax-jsx": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0-beta.34.tgz", - "integrity": "sha512-ul0JfSnAyhhsaW+sUlLQTXNpFVrClUZrRNhS8UKgOtvT89X/1qyB6yINEGRULrFn8Ip7lsVFreib129b76UHQA==", - "dev": true - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.34.tgz", - "integrity": "sha512-tnSX0iX2vkKI6Alam/3Q4m8cuzuS0HeCIgQHVMkd6aVnIeHYJOaPwjCGeJJHAOg/lPXpc5sGecGI2cu2q0abiA==", - "dev": true - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0-beta.34.tgz", - "integrity": "sha512-zrWVkoPmR5aABRNhB4rbCwYq4SykVI7lmcJwLhFTN8yDqoydjj+KE583ikFJCuNjbZ+byU8rMm7bOhwJvrioWw==", - "dev": true - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.34.tgz", - "integrity": "sha512-ECCEH+tr0Ei5DPglMTR51KV/h1lLac0N06tf69/sKxjq+m+mLiYM2y82AgEFBtAxlRepkBt3LcnVM8AvWv+W4A==", - "dev": true - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.34.tgz", - "integrity": "sha512-pl5MtQkMq9Aj0Vk4wdTHwVjw38d7C+OceVlcTnW6epscpdG3DJgzeFviteBfZg/IzULvYCQ4p1leN+Kw3PMLag==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "7.0.0-beta.34", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.34.tgz", - "integrity": "sha512-yEXiN1GOkHdTd3ILpyaWrOHwEl3yk7kSuhkPpwevPDEbAazmcEKO73YWZz1b/pIbhTlP9swZvKiKVgaGDpc32A==", - "dev": true - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.34.tgz", - "integrity": "sha512-O1uLtQyaDYDDbKcib27yQnVmWQQYGWq1aR9wsQCHG9+qt24TOqN3NIUMWako1Blfq9w4bVX/2rIp4kIr1DPiOQ==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-beta.34.tgz", - "integrity": "sha512-t7l8FnjgpYOIYAmdttb50EjaQxqt3tRBbzR8jJ1Q7m6qDmaY+pt0XjbSDE63vg4jjWL9N/2IkTWevgd0nbyoGQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.34", - "@babel/helper-define-map": "7.0.0-beta.34", - "@babel/helper-function-name": "7.0.0-beta.34", - "@babel/helper-optimise-call-expression": "7.0.0-beta.34", - "@babel/helper-replace-supers": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.34.tgz", - "integrity": "sha512-aAD9Fweud+e7wE4uWRfPfWUhx+l63Ywez5ZZ5da/gLMV5dUV3wVg6Rjnduf4VWyUUvdr/meAgT32rMqDVxAb2A==", - "dev": true - }, - "@babel/plugin-transform-destructuring": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.34.tgz", - "integrity": "sha512-D7UNymLG3VmIib9S3u9sMPFmnOoWNKkJQz7GI/NLMgtNem0Ew900ZhS9fxsqGF9muawhQeni9adKr15WdDsUCg==", - "dev": true - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.34.tgz", - "integrity": "sha512-v+LVs2wE4GnUexdFiINF6KrBptyOxE5OArl3gaBUcdIgLAh/CCHixbLr9VaJPaGlFf6D66K/s9KYNxXkgHQvBQ==", - "dev": true - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.34.tgz", - "integrity": "sha512-RibodTBl0AywXHMEhI0UUywlLiWiP42ACvun4+QOSkOQRMj+PKkyNbC/Nc7bgE8vgih+LrnzJ0KXI3lvHs6q8A==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.34.tgz", - "integrity": "sha512-BwSxmFpZJ0pWjokMiXdhuH+oApTkrn26m50LMQk346A6Zvu+HJ/UdmaS4zw58809twrggOMaEoip0XuPfMPLUw==", - "dev": true - }, - "@babel/plugin-transform-function-name": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.34.tgz", - "integrity": "sha512-JO/xCDIiQnNbnkNQNVeOXA5rzcBGotxWH5Z8K5NROeMofIJqB30cJWJwF45WpkPlSVJZV9nMVgMVASD9LEJpnQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.34.tgz", - "integrity": "sha512-rqyb71ffA6Lqlt29G0QotDTt8z/vGghGJyftrG88+9/M6LC+azaR1yzoaoV2UQl+YsErEtm+OHfD+1F7aCVCNg==", - "dev": true - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.34.tgz", - "integrity": "sha512-9T32hwmkBgrkuQk0Lu9lLGNIUFzvCNMN1GS7/M/3P+MgXj610yQTlYF1a1ED1qLP1xFR1EbB4B43wbtIyXUmqQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.34.tgz", - "integrity": "sha512-Ipi7A5L7SsYdXKy4J73TKBLrm1SMA9yJq1KgjSDDpn94Q2Ylpo2cXRfr0OqwgYK7d2KzBSrFypIULLQgOXY2Ag==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.34", - "@babel/helper-simple-access": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.0.0-beta.34.tgz", - "integrity": "sha512-Z0xcWUq52XASvWgsqAMW0XRzxJkF5Ymyt5xuFaCWOyLgKS65tkD5U6sElfh/TSq+3x6ooDsWUeKWo8kJ7F76hw==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.0.0-beta.34.tgz", - "integrity": "sha512-3mcs1onCT78fymeGPzxhOriit1nMNnR1iIS6xNatS7w2tsldDY87bEUUhfEC1xUbuSEIKk8QeEoKth0o2sPU/A==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0-beta.34.tgz", - "integrity": "sha512-HC3FJS9k9+KU7ReGah9Ii7ADdF8NNP4S0yHtmp22sb9MJOCahRMvjqdvaLAGomUMEPMeo/F0YVAJPdgNWflX3w==", - "dev": true - }, - "@babel/plugin-transform-object-super": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.34.tgz", - "integrity": "sha512-jiP0H9E0rjLVBKYHiSxO47iFyyKZQeSvViXFHipw93W7Fk+nCFZmtM1dl4Vy8KaKd4ET8d2zck83EcPU6flPtw==", - "dev": true, - "requires": { - "@babel/helper-replace-supers": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.34.tgz", - "integrity": "sha512-pEQtoqpwaEgE7ZIqgLJmJTrKIJ19kaUr2ExoxujbQe/m96eJF7d6TtBBLbpHbJEgGBbjzWI7uRQRSnTTfoomZQ==", - "dev": true, - "requires": { - "@babel/helper-call-delegate": "7.0.0-beta.34", - "@babel/helper-get-function-arity": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.0.0-beta.34.tgz", - "integrity": "sha512-sx11AW/Vo3rtPLjUvzH7lPdHI4AqpL8OA04hGkfhMr9vwEf6f36+12m0lb6adK4rta6hyjXccN7B7nyjlQl3Yg==", - "dev": true - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.0.0-beta.34.tgz", - "integrity": "sha512-J2TSjbhuXf9kEarsU3DlwwarVsQky5H/H5XNoIMPbWMoaTaG5q1GjRhQaYylvVe58siAkTvmXdy9JD/XJBDYcA==", - "dev": true, - "requires": { - "@babel/helper-builder-react-jsx": "7.0.0-beta.34", - "@babel/plugin-syntax-jsx": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.0.0-beta.34.tgz", - "integrity": "sha512-pHfuWmIUgn4R78+9CvbtTeG1zx+3l7uVWOz0sqgTqnLnaWFzpDnwbPOeTYW73htP+NkqTHb5aNBQyN3hi3/85g==", - "dev": true, - "requires": { - "@babel/plugin-syntax-jsx": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.0.0-beta.34.tgz", - "integrity": "sha512-2AQcMrZCACdAHhXIwopIyrs/UrkkL/NAJbKWuX2otiiw0f6+VWY6Y/FbPgYqMEGIkkXXgI1ctC2DT9SqwXdR3g==", - "dev": true, - "requires": { - "@babel/plugin-syntax-jsx": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.34.tgz", - "integrity": "sha512-r+RFXpfW11wlq9omPaw4ELCBQdTJDdYRoiO45G161xVRUBRVURU+acRe6VhNIhMv+fZGTVi9Y5/tuIZc/0sOIw==", - "dev": true, - "requires": { - "regenerator-transform": "0.12.1" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.34.tgz", - "integrity": "sha512-ShonB4hSsHxSP3hhpyXIJShm2et0NW40nJCaSlZvw0yCPeIr0IEnwB4g8gjZXsYBUWLiEz3JanmWuZEIIrBBTA==", - "dev": true - }, - "@babel/plugin-transform-spread": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.34.tgz", - "integrity": "sha512-loj8b4CTpgOjmGCODMCZ6Nbn+CZWcgU9MsFBWomfliOyQpDu/9WqNuRdiGcMlUmbnIoVpiNUAf5ZKcd3wAgJnw==", - "dev": true - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.34.tgz", - "integrity": "sha512-tn7GJz07Z8pV8j2mCGeaqIc/pEtCtnLmCGpAfbwOBkccyEI81YUBF0HnvqHdMTLcmN6A7TXBZmaXovEOmlZuhQ==", - "dev": true, - "requires": { - "@babel/helper-regex": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.34.tgz", - "integrity": "sha512-3cZ6ZW8ONemEbhpA+zM+yEuINmxqyVNiU2vo3je/7FjslFwbYR75eU4sLGVTyGs00DJ5QVSImDOVM9eHdyc8yw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.34" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.34.tgz", - "integrity": "sha512-zK2W/I6/eafbN7eWtrn7mWUpRdZMbV2GrOfS9yzbMifs9QvLK3jSmXXDSYu1dUPaG2sufxyp73mzvvr3owzpAg==", - "dev": true - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.34.tgz", - "integrity": "sha512-tMMOEBxLOzEJ59k3EFzOeU2RuF5cHndvto9LeBhtkrBmG6O+gZXmMSzIxv8QbWnS92ujmUIHvJSDSfgdsOHlsQ==", - "dev": true, - "requires": { - "@babel/helper-regex": "7.0.0-beta.34", - "regexpu-core": "4.1.3" - } - }, - "@babel/preset-env": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.0.0-beta.34.tgz", - "integrity": "sha512-3bNsDe+QzKJePf5IJ14O6gvE5PkJgC3nQ2bBZDKanccReTffc5n4/2TeFyfTyT4S23MqKhXF8AhMES4s4lLs/g==", - "dev": true, - "requires": { - "@babel/plugin-check-constants": "7.0.0-beta.34", - "@babel/plugin-proposal-async-generator-functions": "7.0.0-beta.34", - "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.34", - "@babel/plugin-proposal-optional-catch-binding": "7.0.0-beta.34", - "@babel/plugin-proposal-unicode-property-regex": "7.0.0-beta.34", - "@babel/plugin-syntax-async-generators": "7.0.0-beta.34", - "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.34", - "@babel/plugin-syntax-optional-catch-binding": "7.0.0-beta.34", - "@babel/plugin-transform-arrow-functions": "7.0.0-beta.34", - "@babel/plugin-transform-async-to-generator": "7.0.0-beta.34", - "@babel/plugin-transform-block-scoped-functions": "7.0.0-beta.34", - "@babel/plugin-transform-block-scoping": "7.0.0-beta.34", - "@babel/plugin-transform-classes": "7.0.0-beta.34", - "@babel/plugin-transform-computed-properties": "7.0.0-beta.34", - "@babel/plugin-transform-destructuring": "7.0.0-beta.34", - "@babel/plugin-transform-duplicate-keys": "7.0.0-beta.34", - "@babel/plugin-transform-exponentiation-operator": "7.0.0-beta.34", - "@babel/plugin-transform-for-of": "7.0.0-beta.34", - "@babel/plugin-transform-function-name": "7.0.0-beta.34", - "@babel/plugin-transform-literals": "7.0.0-beta.34", - "@babel/plugin-transform-modules-amd": "7.0.0-beta.34", - "@babel/plugin-transform-modules-commonjs": "7.0.0-beta.34", - "@babel/plugin-transform-modules-systemjs": "7.0.0-beta.34", - "@babel/plugin-transform-modules-umd": "7.0.0-beta.34", - "@babel/plugin-transform-new-target": "7.0.0-beta.34", - "@babel/plugin-transform-object-super": "7.0.0-beta.34", - "@babel/plugin-transform-parameters": "7.0.0-beta.34", - "@babel/plugin-transform-regenerator": "7.0.0-beta.34", - "@babel/plugin-transform-shorthand-properties": "7.0.0-beta.34", - "@babel/plugin-transform-spread": "7.0.0-beta.34", - "@babel/plugin-transform-sticky-regex": "7.0.0-beta.34", - "@babel/plugin-transform-template-literals": "7.0.0-beta.34", - "@babel/plugin-transform-typeof-symbol": "7.0.0-beta.34", - "@babel/plugin-transform-unicode-regex": "7.0.0-beta.34", - "browserslist": "2.9.1", - "invariant": "2.2.2", - "semver": "5.4.1" - } - }, - "@babel/preset-react": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0-beta.34.tgz", - "integrity": "sha512-yITAUEZz7pqlXaIhn2nZZIB2wfjT/uSh5XuSMcWu2eIuUCJP162UYA1yqM6tLZ2dBmxcMmeF9FPL8fc1L1FFIQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-jsx": "7.0.0-beta.34", - "@babel/plugin-transform-react-display-name": "7.0.0-beta.34", - "@babel/plugin-transform-react-jsx": "7.0.0-beta.34", - "@babel/plugin-transform-react-jsx-self": "7.0.0-beta.34", - "@babel/plugin-transform-react-jsx-source": "7.0.0-beta.34" - } - }, - "@babel/preset-stage-3": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/preset-stage-3/-/preset-stage-3-7.0.0-beta.34.tgz", - "integrity": "sha512-CMtfBWn5uaz/EAwot5+mQcbyijDGPQJKRSSfl9dql0fzldwE5cwYD0LeIE3g6qXik/r6u8J8fQTbVjSbPS21Vw==", - "dev": true, - "requires": { - "@babel/plugin-proposal-async-generator-functions": "7.0.0-beta.34", - "@babel/plugin-proposal-class-properties": "7.0.0-beta.34", - "@babel/plugin-proposal-object-rest-spread": "7.0.0-beta.34", - "@babel/plugin-proposal-optional-catch-binding": "7.0.0-beta.34", - "@babel/plugin-proposal-unicode-property-regex": "7.0.0-beta.34", - "@babel/plugin-syntax-dynamic-import": "7.0.0-beta.34" - } - }, - "@babel/template": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.34.tgz", - "integrity": "sha512-dldLn8DUQEb3aq8bIiMcmffKIKn4jetne3cnzS2nk0LBSm/8Uhh0yQc4REk4uRs0Bz24iUBdlrb/9cjDqTDgow==", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34", - "babylon": "7.0.0-beta.34", - "lodash": "4.17.4" - } - }, - "@babel/traverse": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.34.tgz", - "integrity": "sha512-FmqSqGu+x3kjzWOww7Ro9Od7BPNCwgqZOrzj9hmvACCrxXcHIhwqFG+mFZSDfew35QlAh54YIfWGq0xGW5ztJw==", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.34", - "@babel/helper-function-name": "7.0.0-beta.34", - "@babel/types": "7.0.0-beta.34", - "babylon": "7.0.0-beta.34", - "debug": "3.1.0", - "globals": "10.4.0", - "invariant": "2.2.2", - "lodash": "4.17.4" - } - }, - "@babel/types": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.34.tgz", - "integrity": "sha512-KD5CF1dmZE8i9ggW9L+YSak7j+b3YTGQZUs9WMF6tgG/aw2x/VhTxfooqXR0Bbfzkb5M6WGiTHbzCmx8ll4tQQ==", - "dev": true, - "requires": { - "esutils": "2.0.2", - "lodash": "4.17.4", - "to-fast-properties": "2.0.0" - } - }, - "acorn": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", - "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", - "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", - "dev": true, - "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, - "ajv": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz", - "integrity": "sha1-s4u4h22ehr7plJVqBOch6IskjrI=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "requires": { - "micromatch": "2.3.11", - "normalize-path": "2.1.1" - } - }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "asn1.js": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", - "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, - "requires": { - "util": "0.10.3" - } - }, - "ast-types": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz", - "integrity": "sha1-ju8IJ/BN/w7IhXupJavj/qYZTlI=" - }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.4" - } - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "attr-accept": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.0.tgz", - "integrity": "sha1-tc01In8WOTWo8d4Q7T66FpQfa+Y=" - }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000778", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000778", - "electron-to-chromium": "1.3.27" - } - } - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-loader": { - "version": "8.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.0-beta.0.tgz", - "integrity": "sha512-qVXXyIqTrLBH3Ki2VCJog1fUd6qzKUk9lHS34WJPW93Bh0BUvXTFSD5ZkG3a5+Uxxje+RgCk8Y7RyB6zyK9rWw==", - "dev": true, - "requires": { - "find-cache-dir": "1.0.0", - "loader-utils": "1.1.0", - "mkdirp": "0.5.1" - } - }, - "babel-plugin-syntax-dynamic-import": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", - "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", - "dev": true - }, - "babylon": { - "version": "7.0.0-beta.34", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.34.tgz", - "integrity": "sha512-ribEzWEhWKKjY+1FdKCryo+HiN/1idPjUB8vyR5Yf221MtGzCd5+7OwPvWvYHerHHC2eJLr6MhvumbTocXGY7Q==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base62": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", - "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ=" - }, - "base64-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", - "dev": true - }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browserify-aes": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", - "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", - "dev": true, - "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "browserify-cipher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", - "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", - "dev": true, - "requires": { - "browserify-aes": "1.1.1", - "browserify-des": "1.0.0", - "evp_bytestokey": "1.0.3" - } - }, - "browserify-des": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", - "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.5" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "1.0.6" - } - }, - "browserslist": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.9.1.tgz", - "integrity": "sha512-3n3nPdbUqn3nWmsy4PeSQthz2ja1ndpoXta+dwFFNhveGjMg6FXpWYe12vsTpNoXJbzx3j7GZXdtoVIdvh3JbA==", - "dev": true, - "requires": { - "caniuse-lite": "1.0.30000778", - "electron-to-chromium": "1.3.27" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "1.2.1", - "ieee754": "1.1.8", - "isarray": "1.0.0" - } - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000778", - "lodash.memoize": "4.1.2", - "lodash.uniq": "4.5.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000778", - "electron-to-chromium": "1.3.27" - } - } - } - }, - "caniuse-db": { - "version": "1.0.30000778", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000778.tgz", - "integrity": "sha1-Fnxg6VQqKqYFN8RG+ziB2FOjByo=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000778", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000778.tgz", - "integrity": "sha1-8efLixOx9nREAikddfC81MMWA2k=", - "dev": true - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - } - }, - "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" - } - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "requires": { - "anymatch": "1.3.2", - "async-each": "1.0.1", - "glob-parent": "2.0.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "2.0.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "1.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "classnames": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz", - "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=" - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - } - }, - "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "1.5.1" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "1.0.3", - "color-convert": "1.9.1", - "color-string": "0.3.0" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "0.11.4", - "css-color-names": "0.0.4", - "has": "1.0.1" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "0.1.4" - } - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "create-ecdh": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", - "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" - } - }, - "create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "sha.js": "2.4.9" - } - }, - "create-hmac": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", - "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.9" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "1.0.0", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.5", - "randomfill": "1.0.3" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-loader": { - "version": "0.28.7", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.7.tgz", - "integrity": "sha512-GxMpax8a/VgcfRrVy0gXD6yLd5ePYbXX/5zGgTVYp4wXtJklS8Z2VaUArJgc//f6/Dzil7BaJObdSv8eKKCPgg==", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "css-selector-tokenizer": "0.7.0", - "cssnano": "3.10.0", - "icss-utils": "2.1.0", - "loader-utils": "1.1.0", - "lodash.camelcase": "4.3.0", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-modules-extract-imports": "1.1.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0", - "postcss-value-parser": "3.3.0", - "source-list-map": "2.0.0" - } - }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "dev": true, - "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - } - } - } - }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", - "dev": true - }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "6.7.7", - "decamelize": "1.2.0", - "defined": "1.0.0", - "has": "1.0.1", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-calc": "5.3.1", - "postcss-colormin": "2.2.2", - "postcss-convert-values": "2.6.1", - "postcss-discard-comments": "2.0.4", - "postcss-discard-duplicates": "2.1.0", - "postcss-discard-empty": "2.1.0", - "postcss-discard-overridden": "0.1.1", - "postcss-discard-unused": "2.2.3", - "postcss-filter-plugins": "2.0.2", - "postcss-merge-idents": "2.1.7", - "postcss-merge-longhand": "2.0.2", - "postcss-merge-rules": "2.1.2", - "postcss-minify-font-values": "1.0.5", - "postcss-minify-gradients": "1.0.5", - "postcss-minify-params": "1.2.2", - "postcss-minify-selectors": "2.1.1", - "postcss-normalize-charset": "1.1.1", - "postcss-normalize-url": "3.0.8", - "postcss-ordered-values": "2.2.3", - "postcss-reduce-idents": "2.4.0", - "postcss-reduce-initial": "1.0.1", - "postcss-reduce-transforms": "1.0.4", - "postcss-svgo": "2.1.6", - "postcss-unique-selectors": "2.0.2", - "postcss-value-parser": "3.3.0", - "postcss-zindex": "2.2.0" - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "1.2.3", - "source-map": "0.5.7" - } - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "0.10.37" - } - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "diffie-hellman": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", - "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.5" - } - }, - "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.27", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz", - "integrity": "sha1-eOy4o5kGYYe7N07t412ccFZagD0=", - "dev": true - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "0.4.19" - } - }, - "enhanced-resolve": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", - "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "object-assign": "4.1.1", - "tapable": "0.2.8" - } - }, - "errno": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", - "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", - "dev": true, - "requires": { - "prr": "0.0.0" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es3ify": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/es3ify/-/es3ify-0.1.4.tgz", - "integrity": "sha1-rZ+l3xrjTz8x4SEbWBiy1RB439E=", - "requires": { - "esprima-fb": "3001.1.0-dev-harmony-fb", - "jstransform": "3.0.0", - "through": "2.3.8" - }, - "dependencies": { - "esprima-fb": { - "version": "3001.1.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", - "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=" - } - } - }, - "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", - "dev": true, - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-symbol": "3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.0", - "estraverse": "4.2.0" - } - }, - "esmangle-evaluator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz", - "integrity": "sha1-Yg2GbvSGGzMR91dm1SqFcrs8YzY=" - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", - "dev": true, - "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.37" - } - }, - "eventemitter3": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", - "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=" - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "2.2.3" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "falafel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/falafel/-/falafel-1.2.0.tgz", - "integrity": "sha1-wY0k71CRF0pJfzGM0ksCaiXN2rQ=", - "requires": { - "acorn": "1.2.2", - "foreach": "2.0.5", - "isarray": "0.0.1", - "object-keys": "1.0.11" - }, - "dependencies": { - "acorn": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz", - "integrity": "sha1-yM4n3grMdtiW0rH6099YjZ6C8BQ=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } - } - }, - "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", - "dev": true - }, - "fbjs": { - "version": "0.8.16", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", - "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", - "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.17" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "make-dir": "1.1.0", - "pkg-dir": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "globals": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-10.4.0.tgz", - "integrity": "sha512-uNUtxIZpGyuaq+5BqGGQHsL4wUlJAXRqOm6g3Y48/CWNGTLONgBibI0lh6lGxjR2HljFYUfszb+mk4WkgMntsA==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, - "requires": { - "function-bind": "1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", - "dev": true - }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", - "dev": true - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, - "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", - "dev": true, - "requires": { - "postcss": "6.0.14" - }, - "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "4.5.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", - "dev": true - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "inline-process-browser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/inline-process-browser/-/inline-process-browser-1.0.0.tgz", - "integrity": "sha1-RqYbFT3TybFiSxoAYm7bT39BTyI=", - "requires": { - "falafel": "1.2.0", - "through2": "0.6.5" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "invariant": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", - "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", - "dev": true, - "requires": { - "loose-envify": "1.3.1" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.11.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "1.1.1" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.3" - } - }, - "js-base64": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.0.tgz", - "integrity": "sha512-Wehd+7Pf9tFvGb+ydPm9TjYjV8X1YHOVyG8QyELZxEMqOhemVwGRmoG8iQ/soqI3n8v4xn59zaLxiCJiaaRzKA==", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "2.7.3" - } - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jstransform": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-3.0.0.tgz", - "integrity": "sha1-olkats7o2XvzvoMNv6IxO4fNZAs=", - "requires": { - "base62": "0.1.1", - "esprima-fb": "3001.1.0-dev-harmony-fb", - "source-map": "0.1.31" - }, - "dependencies": { - "esprima-fb": { - "version": "3001.1.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz", - "integrity": "sha1-t303q8046gt3Qmu4vCkizmtCZBE=" - }, - "source-map": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", - "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=", - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "1.0.0" - } - }, - "lie": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.0.2.tgz", - "integrity": "sha1-/9oh17uibzd8rYZdNkmy/Izjn+o=", - "requires": { - "es3ify": "0.1.4", - "immediate": "3.0.6", - "inline-process-browser": "1.0.0", - "unreachable-branch-transform": "0.3.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", - "dev": true - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" - } - }, - "localforage": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.5.5.tgz", - "integrity": "sha1-VfwcOoikf2f1+sbxIxsl/xNVZCM=", - "requires": { - "lie": "3.0.2" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "requires": { - "js-tokens": "3.0.2" - } - }, - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "macaddress": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", - "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", - "dev": true - }, - "make-dir": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz", - "integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, - "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" - }, - "dependencies": { - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - } - } - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "1.1.0" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "0.1.4", - "readable-stream": "2.3.3" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" - } - }, - "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.8" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" - } - }, - "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, - "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.1.7", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.3", - "stream-browserify": "2.0.1", - "stream-http": "2.7.2", - "string_decoder": "1.0.3", - "timers-browserify": "2.0.4", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.4.1", - "validate-npm-package-license": "3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "prepend-http": "1.0.4", - "query-string": "4.3.4", - "sort-keys": "1.1.2" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "2.0.1" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", - "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", - "dev": true - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "1.1.0" - } - }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - }, - "parse-asn1": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", - "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", - "dev": true, - "requires": { - "asn1.js": "4.9.2", - "browserify-aes": "1.1.1", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.1" - } - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", - "dev": true, - "requires": { - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.9" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "2.1.0" - } - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.0", - "source-map": "0.5.7", - "supports-color": "3.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-message-helpers": "2.0.0", - "reduce-css-calc": "1.3.0" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "1.1.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", - "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqid": "4.1.1" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-api": "1.6.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "vendors": "1.0.1" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000778", - "electron-to-chromium": "1.3.27" - } - } - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "uniqs": "2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3" - } - }, - "postcss-modules-extract-imports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz", - "integrity": "sha1-thTJcgvmgW6u41+zpfqh26agXds=", - "dev": true, - "requires": { - "postcss": "6.0.14" - }, - "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "4.5.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "dev": true, - "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.14" - }, - "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "4.5.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "dev": true, - "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.14" - }, - "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "4.5.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "dev": true, - "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.14" - }, - "dependencies": { - "postcss": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.14.tgz", - "integrity": "sha512-NJ1z0f+1offCgadPhz+DvGm5Mkci+mmV5BqD13S992o0Xk9eElxUfPPF+t2ksH5R/17gz4xVK8KWocUQ5o3Rog==", - "dev": true, - "requires": { - "chalk": "2.3.0", - "source-map": "0.6.1", - "supports-color": "4.5.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "2.1.0", - "normalize-url": "1.9.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "1.0.2", - "indexes-of": "1.0.1", - "uniq": "1.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "2.1.0", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "svgo": "0.7.2" - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", - "dev": true - }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "2.0.6" - } - }, - "prop-types": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", - "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" - } - }, - "prr": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", - "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", - "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "parse-asn1": "5.1.0", - "randombytes": "2.0.5" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", - "requires": { - "performance-now": "2.1.0" - } - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "randombytes": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", - "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "randomfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", - "integrity": "sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==", - "dev": true, - "requires": { - "randombytes": "2.0.5", - "safe-buffer": "5.1.1" - } - }, - "react": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.2.0.tgz", - "integrity": "sha512-ZmIomM7EE1DvPEnSFAHZn9Vs9zJl5A9H7el0EGTE6ZbW9FKe/14IYAlPbC8iH25YarEQxZL+E8VW7Mi7kfQrDQ==", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, - "react-addons-shallow-compare": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.2.tgz", - "integrity": "sha1-GYoAuR/DdiPbZKKP0XtZa6NicC8=", - "requires": { - "fbjs": "0.8.16", - "object-assign": "4.1.1" - } - }, - "react-autosuggest": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-9.3.2.tgz", - "integrity": "sha512-/wY64zaFVny9OqcRvLwhcJz9SzpYK5qni+7sjrQOPKN1gFKi5cxy+cMOPEE7u5ZfXLj5mDxf+RNxNWM1wo39Xg==", - "requires": { - "prop-types": "15.6.0", - "react-autowhatever": "10.1.0", - "shallow-equal": "1.0.0" - } - }, - "react-autowhatever": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-autowhatever/-/react-autowhatever-10.1.0.tgz", - "integrity": "sha512-LMZggoRgcmldAMyABY3Dz/DRiTQViMsQllXtOsDrZeBRwPIfn0RAOySaQMUNyECrHaCB5pm66jgQvkyNSh/BjA==", - "requires": { - "prop-types": "15.6.0", - "react-themeable": "1.1.0", - "section-iterator": "2.0.0" - } - }, - "react-dom": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.0.tgz", - "integrity": "sha512-zpGAdwHVn9K0091d+hr+R0qrjoJ84cIBFL2uU60KvWBPfZ7LPSrfqviTxGHWN0sjPZb2hxWzMexwrvJdKePvjg==", - "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.0" - } - }, - "react-dropzone": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.2.3.tgz", - "integrity": "sha512-QAXuGDqBUPC0p560pskC3yyS8I1jJUnzvZC0PHrd5NayYBQRD4poQfM1D/bxg4jhUaFU4avNhOB3ehMQd4JMvA==", - "requires": { - "attr-accept": "1.1.0", - "prop-types": "15.6.0" - } - }, - "react-icon-base": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/react-icon-base/-/react-icon-base-2.1.0.tgz", - "integrity": "sha1-oZbjP98eeqof2jrvu2i9rZ6Cp50=" - }, - "react-icons": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-2.2.7.tgz", - "integrity": "sha512-0n4lcGqzJFcIQLoQytLdJCE0DKSA9dkwEZRYoGrIDJZFvIT6Hbajx5mv9geqhqFiNjUgtxg8kPyDfjlhymbGFg==", - "requires": { - "react-icon-base": "2.1.0" - } - }, - "react-input-autosize": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz", - "integrity": "sha512-uAfIE4XEfBNXqjqQvd31Eoo20UkVk0xHJpfgP8HRT8gLczaN4LEmB1e2d8CJ5ziEt4clWnsk/1+QhTN27iO/EA==", - "requires": { - "prop-types": "15.6.0" - } - }, - "react-stickynode": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/react-stickynode/-/react-stickynode-1.4.0.tgz", - "integrity": "sha512-36Rm+wt9rF+InkF/iT+Je1DCCCJyNd610FvS45JA3cFhbzKqrt4ZE6/oPv160Anlp/ppmbkFgRcmSq4mwlb78A==", - "requires": { - "classnames": "2.2.5", - "prop-types": "15.6.0", - "react-addons-shallow-compare": "15.6.2", - "subscribe-ui-event": "1.0.14" - } - }, - "react-tagsinput": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/react-tagsinput/-/react-tagsinput-3.19.0.tgz", - "integrity": "sha512-ni+/qnZrYrvLg83LtTFHErKy1KQHL0fi0Y6C5jgC1dNUePE9cS/OlQ4XH6JRSjv9GGoeVE0R/ujSBaS1uzCRYQ==" - }, - "react-themeable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz", - "integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=", - "requires": { - "object-assign": "3.0.0" - }, - "dependencies": { - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" - } - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.3", - "set-immediate-shim": "1.0.1" - } - }, - "recast": { - "version": "0.10.43", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz", - "integrity": "sha1-uV1Q9tYHYaX2JS4V2AZ4FoSRzn8=", - "requires": { - "ast-types": "0.8.15", - "esprima-fb": "15001.1001.0-dev-harmony-fb", - "private": "0.1.8", - "source-map": "0.5.7" - }, - "dependencies": { - "esprima-fb": { - "version": "15001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-Q761fsJujPI3092LM+QlM1d/Jlk=" - } - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "math-expression-evaluator": "1.2.17", - "reduce-function-call": "1.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-5.1.3.tgz", - "integrity": "sha512-Yjy6t7jFQczDhYE+WVm7pg6gWYE258q4sUkk9qDErwXJIqx7jU9jGrMFHutJK/SRfcg7MEkXjGaYiVlOZyev/A==", - "dev": true, - "requires": { - "regenerate": "1.3.3" - } - }, - "regenerator-transform": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.12.1.tgz", - "integrity": "sha512-RAcAGzEuU74v7FgnOLT7qG1Nk5mTJSXzZjYAfZ3gZWQxNI6aWn0ny2uX6ZRdYT/oZraT3o+YgFt3rDBEfUPZjw==", - "dev": true, - "requires": { - "private": "0.1.8" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "regexpu-core": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.1.3.tgz", - "integrity": "sha512-mB+njEzO7oezA57IbQxxd6fVPOeWKDmnGvJ485CwmfNchjHe5jWwqKepapmzUEj41yxIAqOg+C4LbXuJlkiO8A==", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regenerate-unicode-properties": "5.1.3", - "regjsgen": "0.3.0", - "regjsparser": "0.2.1", - "unicode-match-property-ecmascript": "1.0.3", - "unicode-match-property-value-ecmascript": "1.0.1" - } - }, - "regjsgen": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.3.0.tgz", - "integrity": "sha1-DuSj6SdkMM2iXx54nqbBW4ewy0M=", - "dev": true - }, - "regjsparser": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.2.1.tgz", - "integrity": "sha1-w3h1U/rwTndcMCEC7zRtmVAA7Bw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", - "dev": true, - "requires": { - "path-parse": "1.0.5" - } - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "0.1.4" - } - }, - "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", - "dev": true, - "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "5.5.1" - } - }, - "section-iterator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz", - "integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio=" - }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "sha.js": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", - "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "shallow-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.0.0.tgz", - "integrity": "sha1-UI0YOLPeWQq4dXsBGyXkMJAJRfc=" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - }, - "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true, - "requires": { - "spdx-license-ids": "1.2.2" - } - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3" - } - }, - "stream-http": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", - "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "style-loader": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.19.0.tgz", - "integrity": "sha512-9mx9sC9nX1dgP96MZOODpGC6l1RzQBITI2D5WJhu+wnbrSYVKLGuy14XJSLVQih/0GFrPpjelt+s//VcZQ2Evw==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.3.0" - } - }, - "subscribe-ui-event": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/subscribe-ui-event/-/subscribe-ui-event-1.0.14.tgz", - "integrity": "sha1-xQYQS8Ncert2LrNHtZVEKiVnJn8=", - "requires": { - "eventemitter3": "2.0.3", - "lodash": "4.17.4", - "raf": "3.4.0" - } - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "1.0.4", - "colors": "1.1.2", - "csso": "2.3.2", - "js-yaml": "3.7.0", - "mkdirp": "0.5.1", - "sax": "1.2.4", - "whet.extend": "0.9.9" - } - }, - "tapable": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", - "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": "1.0.34", - "xtend": "4.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "timers-browserify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", - "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", - "dev": true, - "requires": { - "setimmediate": "1.0.5" - } - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.17", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", - "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "uglifyjs-webpack-plugin": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", - "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", - "dev": true, - "requires": { - "source-map": "0.5.7", - "uglify-js": "2.8.29", - "webpack-sources": "1.1.0" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.3.tgz", - "integrity": "sha512-iG/2t0F2LAU8aZYPkX5gi7ebukHnr3sWFESpb+zPQeeaQwOkfoO6ZW17YX7MdRPNG9pCy+tjzGill+Ah0Em0HA==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.3.tgz", - "integrity": "sha512-nFcaBFcr08UQNF15ZgI5ISh3yUnQm7SJRRxwYrL5VYX46pS+6Q7TCTv4zbK+j6/l7rQt0mMiTL2zpmeygny6rA==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "1.0.3", - "unicode-property-aliases-ecmascript": "1.0.3" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.1.tgz", - "integrity": "sha512-lM8B0FDZQh9yYGgiabRQcyWicB27VLOolSBRIxsO7FeQPtg+79Oe7sC8Mzr8BObDs+G9CeYmC/shHo6OggNEog==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.3.tgz", - "integrity": "sha512-TdDmDOTxEf2ad1g3ZBpM6cqKIb2nJpVlz1Q++casDryKz18tpeMBhSng9hjC1CTQCkOV9Rw2knlSB6iRo7ad1w==", - "dev": true - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqid": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", - "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", - "dev": true, - "requires": { - "macaddress": "0.2.8" - } - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unreachable-branch-transform": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz", - "integrity": "sha1-2ZzExudG0mSSiEW2EdtUsPNHTKo=", - "requires": { - "esmangle-evaluator": "1.0.1", - "recast": "0.10.43", - "through2": "0.6.5" - } - }, - "urijs": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.0.tgz", - "integrity": "sha512-Qs2odXn0hST5VSPVjpi73CMqtbAoanahaqWBujGU+IyMrMqpWcIhDewxQRhCkmqYxuyvICDcSuLdv2O7ncWBGw==" - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true, - "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" - } - }, - "vendors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", - "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", - "dev": true - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "watchpack": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", - "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", - "dev": true, - "requires": { - "async": "2.6.0", - "chokidar": "1.7.0", - "graceful-fs": "4.1.11" - } - }, - "webpack": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.9.1.tgz", - "integrity": "sha512-jONJ0l8nqgiQVkqs15O9TFWLozbFkCgVodVrBXBK/PIBFeGkaOGo30Ov57iQqYRwAWNDM5vyLPZYmAIpPa5QSw==", - "dev": true, - "requires": { - "acorn": "5.2.1", - "acorn-dynamic-import": "2.0.2", - "ajv": "5.5.1", - "ajv-keywords": "2.1.1", - "async": "2.6.0", - "enhanced-resolve": "3.4.1", - "escope": "3.6.0", - "interpret": "1.1.0", - "json-loader": "0.5.7", - "json5": "0.5.1", - "loader-runner": "2.3.0", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "mkdirp": "0.5.1", - "node-libs-browser": "2.1.0", - "source-map": "0.5.7", - "supports-color": "4.5.0", - "tapable": "0.2.8", - "uglifyjs-webpack-plugin": "0.4.6", - "watchpack": "1.4.0", - "webpack-sources": "1.1.0", - "yargs": "8.0.2" - } - }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", - "dev": true, - "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" - }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "dev": true, - "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - } - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index d5d2c47b..00000000 --- a/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "kizuna", - "version": "1.0.0", - "description": ":^)", - "main": "kizuna_js/index.js", - "dependencies": { - "localforage": "^1.5.5", - "lodash": "^4.17.4", - "react": "^16.2.0", - "react-autosuggest": "^9.3.2", - "react-dom": "^16.2.0", - "react-dropzone": "^4.2.3", - "react-icons": "^2.2.7", - "react-input-autosize": "^2.1.2", - "react-stickynode": "^1.4.0", - "react-tagsinput": "^3.19.0", - "urijs": "^1.19.0", - "whatwg-fetch": "^2.0.3" - }, - "devDependencies": { - "@babel/core": "^7.0.0-beta.34", - "@babel/plugin-proposal-class-properties": "^7.0.0-beta.34", - "@babel/preset-env": "^7.0.0-beta.34", - "@babel/preset-react": "^7.0.0-beta.34", - "@babel/preset-stage-3": "^7.0.0-beta.34", - "babel-loader": "^8.0.0-beta.0", - "babel-plugin-syntax-dynamic-import": "^6.18.0", - "css-loader": "^0.28.7", - "style-loader": "^0.19.0", - "webpack": "^3.9.1" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "watch": "npm ci && webpack --watch", - "build": "npm ci && webpack -p" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/austinpray/kizuna.git" - }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/austinpray/kizuna/issues" - }, - "homepage": "https://github.com/austinpray/kizuna#readme" -} diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 2c5af202..00000000 --- a/requirements.in +++ /dev/null @@ -1,35 +0,0 @@ -# web related -Flask - -# global -slackclient # for slack obviously - -gevent -watchdog-gevent - -gunicorn # wsgi server -raven[flask] # sentry error reporting -backoff # retryable functions -cryptography # for auth tokens -boto3 # aws s3 client -arrow # dates and time - -# testing -pytest - -# worker related -dramatiq[rabbitmq, watch] -requests - -# db related -SQLAlchemy -alembic -psycopg2 - -# falcon api related -falcon -ujson - -# mention graph related -graphviz -palettable diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f21e69a7..00000000 --- a/requirements.txt +++ /dev/null @@ -1,61 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile --output-file requirements.txt requirements.in -# -alembic==0.9.10 -argh==0.26.2 # via watchdog -arrow==0.12.1 -asn1crypto==0.24.0 # via cryptography -atomicwrites==1.1.5 # via pytest -attrs==18.1.0 # via pytest -backoff==1.5.0 -blinker==1.4 # via raven -boto3==1.7.57 -botocore==1.10.57 # via boto3, s3transfer -certifi==2018.4.16 # via requests -cffi==1.11.5 # via cryptography -chardet==3.0.4 # via requests -click==6.7 # via flask -cryptography==2.2.2 -docutils==0.14 # via botocore -dramatiq[rabbitmq,watch]==1.3.0 -falcon==1.4.1 -flask==1.0.2 -gevent==1.3.4 -graphviz==0.8.4 -greenlet==0.4.13 # via gevent -gunicorn==19.9.0 -idna==2.7 # via cryptography, requests -itsdangerous==0.24 # via flask -jinja2==2.10 # via flask -jmespath==0.9.3 # via boto3, botocore -mako==1.0.7 # via alembic -markupsafe==1.0 # via jinja2, mako -more-itertools==4.2.0 # via pytest -palettable==3.1.1 -pathtools==0.1.2 # via watchdog -pika==0.12.0 # via dramatiq -pluggy==0.6.0 # via pytest -prometheus-client==0.2.0 # via dramatiq -psycopg2==2.7.5 -py==1.5.4 # via pytest -pycparser==2.18 # via cffi -pytest==3.6.3 -python-dateutil==2.7.3 # via alembic, arrow, botocore -python-editor==1.0.3 # via alembic -python-mimeparse==1.6.0 # via falcon -pyyaml==3.13 # via watchdog -raven[flask]==6.9.0 -requests==2.19.1 -s3transfer==0.1.13 # via boto3 -six==1.11.0 # via cryptography, falcon, more-itertools, pytest, python-dateutil, slackclient, websocket-client -slackclient==1.2.1 -sqlalchemy==1.2.9 -ujson==1.35 -urllib3==1.23 # via requests -watchdog-gevent==0.1 -watchdog==0.8.3 # via dramatiq, watchdog-gevent -websocket-client==0.48.0 # via slackclient -werkzeug==0.14.1 # via flask diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..529302e9 --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from setuptools import setup +import glob + +gevent_deps = [ + 'gevent', + 'watchdog-gevent', +] + +gunicorn_deps = [ + *gevent_deps, + 'gunicorn', +] + +# Where the magic happens: +setup( + name='kizuna', + python_requires='>=3.6.0', + package_dir={'': 'src'}, + packages=[ + 'kizuna.api', + 'kizuna.plugins', + 'kizuna.skills', + 'kizuna.support', + #'kizuna.web', + 'kizuna.worker', + ], + data_files=[('kizuna_static', glob.glob('static/**/*'))], + tests_require=[ + 'pytest' + ], + install_requires=[ + 'SQLAlchemy', + 'alembic', + 'arrow', + 'backoff', + 'boto3', + 'cryptography', + 'psycopg2', + 'raven[flask]', + 'requests', + 'slackclient', + 'slacktools', + 'ujson', + ], + extras_require={ + 'web': [ + #'Flask', + #*gunicorn_deps, + ], + 'api': [ + 'dramatiq[rabbitmq]', + 'falcon', + *gunicorn_deps, + ], + 'worker': [ + 'dramatiq[rabbitmq, watch]', + 'rx>=1.6.1<2', + 'graphviz', + 'palettable', + *gevent_deps, + ] + } +) diff --git a/kizuna/__init__.py b/src/kizuna/__init__.py similarity index 100% rename from kizuna/__init__.py rename to src/kizuna/__init__.py diff --git a/src/kizuna/adapters/__init__.py b/src/kizuna/adapters/__init__.py new file mode 100644 index 00000000..c1b08db9 --- /dev/null +++ b/src/kizuna/adapters/__init__.py @@ -0,0 +1,17 @@ +from abc import ABC, abstractproperty, abstractmethod + + +class Adapter(ABC): + + @abstractproperty + def provides(self): + raise NotImplementedError + + @abstractproperty + def handles(self): + raise NotImplementedError + + @staticmethod + @abstractmethod + def convert_payload(payload): + raise NotImplementedError diff --git a/src/kizuna/adapters/slack.py b/src/kizuna/adapters/slack.py new file mode 100644 index 00000000..3edab284 --- /dev/null +++ b/src/kizuna/adapters/slack.py @@ -0,0 +1,133 @@ +import re +from abc import ABC, abstractmethod +from functools import partial +from types import SimpleNamespace +from typing import Pattern, Callable + +from slackclient import SlackClient +from slacktools.chat import send, send_ephemeral +from slacktools.message import format_slack_mention + +from kizuna.support.strings import KIZUNA +from kizuna.support.utils import strip_tokens +from . import Adapter + + +class SlackEvent: + def __init__(self, payload) -> None: + self.team_id = payload.get('team_id') + event = payload.get('event') + self.type = event.get('type') + self.user = event.get('user') + self.ts = event.get('ts') + self.text = event.get('text') + self.item = event.get('item') + + @staticmethod + def create_from(payload): + type = payload.get('event', {}).get('type') + if type == 'message': + return SlackMessage(payload) + + return SlackEvent(payload) + + +class SlackMessage(SlackEvent): + def __init__(self, payload) -> None: + super().__init__(payload) + event = payload.get('event') + self.channel = event.get('channel') + self.text: str = event.get('text') + + +class SlackCommand(ABC): + + @staticmethod + @abstractmethod + async def handle(*args, **kwargs): + raise NotImplementedError + + +class SlackAdapter(Adapter): + def __init__(self, slack_client: SlackClient) -> None: + super().__init__() + self.client = slack_client + self._cached_bot_id = None + + handles = {SlackCommand} + provides = {SlackMessage} + + @staticmethod + def convert_payload(payload): + return SlackEvent.create_from(payload) + + @property + def id(self) -> str: + if not self._cached_bot_id: + + user_id = self.client.api_call('auth.test').get('user_id') + if not user_id: + raise RuntimeError('no user_id') + + self._cached_bot_id = user_id + + return self._cached_bot_id + + @staticmethod + def make_mention_token(token: str) -> Pattern: + return re.compile(token, re.IGNORECASE) + + @property + def mention_tokens(self): + return ( + self.make_mention_token(re.escape(format_slack_mention(self.id))), + self.make_mention_token('@?kazu?(?:ha)?'), + self.make_mention_token(f'@?{re.escape("カズハ")}'), + self.make_mention_token(f'@?{re.escape("かずは")}'), + self.make_mention_token(f'@?{re.escape("和葉")}'), + # old kizuna name + self.make_mention_token('@?kiz(?:una)?'), + self.make_mention_token(f'@?{re.escape(KIZUNA)}'), + ) + + @property + def mentioned(self) -> SimpleNamespace: + ns = SimpleNamespace() + + def anywhere(token: Pattern, text: str): + return token.search(text) + + def directly(token: Pattern, text: str): + return token.match(text) + + ns.anywhere = partial(self.mentioned_by, self.mention_tokens, anywhere) + ns.directly = partial(self.mentioned_by, self.mention_tokens, directly) + + return ns + + @staticmethod + def mentioned_by(tokens, fn: Callable, text: str) -> bool: + for token in tokens: + if fn(token, text): + return True + + return False + + def addressed_by(self, message: SlackMessage): + if message.user == self.id: + return False + + return self.mentioned.directly(message.text) + + def understands(self, message: SlackMessage, with_pattern: Pattern): + return with_pattern.match(strip_tokens(self.mention_tokens, message.text.strip())) + + def respond(self, message: SlackMessage, text: str): + send(self.client, message.channel, text) + + def reply(self, message: SlackMessage, text: str, ephemeral: bool = False): + text_with_mention = f'{format_slack_mention(message.user)} {text}' + if ephemeral: + return send_ephemeral(self.client, message.channel, message.user, text_with_mention) + + return send(self.client, message.channel, text_with_mention) diff --git a/src/kizuna/api/__init__.py b/src/kizuna/api/__init__.py new file mode 100644 index 00000000..64ad4206 --- /dev/null +++ b/src/kizuna/api/__init__.py @@ -0,0 +1,40 @@ +import importlib.util +import os + +import falcon +from dramatiq.brokers.rabbitmq import RabbitmqBroker +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from .events import EventsResource +from .fax_messages import FaxMessagesResource, FaxMessageResource +from .health_checks import HealthCheckResource +from .slash_commands import SlashCommandsResource + +spec = importlib.util.spec_from_file_location("config.kizuna", os.path.join(os.getcwd(), 'config/kizuna.py')) +config = importlib.util.module_from_spec(spec) +spec.loader.exec_module(config) + +db_engine = create_engine(config.DATABASE_URL) +make_session = sessionmaker(bind=db_engine) + +rabbitmq_broker = RabbitmqBroker(url=config.RABBITMQ_URL) + +app = falcon.API() + +app.req_options.auto_parse_form_urlencoded = True + +events = EventsResource(config, rabbitmq_broker) +app.add_route('/slack/events', events) + +slash_commands = SlashCommandsResource(config, make_session) +app.add_route('/slack/slash_commands', slash_commands) + +fax_messages = FaxMessagesResource(config, make_session) +app.add_route('/fax_messages', fax_messages) + +fax_message = FaxMessageResource(config, make_session) +app.add_route('/fax_messages/{message_id}', fax_message) + +healthChecks = HealthCheckResource() +app.add_route('/', healthChecks) diff --git a/src/kizuna/api/events.py b/src/kizuna/api/events.py new file mode 100644 index 00000000..32a9a20e --- /dev/null +++ b/src/kizuna/api/events.py @@ -0,0 +1,57 @@ +import logging +import ujson as json + +import falcon +from dramatiq.message import Message +from falcon import Request +from slacktools.authorization import verify_signature + + +class EventsResource(object): + + def __init__(self, config, rabbitmq_broker): + self.config = config + self.logger = logging.getLogger('kizuna_api.' + __name__) + self.rabbitmq_broker = rabbitmq_broker + + def on_post(self, req: Request, resp): + + go_away = json.dumps({'ok': False, 'msg': 'go away'}) + + if not req.content_length: + resp.status = falcon.HTTP_400 + resp.body = go_away + return + + # defaults to utf8 but should probably look at http headers to get this value, charset and stuff + body = req.bounded_stream.read().decode('utf8') + + try: + if not verify_signature(self.config.SLACK_SIGNING_SECRET, + int(req.get_header('X-Slack-Request-Timestamp')), + body, + req.get_header('X-Slack-Signature')): + resp.status = falcon.HTTP_401 + resp.body = go_away + return + except ValueError as e: + resp.status = falcon.HTTP_400 + resp.body = json.dumps({'ok': False, 'msg': str(e)}) + return + + doc = json.loads(body) + callback_type = doc['type'] + + if callback_type == 'url_verification': + resp.body = doc['challenge'] + return + + if callback_type == 'event_callback': + self.logger.debug(doc) + self.rabbitmq_broker.enqueue(Message(queue_name='default', + actor_name='slack_worker', + args=(doc,), + options={}, + kwargs={})) + + resp.body = json.dumps({'ok': True, 'msg': 'thanks!'}) diff --git a/src/kizuna/api/fax_messages.py b/src/kizuna/api/fax_messages.py new file mode 100644 index 00000000..18a4607e --- /dev/null +++ b/src/kizuna/api/fax_messages.py @@ -0,0 +1,66 @@ +import json +import logging + +import arrow +import falcon +from sqlalchemy import asc +from sqlalchemy.orm import sessionmaker + +from kizuna.support.models import FaxMessage +from kizuna.support.utils import db_session_scope +from .utils import valid_auth_header + + +class FaxMessagesResource(object): + def __init__(self, config, make_session: sessionmaker): + self.config = config + self.logger = logging.getLogger('kizuna_api.' + __name__) + self.make_session = make_session + + def on_get(self, req, resp): + if not valid_auth_header(self.config.API_KEY, req.auth): + resp.status = falcon.HTTP_401 + return + + with db_session_scope(self.make_session) as session: + messages = session \ + .query(FaxMessage) \ + .filter(FaxMessage.printed_at.is_(None)) \ + .order_by(asc(FaxMessage.created_at)) \ + .all() + + resp.body = json.dumps({'messages': [m.to_json_serializable() for m in messages]}) + + +class FaxMessageResource(object): + def __init__(self, config, make_session: sessionmaker): + self.config = config + self.make_session = make_session + + def on_put(self, req, resp, message_id): + # todo: put me in a middleware + if not valid_auth_header(self.config.API_KEY, req.auth): + resp.status = falcon.HTTP_401 + return + + message_id = int(message_id) + + data = json.load(req.stream) + + with db_session_scope(self.make_session) as session: + message = session.query(FaxMessage).filter(FaxMessage.id == message_id).first() + + if not message: + resp.status = falcon.HTTP_404 + return + + if 'printed' not in data: + resp.status = falcon.HTTP_400 + return + + printed = data['printed'] + message.printed_at = arrow.get().datetime if printed else None + + session.add(message) + + resp.body = json.dumps({'ok': True, 'msg': 'ayy lmao'}) diff --git a/src/kizuna/api/health_checks.py b/src/kizuna/api/health_checks.py new file mode 100644 index 00000000..056cae47 --- /dev/null +++ b/src/kizuna/api/health_checks.py @@ -0,0 +1,8 @@ +import ujson as json + +from falcon import Response + + +class HealthCheckResource(object): + def on_get(self, req, resp: Response): + resp.body = json.dumps({'ok': True}) diff --git a/src/kizuna/api/slash_commands.py b/src/kizuna/api/slash_commands.py new file mode 100644 index 00000000..f0e8d380 --- /dev/null +++ b/src/kizuna/api/slash_commands.py @@ -0,0 +1,59 @@ +import logging + +import falcon +from sqlalchemy.orm import sessionmaker + +from kizuna.support.models.FaxMessage import FaxMessage +from kizuna.support.utils import db_session_scope + + +class SlashCommandsResource(object): + + def __init__(self, config, make_session: sessionmaker): + self.config = config + self.logger = logging.getLogger('kizuna_api.' + __name__) + self.make_session = make_session + + def on_post(self, req, resp): + if not req.content_length: + resp.status = falcon.HTTP_400 + resp.body = 'go away' + return + + verification_token = req.get_param('token') + if verification_token not in self.config.SLACK_WEBHOOK_TOKENS: + resp.status = falcon.HTTP_401 + resp.body = 'this slack not allowed to send slash commands to kizuna :/' + return + + command = req.get_param('command') + if command not in ['/faxaustin']: + resp.status = falcon.HTTP_400 + resp.body = 'unknown command' + return + + text = req.get_param('text') + if not text: + resp.status = falcon.HTTP_200 + resp.body = 'blank fax, you gotta type some text :^(' + return + if len(text) > 280: + resp.status = falcon.HTTP_200 + resp.body = 'faxes have to be 280 chars or less' + return + + team_id = req.get_param('team_id', required=True) + trigger_id = req.get_param('trigger_id', required=True) + user_id = req.get_param('user_id', required=True) + user_name = req.get_param('user_name', required=True) + + with db_session_scope(self.make_session) as session: + message = FaxMessage(team_id=team_id, + text=text, + trigger_id=trigger_id, + user_id=user_id, + user_name=user_name) + + session.add(message) + + resp.body = 'sent fax to austin :^)' diff --git a/src/kizuna/api/utils.py b/src/kizuna/api/utils.py new file mode 100644 index 00000000..e451e0b7 --- /dev/null +++ b/src/kizuna/api/utils.py @@ -0,0 +1,10 @@ +def valid_auth_header(api_key, auth_header) -> bool: + if not auth_header: + return False + + auth_token = auth_header.split(' ')[1] + + if not auth_token or auth_token != api_key: + return False + + return True diff --git a/kizuna/commands/__init__.py b/src/kizuna/plugins/__init__.py similarity index 100% rename from kizuna/commands/__init__.py rename to src/kizuna/plugins/__init__.py diff --git a/src/kizuna/plugins/chat.py b/src/kizuna/plugins/chat.py new file mode 100644 index 00000000..585dd5a0 --- /dev/null +++ b/src/kizuna/plugins/chat.py @@ -0,0 +1,62 @@ +import random +import re +from typing import List, Pattern + +from kizuna.adapters.slack import SlackCommand, SlackMessage, SlackAdapter + + +def to_pattern(strings: List[str]) -> Pattern: + pattern = '|'.join(strings) + return re.compile(pattern, re.I) + + +greeting = to_pattern([ + '(?:hi|h[e|u]llo)(?: there)?', + 'waddup', + "what['|’]?s up", +]) + +greeting_response = [ + 'Hello!', + 'はいども!', +] + +interrogative_greeting = to_pattern([ + 'how are [you|ya](?: doing)?(?: today)?' +]) + +interrogative_greeting_response = [ + 'Doing just fine', + 'Doing just fine, thanks for asking' + '悪くないです', + '絶好調です', +] + +im_here_response = [ + 'I’m here.', + 'Need something?', + 'Yeah?' +] + + +class GreetingCommand(SlackCommand): + """kizuna says hi back""" + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter): + if message.user == bot.id: + return + + text = message.text.strip() + tokens = text.split() + if not text: + return + + if bot.mentioned.directly(tokens[0]) and tokens[0].endswith('?'): + bot.reply(message, random.choice(im_here_response)) + + if interrogative_greeting.search(text) and bot.mentioned.anywhere(text): + return bot.reply(message, random.choice(interrogative_greeting_response)) + + if greeting.search(text) and bot.mentioned.anywhere(text): + return bot.reply(message, random.choice(greeting_response)) diff --git a/src/kizuna/plugins/clap.py b/src/kizuna/plugins/clap.py new file mode 100644 index 00000000..445d30ba --- /dev/null +++ b/src/kizuna/plugins/clap.py @@ -0,0 +1,61 @@ +import re +from argparse import REMAINDER + +from slacktools.arguments import SlackArgumentParserException, SlackArgumentParser + +from kizuna.adapters.slack import SlackCommand, SlackMessage, SlackAdapter +from kizuna.support.strings import random_insult + +clap_parser = SlackArgumentParser(prog='kizuna clap', description='obnoxiously clap', add_help=False) +clap_parser.add_argument('-s', + '--separator', + dest='separator', + default=':clap:', + help='defaults to :clap:') + +clap_parser.add_argument('-a', + '--at', + dest='at', + metavar='PERSON', + help='a user to send this message to') + +clap_parser.add_argument('message', + metavar='MESSAGE', + nargs=REMAINDER, + help='the message to clappify') + +clap_parser.add_help_argument() + + +class ClapCommand(SlackCommand): + """usage: {bot} clap TEXT - adds obnoxious claps between each word in TEXT""" + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter): + if not bot.addressed_by(message): + return + + match = bot.understands(message, with_pattern=re.compile('clap (.*)')) + + if not match: + return + + try: + args = clap_parser.parse_args(match.group(1).split()) + except SlackArgumentParserException as err: + # lol commented out for max sass + # send(str(err)) + return bot.respond(message, random_insult()) + + if args.help: + return bot.respond(message, clap_parser.get_help()) + + if not args.message: + return bot.respond(message, random_insult()) + + new_message = ' {} '.format(args.separator).join(args.message) + + if args.at: + new_message = '{} {}'.format(args.at, new_message) + + bot.respond(message, new_message) diff --git a/src/kizuna/plugins/kkreds/__init__.py b/src/kizuna/plugins/kkreds/__init__.py new file mode 100644 index 00000000..597921ce --- /dev/null +++ b/src/kizuna/plugins/kkreds/__init__.py @@ -0,0 +1,199 @@ +import re +from datetime import datetime +from decimal import Decimal, InvalidOperation + +import arrow +from arrow import Arrow +from slacktools.message import is_user_mention, extract_user_id_from_mention +from sqlalchemy import func + +from kizuna.adapters.slack import SlackAdapter, SlackMessage, SlackCommand +from kizuna.plugins.users import User +from kizuna.skills.db import DB +from .models import KKredsTransaction + + +def get_kkred_balance(user: User, session) -> Decimal: + kkred_debits = session \ + .query(func.sum(KKredsTransaction.amount)) \ + .filter(KKredsTransaction.from_user_id == user.id) \ + .first()[0] + + if kkred_debits is None: + kkred_debits = Decimal(0) + + kkred_credits = session \ + .query(func.sum(KKredsTransaction.amount)) \ + .filter(KKredsTransaction.to_user_id == user.id) \ + .first()[0] + + if kkred_credits is None: + kkred_credits = Decimal(0) + + return kkred_credits - kkred_debits + + +def is_payable(utc: Arrow) -> bool: + """Should return True if time is 4:20AM or 4:20PM in Texas""" + + central = utc.to('America/Chicago') + + hour = central.hour + minute = central.minute + + if minute != 20: + return False + + if hour not in [4, 16]: + return False + + return True + + +def strip_date(target_date): + return arrow.get(datetime(year=target_date.year, + month=target_date.month, + day=target_date.day, + hour=target_date.hour, + minute=target_date.minute)) + + +class KKredsBalanceCommand(SlackCommand): + """{bot} balance - show your kkreds balance""" + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): + pattern = re.compile('balance', re.IGNORECASE) + + if not bot.addressed_by(message) or not bot.understands(message, with_pattern=pattern): + return + + with db.session_scope() as session: + user = User.get_by_slack_id(session, message.user) + balance = get_kkred_balance(user, session) + + pluralized_kkreds = 'kkred' if balance == 1 else 'kkreds' + return bot.reply(message, f'your balance is {balance} {pluralized_kkreds}') + + +class KKredsMiningCommand(SlackCommand): + """ + {bot} pay me - at 4:20 in the America/Chicago time zone on both meridians you can say "pay me" and you will be + awarded a kkred + """ + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): + + pattern = "|".join([ + ".*(?:gibbe|give) money.*", + ".*pay me.*", + ".*:watermelon:.*" + ]) + + trigger = re.compile(pattern, re.IGNORECASE) + + message_ts = arrow.get(message.ts) + + if not is_payable(message_ts) or not bot.understands(message, with_pattern=trigger): + return + + user_id = message.user + + with db.session_scope() as session: + user = User.get_by_slack_id(session, user_id) + + if not user: + return + + latest_mine = session \ + .query(KKredsTransaction) \ + .filter(KKredsTransaction.to_user_id == user.id) \ + .filter(KKredsTransaction.is_mined) \ + .order_by(KKredsTransaction.created_at.desc()) \ + .first() + + if latest_mine and latest_mine.created_at: + message_ts_stripped = strip_date(message_ts) + latest_mine_time_stripped = strip_date(latest_mine.created_at) + if latest_mine_time_stripped >= message_ts_stripped: + return + + kizuna_user = User.get_by_slack_id(session, bot.id) + mined_kkred = KKredsTransaction(from_user=kizuna_user, + to_user=user, + amount=1, + is_mined=True, + created_at=message_ts.datetime) + + session.add(mined_kkred) + + bot.reply(message, 'successfully mined 1 kkred') + + +class KKredsTransactionCommand(SlackCommand): + """{bot} pay - pay the user that amount of kkreds""" + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): + + pattern = re.compile(r'(?:pay|tip|give|send)\s+(\S*)\s+(\S*)', re.IGNORECASE) + + if not bot.addressed_by(message): + return + + matches = bot.understands(message, with_pattern=pattern) + + if not matches: + return + + message_ts = arrow.get(message.ts) + + sending_user_id = message.user + + with db.session_scope() as session: + sending_user = User.get_by_slack_id(session, sending_user_id) + + if not sending_user: + return + + receiving_user_raw = matches[1] + + if not is_user_mention(receiving_user_raw): + return bot.reply(message, + 'User has to be an `@` mention. Like it has to be a real blue `@` mention.') + + receiving_user = User.get_by_slack_id(session, + extract_user_id_from_mention(receiving_user_raw)) + + if not receiving_user: + return bot.reply(message, 'Could not find that user') + + if sending_user.id == receiving_user.id: + return bot.reply(message, + 'You can’t send money to yourself.') + + amount_raw = matches[2] + + try: + amount = Decimal(amount_raw) + except InvalidOperation: + return bot.reply(message, + 'That amount is invalid. Try a decimal or integer value') + + if amount <= 0: + return bot.reply(message, + 'Amount has to be non-zero') + + if amount > get_kkred_balance(sending_user, session): + return bot.reply(message, + 'You don’t have enough kkreds') + + transaction = KKredsTransaction(from_user=sending_user, + to_user=receiving_user, + amount=amount, + created_at=message_ts.datetime) + + session.add(transaction) + + bot.reply(message, f'successfully sent {amount} to {receiving_user.name}') diff --git a/src/kizuna/plugins/kkreds/models/__init__.py b/src/kizuna/plugins/kkreds/models/__init__.py new file mode 100644 index 00000000..73015928 --- /dev/null +++ b/src/kizuna/plugins/kkreds/models/__init__.py @@ -0,0 +1 @@ +from .kkreds_transaction import KKredsTransaction diff --git a/kizuna/models/KKredsTransaction.py b/src/kizuna/plugins/kkreds/models/kkreds_transaction.py similarity index 95% rename from kizuna/models/KKredsTransaction.py rename to src/kizuna/plugins/kkreds/models/kkreds_transaction.py index 3c6441f9..20cdab64 100644 --- a/kizuna/models/KKredsTransaction.py +++ b/src/kizuna/plugins/kkreds/models/kkreds_transaction.py @@ -1,6 +1,7 @@ from sqlalchemy import Column, Integer, ForeignKey, DateTime, func, DECIMAL, Boolean from sqlalchemy.orm import relationship -from kizuna.models.Models import Base + +from kizuna.support.models.Models import Base class KKredsTransaction(Base): diff --git a/src/kizuna/plugins/ping.py b/src/kizuna/plugins/ping.py new file mode 100644 index 00000000..6e51ecef --- /dev/null +++ b/src/kizuna/plugins/ping.py @@ -0,0 +1,12 @@ +import re + +from kizuna.adapters.slack import SlackCommand, SlackMessage, SlackAdapter + + +class PingCommand(SlackCommand): + """usage: {bot} ping - respond with pong""" + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter): + if bot.addressed_by(message) and bot.understands(message, with_pattern=re.compile('ping$', re.I)): + bot.reply(message, 'pong') diff --git a/src/kizuna/plugins/users/__init__.py b/src/kizuna/plugins/users/__init__.py new file mode 100644 index 00000000..59158f04 --- /dev/null +++ b/src/kizuna/plugins/users/__init__.py @@ -0,0 +1,76 @@ +from kizuna.adapters.slack import SlackCommand, SlackMessage, SlackAdapter +from kizuna.plugins.users.models.user import User +from kizuna.skills.db import DB +from kizuna.support.utils import build_url +from .models.user import User +import re + + +class UserRefreshCommand(SlackCommand): + """ + usage: {bot} refresh users - Make sure my users database is up-to-date with slack. You probably want to run this if + someone has changed their name or a new user has joined slack. + """ + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): + if not bot.addressed_by(message): + return + + match = bot.understands(message, with_pattern=re.compile('refresh users')) + + if not match: + return + + res = bot.client.api_call('users.list') + + if not res['ok']: + raise RuntimeError('call to users.list failed') + + slack_members = [member for member in res['members'] if not member['deleted']] + + with db.session_scope() as session: + def maybe_create(slack_id): + return User.maybe_create_user_from_slack_id(slack_id, bot.client, session) + + kizuna_members = [maybe_create(member['id']) for member in slack_members] + + for member in kizuna_members: + el = [x for x in slack_members if x['id'] == member.slack_id] + if not el: + continue + + el = el[0] + + if member.name != el['name']: + member.name = el['name'] + + bot.reply(message, 'Refreshed users. :^)') + + +class LoginCommand(SlackCommand): + """usage: {bot} login - login to the web interface""" + + @staticmethod + async def handle(message: SlackMessage, bot: SlackAdapter, db: DB): + if not bot.addressed_by(message): + return + + match = bot.understands(message, with_pattern=re.compile('login')) + + if not match: + return + + # todo: add kiz web url from config + KIZUNA_WEB_URL = 'https://kizuna.austinpray.com' + + with db.session_scope() as session: + user = User.get_by_slack_id(session, message.user) + + if not user: + return bot.reply(message, """ + I don't have your user in the db. Prolly run 'kizuna refresh users' and if that still doesn't fix it: + Austin fucked up somewhere :^( + """.strip(), ephemeral=True) + + bot.reply(message, build_url(KIZUNA_WEB_URL, '/login', {'auth': user.get_token()}), ephemeral=True) diff --git a/src/kizuna/plugins/users/models/__init__.py b/src/kizuna/plugins/users/models/__init__.py new file mode 100644 index 00000000..ee4c00b1 --- /dev/null +++ b/src/kizuna/plugins/users/models/__init__.py @@ -0,0 +1 @@ +from .user import User diff --git a/kizuna/models/User.py b/src/kizuna/plugins/users/models/user.py similarity index 59% rename from kizuna/models/User.py rename to src/kizuna/plugins/users/models/user.py index 2cb9cb00..653cf67c 100644 --- a/kizuna/models/User.py +++ b/src/kizuna/plugins/users/models/user.py @@ -1,10 +1,9 @@ -from sqlalchemy import Column, Integer, String, func -from kizuna.models.Models import Base from secrets import token_hex -from cryptography.fernet import Fernet, InvalidToken -from config import FERNET_KEY, FERNET_TTL -from decimal import Decimal -from kizuna.models.KKredsTransaction import KKredsTransaction + +from cryptography.fernet import Fernet +from sqlalchemy import Column, Integer, String + +from kizuna.support.models.Models import Base def user_generate_api_key(): @@ -32,36 +31,17 @@ def get_by_slack_id(session, slack_id) -> 'User': return session.query(User).filter(User.slack_id == slack_id).first() @staticmethod - def decrypt_token(token): + def decrypt_token(config, token): if isinstance(token, str): token = token.encode('ascii') - f = Fernet(FERNET_KEY) - return f.decrypt(token, ttl=FERNET_TTL).decode('ascii') + f = Fernet(config.FERNET_KEY) + return f.decrypt(token, ttl=config.FERNET_TTL).decode('ascii') - def get_token(self): - f = Fernet(FERNET_KEY) + def get_token(self, config): + f = Fernet(config.FERNET_KEY) return f.encrypt(self.api_key.encode('ascii')) - def get_kkred_balance(self, session) -> Decimal: - kkred_debits = session \ - .query(func.sum(KKredsTransaction.amount)) \ - .filter(KKredsTransaction.from_user_id == self.id) \ - .first()[0] - - if kkred_debits is None: - kkred_debits = Decimal(0) - - kkred_credits = session \ - .query(func.sum(KKredsTransaction.amount)) \ - .filter(KKredsTransaction.to_user_id == self.id) \ - .first()[0] - - if kkred_credits is None: - kkred_credits = Decimal(0) - - return kkred_credits - kkred_debits - @staticmethod def maybe_create_user_from_slack_id(slack_id, slack_client, session): from_user = session.query(User).filter(User.slack_id == slack_id).first() diff --git a/src/kizuna/skills/__init__.py b/src/kizuna/skills/__init__.py new file mode 100644 index 00000000..68408810 --- /dev/null +++ b/src/kizuna/skills/__init__.py @@ -0,0 +1 @@ +from .db import DB diff --git a/src/kizuna/skills/db.py b/src/kizuna/skills/db.py new file mode 100644 index 00000000..18b28ec2 --- /dev/null +++ b/src/kizuna/skills/db.py @@ -0,0 +1,11 @@ +from sqlalchemy.orm import sessionmaker + +from kizuna.support.utils import db_session_scope + + +class DB: + def __init__(self, make_session: sessionmaker) -> None: + self.make_session = make_session + + def session_scope(self): + return db_session_scope(self.make_session) diff --git a/src/kizuna/support/Kizuna.py b/src/kizuna/support/Kizuna.py new file mode 100644 index 00000000..b3f727cc --- /dev/null +++ b/src/kizuna/support/Kizuna.py @@ -0,0 +1,49 @@ +import asyncio +import inspect +from typing import Dict + +from kizuna.adapters import Adapter +from .di import build_di_args, DependencyMissing + + +class Kizuna: + def __init__(self, config) -> None: + self.config = config + self.adapters: Dict[str, Adapter] = {} + self.skills = set() + self.plugins = set() + + @staticmethod + def can_handle(adapter: Adapter, cls) -> bool: + # todo: the [1:2] slice is very fragile + for mro_cls in [mro_cls.__name__ for mro_cls in inspect.getmro(cls)[1:2]]: + for handleable in [handleable.__name__ for handleable in adapter.handles]: + if handleable == mro_cls: + return True + + return False + + def get_handleable(self, adapter: Adapter) -> list: + handleable = [] + for plugin in self.plugins: + for name, obj in inspect.getmembers(plugin, inspect.isclass): + if self.can_handle(adapter, obj): + handleable.append(obj) + + return handleable + + def handle(self, adapter_name: str, payload: dict): + adapter: Adapter = self.adapters.get(adapter_name) + event = adapter.convert_payload(payload) + handleable = self.get_handleable(adapter) + + components = {event} | self.skills | {adapter for adapter in self.adapters.values()} + for command in handleable: + try: + asyncio.run(command.handle(**build_di_args(components, command.handle))) + except DependencyMissing as e: + if e.dependency == event.__class__.__name__: + # this command isn't meant to handle this type of event + return + + raise e diff --git a/src/kizuna/support/__init__.py b/src/kizuna/support/__init__.py new file mode 100644 index 00000000..22a451ad --- /dev/null +++ b/src/kizuna/support/__init__.py @@ -0,0 +1 @@ +from .Kizuna import Kizuna diff --git a/src/kizuna/support/dev_info.py b/src/kizuna/support/dev_info.py new file mode 100644 index 00000000..bd28293e --- /dev/null +++ b/src/kizuna/support/dev_info.py @@ -0,0 +1,18 @@ +import os +import ujson as json +from pprint import pprint + + +def read_dev_info(dev_info_path): + if not dev_info_path: + print('no dev_info_path') + return {} + + if not os.path.isfile(dev_info_path): + print('startup: no dev file at "{}"'.format(dev_info_path)) + return {} + + dev_info = json.load(open(dev_info_path)) + print('startup: dev info loaded from "{}"'.format(dev_info_path)) + pprint(dev_info) + return dev_info diff --git a/src/kizuna/support/di.py b/src/kizuna/support/di.py new file mode 100644 index 00000000..71940ca8 --- /dev/null +++ b/src/kizuna/support/di.py @@ -0,0 +1,28 @@ +import inspect +from inspect import Parameter + +from typing import Dict, Any, Set, Callable + + +class DependencyMissing(RuntimeError): + def __init__(self, *args: object, **kwargs: object) -> None: + super().__init__(*args, **kwargs) + self.dependency = kwargs.get('dependency', None) + + +def build_di_args(components: Set, fn: Callable) -> Dict[str, Any]: + args_dict = {} + params = inspect.signature(fn).parameters + + components_dict = {component.__class__.__name__: component for component in components} + + param: Parameter + for name, param in params.items(): + dep_name = param.annotation.__name__ + resolved = components_dict.get(dep_name) + if not resolved: + raise DependencyMissing(f'Do not know how to handle dependency "{dep_name}"', + dependency=dep_name) + args_dict[name] = resolved + + return args_dict diff --git a/kizuna/models/FaxMessage.py b/src/kizuna/support/models/FaxMessage.py similarity index 96% rename from kizuna/models/FaxMessage.py rename to src/kizuna/support/models/FaxMessage.py index 1b5f00f3..0a0461d1 100644 --- a/kizuna/models/FaxMessage.py +++ b/src/kizuna/support/models/FaxMessage.py @@ -1,6 +1,7 @@ -from sqlalchemy import Column, String, Text, Integer, DateTime, func -from kizuna.models.Models import Base import arrow +from sqlalchemy import Column, String, Text, Integer, DateTime, func + +from .Models import Base class FaxMessage(Base): diff --git a/kizuna/models/Meta.py b/src/kizuna/support/models/Meta.py similarity index 89% rename from kizuna/models/Meta.py rename to src/kizuna/support/models/Meta.py index a3b34d2a..808bfb10 100644 --- a/kizuna/models/Meta.py +++ b/src/kizuna/support/models/Meta.py @@ -1,5 +1,6 @@ from sqlalchemy import Column, Integer, String -from kizuna.models.Models import Base + +from .Models import Base class Meta(Base): diff --git a/kizuna/models/Models.py b/src/kizuna/support/models/Models.py similarity index 100% rename from kizuna/models/Models.py rename to src/kizuna/support/models/Models.py index 8ca6ce62..5a3f2868 100644 --- a/kizuna/models/Models.py +++ b/src/kizuna/support/models/Models.py @@ -1,5 +1,5 @@ -from sqlalchemy.ext.declarative import declarative_base import sqlalchemy as sa +from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() diff --git a/src/kizuna/support/models/__init__.py b/src/kizuna/support/models/__init__.py new file mode 100644 index 00000000..b84ff37a --- /dev/null +++ b/src/kizuna/support/models/__init__.py @@ -0,0 +1,4 @@ +from .FaxMessage import FaxMessage +from .Language import Language +from .Meta import Meta +from .Models import Base, reaction_images_tags_join_table diff --git a/src/kizuna/support/strings.py b/src/kizuna/support/strings.py new file mode 100644 index 00000000..8d982136 --- /dev/null +++ b/src/kizuna/support/strings.py @@ -0,0 +1,28 @@ +# coding=utf-8 +import random + +HAI_DOMO = 'はいども!キズナです!' +KIZUNA = 'キズナ' +WAIT_A_SEC = 'ちょっと待ってください' +JAP_DOT = '。' + +AHO = 'あほ' +BAKA = 'ばか' +INSULTS = [AHO, BAKA] + + +def random_insult(): + return random.choice(INSULTS) + + +YOSHI = 'よし' + +VERSION_UPDATE_TEMPLATE = '私は{{VERSION}}に更新しました' +VERSION_TRANSITION_TEMPLATE = '{{FROM_VERSION}}から{{TO_VERSION}}にバージョンアップしました。' + +LQUO = '「' +RQUO = '」' + +VERSION_UP = 'バージョンアップ' + +GOODBYE = 'さよなら' diff --git a/kizuna/utils.py b/src/kizuna/support/utils.py similarity index 60% rename from kizuna/utils.py rename to src/kizuna/support/utils.py index c98f6589..a72e8a40 100644 --- a/kizuna/utils.py +++ b/src/kizuna/support/utils.py @@ -1,5 +1,5 @@ -from urllib.parse import urlparse, urlencode, urlunparse from contextlib import contextmanager +from urllib.parse import urlparse, urlencode, urlunparse def build_url(baseurl, path, args_dict=None): @@ -11,8 +11,14 @@ def build_url(baseurl, path, args_dict=None): return urlunparse(url_parts) -def slack_link(text, url): - return '<{}|{}>'.format(url, text) +def strip_tokens(tokens, message: str): + message = message.strip() + for token in tokens: + match = token.match(message) + if match: + message = message[match.end(0):].strip() + + return message @contextmanager @@ -29,3 +35,11 @@ def db_session_scope(session_maker): raise finally: session.close() + + +def linear_scale(old_max, old_min, new_max, new_min, value): + old_range = (old_max - old_min) + if old_range == 0: + return value + new_range = (new_max - new_min) + return (((value - old_min) * new_range) / old_range) + new_min diff --git a/src/kizuna/worker/__init__.py b/src/kizuna/worker/__init__.py new file mode 100644 index 00000000..ca65ef8b --- /dev/null +++ b/src/kizuna/worker/__init__.py @@ -0,0 +1,64 @@ +import importlib.util +import logging +import os + +import dramatiq +from dramatiq.brokers.rabbitmq import RabbitmqBroker +from raven import Client +from slackclient import SlackClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +import kizuna.plugins.chat +import kizuna.plugins.clap +import kizuna.plugins.kkreds +import kizuna.plugins.ping +import kizuna.plugins.users +from kizuna.adapters.slack import SlackAdapter +from kizuna.skills import DB +from kizuna.support import Kizuna + +logging.basicConfig(level=logging.DEBUG) + +# DEV_INFO = read_dev_info('./.dev-info.json') + +spec = importlib.util.spec_from_file_location("config.kizuna", os.path.join(os.getcwd(), 'config/kizuna.py')) +config = importlib.util.module_from_spec(spec) +spec.loader.exec_module(config) + +sentry = Client(config.SENTRY_URL, + # release=DEV_INFO.get('revision'), + environment=config.KIZUNA_ENV) if config.SENTRY_URL else None + +rabbitmq_broker = RabbitmqBroker(url=config.RABBITMQ_URL) +dramatiq.set_broker(rabbitmq_broker) + +if not config.SLACK_API_TOKEN: + raise ValueError('You are missing a slack token! Please set the SLACK_API_TOKEN environment variable in your ' + '.env file or in the system environment') + +sc = SlackClient(config.SLACK_API_TOKEN) +db_engine = create_engine(config.DATABASE_URL) +make_session = sessionmaker(bind=db_engine) + +k = Kizuna(config) + +k.adapters['slack'] = SlackAdapter(slack_client=sc) + +k.skills |= { + DB(make_session=make_session), +} + +k.plugins |= { + kizuna.plugins.chat, + kizuna.plugins.clap, + kizuna.plugins.ping, + kizuna.plugins.users, + kizuna.plugins.kkreds, +} + + +@dramatiq.actor +def slack_worker(payload): + logging.debug(payload) + k.handle('slack', payload) diff --git a/test/scenarios/delete_user_kkreds.py b/test/e2e/delete_user_kkreds.py similarity index 89% rename from test/scenarios/delete_user_kkreds.py rename to test/e2e/delete_user_kkreds.py index 0734b9a8..4ffc56eb 100644 --- a/test/scenarios/delete_user_kkreds.py +++ b/test/e2e/delete_user_kkreds.py @@ -3,8 +3,8 @@ import config import arrow -from kizuna.models.User import User -from kizuna.models.KKredsTransaction import KKredsTransaction +from src.support import User +from src.support.models.KKredsTransaction import KKredsTransaction if __name__ == "__main__": diff --git a/test/scenarios/user_get_balance.py b/test/e2e/user_get_balance.py similarity index 95% rename from test/scenarios/user_get_balance.py rename to test/e2e/user_get_balance.py index a22fe2ce..577b8fac 100644 --- a/test/scenarios/user_get_balance.py +++ b/test/e2e/user_get_balance.py @@ -1,5 +1,4 @@ -from kizuna.models.User import User -from kizuna.models.KKredsTransaction import KKredsTransaction +from src.support.models import User, KKredsTransaction from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker import arrow diff --git a/test/kizuna/test_kizuna.py b/test/kizuna/test_kizuna.py new file mode 100644 index 00000000..356d6c73 --- /dev/null +++ b/test/kizuna/test_kizuna.py @@ -0,0 +1,42 @@ +from kizuna.support import Kizuna +from kizuna.adapters import Adapter +from kizuna.adapters.slack import SlackAdapter +import kizuna.plugins.ping + +class FakeSlackClient: + def api_call(*args, **kwargs): + if args[0] == 'auth.test': + return {'user_id': 'UKIZUNAAA'} + + print(kwargs.get('text'), end='') + +slack_ping_message = { + "token": "BOGUS", + "team_id": "BOGUS_TEAM", + "api_app_id": "BOGUS_APP_ID", + "event": { + "type": "message", + "user": "UUSERRRRR", + "text": "<@UKIZUNAAA> ping", + "client_msg_id": "fda9651b-3467-4a98-861b-3b254c1614c6", + "ts": "1538496960.000100", + "channel": "C5H8YMX3Q", + "event_ts": "1538496960.000100", + "channel_type": "channel" + }, + "type": "event_callback", + "event_id": "EvD5NLL82H", + "event_time": 1538496960, + "authed_users": [ + "UUUUUUUU1", + "UUUUUUUU2" + ] +} + +def test_plugin(capsys): + k = Kizuna() + k.adapters['slack'] = SlackAdapter(slack_client=FakeSlackClient) + k.plugins |= {kizuna.plugins.ping} + k.handle('slack', slack_ping_message.copy()) + out, err = capsys.readouterr() + assert out == '<@UUSERRRRR> pong' diff --git a/test/kizuna/test_kkreds.py b/test/kizuna/test_kkreds.py index d1a04e18..d0b56376 100644 --- a/test/kizuna/test_kkreds.py +++ b/test/kizuna/test_kkreds.py @@ -1,5 +1,5 @@ import arrow -from kizuna.kkreds import is_payable +from kizuna.plugins.kkreds import is_payable def test_is_payable_time(): diff --git a/vendor/python-slacktools/setup.py b/vendor/python-slacktools/setup.py old mode 100644 new mode 100755 diff --git a/web.py b/web.py deleted file mode 100644 index 3ba18cca..00000000 --- a/web.py +++ /dev/null @@ -1 +0,0 @@ -from kizuna_web.app import app diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 5333b648..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,42 +0,0 @@ -const path = require('path'); -const webpack = require("webpack"); - -module.exports = { - entry: [ - 'whatwg-fetch', - './kizuna_js/index.js' - ], - output: { - path: path.resolve(__dirname, 'static/dist'), - filename: 'kizuna.bundle.js', - publicPath: "/static/dist/" - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'] - } - } - }, - { - test: /\.css$/, - use: [ - { loader: "style-loader" }, - { loader: "css-loader" } - ] - } - ] - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify(process.env.NODE_ENV) - } - }), - ] -}; diff --git a/worker.py b/worker.py deleted file mode 100644 index e2db2229..00000000 --- a/worker.py +++ /dev/null @@ -1,88 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from slackclient import SlackClient - -from kizuna.Kizuna import Kizuna -from kizuna.commands.AtGraphCommand import AtGraphCommand -from kizuna.commands.AtGraphDataCollector import AtGraphDataCollector -from kizuna.commands.ClapCommand import ClapCommand -from kizuna.commands.KKredsMiningCommand import KKredsMiningCommand -from kizuna.commands.KKredsBalanceCommand import KKredsBalanceCommand -from kizuna.commands.LoginCommand import LoginCommand -from kizuna.commands.PingCommand import PingCommand -from kizuna.commands.ReactCommand import ReactCommand -from kizuna.commands.UserRefreshCommand import UserRefreshCommand -from kizuna.commands.KKredsTransactionCommand import KKredsTransactionCommand - -import spacy - -from raven import Client -import config -import dramatiq - -from dramatiq.brokers.rabbitmq import RabbitmqBroker - -rabbitmq_broker = RabbitmqBroker(url=config.RABBITMQ_URL) -dramatiq.set_broker(rabbitmq_broker) - -nlp = spacy.load('en') - -DEV_INFO = Kizuna.read_dev_info('./.dev-info.json') - -sentry = Client(config.SENTRY_URL, - release=DEV_INFO.get('revision'), - environment=config.KIZUNA_ENV) if config.SENTRY_URL else None - -if not config.SLACK_API_TOKEN: - raise ValueError('You are missing a slack token! Please set the SLACK_API_TOKEN environment variable in your ' - '.env file or in the system environment') - -sc = SlackClient(config.SLACK_API_TOKEN) -db_engine = create_engine(config.DATABASE_URL) -make_session = sessionmaker(bind=db_engine) - -auth = sc.api_call('auth.test') -bot_id = auth['user_id'] - -k = Kizuna(bot_id, - slack_client=sc, - main_channel=config.MAIN_CHANNEL, - home_channel=config.KIZUNA_HOME_CHANNEL) - -# k.handle_startup(DEV_INFO, make_session()) - -pc = PingCommand() -k.register_command(pc) - -lc = LoginCommand(make_session) -k.register_command(lc) - -clap = ClapCommand() -k.register_command(clap) - -at_graph_command = AtGraphCommand(make_session) -k.register_command(at_graph_command) - -at_graph_data_collector = AtGraphDataCollector(make_session, sc) -k.register_command(at_graph_data_collector) - -user_refresh_command = UserRefreshCommand(db_session=make_session) -k.register_command(user_refresh_command) - -react_command = ReactCommand(make_session, nlp=nlp) -k.register_command(react_command) - -kkreds_command = KKredsMiningCommand(make_session, kizuna=k) -k.register_command(kkreds_command) - -kkreds_balance_command = KKredsBalanceCommand(make_session, kizuna=k) -k.register_command(kkreds_balance_command) - -kkreds_transaction_command = KKredsTransactionCommand(make_session, kizuna=k) -k.register_command(kkreds_transaction_command) - - -@dramatiq.actor -def worker(payload): - if payload['type'] == 'message': - k.handle_message(payload)