NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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