daybreakz / Twitch Tab Completion (compatible with BTTV)

// ==UserScript==
// @name         Twitch Tab Completion (compatible with BTTV)
// @namespace    https://openuserjs.org/users/daybreakz
// @version      1.0.9
// @description  Tab completion for emotes (and users) also add chat history. BetterTTV
// @author       Daybr3akz
// @license      MIT
// @copyright 2017, daybreakz (https://openuserjs.org/users/daybreakz)
// @updateURL    https://openuserjs.org/meta/daybreakz/Twitch_Tab_Completion_(compatible_with_BTTV).meta.js
// @match        https://www.twitch.tv/*
// @require      http://code.jquery.com/jquery-3.2.1.slim.min.js
// @grant        none
// ==/UserScript==

// // ==OpenUserJS==
// @author daybreakz
// ==/OpenUserJS==

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.getConnectRoot = getConnectRoot;
exports.getChatController = getChatController;
exports.getChatInputController = getChatInputController;
exports.getCurrentChat = getCurrentChat;
exports.setCurrentUser = setCurrentUser;
exports.getCurrentUser = getCurrentUser;
exports.getCurrentChannel = getCurrentChannel;
exports.updateCurrentChannel = updateCurrentChannel;
var REACT_ROOT = '#root div[data-reactroot]';
var CHAT_CONTAINER = '.chat-room__container';
var CHAT_INPUT = '.chat-input';
// const $ = s => document.querySelectorAll(s);
var $ = window.$;

function getReactInstance(element) {
    for (var key in element) {
        if (key.startsWith('__reactInternalInstance$')) {
            return element[key];
        }
    }
    return null;
}

function getReactElement(element) {
    var instance = getReactInstance(element);
    if (!instance) return null;
    return instance._currentElement;
}

function getParentNode(reactElement) {
    try {
        return reactElement._owner._currentElement._owner;
    } catch (_) {
        return null;
    }
}

function searchReactChildren(node, predicate) {
    var maxDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 15;
    var depth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;

    try {
        if (predicate(node)) {
            return node;
        }
    } catch (_) {}

    if (!node || depth > maxDepth) {
        return null;
    }

    var children = node._renderedChildren,
        component = node._renderedComponent;


    if (children) {
        var _iteratorNormalCompletion = true;
        var _didIteratorError = false;
        var _iteratorError = undefined;

        try {
            for (var _iterator = Object.keys(children)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                var key = _step.value;

                var childResult = searchReactChildren(children[key], predicate, maxDepth, depth + 1);
                if (childResult) {
                    return childResult;
                }
            }
        } catch (err) {
            _didIteratorError = true;
            _iteratorError = err;
        } finally {
            try {
                if (!_iteratorNormalCompletion && _iterator.return) {
                    _iterator.return();
                }
            } finally {
                if (_didIteratorError) {
                    throw _iteratorError;
                }
            }
        }
    }

    if (component) {
        return searchReactChildren(component, predicate, maxDepth, depth + 1);
    }

    return null;
}

function getConnectRoot() {
    var root = void 0;
    try {
        root = getParentNode(getReactElement($(REACT_ROOT)[0]));
    } catch (_) {}
    return root;
}

function getChatController() {
    var container = $(CHAT_CONTAINER).parent()[0];
    if (!container) return null;

    var controller = searchReactChildren(getReactInstance(container), function (node) {
        return node._instance && node._instance.chatBuffer;
    });

    if (controller) {
        controller = controller._instance;
    }

    return controller;
}

function getChatInputController() {
    var container = $(CHAT_INPUT)[0];
    if (!container) return null;

    var controller = void 0;
    try {
        controller = getParentNode(getReactElement(container))._instance;
    } catch (_) {}

    return controller;
}

function getCurrentChat() {
    var container = $(CHAT_CONTAINER)[0];
    if (!container) return null;
    var controller = void 0;
    try {
        controller = getParentNode(getReactElement(container))._instance;
    } catch (_) {}
    return controller;
}

var currentUser = null;
function setCurrentUser(accessToken, id, name, displayName) {
    // twitchAPI.setAccessToken(accessToken);
    currentUser = {
        id: id.toString(),
        name: name,
        displayName: displayName
    };
}

function getCurrentUser() {
    return currentUser;
}

var currentChannel = void 0;
function getCurrentChannel() {
    return currentChannel;
}

function updateCurrentChannel() {
    var rv = void 0;
    var currentChat = getCurrentChat();
    if (currentChat && currentChat.props && currentChat.props.channelID) {
        var _currentChat$props = currentChat.props,
            channelID = _currentChat$props.channelID,
            channelLogin = _currentChat$props.channelLogin,
            channelDisplayName = _currentChat$props.channelDisplayName;

        rv = {
            id: channelID.toString(),
            name: channelLogin,
            displayName: channelDisplayName
        };
    }
    currentChannel = rv;
    return rv;
}

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});
window.customLog = localStorage.getItem('customLog') === 'true';

var console = window.console;
try {
    console = $('iframe')[0].contentWindow.console;
} catch (e) {}

function log(type) {
    if (!console || !window.customLog) return;

    for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
    }

    console[type].apply(console, ['SCRIPT:'].concat(args));
}

var debug = {
    log: log.bind(undefined, 'log'),
    error: log.bind(undefined, 'error'),
    warn: log.bind(undefined, 'warn'),
    info: log.bind(undefined, 'info')
};

exports.default = debug;

/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _debug = __webpack_require__(1);

var _debug2 = _interopRequireDefault(_debug);

var _twitch = __webpack_require__(0);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var API_ENDPOINT = 'https://api.betterttv.net/2/';
var fetchJson = function fetchJson(path) {
    return fetch('' + API_ENDPOINT + path).then(function (r) {
        return r.json();
    });
};

var channel = {};

var EmotesProvider = function () {
    function EmotesProvider() {
        _classCallCheck(this, EmotesProvider);

        this.channelEmotes = new Set();
        this.globalEmotes = new Set();
        this.emojis = new Set();
        this.loadBTTVGlobalEmotes();
        // this.loadEmojis();
    }

    _createClass(EmotesProvider, [{
        key: 'updateChannel',
        value: function () {
            var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
                var currentChannel;
                return regeneratorRuntime.wrap(function _callee$(_context) {
                    while (1) {
                        switch (_context.prev = _context.next) {
                            case 0:
                                currentChannel = (0, _twitch.getCurrentChannel)();

                                if (currentChannel) {
                                    _context.next = 3;
                                    break;
                                }

                                return _context.abrupt('return');

                            case 3:
                                if (!(currentChannel.id === channel.id)) {
                                    _context.next = 5;
                                    break;
                                }

                                return _context.abrupt('return');

                            case 5:
                                channel = currentChannel;
                                _context.next = 8;
                                return fetchJson('channels/' + channel.name);

                            case 8:
                                return _context.abrupt('return', _context.sent);

                            case 9:
                            case 'end':
                                return _context.stop();
                        }
                    }
                }, _callee, this);
            }));

            function updateChannel() {
                return _ref.apply(this, arguments);
            }

            return updateChannel;
        }()
    }, {
        key: 'load',
        value: function () {
            var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
                var channelData;
                return regeneratorRuntime.wrap(function _callee2$(_context2) {
                    while (1) {
                        switch (_context2.prev = _context2.next) {
                            case 0:
                                _context2.next = 2;
                                return this.updateChannel();

                            case 2:
                                channelData = _context2.sent;

                                if (channelData && channelData.emotes) {
                                    this.loadBTTVChannelEmotes(channelData.emotes);
                                }

                            case 4:
                            case 'end':
                                return _context2.stop();
                        }
                    }
                }, _callee2, this);
            }));

            function load() {
                return _ref2.apply(this, arguments);
            }

            return load;
        }()
    }, {
        key: 'loadBTTVGlobalEmotes',
        value: function () {
            var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {
                var _this = this;

                var x, emotesJson;
                return regeneratorRuntime.wrap(function _callee3$(_context3) {
                    while (1) {
                        switch (_context3.prev = _context3.next) {
                            case 0:
                                _context3.next = 2;
                                return fetchJson('emotes');

                            case 2:
                                x = _context3.sent;
                                emotesJson = x.emotes;

                                emotesJson.forEach(function (em) {
                                    if (!em || !em.code) return;
                                    _this.globalEmotes.add(em.code);
                                });
                                _debug2.default.log('Got globalEmotes');

                            case 6:
                            case 'end':
                                return _context3.stop();
                        }
                    }
                }, _callee3, this);
            }));

            function loadBTTVGlobalEmotes() {
                return _ref3.apply(this, arguments);
            }

            return loadBTTVGlobalEmotes;
        }()
    }, {
        key: 'loadBTTVChannelEmotes',
        value: function loadBTTVChannelEmotes(emotesJson) {
            var _this2 = this;

            this.channelEmotes.clear();
            emotesJson.forEach(function (em) {
                if (!em || !em.code) return;
                _this2.channelEmotes.add(em.code);
            });
            _debug2.default.log('Got channel emotes');
        }
    }, {
        key: 'getEmotes',
        value: function getEmotes() {
            if (!window.BetterTTV) {
                return [];
            }
            var user = (0, _twitch.getCurrentUser)();user;

            var emotes = [].concat(_toConsumableArray(this.globalEmotes))
            // .concat([...this.emojis])
            .concat([].concat(_toConsumableArray(this.channelEmotes)));
            return emotes;
        }
    }]);

    return EmotesProvider;
}();

// let provider = new NopeProvider();
// if (window.BetterTTV) {


var provider = new EmotesProvider();
exports.default = provider;

/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


var _routeKeysToPaths;

var waitForChat = function () {
    var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
        var abort, currentIsWaiting;
        return regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        abort = false;

                        wait(15000).then(function () {
                            return abort = true;
                        });
                        isWaiting = Symbol('waitingForChat');
                        currentIsWaiting = isWaiting;

                    case 4:
                        if (abort) {
                            _context.next = 14;
                            break;
                        }

                        if (!checkChat()) {
                            _context.next = 7;
                            break;
                        }

                        return _context.abrupt('return', true);

                    case 7:
                        if (!(isWaiting !== currentIsWaiting)) {
                            _context.next = 10;
                            break;
                        }

                        _debug2.default.log('waitForChat was cancelled');
                        return _context.abrupt('return', false);

                    case 10:
                        _context.next = 12;
                        return wait(25);

                    case 12:
                        _context.next = 4;
                        break;

                    case 14:
                        return _context.abrupt('return', false);

                    case 15:
                    case 'end':
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function waitForChat() {
        return _ref.apply(this, arguments);
    };
}();

var main = function () {
    var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
        var router;
        return regeneratorRuntime.wrap(function _callee2$(_context2) {
            while (1) {
                switch (_context2.prev = _context2.next) {
                    case 0:
                        _context2.next = 2;
                        return getRouter();

                    case 2:
                        router = _context2.sent;

                        router.history.listen(function (location) {
                            return onRouteChange(location);
                        });
                        onRouteChange(router.history.location);

                    case 5:
                    case 'end':
                        return _context2.stop();
                }
            }
        }, _callee2, this);
    }));

    return function main() {
        return _ref2.apply(this, arguments);
    };
}();

var _debug = __webpack_require__(1);

var _debug2 = _interopRequireDefault(_debug);

var _tab_completion = __webpack_require__(4);

var _tab_completion2 = _interopRequireDefault(_tab_completion);

var _emotes = __webpack_require__(2);

var _emotes2 = _interopRequireDefault(_emotes);

var _twitch = __webpack_require__(0);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

var currentPath = void 0;

var routes = {
    DIRECTORY_FOLLOWING_LIVE: 'DIRECTORY_FOLLOWING_LIVE',
    DIRECTORY_FOLLOWING: 'DIRECTORY_FOLLOWING',
    DIRECTORY: 'DIRECTORY',
    CHAT: 'CHAT',
    CHANNEL: 'CHANNEL',
    VOD: 'VOD'
};

var routeKeysToPaths = (_routeKeysToPaths = {}, _defineProperty(_routeKeysToPaths, routes.DIRECTORY_FOLLOWING_LIVE, /^\/directory\/following\/live$/i), _defineProperty(_routeKeysToPaths, routes.DIRECTORY_FOLLOWING, /^\/directory\/following$/i), _defineProperty(_routeKeysToPaths, routes.DIRECTORY, /^\/directory/i), _defineProperty(_routeKeysToPaths, routes.CHAT, /^(\/popout)?\/[a-z0-9-_]+\/chat$/i), _defineProperty(_routeKeysToPaths, routes.VOD, /^\/videos\/[0-9]+$/i), _defineProperty(_routeKeysToPaths, routes.CHANNEL, /^\/[a-z0-9-_]+/i), _routeKeysToPaths);

function getRouteFromPath(path) {
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;

    try {
        for (var _iterator = Object.keys(routeKeysToPaths)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
            var name = _step.value;

            var regex = routeKeysToPaths[name];
            if (!regex.test(path)) continue;
            return name;
        }
    } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
    } finally {
        try {
            if (!_iteratorNormalCompletion && _iterator.return) {
                _iterator.return();
            }
        } finally {
            if (_didIteratorError) {
                throw _iteratorError;
            }
        }
    }

    return null;
}

function getRouter() {
    return new Promise(function (resolve) {
        var loadInterval = setInterval(function () {
            var user = void 0,
                router = void 0;
            try {
                var connectRoot = (0, _twitch.getConnectRoot)();
                if (!connectRoot) return;
                var context = connectRoot._context;
                router = context.router;
                user = context.store.getState().session.user;
            } catch (_) {
                return;
            }

            if (!router || !user) return;
            clearInterval(loadInterval);

            (0, _twitch.setCurrentUser)(user.authToken, user.id, user.login, user.displayName);
            resolve(router);
        }, 25);
    });
}

function makeCheckChat() {
    var currentChatReference = null;
    return function () {
        if (!(0, _twitch.updateCurrentChannel)()) return false;
        var lastReference = currentChatReference;
        var currentChat = (0, _twitch.getCurrentChat)();

        if (currentChat && currentChat === lastReference) {
            return false;
        }
        if (lastReference && currentChat.props.channelID.toString() === lastReference.props.channelID.toString()) {
            return false;
        }

        currentChatReference = currentChat;

        return true;
    };
}
var checkChat = makeCheckChat();

var wait = function wait(t) {
    return new Promise(function (r) {
        return setTimeout(r, t);
    });
};
var isWaiting = false;


function triggerRouteChanged() {}

function triggerChatLoaded() {
    _debug2.default.log('CHAT WAS LOADED');
    _tab_completion2.default.load(true);
    _emotes2.default.load();
}

function onRouteChange(location) {
    var lastPath = currentPath;
    var path = location.pathname;
    var route = getRouteFromPath(path);
    _debug2.default.log('New route: ' + location.pathname + ' as ' + route);

    // emit load
    triggerRouteChanged();
    currentPath = path;
    if (currentPath === lastPath) return;
    if (route === routes.CHAT || route === routes.CHANNEL) {
        waitForChat().then(function (loaded) {
            return loaded && triggerChatLoaded();
        });
    }
}

main();

/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
// const InputPatcherModule = require('./input-patcher-module');


var _customInputModule = __webpack_require__(5);

var _customInputModule2 = _interopRequireDefault(_customInputModule);

var _chatHistoryModule = __webpack_require__(6);

var _chatHistoryModule2 = _interopRequireDefault(_chatHistoryModule);

var _twitch = __webpack_require__(0);

var _debug = __webpack_require__(1);

var _debug2 = _interopRequireDefault(_debug);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

// window.getChatController = getChatController;

var CHAT_INPUT = '.chat-input';
var $ = window.$;

function patchSendMessage(callback) {
    var chatBuffer = (0, _twitch.getChatController)().chatBuffer;
    _debug2.default.log(chatBuffer);

    if (chatBuffer._consumeChatEventPatched === true) {
        return;
    }
    chatBuffer._consumeChatEventPatched = true;
    var twitchConsumeChatEvent = chatBuffer.consumeChatEvent;

    function myConsumeChatEvent(event) {
        if (event && event.type === 0) {
            try {
                callback(event);
            } catch (error) {
                _debug2.default.error(error);
            }
        }
        return twitchConsumeChatEvent.apply(this, arguments);
    }
    chatBuffer.consumeChatEvent = myConsumeChatEvent;
}

var ChatTabCompletionModule = function () {
    function ChatTabCompletionModule() {
        var _this = this;

        _classCallCheck(this, ChatTabCompletionModule);

        this.customInput = new _customInputModule2.default(this);
        this.chatHistory = new _chatHistoryModule2.default(this);
        this.currentInput = null;

        // watcher.on('load.chat', () => this.load());
        // settings.on('changed.tabAutocomplete', () => this.load(false));

        $('body').off('click.tabComplete focus.tabComplete keydown.tabComplete').on('click.tabComplete focus.tabComplete', CHAT_INPUT, function () {
            return _this.onFocus();
        }).on('keydown.tabComplete', CHAT_INPUT, function (e) {
            return _this.onKeydown(e);
        });
    }

    _createClass(ChatTabCompletionModule, [{
        key: 'load',
        value: function load() {
            var _this2 = this;

            var chatLoad = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;

            this.customInput.load(chatLoad);
            this.chatHistory.load(chatLoad);
            // if (settings.get('tabAutocomplete')) {
            this.customInput.enable();
            this.currentInput = this.customInput;
            // } else {
            //     this.customInput.disable();
            //     this.currentInput = this.patchedInput;
            // }
            patchSendMessage(function (event) {
                _this2.currentInput.storeUser(event.user);
            });
        }
    }, {
        key: 'onKeydown',
        value: function onKeydown(e) {
            if (this.currentInput) {
                this.currentInput.onKeydown(e);
            }
            this.chatHistory.onKeydown(e);
        }
    }, {
        key: 'onFocus',
        value: function onFocus() {
            if (this.currentInput) {
                this.currentInput.onFocus();
            }
            this.chatHistory.onFocus();
        }
    }, {
        key: 'onSendMessage',
        value: function onSendMessage(message) {
            this.chatHistory.onSendMessage(message);
        }
    }]);

    return ChatTabCompletionModule;
}();

var mod = new ChatTabCompletionModule();
exports.default = mod;

/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _emotes = __webpack_require__(2);

var _emotes2 = _interopRequireDefault(_emotes);

var _twitch = __webpack_require__(0);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var $ = window.$;


var ORIGINAL_TEXTAREA = '.chat-input textarea';

function setReactTextareaValue(txt, msg) {
    txt.value = msg;
    var ev = new Event('input', { target: txt, bubbles: true });
    txt.dispatchEvent(ev);
}

function newTextArea() {
    var $oldText = $(ORIGINAL_TEXTAREA);
    var $text = $oldText.clone().insertBefore(ORIGINAL_TEXTAREA);
    $text.attr('id', 'bttv-chat-input');
    $oldText.attr('id', 'twitch-chat-input');
    $oldText.hide();
    $text.focus();

    $text[0].customSetValue = function (value) {
        $text.val(value);
    };

    $oldText[0].customSetValue = function (value) {
        setReactTextareaValue($oldText[0], value);
    };

    return { $text: $text, $oldText: $oldText };
}

var CustomInputModule = function () {
    function CustomInputModule(parentModule) {
        _classCallCheck(this, CustomInputModule);

        this.parentModule = parentModule;
        this.init();
        // watcher.on('input.onSendMessage', () => this.sendMessage());
        // watcher.on('chat.message', ($el, msg) => this.storeUser($el, msg));
    }

    _createClass(CustomInputModule, [{
        key: 'init',
        value: function init() {
            this.userList = new Set();
            this.tabTries = -1;
            this.suggestions = null;
            this.textSplit = ['', '', ''];
        }
    }, {
        key: 'storeUser',
        value: function storeUser(user) {
            this.userList.add(user.userDisplayName || user.userLogin);
        }
    }, {
        key: 'sendMessage',
        value: function sendMessage() {
            var message = this.$text.val();
            if (message.trim().length === 0) {
                return;
            }
            this.chatInputCtrl.props.onSendMessage(message);
            this.parentModule.onSendMessage(message);
            this.$text.val('');
        }
    }, {
        key: 'load',
        value: function load() {
            var createTextarea = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;

            this.chatInputCtrl = (0, _twitch.getChatInputController)();
            if (createTextarea) {
                var _newTextArea = newTextArea(),
                    $text = _newTextArea.$text,
                    $oldText = _newTextArea.$oldText;

                this.$text = $text;
                this.$oldText = $oldText;
                this.userList = new Set();
            }
        }
    }, {
        key: 'enable',
        value: function enable() {
            this.$text.show();
            this.$oldText.hide();
        }
    }, {
        key: 'disable',
        value: function disable() {
            this.$text.hide();
            this.$oldText.show();
        }
    }, {
        key: 'getSuggestions',
        value: function getSuggestions(prefix) {
            var includeUsers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
            var includeEmotes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;

            var userList = [];
            var emoteList = [];

            if (includeEmotes) {
                var _emoteList;

                emoteList = _emotes2.default.getEmotes(); // .map(emote => emote.code);
                (_emoteList = emoteList).push.apply(_emoteList, _toConsumableArray(this.getTwitchEmotes()));
                emoteList = emoteList.filter(function (word) {
                    return word.toLowerCase().indexOf(prefix.toLowerCase()) === 0;
                });
                emoteList = Array.from(new Set(emoteList).values());
                emoteList.sort();
            }

            if (includeUsers) {
                userList = this.getChatMembers().filter(function (word) {
                    return word.toLowerCase().indexOf(prefix.toLowerCase()) === 0;
                });
                userList.sort();
            }

            return [].concat(_toConsumableArray(emoteList), _toConsumableArray(userList));
        }
    }, {
        key: 'onKeydown',
        value: function onKeydown(e, includeUsers) {
            var keyCode = e.key;
            if (e.ctrlKey) {
                return;
            }
            var $inputField = this.$text;

            if (keyCode === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                this.sendMessage();
            } else if (keyCode === 'Tab') {
                e.preventDefault();
                this.onAutoComplete(includeUsers, e.shiftKey);
            } else if (keyCode === 'Escape' && this.tabTries >= 0) {
                $inputField.val(this.textSplit.join(''));
            } else if (keyCode !== 'Shift') {
                this.tabTries = -1;
            }
        }
    }, {
        key: 'onFocus',
        value: function onFocus() {
            this.tabTries = -1;
        }
    }, {
        key: 'onAutoComplete',
        value: function onAutoComplete(includeUsers, shiftKey) {
            var $inputField = this.$text;

            // First time pressing tab, split before and after the word
            if (this.tabTries === -1) {
                var caretPos = $inputField[0].selectionStart;
                var text = $inputField.val();

                var start = (/[\:\(\)\w]+$/.exec(text.substr(0, caretPos)) || { index: caretPos }).index;
                var end = caretPos + (/^\w+/.exec(text.substr(caretPos)) || [''])[0].length;
                this.textSplit = [text.substring(0, start), text.substring(start, end), text.substring(end + 1)];

                // If there are no words in front of the caret, exit
                if (this.textSplit[1] === '') return;

                // Get all matching completions
                var includeEmotes = this.textSplit[0].slice(-1) !== '@';
                this.suggestions = this.getSuggestions(this.textSplit[1], includeUsers, includeEmotes);
            }

            if (this.suggestions.length > 0) {
                this.tabTries += shiftKey ? -1 : 1; // shift key iterates backwards
                if (this.tabTries >= this.suggestions.length) this.tabTries = 0;
                if (this.tabTries < 0) this.tabTries = this.suggestions.length - 1;
                if (!this.suggestions[this.tabTries]) return;

                var cursorOffset = 0;
                if (this.textSplit[2].trim() === '') {
                    this.textSplit[2] = ' ';
                    cursorOffset = 1;
                }

                var cursorPos = this.textSplit[0].length + this.suggestions[this.tabTries].length + cursorOffset;
                $inputField.val(this.textSplit[0] + this.suggestions[this.tabTries] + this.textSplit[2]);
                $inputField[0].setSelectionRange(cursorPos, cursorPos);
            }
        }
    }, {
        key: 'getTwitchEmotes',
        value: function getTwitchEmotes() {
            var twEmotes = this.chatInputCtrl.props.emotes;
            if (!twEmotes) {
                return [];
            }
            return twEmotes.reduce(function (accum, v) {
                return accum.concat(v.emotes);
            }, []).map(function (emote) {
                return emote.displayName;
            });
        }
    }, {
        key: 'getChatMembers',
        value: function getChatMembers() {
            var broadcasterName = this.chatInputCtrl.props.channelDisplayName;
            this.userList.add(broadcasterName);
            return Array.from(this.userList.values());
        }
    }]);

    return CustomInputModule;
}();

exports.default = CustomInputModule;

/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
    value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var $ = window.$;

function isSuggestionsShowing() {
    return !!$('[data-a-target="autocomplete-balloon"]')[0];
}

var ChatHistoryModule = function () {
    function ChatHistoryModule(parentModule) {
        _classCallCheck(this, ChatHistoryModule);

        this.parentModule = parentModule;
    }

    _createClass(ChatHistoryModule, [{
        key: 'load',
        value: function load(resetHistory) {
            if (resetHistory) {
                this.messageHistory = [];
            }
            this.historyPos = -1;
        }
    }, {
        key: 'onKeydown',
        value: function onKeydown(e) {
            var keyCode = e.key;
            if (e.ctrlKey) {
                return;
            }
            var $inputField = $(e.target);
            var setInputValue = function setInputValue(value) {
                e.target.customSetValue(value);
            };

            if (keyCode === 'ArrowUp') {
                if (isSuggestionsShowing()) return;
                if ($inputField[0].selectionStart > 0) return;
                if (this.historyPos + 1 === this.messageHistory.length) return;
                var prevMsg = this.messageHistory[++this.historyPos];
                setInputValue(prevMsg);
                $inputField[0].setSelectionRange(0, 0);
            } else if (keyCode === 'ArrowDown') {
                if (isSuggestionsShowing()) return;
                if ($inputField[0].selectionStart < $inputField.val().length) return;
                if (this.historyPos > 0) {
                    var _prevMsg = this.messageHistory[--this.historyPos];
                    setInputValue(_prevMsg);
                    $inputField[0].setSelectionRange(_prevMsg.length, _prevMsg.length);
                } else {
                    var draft = $inputField.val().trim();
                    if (this.historyPos < 0 && draft.length > 0) {
                        this.messageHistory.unshift(draft);
                    }
                    this.historyPos = -1;
                    $inputField.val('');
                    setInputValue('');
                }
            } else if (this.historyPos >= 0) {
                this.messageHistory[this.historyPos] = $inputField.val();
            }
        }
    }, {
        key: 'onSendMessage',
        value: function onSendMessage(message) {
            if (message.trim().length === 0) return;
            this.messageHistory.unshift(message);
            this.historyPos = -1;
            // watcher.emit('input.onSendMessage', message);
        }
    }, {
        key: 'onFocus',
        value: function onFocus() {
            this.historyPos = -1;
        }
    }]);

    return ChatHistoryModule;
}();

exports.default = ChatHistoryModule;

/***/ })
/******/ ]);
//# sourceMappingURL=main.bundle.js.map