Skip to content

Instantly share code, notes, and snippets.

@ferrybig
Last active December 5, 2016 09:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ferrybig/12dc3dd9560bbd4cd39f5129a2f07314 to your computer and use it in GitHub Desktop.
Save ferrybig/12dc3dd9560bbd4cd39f5129a2f07314 to your computer and use it in GitHub Desktop.
Spam Tracker
// ==UserScript==
// @name Spamtracker
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Alerts you when new smokedetector messages show up
// @author NormalHuman
// @author Ferrybig
// @match *://chat.meta.stackexchange.com/*
// @match *://chat.stackexchange.com/*
// @match *://chat.stackoverflow.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
var sumRules = [/^\S*$/i, /\bcolon.*clean/i, /cleans/i, /\b(phone|support).*number\b/i, /\bwow\sgold\b/i, /\bessays?\b/i, /\bbaba\s?ji\b/i,
/\+91[\s\-\(]/i, /professional.*writ/i, /kickstarter/i, /natural.*ingredient/i, /\baffiliate\b/i, /\baging\b/i, /\bfifa\b/i, /\bbajotz\b/i,
/\bbagprada\b/i, /\bbabyliss/i, /\bblack magic\b/i, /vashikaran/i, /advantage.*price/i, /natural(ly)?\b/i, /pure\sbody/i, /fuck/i, /\bshit/i, /bitch/i, /\bsuck/i,
/brain.*(boost|power)/i, /facts?\sabout/i, /\b100%\b/i, /live\sstream/i, /make\smoney/i, /sale/i, /\bhack/i, /cheat/i, /\bwow\sgold\b/i, /runescape/i,
/\bfifa.*coin/i, /\bcheap/i, /\bskin/i, /\bweight\b/i, /\bacne\b/i, /\bage\b/i, /\bbody.*build/i, /\bsupplements?\b/i, /\bhealth/i, /\bpenis\b/i,
/\bnutrition/i, /\bfat\b/i, /\bwrinkl/i, /\bdiet/i, /muscle/i, /\bbrain\b/i, /\bbaba\b/i, /clash ?of ?clans/i, /\bmale\b/i, /testo/i,
/\blover?\b/i, /\bloans?/i, /serum/i, /overcome/i, /workout/i, /fitness/i, /\bAlpha\b/, /\bultra\b/i, /\bPro\b/, /beauty/i ];
var titleRules = sumRules.concat([/(\d)\1{2}/, /care\b/i, /\bwatch\b/i, /\bsell/i, /\bcleans/i, /\bloss\b/i, /\blose\b/i, /\bhelpline\b/i, /\bbuy\b/i, /\blose\b/i,
/\b(phone|support).*number\b/i, /\bimprove/i, /\bonline\b/i, /\byou\scan\b/i, /\bfree\b/i, /\bwholesale\b/i, /\bmarriage\b/i, /\blove\b/i,
/\bpurchas/i, /\bfull\shd\b/i, /\bcraigslist\b/i, /\bbenefits?\b/i, /beneficial/i, /advice/i, /perfect/i ]);
var urlRules = []; // Will be loaded from the SmokeDetector github
$.ajax("https://cdn.rawgit.com/Charcoal-SE/SmokeDetector/master/blacklisted_websites.txt",{
cache: true,
success: function(e)
{
urlRules = e
.filter(function(i){return i;})
.map(function(i){try{return new RegExp(i, "i");}catch(e){}})
.filter(function(i){return !!i;});
},
dataType: "text",
dataFilter: function(i){return i.split("\n");}
});
var style= "#priorityList + #roomtitle + #roomdesc, #priorityList + #roomtitle + #roomdesc + #room-tags {display: none;}";
style += ".q-block {padding: 5px 0px;font-size: 11px;line-height: 14px;}";
style += ".q-title {color: #333;font-weight: bold;margin: 2px;display: block;}";
style += "a.q-title:visited {color: #888 !important;}";
style += "#priorityList {background-color: #ffffff;}";
style += "#clearchat, #clearside {margin-left: 2px;}";
var prioritySites = ['academia', 'android', 'beer', 'boardgames', 'chess', 'coffee', 'computergraphics', 'cooking', 'datascience',
'drupal', 'ebooks', 'engineering', 'expatriates', 'freelancing', 'hsm', 'law', 'mechanics', 'money', 'mythology', 'opensource', 'patents',
'poker', 'productivity', 'quant', 'ru', 'startups', 'travel', 'webapps', 'webmasters', 'writers'];
var timeSensitiveSites = ['drupal', 'superuser', 'askubuntu', 'meta'];
var ignoredSites = ['biology', 'fitness', 'health', 'ja', 'pt', 'es', 'islam'];
var insertRef, ws, clearchat, clearside, priorityList, savingData, wsVolume=0;
var box = document.getElementById('input');
var chat = document.getElementById('chat');
var room = window.location.href.match(/chat[^/]*\/rooms\/\d+/)[0];
var keepGoing = true;
var currentStatus = 'chat only';
var notifications = {};
window.setInterval(checkForSpam, 500);
if (box && chat && room) {
insertRef = document.getElementById('footer-legal');
var separator = document.createTextNode(' | ');
insertRef.insertBefore(separator, insertRef.firstChild);
var onoff = newElem('a', 'on-off', '', 'spamtracker: '+currentStatus);
onoff.title = 'toggle spam tracking';
onoff.onclick = toggleTracking;
onoff.style.cursor = 'pointer';
insertRef.insertBefore(onoff, insertRef.firstChild);
var metabeep = new Audio('//cdn-chat.sstatic.net/chat/meta2.mp3');
var apiKey = '1gtS)lKgyVceC11VlgjyQw((';
var stored = {maxQ: {}, maxU: {}};
var inserted = [], time = 0;
clearchat = newElem('a', 'clearchat', 'button', 'clear chat');
clearchat.title = 'remove all chat messages';
clearchat.onclick = clearChat;
insertRef = document.querySelector('#chat-buttons');
insertRef.appendChild(clearchat, insertRef);
addStyleString(style);
//chrome.storage.sync.get(stored, function(items) {
// var room = window.location.href.match(/chat[^/]*\/rooms\/\d+/)[0];
// stored = items;
//});
}
function checkForSpam() {
if (currentStatus == 'off') {
return;
}
var messageList = document.getElementsByClassName('message');
var message = messageList[messageList.length-1];
if (message && !message.classList.contains('checkedForSpam')) {
message.classList.add('checkedForSpam');
if (message.children[1] && !message.parentNode.parentNode.classList.contains('mine') && !message.querySelector('.onebox')) {
processChatMessage(message);
}
}
}
function switchOn() {
var prot = (window.location.protocol === 'https:' ? 'wss' : 'ws');
ws = new WebSocket(prot+'://qa.sockets.stackexchange.com/');
ws.onmessage = function(e) {
wsVolume = wsVolume + e.data.length;
processQuestion(JSON.parse(JSON.parse(e.data).data));
};
ws.onopen = function() { ws.send('155-questions-active'); };
ws.onclose = function() {if (keepGoing) {window.setTimeout(switchOn, 10000);} };
clearside = newElem('a', 'clearside', 'button', 'clear');
clearside.title = 'dismiss all reports';
clearside.onclick = clearSide;
insertRef = document.querySelector('.fl');
insertRef.appendChild(clearside);
insertRef = document.getElementById('roomtitle');
priorityList = newElem('div','priorityList','question-list','');
insertRef.parentNode.insertBefore(priorityList, insertRef);
currentStatus = 'on';
savingData = window.setInterval(function() {/*chrome.storage.sync.set(stored);*/}, 120000);
keepGoing = true;
console.log('Max post Id');
console.log(stored.maxQ);
console.log('Max user Id');
console.log(stored.maxU);
}
function pauseST() {
keepGoing = false;
ws.close();
clearside.remove();
priorityList.remove();
currentStatus = 'chat only';
//chrome.storage.sync.set(stored);
window.clearInterval(savingData);
}
function switchOff() {
currentStatus = 'off';
}
function processQuestion(q) {
var i, data, url, site, shortSite, qId, uId, title, user, summary, qblock, insert, consider, hh, report, reg;
var msgId, msgTitle;
time = Math.max(time, q.lastActivityDate);
title = q.titleEncodedFancy;
site = q.siteBaseHostAddress;
shortSite = site.split('.')[0];
qId = q.id;
url = '//'+site+'/q/'+qId;
user = q.ownerDisplayName;
summary = q.bodySummary;
uId = (q.ownerUrl ? parseInt(q.ownerUrl.split('/')[4],10) : 0);
if (!stored.maxQ[site]) {
stored.maxQ[site] = 1;
}
if (!stored.maxU[site]) {
stored.maxU[site] = 1;
}
insert = false;
consider = (uId > stored.maxU[site]-3 && uId < 1.01*stored.maxU[site]) ;
consider = consider && (qId > stored.maxQ[site] && qId < 1.01*stored.maxQ[site]);
consider = consider && (ignoredSites.indexOf(shortSite) == -1);
report = 'http://'+site+'/q/'+qId+' ';
if (consider) {
if (summary.length < 100) {
insert = true;
report = report + 'short summary: ' + summary + '\n';
}
hh = new Date().getUTCHours();
if (timeSensitiveSites.indexOf(shortSite)!=-1 && hh>=4 && hh<= 12) {
insert = true;
report = report + 'peak spam time\n';
}
reg = bad(title, titleRules);
if (reg) {
insert = true;
report = report + 'title matched ' + reg + '\n';
}
reg = bad(summary, sumRules);
if (reg) {
insert = true;
report = report + 'summary matched ' + reg + '\n';
}
reg = bad(summary, urlRules);
if (reg) {
insert = true;
report = report + 'smokedetector pattern matched ' + reg + '\n';
}
if (insert) {
reportIt(report, site, qId, 'Q', title, url, q.ownerUrl, user, summary);
}
}
if (!insert && prioritySites.indexOf(shortSite)!=-1) {
window.setTimeout(fetchBody, 60000, shortSite);
}
if (qId>stored.maxQ[site]) {
stored.maxQ[site] = qId;
}
if (uId>stored.maxU[site]) {
stored.maxU[site] = uId;
}
}
function reportIt(report, site, qId, type, title, url, ownerURL, ownerName, summary) {
var qblock, elem, msgId, shortSite = site.split('.')[0], ueTitle, ueSummary;
if (inserted.indexOf(shortSite+qId) == -1) {
report = report + 'At ' + new Date().toString().split(" ")[4] + '\nWebsockets volume: '+wsVolume+'\n';
console.log(report);
inserted.push(shortSite+qId);
msgId = room+'-'+site+'-'+qId+'-'+Date.now();
elem = newElem('span', '', '', '');
elem.innerHTML = title;
ueTitle = elem.textContent;
elem.innerHTML = summary;
ueSummary = elem.textContent;
notifyMe(msgId, shortSite + ' ' + type + ': ' + ueTitle, ueSummary);
qblock = newElem('div',msgId,'q-block','');
qblock.innerHTML = '<a class="q-title" target="_blank" href="'+url+'">' + shortSite + ' ' + type+': '+title + '</a><a target="_blank" href="'+ownerURL+'">' + ownerName + '</a>';
qblock.onclick = removeBlock;
elem.innerHTML = ': '+summary;
qblock.appendChild(elem);
priorityList.insertBefore(qblock, priorityList.firstElementChild);
}
}
function fetchBody(shortSite) {
var request = '//api.stackexchange.com/2.2/posts?pagesize=1&order=desc&sort=creation&site='+(shortSite=='ru'?'ru.stackoverflow':shortSite)+'&filter=!5RBFam4sA56hQ2Q5G3*uvo3fl&key='+apiKey;
getStuff(request, 'json', function(e) {
var q=e.currentTarget.response.items[0], url, site, body, elem, report, qId, insert, reg;
url = q.share_link;
site = url.split('/')[2];
qId = q.post_id;
elem = document.createElement('span');
elem.innerHTML = q.body;
body = elem.textContent;
report = url+' ';
insert = false;
if (q.owner && q.owner.reputation == 1) {
if (q.post_type == 'answer') {
insert = true;
report = report+' answer by a rep 1 user\n';
}
if (body.length < 100) {
insert = true;
report = report + 'short post: ' + body + '\n';
}
if (q.post_type == 'question') {
reg = bad(body, sumRules);
}
if (reg) {
insert = true;
report = report + 'body matched ' + reg + '\n';
}
}
if (insert) {
reportIt(report, site, qId, (q.post_type=='question' ? 'Q' : 'A'), q.title, url, q.owner.link, q.owner.display_name, body.slice(0,150));
report = 'Quota remaining: '+e.currentTarget.response.quota_remaining;
console.log(report);
}
});
}
function notifyMe(id,title,message) {
// var msg = {};
// msg.id = id;
// msg.title = title;
// msg.message = message;
// notifications[id] = msg;
var notification = new Notification(title, { body: message, icon: "//i.imgur.com/kS4QNIv.png" });
notification.onshow = function() {
setTimeout(notification.close, 15000);
};
notification.onclick = function() {
window.focus();
};
notifications[id] = notification;
}
function dismissNotification(id) {
notifications[id].close();
}
function removeBlock(e) {
killBlock(e.target);
}
function killBlock(elem) {
var list = elem.parentNode;
if (list.classList.contains('question-list')) {
dismissNotification(elem.id);
list.removeChild(elem);
}
else {
killBlock(list);
}
}
function processChatMessage(message) {
var smoke = /spam|vgx7b|offensive|abusive/i;
var content = message.children[1].innerHTML;
var i, msg = {}, parts, ch, path, hash, site = '', qId = '', sq;
if (smoke.test(content) && /\/\/[a-z]*.stackexchange.com|stackoverflow.com|superuser.com|serverfault.com|askubuntu.com|stackapps.com|mathoverflow.net/i.test(content)) {
ch = message.children[1].children;
for (i=ch.length-1; i>=0; i--) {
if (ch[i].tagName == 'A') {
hash = ch[i].href.split('#');
path = ch[i].href.split('/');
if (path[3] == 'questions' && hash.length>1) {
site = path[2];
qId = hash[1];
}
else if (/^[qa]/.test(path[3])) {
site = path[2];
qId = path[4];
}
}
}
if (site && qId) {
metabeep.play();
sq = site.split('.')[0] + qId;
if (inserted.indexOf(sq) != -1) {
return;
}
else {
inserted.push();
}
}
msg.id = room+'-'+site+'-'+qId+'-'+Date.now();
parts = message.children[1].textContent.split(': ');
if (parts.length > 1) {
msg.title = parts[0];
msg.message = parts[1];
}
else {
msg.title = 'Flag Request';
msg.message = message.children[1].textContent;
return; // Not used for now...
}
msg.type = 'chat';
notifyMe(msg.id, msg.title, msg.message);
}
}
function clearChat() {
var chat = document.getElementById('chat');
while (chat.firstElementChild) {
chat.removeChild(chat.firstElementChild);
}
var starred = document.querySelector('#starred-posts ul');
while (starred.firstElementChild) {
starred.removeChild(starred.firstElementChild);
}
}
function clearSide() {
while (priorityList.firstElementChild) {
priorityList.removeChild(priorityList.firstElementChild);
}
}
function toggleTracking() {
switch (currentStatus) {
case "off":
switchOn();
break;
case "on":
pauseST();
break;
case "chat only":
switchOff();
}
onoff.textContent = 'spamtracker: '+currentStatus;
}
function bad(text, rules) {
for (var i=0; i<rules.length; i++) {
if (rules[i].test(text)) {
return rules[i];
}
}
return false;
}
function newElem(eType,eId,eClass,eText) {
var e = document.createElement(eType);
if (eId.length>0) {e.id = eId;}
if (eClass.length>0) {e.classList.add(eClass);}
if (eText.length>0) {e.textContent = eText;}
return e;
}
function getStuff(theUrl, type, listener) {
var req = new XMLHttpRequest();
req.responseType = type;
req.onload = listener;
req.open("GET", theUrl, true);
req.send();
}
function htmlDecode(input){
var e = document.createElement('div');
e.innerHTML = input;
return e.textContent;
}
function addStyleString(str) {
var node = document.createElement('style');
node.innerHTML = str;
document.body.appendChild(node);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment