Originally, this script stemmed from a small script that watched stars and who made them, but a few days after launching, SE patched it so the people behind the stars aren't sent.
Regardless, BigBrother, named after the character from 1984, had other functions, like monitoring obscene or unsightly chat messages and monitoring entering and exiting the room.
I was in a heavy rush while coding this, so it's a lot more spaghetti than I expected.
There's a few things you need to change manually to run this:
- Username, so it can avoid reflagging obscene posts it shares
- Chatroom id, on line 162. Currently set to The Sandbox
Here's the code on GitHub too.
If you want to run this code, comment out the lines relating to calling emit
and it should run with posting messages in console only.
// ==UserScript==
// @name BigBrother
// @namespace github.com/The-Quill
// @version 0.1
// @description A chatroom watcher
// @author Quill
// @match http://chat.stackexchange.com/rooms/1/sandbox
// @grant none
// ==/UserScript==
(function (global) {
"use strict";
//username
var USERNAME = 'BigBrother';
var WELCOME_TEXT = '*initialising.*';
//magic numbers
var EVENT_TYPES = {
MessagePosted: 1
, MessageEdited: 2
, UserEntered: 3
, UserLeft: 4
, RoomNameChanged: 5
, MessageStarred: 6
, DebugMessage: 7
, UserMentioned: 8
, MessageFlagged: 9
, MessageDeleted: 10
, FileAdded: 11
, ModeratorFlag: 12
, UserSettingsChanged: 13
, GlobalNotification: 14
, AccessLevelChanged: 15
, UserNotification: 16
, Invitation: 17
, MessageReply: 18
, MessageMovedOut: 19
, MessageMovedIn: 20
, TimeBreak: 21
, FeedTicker: 22
, UserSuspended: 29
, UserMerged: 30
// Custom Event Types
, Notable: 'notable'
, Welcome: 'welcome'
};
// bad or notable words
var KEY_WORDS = {
'off-topic': {
regex: /off[\s|-]*topic/mi,
value: .4
},
'troll': {
value: .5
},
'shit': {
regex: /s[\-\*hi]*t/mi,
value: 1
},
'crap': {
regex: /c[\-\*ra]*p/mi,
value: 1
},
'fuck': {
regex: /f[\-\*uc]*k/mi,
value: 1
}
};
function convertToMarkdownLink(value, href){
return '[' + value + '](' + href + ')';
}
// storage
var key = 'sechatmonitor_data';
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position) {
position = position || 0;
return this.indexOf(searchString, position) === position;
};
}
var storage = {
data: {
star: [],
messages: [],
kick: [],
notable: [],
users: {}
},
get: function (name) {
return storage.data[name];
},
clear: function (name) {
storage.data[name] = [];
},
add: function (name, value) {
if (storage.contains(name, value)) {
return;
}
if (storage.data[name]) {
storage.data[name].push(value);
} else {
storage.data[name] = [value];
}
},
modify_stars: function(name, room, value){
if (!storage.data.users){
storage.add('users', {});
}
if (!(storage.contains('users', name))){
storage.data.users[name] = {};
}
if(!(room in storage.data.users[name])){
storage.data.users[name][room] = {stars: 20, starred_posts: []};
}
storage.data.users[name][room].stars--;
storage.data.users[name][room].starred_posts.push(value);
},
remove: function (name, value) {
if (storage.contains(name, value)) {
return;
}
if (storage.data[name]) {
storage.data[name].remove(value);
} else {
storage.data[name] = [];
}
},
save: function () {
global.localStorage[key] = JSON.stringify(storage.data);
},
contains: function(name, value) {
if (!storage.data[name]){ return false };
for (var i = 0; i < storage.data[name].length; i++) {
if (JSON.stringify(storage.data[name][i]) === JSON.stringify(value)) {
return true;
}
}
return false;
},
load: function () {
if (!global.localStorage[key]) {
return;
}
storage.data = JSON.parse(global.localStorage[key]);
}
};
storage.load();
// data collection
var socket, roomid, url, report, noisy, kickRe;
report = true;
noisy = true;
roomid = Number(/\d+/.exec(location)[0]);
kickRe = /^priv/;
connect();
function connect() {
$.post('/ws-auth', fkey({
roomid: 1
})).done(function (data) {
url = data.url;
if (report) console.log('Connected');
poll();
});
}
function poll() {
socket = new WebSocket(url + '?l=' + Date.now());
socket.onmessage = ondata;
socket.onclose = onclose;
}
function ondata(data) {
var frame = JSON.parse(data.data);
for (var room in frame) {
if ('e' in frame[room]) {
processEvent(frame[room].e[0]);
}
}
}
function onclose() {
socket.close();
socket = null;
setTimeout(poll, 1000 * 10);
}
function processMessage(body){
var sum = 0;
for (var i in KEY_WORDS){
var matchedContent = 0;
if ('regex' in KEY_WORDS[i]){
matchedContent = (body.match(KEY_WORDS[i].regex) == null ? 0 : body.match(KEY_WORDS[i].regex).length);
} else {
matchedContent = (body.split(' ' + i + ' ').length === 1 ? 0 : body.split(i).length / 2)
}
if (matchedContent != 0){
matchedContent > 0 ? console.log([matchedContent, 'instances of', i].join(' ')) : '';
sum += matchedContent * KEY_WORDS[i].value;
}
}
return sum >= 1 ? sum : false;
}
function processStars(evt){
console.log(evt);
storage.modify_stars(evt.user_name, evt.room_name, {post_id: evt.post_id});
}
function processEvent(evt) {
//console.log(evt);
switch (evt.event_type) {
case EVENT_TYPES.MessagePosted:
if (USERNAME == evt.user_name){ return; }
var sum = processMessage(evt.content);
if (sum >= 1){
storage.add('notable', evt);
emit({event_type: 'notable', sum: sum, evt: evt});
}
break;
case EVENT_TYPES.MessageStarred:
processStars(evt);
storage.add('star', evt);
if (report){ console.log('star registered in', evt.room_name); }
break;
}
emit(evt);
}
function emit(evt) {
var bodyText = '',
send = false;
//console.log(evt);
var afterEffects = '';
switch(evt.event_type){
case EVENT_TYPES.Welcome:
bodyText = WELCOME_TEXT;
send = true;
break;
case EVENT_TYPES.Notable:
bodyText = ['Notable post detected in', evt.evt.room_name, 'by', evt.evt.user_name + ';', 'Notariety level:', evt.sum].join(' ');
afterEffects = 'http://chat.stackexchange.com/transcript/message/' + evt.evt.message_id;
send = true;
break;
case EVENT_TYPES.UserEntered:
bodyText = [
convertToMarkdownLink(evt.user_name, 'http://chat.stackexchange.com/users/' + evt.user_id)
, 'joined'
, convertToMarkdownLink(evt.room_name, 'http://chat.stackexchange.com/rooms/' + evt.room_id) + '.'
, roomid != evt.room_id ? '' :
[
'Welcome'
, '@' + evt.user_name.replace(' ', '') + '.'
].join(', ')
].join(' ');
send = true;
break;
case EVENT_TYPES.UserLeft:
bodyText = [
convertToMarkdownLink(evt.user_name, 'http://chat.stackexchange.com/users/' + evt.user_id)
, 'left'
, convertToMarkdownLink(evt.room_name, 'http://chat.stackexchange.com/rooms/' + evt.room_id)
].join(' ');
send = true;
break;
case EVENT_TYPES.MessageStarred:
//console.log(evt);
bodyText = [
evt.user_name
, ('message_stars' in evt ? '' : 'un') + 'starred'
, convertToMarkdownLink('this message', 'http://chat.stackexchange.com/transcript/message/' + evt.message_id)
, 'in'
, convertToMarkdownLink(evt.room_name, 'http://chat.stackexchange.com/rooms/' + evt.room_id)
].join(' ');
send = true;
break;
default:
return;
break;
}
if (!send){ return; }
var d = new Date();
var time = [d.getHours(), d.getMinutes(), d.getSeconds()].join(':');
bodyText = [time, 'BB>', bodyText].join(' ');
console.log(bodyText);
storage.add('messages', bodyText);
if (afterEffects != ''){
storage.add('messages', afterEffects);
}
storage.save();
}
function messagePool (){
setInterval(function(){
var messages = storage.get('messages');
if (messages.length > 0){
sendMessageToAPI(messages[messages.length - 1]);
}
}, 10000);
}
function sendMessageToAPI(bodyText){
return $.ajax({
type: 'POST',
url: ['/chats/', roomid, '/messages/new'].join(''),
data: {
fkey: fkey().fkey,
text: bodyText
}
}).done(function(){
storage.remove('messages', bodyText);
});
}
function createTaskPool() {
return setInterval(function() {
if(tasks.length > 0) {
// dequeue the message
var lastTaskIndex = tasks.length - 1;
var task = tasks[lastMessageIndex];
tasks.splice(0, lastTaskIndex);
executeTask(task);
}
});
}
function executeTask(task) {
return $.ajax({
type: 'POST',
url: ['/chats/', roomid, '/messages/new'].join(''),
data: {
fkey: fkey().fkey,
text: task.message
}
}).done(task.callback);
}
function enqueueMessage(message, callback) {
var task = {
message: message,
callback: callback || function() {}
};
tasks.push(task);
}
global.reporter = {
welcome: function(){
emit({event_type: 'welcome'});
},
toggle: function(what, hrm) {
if(!what) return 'you must toggle something';
switch(what) {
case 'noisy':
noisy = typeof hrm === 'undefined' ? !noisy : !!hrm;
return 'noisy is ' + noisy ? 'enabled' : 'disabled';
break;
case 'report':
report = typeof hrm === 'undefined' ? !report : !!hrm;
return 'report is ' + report ? 'enabled' : 'disabled';
break
default:
return 'you cannot toggle that';
break
}
},
get: function(what) {
if(!what) return 'you must get something';
switch(what) {
case 'kicks':
case 'kick':
return storage.get('kick');
break;
case 'stars':
case 'star':
return storage.get('star');
break
case 'users':
case 'user':
return storage.get('rooms');
break;
default:
return 'there is no storage for `' + what + '`';
break
}
},
clear: function(what) {
if(!what){ return 'you must clear something' };
switch(what) {
case 'kicks':
case 'kick':
return storage.clear('kick');
break;
case 'stars':
case 'star':
return storage.clear('star');
break;
case 'users':
return storage.clear('rooms');
break;
default:
return 'there is no storage for `' + what + '`';
break;
}
storage.save();
}
};
messagePool();
}(window));reporter.welcome();
EVENT_TYPE
actually meaningful? Just curious but it must have taken you a mighty long time to figure out each one! \$\endgroup\$EVENT_TYPE
s and their respective meaning \$\endgroup\$