├── .gitignore ├── config.example.js ├── package.json ├── readme.md ├── stfu.jpg └── stfu.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.js 3 | -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | userName: 'you@your.homeserver.tld', 3 | accessToken: 'go in your settings in riot, scroll to "Advanced", click to show "Access Token" and paste that here', 4 | historyLimit: 200 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matrix-stfu", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "stfu.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/xwiki-labs/matrix-stfu.git" 12 | }, 13 | "author": "", 14 | "license": "LGPL-2.0+", 15 | "bugs": { 16 | "url": "https://github.com/xwiki-labs/matrix-stfu/issues" 17 | }, 18 | "homepage": "https://github.com/xwiki-labs/matrix-stfu#readme", 19 | "dependencies": { 20 | "babel-runtime": "^6.26.0", 21 | "matrix-js-sdk": "*", 22 | "nthen": "^0.1.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Matrix STFU 2 | 3 | Spam / Trolling Filtration Utility 4 | 5 | ![the title says it all](https://raw.github.com/xwiki-labs/matrix-stfu/master/stfu.jpg) 6 | 7 | Mass remove everything which was said by a particular user in a particular room. 8 | 9 | ## How to use 10 | 11 | 1. `cp ./config.example.js ./config.js` 12 | 2. Edit config.js to make it use your username and access token, you can also change how far into history STFU will search (number of events). 13 | 3. `node ./stfu.js ` 14 | 15 | For example, in case cjd is saying silly stuff in the Matrix HQ room, and you're an admin there, you can delete all of it. 16 | 17 | node ./stfu.js '#matrix:matrix.org' '@cjd:matrix.org' -------------------------------------------------------------------------------- /stfu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwiki-labs/matrix-stfu/4dc4e14ad74414ba0813d5d4e6997f8b6fa17851/stfu.jpg -------------------------------------------------------------------------------- /stfu.js: -------------------------------------------------------------------------------- 1 | const Matrix = require("matrix-js-sdk"); 2 | const nThen = require('nthen'); 3 | 4 | const Config = require('./config.js'); 5 | 6 | const main = () => { 7 | const args = process.argv.slice(2); 8 | 9 | if (args.length !== 2) { 10 | console.log('Usage: node ./stfu.js '); 11 | console.log('Mass-redact everything that was said by a given user in a given room'); 12 | return; 13 | } 14 | 15 | const roomName = args[0]; 16 | const userName = args[1]; 17 | 18 | console.log("Removing all messages in [" + roomName + '] written by [' + userName + ']'); 19 | 20 | let client; 21 | let room; 22 | let events; 23 | nThen((waitFor) => { 24 | client = Matrix.createClient({ 25 | baseUrl: 'https://' + Config.userName.split(':')[1], 26 | accessToken: Config.accessToken, 27 | userId: Config.userName 28 | }); 29 | 30 | const done = waitFor(); 31 | client.on('sync', function (state, prevState, data) { 32 | if (state === 'ERROR') { console.log(data); } 33 | if (state === 'PREPARED') { done(); } 34 | }); 35 | client.startClient(); 36 | }).nThen((waitFor) => { 37 | client.getRoomIdForAlias(roomName, waitFor((err, id) => { 38 | if (err) { throw err; } 39 | console.log('Got room internal id>', id.room_id); 40 | room = client.getRoom(id.room_id); 41 | })); 42 | }).nThen((waitFor) => { 43 | console.log('Getting history'); 44 | client.scrollback(room, Config.historyLimit, (err, evts) => { 45 | if (err) { throw err; } 46 | if (evts.chunk) { 47 | if (events) { throw new Error("Events is already defined"); } 48 | events = evts; 49 | } 50 | }).then(waitFor()); 51 | }).nThen((waitFor) => { 52 | console.log('Got history'); 53 | // I have no clue why we need both the events query and the LiveTimeline but without 54 | // them both, the list is incomplete. 55 | const allEvents = []; 56 | Array.prototype.push.apply(allEvents, events.chunk); 57 | Array.prototype.push.apply(allEvents, 58 | room.getLiveTimeline().getEvents().map((e)=>(e.event))); 59 | 60 | let nt = nThen; 61 | allEvents.forEach((x) => { 62 | if (x.type !== 'm.room.message') { return; } 63 | if (x.sender !== userName) { return; } 64 | if (JSON.stringify(x.content) === '{}') { return; } 65 | 66 | nt = nt((waitFor) => { 67 | const doit = () => { 68 | //console.log('going to nuke', x.event_id, JSON.stringify(x.content)); 69 | client.redactEvent(room.roomId, x.event_id, waitFor((err, ret) => { 70 | if (err && err.errcode === 'M_LIMIT_EXCEEDED') { 71 | console.log("M_LIMIT_EXCEEDED"); 72 | setTimeout(waitFor(doit), err.data.retry_after_ms); 73 | return; 74 | } 75 | console.log('Deleting>', x.event_id, JSON.stringify(x.content)); 76 | if (err) { console.log("ERROR:"); console.log(err); console.log(r); } 77 | })); 78 | }; 79 | doit(); 80 | }).nThen; 81 | }); 82 | nt(waitFor()); 83 | 84 | }).nThen((waitFor) => { 85 | console.log("\nDone"); 86 | client.stopClient(); 87 | }); 88 | }; 89 | main(); --------------------------------------------------------------------------------