Twitch Tab Completion (compatible with BTTV)

Tab completion for emotes (and users) also add chat history. BetterTTV

  1. // ==UserScript==
  2. // @name Twitch Tab Completion (compatible with BTTV)
  3. // @namespace https://openuserjs.org/users/daybreakz
  4. // @version 1.0.9
  5. // @description Tab completion for emotes (and users) also add chat history. BetterTTV
  6. // @author Daybr3akz
  7. // @license MIT
  8. // @copyright 2017, daybreakz (https://openuserjs.org/users/daybreakz)
  9. // @match https://www.twitch.tv/*
  10. // @require http://code.jquery.com/jquery-3.2.1.slim.min.js
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. // // ==OpenUserJS==
  15. // @author daybreakz
  16. // ==/OpenUserJS==
  17.  
  18.  
  19.  
  20.  
  21. /******/ (function(modules) { // webpackBootstrap
  22. /******/ // The module cache
  23. /******/ var installedModules = {};
  24. /******/
  25. /******/ // The require function
  26. /******/ function __webpack_require__(moduleId) {
  27. /******/
  28. /******/ // Check if module is in cache
  29. /******/ if(installedModules[moduleId]) {
  30. /******/ return installedModules[moduleId].exports;
  31. /******/ }
  32. /******/ // Create a new module (and put it into the cache)
  33. /******/ var module = installedModules[moduleId] = {
  34. /******/ i: moduleId,
  35. /******/ l: false,
  36. /******/ exports: {}
  37. /******/ };
  38. /******/
  39. /******/ // Execute the module function
  40. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  41. /******/
  42. /******/ // Flag the module as loaded
  43. /******/ module.l = true;
  44. /******/
  45. /******/ // Return the exports of the module
  46. /******/ return module.exports;
  47. /******/ }
  48. /******/
  49. /******/
  50. /******/ // expose the modules object (__webpack_modules__)
  51. /******/ __webpack_require__.m = modules;
  52. /******/
  53. /******/ // expose the module cache
  54. /******/ __webpack_require__.c = installedModules;
  55. /******/
  56. /******/ // define getter function for harmony exports
  57. /******/ __webpack_require__.d = function(exports, name, getter) {
  58. /******/ if(!__webpack_require__.o(exports, name)) {
  59. /******/ Object.defineProperty(exports, name, {
  60. /******/ configurable: false,
  61. /******/ enumerable: true,
  62. /******/ get: getter
  63. /******/ });
  64. /******/ }
  65. /******/ };
  66. /******/
  67. /******/ // getDefaultExport function for compatibility with non-harmony modules
  68. /******/ __webpack_require__.n = function(module) {
  69. /******/ var getter = module && module.__esModule ?
  70. /******/ function getDefault() { return module['default']; } :
  71. /******/ function getModuleExports() { return module; };
  72. /******/ __webpack_require__.d(getter, 'a', getter);
  73. /******/ return getter;
  74. /******/ };
  75. /******/
  76. /******/ // Object.prototype.hasOwnProperty.call
  77. /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
  78. /******/
  79. /******/ // __webpack_public_path__
  80. /******/ __webpack_require__.p = "";
  81. /******/
  82. /******/ // Load entry module and return exports
  83. /******/ return __webpack_require__(__webpack_require__.s = 3);
  84. /******/ })
  85. /************************************************************************/
  86. /******/ ([
  87. /* 0 */
  88. /***/ (function(module, exports, __webpack_require__) {
  89.  
  90. "use strict";
  91.  
  92.  
  93. Object.defineProperty(exports, "__esModule", {
  94. value: true
  95. });
  96. exports.getConnectRoot = getConnectRoot;
  97. exports.getChatController = getChatController;
  98. exports.getChatInputController = getChatInputController;
  99. exports.getCurrentChat = getCurrentChat;
  100. exports.setCurrentUser = setCurrentUser;
  101. exports.getCurrentUser = getCurrentUser;
  102. exports.getCurrentChannel = getCurrentChannel;
  103. exports.updateCurrentChannel = updateCurrentChannel;
  104. var REACT_ROOT = '#root div[data-reactroot]';
  105. var CHAT_CONTAINER = '.chat-room__container';
  106. var CHAT_INPUT = '.chat-input';
  107. // const $ = s => document.querySelectorAll(s);
  108. var $ = window.$;
  109.  
  110. function getReactInstance(element) {
  111. for (var key in element) {
  112. if (key.startsWith('__reactInternalInstance$')) {
  113. return element[key];
  114. }
  115. }
  116. return null;
  117. }
  118.  
  119. function getReactElement(element) {
  120. var instance = getReactInstance(element);
  121. if (!instance) return null;
  122. return instance._currentElement;
  123. }
  124.  
  125. function getParentNode(reactElement) {
  126. try {
  127. return reactElement._owner._currentElement._owner;
  128. } catch (_) {
  129. return null;
  130. }
  131. }
  132.  
  133. function searchReactChildren(node, predicate) {
  134. var maxDepth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 15;
  135. var depth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  136.  
  137. try {
  138. if (predicate(node)) {
  139. return node;
  140. }
  141. } catch (_) {}
  142.  
  143. if (!node || depth > maxDepth) {
  144. return null;
  145. }
  146.  
  147. var children = node._renderedChildren,
  148. component = node._renderedComponent;
  149.  
  150.  
  151. if (children) {
  152. var _iteratorNormalCompletion = true;
  153. var _didIteratorError = false;
  154. var _iteratorError = undefined;
  155.  
  156. try {
  157. for (var _iterator = Object.keys(children)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
  158. var key = _step.value;
  159.  
  160. var childResult = searchReactChildren(children[key], predicate, maxDepth, depth + 1);
  161. if (childResult) {
  162. return childResult;
  163. }
  164. }
  165. } catch (err) {
  166. _didIteratorError = true;
  167. _iteratorError = err;
  168. } finally {
  169. try {
  170. if (!_iteratorNormalCompletion && _iterator.return) {
  171. _iterator.return();
  172. }
  173. } finally {
  174. if (_didIteratorError) {
  175. throw _iteratorError;
  176. }
  177. }
  178. }
  179. }
  180.  
  181. if (component) {
  182. return searchReactChildren(component, predicate, maxDepth, depth + 1);
  183. }
  184.  
  185. return null;
  186. }
  187.  
  188. function getConnectRoot() {
  189. var root = void 0;
  190. try {
  191. root = getParentNode(getReactElement($(REACT_ROOT)[0]));
  192. } catch (_) {}
  193. return root;
  194. }
  195.  
  196. function getChatController() {
  197. var container = $(CHAT_CONTAINER).parent()[0];
  198. if (!container) return null;
  199.  
  200. var controller = searchReactChildren(getReactInstance(container), function (node) {
  201. return node._instance && node._instance.chatBuffer;
  202. });
  203.  
  204. if (controller) {
  205. controller = controller._instance;
  206. }
  207.  
  208. return controller;
  209. }
  210.  
  211. function getChatInputController() {
  212. var container = $(CHAT_INPUT)[0];
  213. if (!container) return null;
  214.  
  215. var controller = void 0;
  216. try {
  217. controller = getParentNode(getReactElement(container))._instance;
  218. } catch (_) {}
  219.  
  220. return controller;
  221. }
  222.  
  223. function getCurrentChat() {
  224. var container = $(CHAT_CONTAINER)[0];
  225. if (!container) return null;
  226. var controller = void 0;
  227. try {
  228. controller = getParentNode(getReactElement(container))._instance;
  229. } catch (_) {}
  230. return controller;
  231. }
  232.  
  233. var currentUser = null;
  234. function setCurrentUser(accessToken, id, name, displayName) {
  235. // twitchAPI.setAccessToken(accessToken);
  236. currentUser = {
  237. id: id.toString(),
  238. name: name,
  239. displayName: displayName
  240. };
  241. }
  242.  
  243. function getCurrentUser() {
  244. return currentUser;
  245. }
  246.  
  247. var currentChannel = void 0;
  248. function getCurrentChannel() {
  249. return currentChannel;
  250. }
  251.  
  252. function updateCurrentChannel() {
  253. var rv = void 0;
  254. var currentChat = getCurrentChat();
  255. if (currentChat && currentChat.props && currentChat.props.channelID) {
  256. var _currentChat$props = currentChat.props,
  257. channelID = _currentChat$props.channelID,
  258. channelLogin = _currentChat$props.channelLogin,
  259. channelDisplayName = _currentChat$props.channelDisplayName;
  260.  
  261. rv = {
  262. id: channelID.toString(),
  263. name: channelLogin,
  264. displayName: channelDisplayName
  265. };
  266. }
  267. currentChannel = rv;
  268. return rv;
  269. }
  270.  
  271. /***/ }),
  272. /* 1 */
  273. /***/ (function(module, exports, __webpack_require__) {
  274.  
  275. "use strict";
  276.  
  277.  
  278. Object.defineProperty(exports, "__esModule", {
  279. value: true
  280. });
  281. window.customLog = localStorage.getItem('customLog') === 'true';
  282.  
  283. var console = window.console;
  284. try {
  285. console = $('iframe')[0].contentWindow.console;
  286. } catch (e) {}
  287.  
  288. function log(type) {
  289. if (!console || !window.customLog) return;
  290.  
  291. for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  292. args[_key - 1] = arguments[_key];
  293. }
  294.  
  295. console[type].apply(console, ['SCRIPT:'].concat(args));
  296. }
  297.  
  298. var debug = {
  299. log: log.bind(undefined, 'log'),
  300. error: log.bind(undefined, 'error'),
  301. warn: log.bind(undefined, 'warn'),
  302. info: log.bind(undefined, 'info')
  303. };
  304.  
  305. exports.default = debug;
  306.  
  307. /***/ }),
  308. /* 2 */
  309. /***/ (function(module, exports, __webpack_require__) {
  310.  
  311. "use strict";
  312.  
  313.  
  314. Object.defineProperty(exports, "__esModule", {
  315. value: true
  316. });
  317.  
  318. 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; }; }();
  319.  
  320. var _debug = __webpack_require__(1);
  321.  
  322. var _debug2 = _interopRequireDefault(_debug);
  323.  
  324. var _twitch = __webpack_require__(0);
  325.  
  326. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  327.  
  328. 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); } }
  329.  
  330. 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"); }); }; }
  331.  
  332. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  333.  
  334. var API_ENDPOINT = 'https://api.betterttv.net/2/';
  335. var fetchJson = function fetchJson(path) {
  336. return fetch('' + API_ENDPOINT + path).then(function (r) {
  337. return r.json();
  338. });
  339. };
  340.  
  341. var channel = {};
  342.  
  343. var EmotesProvider = function () {
  344. function EmotesProvider() {
  345. _classCallCheck(this, EmotesProvider);
  346.  
  347. this.channelEmotes = new Set();
  348. this.globalEmotes = new Set();
  349. this.emojis = new Set();
  350. this.loadBTTVGlobalEmotes();
  351. // this.loadEmojis();
  352. }
  353.  
  354. _createClass(EmotesProvider, [{
  355. key: 'updateChannel',
  356. value: function () {
  357. var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
  358. var currentChannel;
  359. return regeneratorRuntime.wrap(function _callee$(_context) {
  360. while (1) {
  361. switch (_context.prev = _context.next) {
  362. case 0:
  363. currentChannel = (0, _twitch.getCurrentChannel)();
  364.  
  365. if (currentChannel) {
  366. _context.next = 3;
  367. break;
  368. }
  369.  
  370. return _context.abrupt('return');
  371.  
  372. case 3:
  373. if (!(currentChannel.id === channel.id)) {
  374. _context.next = 5;
  375. break;
  376. }
  377.  
  378. return _context.abrupt('return');
  379.  
  380. case 5:
  381. channel = currentChannel;
  382. _context.next = 8;
  383. return fetchJson('channels/' + channel.name);
  384.  
  385. case 8:
  386. return _context.abrupt('return', _context.sent);
  387.  
  388. case 9:
  389. case 'end':
  390. return _context.stop();
  391. }
  392. }
  393. }, _callee, this);
  394. }));
  395.  
  396. function updateChannel() {
  397. return _ref.apply(this, arguments);
  398. }
  399.  
  400. return updateChannel;
  401. }()
  402. }, {
  403. key: 'load',
  404. value: function () {
  405. var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
  406. var channelData;
  407. return regeneratorRuntime.wrap(function _callee2$(_context2) {
  408. while (1) {
  409. switch (_context2.prev = _context2.next) {
  410. case 0:
  411. _context2.next = 2;
  412. return this.updateChannel();
  413.  
  414. case 2:
  415. channelData = _context2.sent;
  416.  
  417. if (channelData && channelData.emotes) {
  418. this.loadBTTVChannelEmotes(channelData.emotes);
  419. }
  420.  
  421. case 4:
  422. case 'end':
  423. return _context2.stop();
  424. }
  425. }
  426. }, _callee2, this);
  427. }));
  428.  
  429. function load() {
  430. return _ref2.apply(this, arguments);
  431. }
  432.  
  433. return load;
  434. }()
  435. }, {
  436. key: 'loadBTTVGlobalEmotes',
  437. value: function () {
  438. var _ref3 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3() {
  439. var _this = this;
  440.  
  441. var x, emotesJson;
  442. return regeneratorRuntime.wrap(function _callee3$(_context3) {
  443. while (1) {
  444. switch (_context3.prev = _context3.next) {
  445. case 0:
  446. _context3.next = 2;
  447. return fetchJson('emotes');
  448.  
  449. case 2:
  450. x = _context3.sent;
  451. emotesJson = x.emotes;
  452.  
  453. emotesJson.forEach(function (em) {
  454. if (!em || !em.code) return;
  455. _this.globalEmotes.add(em.code);
  456. });
  457. _debug2.default.log('Got globalEmotes');
  458.  
  459. case 6:
  460. case 'end':
  461. return _context3.stop();
  462. }
  463. }
  464. }, _callee3, this);
  465. }));
  466.  
  467. function loadBTTVGlobalEmotes() {
  468. return _ref3.apply(this, arguments);
  469. }
  470.  
  471. return loadBTTVGlobalEmotes;
  472. }()
  473. }, {
  474. key: 'loadBTTVChannelEmotes',
  475. value: function loadBTTVChannelEmotes(emotesJson) {
  476. var _this2 = this;
  477.  
  478. this.channelEmotes.clear();
  479. emotesJson.forEach(function (em) {
  480. if (!em || !em.code) return;
  481. _this2.channelEmotes.add(em.code);
  482. });
  483. _debug2.default.log('Got channel emotes');
  484. }
  485. }, {
  486. key: 'getEmotes',
  487. value: function getEmotes() {
  488. if (!window.BetterTTV) {
  489. return [];
  490. }
  491. var user = (0, _twitch.getCurrentUser)();user;
  492.  
  493. var emotes = [].concat(_toConsumableArray(this.globalEmotes))
  494. // .concat([...this.emojis])
  495. .concat([].concat(_toConsumableArray(this.channelEmotes)));
  496. return emotes;
  497. }
  498. }]);
  499.  
  500. return EmotesProvider;
  501. }();
  502.  
  503. // let provider = new NopeProvider();
  504. // if (window.BetterTTV) {
  505.  
  506.  
  507. var provider = new EmotesProvider();
  508. exports.default = provider;
  509.  
  510. /***/ }),
  511. /* 3 */
  512. /***/ (function(module, exports, __webpack_require__) {
  513.  
  514. "use strict";
  515.  
  516.  
  517. var _routeKeysToPaths;
  518.  
  519. var waitForChat = function () {
  520. var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
  521. var abort, currentIsWaiting;
  522. return regeneratorRuntime.wrap(function _callee$(_context) {
  523. while (1) {
  524. switch (_context.prev = _context.next) {
  525. case 0:
  526. abort = false;
  527.  
  528. wait(15000).then(function () {
  529. return abort = true;
  530. });
  531. isWaiting = Symbol('waitingForChat');
  532. currentIsWaiting = isWaiting;
  533.  
  534. case 4:
  535. if (abort) {
  536. _context.next = 14;
  537. break;
  538. }
  539.  
  540. if (!checkChat()) {
  541. _context.next = 7;
  542. break;
  543. }
  544.  
  545. return _context.abrupt('return', true);
  546.  
  547. case 7:
  548. if (!(isWaiting !== currentIsWaiting)) {
  549. _context.next = 10;
  550. break;
  551. }
  552.  
  553. _debug2.default.log('waitForChat was cancelled');
  554. return _context.abrupt('return', false);
  555.  
  556. case 10:
  557. _context.next = 12;
  558. return wait(25);
  559.  
  560. case 12:
  561. _context.next = 4;
  562. break;
  563.  
  564. case 14:
  565. return _context.abrupt('return', false);
  566.  
  567. case 15:
  568. case 'end':
  569. return _context.stop();
  570. }
  571. }
  572. }, _callee, this);
  573. }));
  574.  
  575. return function waitForChat() {
  576. return _ref.apply(this, arguments);
  577. };
  578. }();
  579.  
  580. var main = function () {
  581. var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2() {
  582. var router;
  583. return regeneratorRuntime.wrap(function _callee2$(_context2) {
  584. while (1) {
  585. switch (_context2.prev = _context2.next) {
  586. case 0:
  587. _context2.next = 2;
  588. return getRouter();
  589.  
  590. case 2:
  591. router = _context2.sent;
  592.  
  593. router.history.listen(function (location) {
  594. return onRouteChange(location);
  595. });
  596. onRouteChange(router.history.location);
  597.  
  598. case 5:
  599. case 'end':
  600. return _context2.stop();
  601. }
  602. }
  603. }, _callee2, this);
  604. }));
  605.  
  606. return function main() {
  607. return _ref2.apply(this, arguments);
  608. };
  609. }();
  610.  
  611. var _debug = __webpack_require__(1);
  612.  
  613. var _debug2 = _interopRequireDefault(_debug);
  614.  
  615. var _tab_completion = __webpack_require__(4);
  616.  
  617. var _tab_completion2 = _interopRequireDefault(_tab_completion);
  618.  
  619. var _emotes = __webpack_require__(2);
  620.  
  621. var _emotes2 = _interopRequireDefault(_emotes);
  622.  
  623. var _twitch = __webpack_require__(0);
  624.  
  625. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  626.  
  627. 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"); }); }; }
  628.  
  629. 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; }
  630.  
  631. var currentPath = void 0;
  632.  
  633. var routes = {
  634. DIRECTORY_FOLLOWING_LIVE: 'DIRECTORY_FOLLOWING_LIVE',
  635. DIRECTORY_FOLLOWING: 'DIRECTORY_FOLLOWING',
  636. DIRECTORY: 'DIRECTORY',
  637. CHAT: 'CHAT',
  638. CHANNEL: 'CHANNEL',
  639. VOD: 'VOD'
  640. };
  641.  
  642. 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);
  643.  
  644. function getRouteFromPath(path) {
  645. var _iteratorNormalCompletion = true;
  646. var _didIteratorError = false;
  647. var _iteratorError = undefined;
  648.  
  649. try {
  650. for (var _iterator = Object.keys(routeKeysToPaths)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
  651. var name = _step.value;
  652.  
  653. var regex = routeKeysToPaths[name];
  654. if (!regex.test(path)) continue;
  655. return name;
  656. }
  657. } catch (err) {
  658. _didIteratorError = true;
  659. _iteratorError = err;
  660. } finally {
  661. try {
  662. if (!_iteratorNormalCompletion && _iterator.return) {
  663. _iterator.return();
  664. }
  665. } finally {
  666. if (_didIteratorError) {
  667. throw _iteratorError;
  668. }
  669. }
  670. }
  671.  
  672. return null;
  673. }
  674.  
  675. function getRouter() {
  676. return new Promise(function (resolve) {
  677. var loadInterval = setInterval(function () {
  678. var user = void 0,
  679. router = void 0;
  680. try {
  681. var connectRoot = (0, _twitch.getConnectRoot)();
  682. if (!connectRoot) return;
  683. var context = connectRoot._context;
  684. router = context.router;
  685. user = context.store.getState().session.user;
  686. } catch (_) {
  687. return;
  688. }
  689.  
  690. if (!router || !user) return;
  691. clearInterval(loadInterval);
  692.  
  693. (0, _twitch.setCurrentUser)(user.authToken, user.id, user.login, user.displayName);
  694. resolve(router);
  695. }, 25);
  696. });
  697. }
  698.  
  699. function makeCheckChat() {
  700. var currentChatReference = null;
  701. return function () {
  702. if (!(0, _twitch.updateCurrentChannel)()) return false;
  703. var lastReference = currentChatReference;
  704. var currentChat = (0, _twitch.getCurrentChat)();
  705.  
  706. if (currentChat && currentChat === lastReference) {
  707. return false;
  708. }
  709. if (lastReference && currentChat.props.channelID.toString() === lastReference.props.channelID.toString()) {
  710. return false;
  711. }
  712.  
  713. currentChatReference = currentChat;
  714.  
  715. return true;
  716. };
  717. }
  718. var checkChat = makeCheckChat();
  719.  
  720. var wait = function wait(t) {
  721. return new Promise(function (r) {
  722. return setTimeout(r, t);
  723. });
  724. };
  725. var isWaiting = false;
  726.  
  727.  
  728. function triggerRouteChanged() {}
  729.  
  730. function triggerChatLoaded() {
  731. _debug2.default.log('CHAT WAS LOADED');
  732. _tab_completion2.default.load(true);
  733. _emotes2.default.load();
  734. }
  735.  
  736. function onRouteChange(location) {
  737. var lastPath = currentPath;
  738. var path = location.pathname;
  739. var route = getRouteFromPath(path);
  740. _debug2.default.log('New route: ' + location.pathname + ' as ' + route);
  741.  
  742. // emit load
  743. triggerRouteChanged();
  744. currentPath = path;
  745. if (currentPath === lastPath) return;
  746. if (route === routes.CHAT || route === routes.CHANNEL) {
  747. waitForChat().then(function (loaded) {
  748. return loaded && triggerChatLoaded();
  749. });
  750. }
  751. }
  752.  
  753. main();
  754.  
  755. /***/ }),
  756. /* 4 */
  757. /***/ (function(module, exports, __webpack_require__) {
  758.  
  759. "use strict";
  760.  
  761.  
  762. Object.defineProperty(exports, "__esModule", {
  763. value: true
  764. });
  765.  
  766. 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; }; }();
  767. // const InputPatcherModule = require('./input-patcher-module');
  768.  
  769.  
  770. var _customInputModule = __webpack_require__(5);
  771.  
  772. var _customInputModule2 = _interopRequireDefault(_customInputModule);
  773.  
  774. var _chatHistoryModule = __webpack_require__(6);
  775.  
  776. var _chatHistoryModule2 = _interopRequireDefault(_chatHistoryModule);
  777.  
  778. var _twitch = __webpack_require__(0);
  779.  
  780. var _debug = __webpack_require__(1);
  781.  
  782. var _debug2 = _interopRequireDefault(_debug);
  783.  
  784. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  785.  
  786. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  787.  
  788. // window.getChatController = getChatController;
  789.  
  790. var CHAT_INPUT = '.chat-input';
  791. var $ = window.$;
  792.  
  793. function patchSendMessage(callback) {
  794. var chatBuffer = (0, _twitch.getChatController)().chatBuffer;
  795. _debug2.default.log(chatBuffer);
  796.  
  797. if (chatBuffer._consumeChatEventPatched === true) {
  798. return;
  799. }
  800. chatBuffer._consumeChatEventPatched = true;
  801. var twitchConsumeChatEvent = chatBuffer.consumeChatEvent;
  802.  
  803. function myConsumeChatEvent(event) {
  804. if (event && event.type === 0) {
  805. try {
  806. callback(event);
  807. } catch (error) {
  808. _debug2.default.error(error);
  809. }
  810. }
  811. return twitchConsumeChatEvent.apply(this, arguments);
  812. }
  813. chatBuffer.consumeChatEvent = myConsumeChatEvent;
  814. }
  815.  
  816. var ChatTabCompletionModule = function () {
  817. function ChatTabCompletionModule() {
  818. var _this = this;
  819.  
  820. _classCallCheck(this, ChatTabCompletionModule);
  821.  
  822. this.customInput = new _customInputModule2.default(this);
  823. this.chatHistory = new _chatHistoryModule2.default(this);
  824. this.currentInput = null;
  825.  
  826. // watcher.on('load.chat', () => this.load());
  827. // settings.on('changed.tabAutocomplete', () => this.load(false));
  828.  
  829. $('body').off('click.tabComplete focus.tabComplete keydown.tabComplete').on('click.tabComplete focus.tabComplete', CHAT_INPUT, function () {
  830. return _this.onFocus();
  831. }).on('keydown.tabComplete', CHAT_INPUT, function (e) {
  832. return _this.onKeydown(e);
  833. });
  834. }
  835.  
  836. _createClass(ChatTabCompletionModule, [{
  837. key: 'load',
  838. value: function load() {
  839. var _this2 = this;
  840.  
  841. var chatLoad = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  842.  
  843. this.customInput.load(chatLoad);
  844. this.chatHistory.load(chatLoad);
  845. // if (settings.get('tabAutocomplete')) {
  846. this.customInput.enable();
  847. this.currentInput = this.customInput;
  848. // } else {
  849. // this.customInput.disable();
  850. // this.currentInput = this.patchedInput;
  851. // }
  852. patchSendMessage(function (event) {
  853. _this2.currentInput.storeUser(event.user);
  854. });
  855. }
  856. }, {
  857. key: 'onKeydown',
  858. value: function onKeydown(e) {
  859. if (this.currentInput) {
  860. this.currentInput.onKeydown(e);
  861. }
  862. this.chatHistory.onKeydown(e);
  863. }
  864. }, {
  865. key: 'onFocus',
  866. value: function onFocus() {
  867. if (this.currentInput) {
  868. this.currentInput.onFocus();
  869. }
  870. this.chatHistory.onFocus();
  871. }
  872. }, {
  873. key: 'onSendMessage',
  874. value: function onSendMessage(message) {
  875. this.chatHistory.onSendMessage(message);
  876. }
  877. }]);
  878.  
  879. return ChatTabCompletionModule;
  880. }();
  881.  
  882. var mod = new ChatTabCompletionModule();
  883. exports.default = mod;
  884.  
  885. /***/ }),
  886. /* 5 */
  887. /***/ (function(module, exports, __webpack_require__) {
  888.  
  889. "use strict";
  890.  
  891.  
  892. Object.defineProperty(exports, "__esModule", {
  893. value: true
  894. });
  895.  
  896. 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; }; }();
  897.  
  898. var _emotes = __webpack_require__(2);
  899.  
  900. var _emotes2 = _interopRequireDefault(_emotes);
  901.  
  902. var _twitch = __webpack_require__(0);
  903.  
  904. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  905.  
  906. 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); } }
  907.  
  908. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  909.  
  910. var $ = window.$;
  911.  
  912.  
  913. var ORIGINAL_TEXTAREA = '.chat-input textarea';
  914.  
  915. function setReactTextareaValue(txt, msg) {
  916. txt.value = msg;
  917. var ev = new Event('input', { target: txt, bubbles: true });
  918. txt.dispatchEvent(ev);
  919. }
  920.  
  921. function newTextArea() {
  922. var $oldText = $(ORIGINAL_TEXTAREA);
  923. var $text = $oldText.clone().insertBefore(ORIGINAL_TEXTAREA);
  924. $text.attr('id', 'bttv-chat-input');
  925. $oldText.attr('id', 'twitch-chat-input');
  926. $oldText.hide();
  927. $text.focus();
  928.  
  929. $text[0].customSetValue = function (value) {
  930. $text.val(value);
  931. };
  932.  
  933. $oldText[0].customSetValue = function (value) {
  934. setReactTextareaValue($oldText[0], value);
  935. };
  936.  
  937. return { $text: $text, $oldText: $oldText };
  938. }
  939.  
  940. var CustomInputModule = function () {
  941. function CustomInputModule(parentModule) {
  942. _classCallCheck(this, CustomInputModule);
  943.  
  944. this.parentModule = parentModule;
  945. this.init();
  946. // watcher.on('input.onSendMessage', () => this.sendMessage());
  947. // watcher.on('chat.message', ($el, msg) => this.storeUser($el, msg));
  948. }
  949.  
  950. _createClass(CustomInputModule, [{
  951. key: 'init',
  952. value: function init() {
  953. this.userList = new Set();
  954. this.tabTries = -1;
  955. this.suggestions = null;
  956. this.textSplit = ['', '', ''];
  957. }
  958. }, {
  959. key: 'storeUser',
  960. value: function storeUser(user) {
  961. this.userList.add(user.userDisplayName || user.userLogin);
  962. }
  963. }, {
  964. key: 'sendMessage',
  965. value: function sendMessage() {
  966. var message = this.$text.val();
  967. if (message.trim().length === 0) {
  968. return;
  969. }
  970. this.chatInputCtrl.props.onSendMessage(message);
  971. this.parentModule.onSendMessage(message);
  972. this.$text.val('');
  973. }
  974. }, {
  975. key: 'load',
  976. value: function load() {
  977. var createTextarea = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  978.  
  979. this.chatInputCtrl = (0, _twitch.getChatInputController)();
  980. if (createTextarea) {
  981. var _newTextArea = newTextArea(),
  982. $text = _newTextArea.$text,
  983. $oldText = _newTextArea.$oldText;
  984.  
  985. this.$text = $text;
  986. this.$oldText = $oldText;
  987. this.userList = new Set();
  988. }
  989. }
  990. }, {
  991. key: 'enable',
  992. value: function enable() {
  993. this.$text.show();
  994. this.$oldText.hide();
  995. }
  996. }, {
  997. key: 'disable',
  998. value: function disable() {
  999. this.$text.hide();
  1000. this.$oldText.show();
  1001. }
  1002. }, {
  1003. key: 'getSuggestions',
  1004. value: function getSuggestions(prefix) {
  1005. var includeUsers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
  1006. var includeEmotes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
  1007.  
  1008. var userList = [];
  1009. var emoteList = [];
  1010.  
  1011. if (includeEmotes) {
  1012. var _emoteList;
  1013.  
  1014. emoteList = _emotes2.default.getEmotes(); // .map(emote => emote.code);
  1015. (_emoteList = emoteList).push.apply(_emoteList, _toConsumableArray(this.getTwitchEmotes()));
  1016. emoteList = emoteList.filter(function (word) {
  1017. return word.toLowerCase().indexOf(prefix.toLowerCase()) === 0;
  1018. });
  1019. emoteList = Array.from(new Set(emoteList).values());
  1020. emoteList.sort();
  1021. }
  1022.  
  1023. if (includeUsers) {
  1024. userList = this.getChatMembers().filter(function (word) {
  1025. return word.toLowerCase().indexOf(prefix.toLowerCase()) === 0;
  1026. });
  1027. userList.sort();
  1028. }
  1029.  
  1030. return [].concat(_toConsumableArray(emoteList), _toConsumableArray(userList));
  1031. }
  1032. }, {
  1033. key: 'onKeydown',
  1034. value: function onKeydown(e, includeUsers) {
  1035. var keyCode = e.key;
  1036. if (e.ctrlKey) {
  1037. return;
  1038. }
  1039. var $inputField = this.$text;
  1040.  
  1041. if (keyCode === 'Enter' && !e.shiftKey) {
  1042. e.preventDefault();
  1043. this.sendMessage();
  1044. } else if (keyCode === 'Tab') {
  1045. e.preventDefault();
  1046. this.onAutoComplete(includeUsers, e.shiftKey);
  1047. } else if (keyCode === 'Escape' && this.tabTries >= 0) {
  1048. $inputField.val(this.textSplit.join(''));
  1049. } else if (keyCode !== 'Shift') {
  1050. this.tabTries = -1;
  1051. }
  1052. }
  1053. }, {
  1054. key: 'onFocus',
  1055. value: function onFocus() {
  1056. this.tabTries = -1;
  1057. }
  1058. }, {
  1059. key: 'onAutoComplete',
  1060. value: function onAutoComplete(includeUsers, shiftKey) {
  1061. var $inputField = this.$text;
  1062.  
  1063. // First time pressing tab, split before and after the word
  1064. if (this.tabTries === -1) {
  1065. var caretPos = $inputField[0].selectionStart;
  1066. var text = $inputField.val();
  1067.  
  1068. var start = (/[\:\(\)\w]+$/.exec(text.substr(0, caretPos)) || { index: caretPos }).index;
  1069. var end = caretPos + (/^\w+/.exec(text.substr(caretPos)) || [''])[0].length;
  1070. this.textSplit = [text.substring(0, start), text.substring(start, end), text.substring(end + 1)];
  1071.  
  1072. // If there are no words in front of the caret, exit
  1073. if (this.textSplit[1] === '') return;
  1074.  
  1075. // Get all matching completions
  1076. var includeEmotes = this.textSplit[0].slice(-1) !== '@';
  1077. this.suggestions = this.getSuggestions(this.textSplit[1], includeUsers, includeEmotes);
  1078. }
  1079.  
  1080. if (this.suggestions.length > 0) {
  1081. this.tabTries += shiftKey ? -1 : 1; // shift key iterates backwards
  1082. if (this.tabTries >= this.suggestions.length) this.tabTries = 0;
  1083. if (this.tabTries < 0) this.tabTries = this.suggestions.length - 1;
  1084. if (!this.suggestions[this.tabTries]) return;
  1085.  
  1086. var cursorOffset = 0;
  1087. if (this.textSplit[2].trim() === '') {
  1088. this.textSplit[2] = ' ';
  1089. cursorOffset = 1;
  1090. }
  1091.  
  1092. var cursorPos = this.textSplit[0].length + this.suggestions[this.tabTries].length + cursorOffset;
  1093. $inputField.val(this.textSplit[0] + this.suggestions[this.tabTries] + this.textSplit[2]);
  1094. $inputField[0].setSelectionRange(cursorPos, cursorPos);
  1095. }
  1096. }
  1097. }, {
  1098. key: 'getTwitchEmotes',
  1099. value: function getTwitchEmotes() {
  1100. var twEmotes = this.chatInputCtrl.props.emotes;
  1101. if (!twEmotes) {
  1102. return [];
  1103. }
  1104. return twEmotes.reduce(function (accum, v) {
  1105. return accum.concat(v.emotes);
  1106. }, []).map(function (emote) {
  1107. return emote.displayName;
  1108. });
  1109. }
  1110. }, {
  1111. key: 'getChatMembers',
  1112. value: function getChatMembers() {
  1113. var broadcasterName = this.chatInputCtrl.props.channelDisplayName;
  1114. this.userList.add(broadcasterName);
  1115. return Array.from(this.userList.values());
  1116. }
  1117. }]);
  1118.  
  1119. return CustomInputModule;
  1120. }();
  1121.  
  1122. exports.default = CustomInputModule;
  1123.  
  1124. /***/ }),
  1125. /* 6 */
  1126. /***/ (function(module, exports, __webpack_require__) {
  1127.  
  1128. "use strict";
  1129.  
  1130.  
  1131. Object.defineProperty(exports, "__esModule", {
  1132. value: true
  1133. });
  1134.  
  1135. 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; }; }();
  1136.  
  1137. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  1138.  
  1139. var $ = window.$;
  1140.  
  1141. function isSuggestionsShowing() {
  1142. return !!$('[data-a-target="autocomplete-balloon"]')[0];
  1143. }
  1144.  
  1145. var ChatHistoryModule = function () {
  1146. function ChatHistoryModule(parentModule) {
  1147. _classCallCheck(this, ChatHistoryModule);
  1148.  
  1149. this.parentModule = parentModule;
  1150. }
  1151.  
  1152. _createClass(ChatHistoryModule, [{
  1153. key: 'load',
  1154. value: function load(resetHistory) {
  1155. if (resetHistory) {
  1156. this.messageHistory = [];
  1157. }
  1158. this.historyPos = -1;
  1159. }
  1160. }, {
  1161. key: 'onKeydown',
  1162. value: function onKeydown(e) {
  1163. var keyCode = e.key;
  1164. if (e.ctrlKey) {
  1165. return;
  1166. }
  1167. var $inputField = $(e.target);
  1168. var setInputValue = function setInputValue(value) {
  1169. e.target.customSetValue(value);
  1170. };
  1171.  
  1172. if (keyCode === 'ArrowUp') {
  1173. if (isSuggestionsShowing()) return;
  1174. if ($inputField[0].selectionStart > 0) return;
  1175. if (this.historyPos + 1 === this.messageHistory.length) return;
  1176. var prevMsg = this.messageHistory[++this.historyPos];
  1177. setInputValue(prevMsg);
  1178. $inputField[0].setSelectionRange(0, 0);
  1179. } else if (keyCode === 'ArrowDown') {
  1180. if (isSuggestionsShowing()) return;
  1181. if ($inputField[0].selectionStart < $inputField.val().length) return;
  1182. if (this.historyPos > 0) {
  1183. var _prevMsg = this.messageHistory[--this.historyPos];
  1184. setInputValue(_prevMsg);
  1185. $inputField[0].setSelectionRange(_prevMsg.length, _prevMsg.length);
  1186. } else {
  1187. var draft = $inputField.val().trim();
  1188. if (this.historyPos < 0 && draft.length > 0) {
  1189. this.messageHistory.unshift(draft);
  1190. }
  1191. this.historyPos = -1;
  1192. $inputField.val('');
  1193. setInputValue('');
  1194. }
  1195. } else if (this.historyPos >= 0) {
  1196. this.messageHistory[this.historyPos] = $inputField.val();
  1197. }
  1198. }
  1199. }, {
  1200. key: 'onSendMessage',
  1201. value: function onSendMessage(message) {
  1202. if (message.trim().length === 0) return;
  1203. this.messageHistory.unshift(message);
  1204. this.historyPos = -1;
  1205. // watcher.emit('input.onSendMessage', message);
  1206. }
  1207. }, {
  1208. key: 'onFocus',
  1209. value: function onFocus() {
  1210. this.historyPos = -1;
  1211. }
  1212. }]);
  1213.  
  1214. return ChatHistoryModule;
  1215. }();
  1216.  
  1217. exports.default = ChatHistoryModule;
  1218.  
  1219. /***/ })
  1220. /******/ ]);
  1221. //# sourceMappingURL=main.bundle.js.map
  1222.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址