twitch spam filter

this script reduces twitch's chat spam

  1. // ==UserScript==
  2. // @name twitch spam filter
  3. // @description this script reduces twitch's chat spam
  4. // @namespace twitch
  5. // @include /^https?://www\.twitch\.tv/[a-zA-Z0-9_]+$/
  6. // @version 0.1
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. /* known issues :
  11. ** - the last chat message is sometimes hidden, you need to scroll a bit,
  12. ** or wait for the next message to see it.
  13. ** - if you naviguate to another stream through the website,
  14. ** the spam count will not show. You need to refresh the page to
  15. ** reset it.
  16. */
  17.  
  18. /* todo :
  19. ** - filter 'weird' text, which is most likely copypasta
  20. */
  21.  
  22. window.onload = function() {
  23. /* helper functions */
  24. var uniq = function(str) {
  25. var seen = {};
  26. return str.filter(function(item) {
  27. return seen.hasOwnProperty(item) ? false : (seen[item] = true);
  28. });
  29. };
  30. /* objects */
  31. /* Bucket */
  32. var Bucket = function(size) {
  33. this.maxSize = size;
  34. this.currentSize = 0;
  35. this.array = [];
  36. };
  37. Bucket.prototype.add = function(elem) {
  38. if (this.currentSize < this.maxSize) {
  39. this.array.push(elem);
  40. this.currentSize++;
  41. return ;
  42. }
  43. this.array.shift();
  44. this.array.push(elem);
  45. };
  46. Bucket.prototype.contains = function(elem) {
  47. return this.array.indexOf(elem) != -1;
  48. };
  49. /* * Message */
  50. var Message = function(object) {
  51. this.object = object;
  52. this.parse(object);
  53. };
  54. Message.prototype.parse = function(object) {
  55. this.from = $(".from", object).text();
  56. this.content = $(".message", object).text().trim();
  57. this.emoticons = this.parseEmoticons($(".emoticon", object));
  58. this.badges = this.parseBadges($(".badge", object));
  59. };
  60. Message.prototype.parseEmoticons = function(object) {
  61. var parsed = [];
  62. object.each(function() {
  63. parsed.push($(this).attr("alt"));
  64. });
  65. return parsed;
  66. };
  67. Message.prototype.parseBadges = function(object) {
  68. var parsed = [];
  69. object.each(function() {
  70. parsed.push($(this).attr("class").split(" ").slice(3)[0]);
  71. });
  72. return parsed;
  73. };
  74. Message.prototype.show = function() {
  75. $(this.object).addClass("filtered");
  76. return $(this.object).show();
  77. };
  78. Message.prototype.remove = function() {
  79. return $(this.object).remove();
  80. };
  81. /* * MessageHandler */
  82. var MessageHandler = function() {
  83. this.filtered = 0;
  84. this.filteredBox = null;
  85. this.filterBoxActive = false
  86. this.whiteHandlers = []; // the message is shown if one hadnler returns true
  87. this.blackHandlers = []; // the message is filtered if one handler returns false
  88. };
  89. MessageHandler.prototype.init = function() {
  90.  
  91. };
  92. MessageHandler.prototype.handleMessage = function(message) {
  93. for (var i = 0, max = this.whiteHandlers.length; i < max; i++) {
  94. if (this.whiteHandlers[i](message) === true) {
  95. return true;
  96. }
  97. }
  98. for (var i = 0, max = this.blackHandlers.length; i < max; i++) {
  99. if (this.blackHandlers[i](message) === false) {
  100. return false;
  101. }
  102. }
  103. return true;
  104. };
  105. MessageHandler.prototype.registerHandler = function(type, handler) {
  106. if (! this.hasOwnProperty(type)) {
  107. return false;
  108. }
  109. this[type].push(handler);
  110. return true;
  111. };
  112. MessageHandler.prototype.createFilterBox = function() {
  113. this.filteredBox = $("<p class='filterbox' title='spam count' style='position:relative;top:-2px;left:8px;color: rgb(166, 142, 210)'></p>");
  114. if ($(".chat-buttons-container").append(this.filteredBox)) {
  115. $(this.filteredBox).text(this.filtered);
  116. this.filterBoxActive = true;
  117. }
  118. };
  119. MessageHandler.prototype.incFilterCount = function() {
  120. if (! this.filterBoxActive) {
  121. this.createFilterBox();
  122. }
  123. this.filtered++;
  124. $(this.filteredBox).text(this.filtered);
  125. };
  126. /* whiteHandlers */
  127. var whitelist = [
  128. $(".username").text()
  129. ];
  130. var whitelistHandler = function(message) {
  131. for (var i = 0, max = whitelist.length; i < max; i++) {
  132. if (message.from == whitelist[i]) {
  133. return true;
  134. }
  135. }
  136. if (message.from == "") { // admin messages leave username blank
  137. return true;
  138. }
  139. return false;
  140. };
  141. /* blackHandlers */
  142. var emoteHandler = function(message) {
  143. /* if the message is empty, it only contains emotes */
  144. if (! message.content.length) {
  145. return false;
  146. }
  147. /* that's too much */
  148. if (message.emoticons.length >= 5) {
  149. return false;
  150. }
  151. return true;
  152. };
  153. var numericHandler = function(message) {
  154. /* Kappa 123 */
  155. return isNaN(message.content);
  156. };
  157. var oneWordHandler = function(message) {
  158. /* who cares of false positives */
  159. return message.content.indexOf(" ") != -1;
  160. };
  161. var capsLockPleaseHandler = function(message) {
  162. var caps = 0;
  163. var alphaNum = 0;
  164. for (var i = 0, max = message.content.length; i < max; i++) {
  165. if (message.content[i] >= 'A' && message.content[i] <= 'Z') {
  166. caps++;
  167. alphaNum++;
  168. }
  169. else if (message.content[i] != " ") {
  170. alphaNum++;
  171. }
  172. }
  173. if (alphaNum / caps <= 2.0) {
  174. return false;
  175. }
  176. return true;
  177. };
  178. var sameSameHandler = function(message) {
  179. var msg = message.content.split(" ");
  180. var uniqMsg = uniq(msg);
  181. if (msg.length / uniqMsg.length >= 1.7) {
  182. return false;
  183. }
  184. return true;
  185. };
  186. var latestMessages = new Bucket(30);
  187. /* r9kHandler should be executed last, as storing a filtered
  188. ** message is useless */
  189. var r9kHandler = function(message) {
  190. var ret = true;
  191. if (latestMessages.contains(message.content)) {
  192. ret = false;
  193. }
  194. latestMessages.add(message.content);
  195. return ret;
  196. };
  197. /* code */
  198. var hideMessages = function() {
  199. $("head").append("<style type='text/css'>.chat-line { display: none; }</style>");
  200. };
  201. var messageHandler = new MessageHandler();
  202. var whiteHandlers = [
  203. whitelistHandler
  204. ];
  205. var blackHandlers = [
  206. emoteHandler,
  207. numericHandler,
  208. oneWordHandler,
  209. capsLockPleaseHandler,
  210. sameSameHandler,
  211. r9kHandler
  212. ];
  213. messageHandler.init();
  214. for (var i = 0, max = whiteHandlers.length; i < max; i++) {
  215. messageHandler.registerHandler("whiteHandlers", whiteHandlers[i]);
  216. }
  217. for (var i = 0, max = blackHandlers.length; i < max; i++) {
  218. messageHandler.registerHandler("blackHandlers", blackHandlers[i]);
  219. }
  220. hideMessages();
  221. setInterval(function() {
  222. var messages = $(".chat-line").not(".filtered");
  223. messages.each(function() {
  224. var message = new Message(this);
  225. if (messageHandler.handleMessage(message)) {
  226. message.show();
  227. }
  228. else {
  229. message.remove();
  230. messageHandler.incFilterCount();
  231. }
  232. });
  233. }, 200);
  234. };