diff --git a/cfg.json b/cfg.json index e69de29..5662fe0 100644 --- a/cfg.json +++ b/cfg.json @@ -0,0 +1,58 @@ +{ + "server": [ + { + "name": "Server 1", + "host": "", + "port": "", + "ssl": "1", + "ssl_allow_invalid": "1", + "pass": "", + "nick": "", + "username": "", + "realname": "" + }, + { + "name": "Server 2", + "host": "", + "port": "", + "ssl": "1", + "ssl_allow_invalid": "1", + "pass": "", + "nick": "", + "username": "", + "realname": "" + } + ], + "mysql": { + "host": "localhost", + "user": "", + "password": "", + "database": "", + "multipleStatements": true + }, + "allowedMimes": { + "image/png": "png", + "video/webm": "webm", + "image/gif": "gif", + "image/jpeg": "jpg", + "video/mp4": "mp4", + "audio/mpeg": "mp3", + "audio/ogg": "ogg" + }, + "minRes": 256, + "maxFileSize": 31457280, + "webserver": { + "port": 1488 + }, + "level": { + "~": 80, + "&": 70, + "@": 60, + "%": 50, + "+": 10, + "": 0 + }, + "wlip": { + "::ffff:1.0.0.5":"w0bm" + } +} diff --git a/package.json b/package.json index 3548dfd..b86c3ee 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,21 @@ { "name": "keinBotv2", - "version": "0.1.0", + "version": "0.1.1", "main": "src/main.js", + "scripts": { + "start": "node src/main.js" + }, "dependencies": { "coffea": "^0.4.24", + "du": "^0.1.0", "fs-extra": "^0.30.0", + "mime": "^1.3.4", "mysql": "^2.11.1", + "node-ffprobe": "^1.2.2", "repl": "^0.1.3", "swig": "^1.4.2", - "uuid": "^2.0.2" + "uuid": "^2.0.2", + "ytdl-core": "^0.7.17" }, "repository": { "type": "git", diff --git a/src/lib.js b/src/lib.js new file mode 100644 index 0000000..b5ebbed --- /dev/null +++ b/src/lib.js @@ -0,0 +1,192 @@ +var fs = require('fs-extra'); +var http = require('http'); +var https = require('https'); +var exec = require('child_process').exec; +var probe = require('node-ffprobe'); +var crypto = require('crypto'); +var Mime = require('mime'); + +var bot, sql, cfg, debug; + +module.exports = Lib; +function Lib(tbot, tsql, tcfg) { + this.bot = bot = tbot; + this.sql = sql = tsql; + this.cfg = cfg = tcfg; + this.debug = debug = false; + this.admins = []; +} + +Lib.prototype.getUser = (e, cbgu) => { + var u = e.user.getNick(); + var n = e.network; + if(!e.user.hasOwnProperty('hostname')) { + setTimeout(()=>{ + bot.write('WHOIS '+u, n, () => { + bot.once('data', (err, msg) => { + var params; + var map = []; + map.push(u); + if(msg.command == 'RPL_WHOISUSER') { + params = msg.params.split(' '); + map[u] = map[u] || {}; + map[u].nick = u; + map[u].username = params[2]; + map[u].hostname = params[3]; + map[u].realname = msg.trailing; + } + if(typeof(map[u]) === 'object') + cbgu(map[u]); + }); + }); + }, 100); + } + else { + cbgu({ + nick: e.user.nick, + username: e.user.username, + hostname: e.user.hostname, + realname: e.user.realname + }); + } +}; +Lib.prototype.getUserlevel = (e, cb) => { + Lib.prototype.getUser(e, (cbgu) => { + var host = cbgu.username+'@'+cbgu.hostname; + var lvl_channel = (cbgu.nick in e.channel.names)? cfg.level[ e.channel.names[cbgu.nick] ] : 0; + var lvl_db = 0; + if(host in this.admins) + lvl_db = (this.admins[host].server == e.network)? this.admins[host].level : 0; + cb({ + 'channel': lvl_channel, + 'db': lvl_db, + 'level': Math.max(lvl_channel, lvl_db) + }); + }); +}; +Lib.prototype.loadUser = (cb) => { + this.admins = []; + sql.query("select * from `f0ck`.`user`", (err, rows, fields) => { + rows.forEach((e,i,a) => { + this.admins.push(e.nick); + this.admins[e.vhost] = { + 'id': e.id, + 'nick': e.nick, + 'vhost': e.vhost, + 'level': e.level, + 'server': e.server + }; + }); + cb(true); + }).on('error', () => { + cb(false); + }); +} +Lib.prototype.checkRepost = (url, cbcr) => { + sql.query("select count(*) as count from `f0ck`.`items` where `src` = ?", url, (err, rows, fields) => { + cbcr((rows[0].count == 0)?true:false); + }); +}; +Lib.prototype.checkRepostCheckSum = (cs, cbcrcs) => { + sql.query("select count(*) as count from `f0ck`.`items` where `checksum` = ?", cs, (err, rows, fields) => { + cbcrcs((rows[0].count == 0)?true:false); + }); +}; +Lib.prototype.formatSize = (size) => { + var i = Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; +}; +Lib.prototype.getCheckSum = (file, cbcs) => { + var sha256sum = crypto.createHash('sha256'); + var s = fs.ReadStream(file); + s.on('data', (d) => { + sha256sum.update(d); + }); + s.on('end', () => { + cbcs(sha256sum.digest('hex')); + }); +}; +Lib.prototype.dl = (url, dest, cb) => { + var request = (url.match(/^https/)?https:http).get(url, (response) => { // type:1=post,type:2=stfu + console.log(response.headers['content-type']); + if(cfg.allowedMimes.hasOwnProperty(response.headers['content-type'])) { + if(response.headers['content-length'] <= cfg.maxFileSize) { + Lib.prototype.checkRepost(url, (cbcr) => { + if(cbcr) { + var file = fs.createWriteStream(dest+"."+cfg.allowedMimes[response.headers['content-type']]); + response.pipe(file); + file.on('finish', () => { + file.close(); + var mime = Mime.lookup(dest+"."+cfg.allowedMimes[response.headers['content-type']]); + if(cfg.allowedMimes.hasOwnProperty(mime)) { + probe(dest+"."+cfg.allowedMimes[response.headers['content-type']], (err, probeData) => { + if(probeData.streams[0].height !== undefined || probeData.streams[0].width !== undefined) { + if(probeData.streams[0].height + probeData.streams[0].width <= cfg.minRes) + cb({'status':false, 'msg':'f0ck! your shitpost is too small', 'type':1}); + else + cb({'status':true, 'msg':'downloaded '+dest, 'type':1, 'infos':{'mime':response.headers['content-type'], 'size':response.headers['content-length'], 'ext':cfg.allowedMimes[response.headers['content-type']]}}); + } + else + cb({'status':true, 'msg':'downloaded '+dest, 'type':1, 'infos':{'mime':response.headers['content-type'], 'size':response.headers['content-length'], 'ext':cfg.allowedMimes[response.headers['content-type']]}}); + }); + } + else + cb({'status':false, 'msg':'lol, go f0ck yourself', 'type':1}); + }); + file.on('error', (err) => { + fs.unlink(dest+"."+cfg.allowedMimes[response.headers['content-type']]); + file.close(); + cb({'status':false, 'msg':err.message, 'type':1}); + }); + } + else + cb({'status':false, 'msg':'repost motherf0cker', 'type':1}); + }); + } + else + cb({'status':false, 'msg':'f0ck! your file is too big (~'+formatSize(response.headers['content-length'])+'), max '+formatSize(cfg.maxFileSize)+' allowed', 'type':1}); + } + else + cb({'status':false, 'msg':'f0ck you', 'type':2}); + }).on('error', (msg) => { + cb({'status':false, 'msg':msg, 'type':2}); + }); +}; +Lib.prototype.generateThumbs = () => { + var outdir = './t/'; + sql.query("select * from `f0ck`.`items`", (err, rows, fields) => { + rows.forEach((e,i,a) => { + var thumbnail = outdir+e.id+'.png'; + if(!fs.existsSync(thumbnail)) { + exec('ffmpegthumbnailer -i'+e.dest+' -s1024 -o'+thumbnail, (error) => { + if(error) { + Lib.prototype.log('failed thumbnail for '+e.id+' ('+e.mime+') 1'); + fs.copySync('./s/mp3.png', thumbnail); // copy standardthumbnail + } + else { + exec('convert '+thumbnail+' -resize "128x128^" -gravity center -crop 128x128+0+0 +repage '+thumbnail, (error) => { + if(error) + Lib.prototype.log('failed thumbnail for '+e.id+' ('+e.mime+') 2'); + else + Lib.prototype.log("generated thumbnail for "+e.id+" ("+e.mime+")"); + }); + } + }); + } + }); + }); +}; +Lib.prototype.log = (msg) => { + if(debug) + bot.send("#f0ck", msg, 'n0xy'); +}; +Lib.prototype.toggleDebug = () => { + if(debug) { + debug = false; + return 'debugmode deactivated'; + } + else { + debug = true; + return 'debugmode activated'; + } +}; \ No newline at end of file diff --git a/src/main.js b/src/main.js index f1abb7f..85ad053 100644 --- a/src/main.js +++ b/src/main.js @@ -1,27 +1,16 @@ var cfg = require('../../cfg.json'); var fs = require('fs-extra'); -var http = require('http'); -var https = require('https'); var mysql = require('mysql'); var bot = require('coffea')(); -var uuid = require('uuid'); -var crypto = require('crypto'); -var path = require('path'); -var exec = require('child_process').exec; -var probe = require('node-ffprobe'); - -var swig = require('swig'); - -var templates = {}; -var debug = false; -var upload = true; -var admins = []; +var Lib = require('./lib.js'); +var Websrv = require('./websrv.js'); var sql; + var haDC = () => { sql = mysql.createConnection(cfg.mysql); sql.connect((err) => { - if(err) setTimeout(haDC,2000); + if(err) setTimeout(haDC, 2000); }); sql.on('error', (err) => { if(err.code === 'PROTOCOL_CONNECTION_LOST') haDC(); @@ -29,6 +18,9 @@ var haDC = () => { }; haDC(); +var lib = new Lib(bot, sql, cfg); +var websrv = new Websrv(bot, sql, cfg, lib); + cfg.server.forEach((e,i,a) => { bot.add({ "name": e.name, @@ -44,480 +36,54 @@ cfg.server.forEach((e,i,a) => { console.log("Server "+e.name+" wurde geladen"); }); -var log = (msg) => { - if(debug) - bot.send("#f0ck", msg, 'n0xy'); -}; - bot.on('motd', (e) => { console.log("motd von "+e.network+" erhalten"); - bot.write('MODE f0ck +B', e.network, (c)=>{}); // Botflag }); bot.on('message', (e) => { - var orig = e.message; - if(orig.match(/https?:\/\/[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?/gi) && e.channel.getName() == "#f0ck" && upload) { // shitpostcatcher - if(!orig.match(/\!ignore$/)) { - var tmp = orig.match(/https?:\/\/[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?/gi); // get links - tmp.forEach((entry,i,a) => { - var tmpdest = uuid.v1().split('-')[0]; - dl(entry, "./b/"+tmpdest, (cb) => { // download item - if(cb.status === true) { - getUser(e.user.getNick(), e.network, (cbgu) => { - getCheckSum("./b/"+tmpdest+"."+cb.infos.ext, (cbcs) => { - checkRepostCheckSum(cbcs, (cbcrcs) => { - if(cbcrcs) { - sql.query("insert into `f0ck`.`items` (`src`,`dest`,`mime`,`size`,`checksum`,`username`,`userchannel`,`usernetwork`,`stamp`,`active`) values (?,?,?,?,?,?,?,?,?,?)", [ - entry, - "./b/"+tmpdest+"."+cb.infos.ext, - cb.infos.mime, - cb.infos.size, - cbcs, - cbgu['nick'], - e.channel.getName(), - e.network, - Math.floor(new Date() / 1000), - 0 - ]).on('result', (result) => { - generateThumbs(); - e.reply("https://f0ck.me/"+result.insertId+" - "+path.parse(entry).base+" ("+cb.infos.mime+", ~"+formatSize(cb.infos.size)+") from "+cbgu['nick']+" ("+cbgu['username']+"@"+cbgu['hostname']+")"); - }).on('error', (msg) => { - e.reply(msg); - }); - } - else { - fs.unlink("./b/"+tmpdest+"."+cb.infos.ext); // delete repost - e.reply("repost motherf0cker"); - } - }); - }); - }); - } - else - if(cb.type == 1) - e.reply(cb.msg); - }); + trigger.trigger.forEach((entry,i,a) => { + if(e.message.match(entry.call) && entry.active == 1) { + lib.log('trigger: '+entry.name); + lib.getUserlevel(e, (cb) => { + if(cb.level >= entry.level) + entry.func(e); + else + e.reply('no permission, min level '+entry.level+' required'); }); } - } - else if(orig.match(/^\.user/)) { // (debug) get userinfos - getUser(e.user.getNick(), e.network, (cbgu) => { - e.reply(cbgu); - }); - } - else if(orig.match(/^\!reload tpl$/)) { - getTpls(); - e.reply("templates reloaded"); - } - else if(orig.match(/^\!toggle debug$/)) { - if(debug) { - debug = false; - e.reply("debugmessages deactivated"); - } - else { - debug = true; - e.reply("debugmessages activated"); - } - } - else if(orig.match(/^\!toggle catcher$/)) { - if(upload) { - upload = false; - e.reply("catcher deactivated"); - } - else { - upload = true; - e.reply("catcher activated"); - } - } - else if(orig.match(/^\!load user$/)) { - loadUser((cb) => { - //e.reply(admins); - }); - } - else if(orig.match(/^\!level$/)) { - var tmp_channel = bot.getChannel(e.channel.getName(), e.network).names; - getUserlevel(e, tmp_channel, (cb) => { - e.reply("level from "+e.user.getNick()+": "+cb.level+" (Channel: "+cb.channel+" DB: "+cb.db+")"); - }); - } - else if(orig.match(/^\!del (\d+)$/i)) { - var tmp_channel = bot.getChannel(e.channel.getName(), e.network).names; - getUserlevel(e, tmp_channel, (cb) => { - if(cb.level >= 100) { - var id = orig.split(' ')[1]; - sql.query("delete from `f0ck`.`items` where `id` = ?", id).on('end', () => { - e.reply("f0ck "+id+" deleted"); - }); - } - else - e.reply("no permission"); - }); - } - else if(orig.match(/^\!thumbs$/i)) { - var tmp_channel = bot.getChannel(e.channel.getName(), e.network).names; - getUserlevel(e, tmp_channel, (cb) => { - if(cb.level >= 100) { - //exec('rm ./t/*.png', (error) => { - e.reply("generating Thumbnails..."); - generateThumbs(); - //}); - } - else - e.reply("no permission"); - }); - } + }); }); -var getUser = (u, n, cbgu) => { - bot.write('WHOIS '+u, n, () => { - bot.once('data', (err, msg) => { - var params; - var map = []; - map.push(u); - if(msg.command == 'RPL_WHOISUSER') { - params = msg.params.split(' '); - map[u] = map[u] || {}; - map[u].nick = u; - map[u].username = params[2]; - map[u].hostname = params[3]; - map[u].realname = msg.trailing; - } - if(typeof(map[u]) === 'object') - cbgu(map[u]); - }); - }); -}; - -var getUserlevel = (e, tmp_channel, cb) => { - getUser(e.user.getNick(), e.network, (cbgu) => { - var host = cbgu.username+'@'+cbgu.hostname; - var lvl_channel = (cbgu.nick in tmp_channel)? cfg.level[ tmp_channel[cbgu.nick] ] : 0; - var lvl_db = 0; - if(host in admins) - lvl_db = (admins[host].server == e.network)? admins[host].level : 0; - cb({ - 'channel': lvl_channel, - 'db': lvl_db, - 'level': Math.max(lvl_channel, lvl_db) - }); - }); -}; - -var loadUser = (cb) => { - admins = []; - sql.query("select * from `f0ck`.`user`", (err, rows, fields) => { - rows.forEach((e,i,a) => { - admins.push(e.nick); - admins[e.vhost] = { - 'id': e.id, - 'nick': e.nick, - 'vhost': e.vhost, - 'level': e.level, - 'server': e.server - }; - }); - cb(true); - }).on('error', () => { - cb(false); - });; -}; - -var dl = (url, dest, cb) => { - var request = (url.match(/^https/)?https:http).get(url, (response) => { // type:1=post,type:2=stfu - console.log(response.headers['content-type']); - if(cfg.allowedMimes.hasOwnProperty(response.headers['content-type'])) { - if(response.headers['content-length'] <= cfg.maxFileSize) { - checkRepost(url, (cbcr) => { - if(cbcr) { - var file = fs.createWriteStream(dest+"."+cfg.allowedMimes[response.headers['content-type']]); - response.pipe(file); - file.on('finish', () => { - file.close(); - probe(dest+"."+cfg.allowedMimes[response.headers['content-type']], (err, probeData) => { - if(probeData.streams[0].height !== undefined || probeData.streams[0].width !== undefined) { - if(probeData.streams[0].height <= cfg.minRes || probeData.streams[0].width <= cfg.minRes) - cb({'status':false, 'msg':'f0ck! your shitpost is too small ('+probeData.streams[0].width+' x '+probeData.streams[0].height+'), min '+cfg.minRes+' x '+cfg.minRes+' required', 'type':1}); - else - cb({'status':true, 'msg':'downloaded '+dest, 'type':1, 'infos':{'mime':response.headers['content-type'], 'size':response.headers['content-length'], 'ext':cfg.allowedMimes[response.headers['content-type']]}}); - } - else - cb({'status':true, 'msg':'downloaded '+dest, 'type':1, 'infos':{'mime':response.headers['content-type'], 'size':response.headers['content-length'], 'ext':cfg.allowedMimes[response.headers['content-type']]}}); - }); - }); - file.on('error', (err) => { - fs.unlink(dest+"."+cfg.allowedMimes[response.headers['content-type']]); - file.close(); - cb({'status':false, 'msg':err.message, 'type':1}); - }); - } - else - cb({'status':false, 'msg':'repost motherf0cker', 'type':1}); - }); - } - else - cb({'status':false, 'msg':'f0ck! your file is too big (~'+formatSize(response.headers['content-length'])+'), max '+formatSize(cfg.maxFileSize)+' allowed', 'type':1}); - } - else - cb({'status':false, 'msg':'f0ck you', 'type':2}); - }).on('error', (msg) => { - cb({'status':false, 'msg':msg, 'type':2}); - }); -}; - -var checkRepost = (url, cbcr) => { - sql.query("select count(*) as count from `f0ck`.`items` where `src` = ?", url, (err, rows, fields) => { - cbcr((rows[0].count == 0)?true:false); - }); -}; -var checkRepostCheckSum = (cs, cbcrcs) => { - sql.query("select count(*) as count from `f0ck`.`items` where `checksum` = ?", cs, (err, rows, fields) => { - cbcrcs((rows[0].count == 0)?true:false); - }); -}; - -var formatSize = (size) => { - var i = Math.floor(Math.log(size) / Math.log(1024)); - return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; -}; - -var getCheckSum = (file, cbcs) => { - var sha256sum = crypto.createHash('sha256'); - var s = fs.ReadStream(file); - s.on('data', (d) => { - sha256sum.update(d); - }); - s.on('end', () => { - cbcs(sha256sum.digest('hex')); - }); -}; - - -// Webserver -http.createServer((req, res) => { - if(cfg.wlip.hasOwnProperty(req.connection.remoteAddress)) { - var filePath = '.' + req.url; - var url = req.url.split("/")[1]; - if(filePath == './') - filePath = './index.html'; - var extname = String(path.extname(filePath)).toLowerCase(); - var contentType = 'text/html'; - var mimeTypes = { - '.html': 'text/html', - '.js': 'text/javascript', - '.css': 'text/css', - '.png': 'image/png', - '.jpg': 'image/jpg', - '.gif': 'image/gif', - '.mp3': 'audio/mpeg', - '.mp4': 'video/mp4', - '.webm': 'video/webm', - '.css': 'text/css', - '.ogg': 'audio/ogg' - }; - if(filePath == "./index.html") { // mainpage - var tpl = swig.compile(templates.index); - var data = { items: [] }; - sql.query("select `id`,`mime` from `f0ck`.`items` order by `id` desc", (err, rows, fields) => { - rows.forEach((e,i,a) => { - data.items.push({ "id": e.id, "mime": e.mime }); - }); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(data), 'utf-8'); - }); - } - else if(Number.isInteger(parseInt(url))) { // itempage - var query = "select * from `f0ck`.`items` where `id` = ? limit 1; " // get item - + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item - + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item - sql.query(query, [url, url, url], (err, rows, fields) => { - var tpl = swig.compile(templates.item); - var data = { - id: '', - username: '', - item: '', - src: '', - dest: '', - mime: '', - size: '', - userchannel: '', - usernetwork: '', - next: null, - prev: null - }; - if(rows[0].length) { - var e = rows[0][0]; - switch(e.mime) { - case "image/png": - case "image/jpeg": - case "image/gif": - data.item = 'image'; - break; - case "video/webm": - case "video/mp4": - data.item = 'video'; - break; - case "audio/mpeg": - case "audio/ogg": - data.item = 'audio'; - break; - } - data.id = e.id; - data.username = e.username; - data.src = e.src; - data.dest = e.dest; - data.mime = e.mime; - data.size = formatSize(e.size); - data.userchannel = e.userchannel; - data.usernetwork = e.usernetwork; - - if(rows[1].length) - data.next = rows[1][0].id; - if(rows[2].length) - data.prev = rows[2][0].id; - } - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(data), 'utf-8'); - }); - } - else if(filePath == "./random") { - sql.query("select `id` from `f0ck`.`items` order by rand() limit 1", (err, rows, fields) => { - res.writeHead(301, { - 'Cache-Control': 'no-cache, public', - 'Location': '/' + rows[0].id - }); - res.end(); - }); - } - else if(filePath == "./how") { - var tpl = swig.compile(templates.how); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(), 'utf-8'); - } - else if(filePath == "./contact") { - var tpl = swig.compile(templates.contact); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(), 'utf-8'); - } - else if(filePath == "./scripts") { - var tpl = swig.compile(templates.scripts); - res.writeHead(200, { 'Content-Type': 'text/html' }); - res.end(tpl(), 'utf-8'); - } - else if(filePath.match(/^\.\/(b|s|t)\/.*/)) { // file - contentType = mimeTypes[extname]; - switch(contentType) { - case "video/webm": - case "video/mp4": - case "audio/mpeg": - case "audio/ogg": - var start = 0; - var end = 0; - var range = req.headers['range']; - var stat = fs.statSync(filePath); - if(range != null) { - start = parseInt(range.slice(range.indexOf('bytes=')+6, range.indexOf('-'))); - end = parseInt(range.slice(range.indexOf('-')+1, range.length)); - } - if(isNaN(end) || end == 0) end = stat.size-1; - if(start > end) return; - res.writeHead(206, { - 'Connection':'close', - 'Content-Type':contentType, - 'Content-Length':end - start, - 'Content-Range':'bytes '+start+'-'+end+'/'+stat.size, - 'Transfer-Encoding':'chunked' - }); - var stream = fs.createReadStream(filePath, { flags: 'r', start: start, end: end}); - stream.pipe(res); - break; - default: - fs.readFile(filePath, (error, content) => { - if(error) { - if(error.code == 'ENOENT') { - res.writeHead(200, { 'Content-Type': contentType }); - res.end('404 - f0ck you', 'utf-8'); - } - else { - res.writeHead(500); - res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); - res.end(); - } - } - else { - res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.length, 'Cache-Control': 'max-age=2592000, public' }); - res.end(content, 'utf-8'); - } - }); - break; - } - fs.readFile(filePath, (error, content) => { - if(error) { - if(error.code == 'ENOENT') { - res.writeHead(200, { 'Content-Type': contentType }); - res.end('404 - f0ck you', 'utf-8'); - } - else { - res.writeHead(500); - res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); - res.end(); - } - } - else { - - } - }); - } - else { // errorpage - res.writeHead(404); - res.end('404 - f0ck you', 'utf-8'); - } - } - else { - res.writeHead(403); - res.end('403 - forbidden'); - } -}).listen(cfg.webserver.port); - -var getTpls = () => { - templates = { - "index": fs.readFileSync("./s/index.tpl.html", "utf-8"), - "item": fs.readFileSync("./s/item.tpl.html", "utf-8"), - "how": fs.readFileSync("./s/how.tpl.html", "utf-8"), - "contact": fs.readFileSync("./s/contact.tpl.html", "utf-8"), - "scripts": fs.readFileSync("./s/scripts.tpl.html", "utf-8") - }; -}; - -var generateThumbs = () => { - var outdir = './t/'; - sql.query("select * from `f0ck`.`items`", (err, rows, fields) => { - rows.forEach((e,i,a) => { - var thumbnail = outdir+e.id+'.png'; - if(!fs.existsSync(thumbnail)) { - exec('ffmpegthumbnailer -i'+e.dest+' -s256 -o'+thumbnail, (error) => { - if(error) { - log('failed thumbnail for '+e.id+' ('+e.mime+') 1'); - //fs.unlink(thumbnail); - fs.copySync('./s/mp3.png', thumbnail); // copy standardthumbnail - } - else { - exec('convert '+thumbnail+' -resize "128x128^" -gravity center -crop 128x128+0+0 +repage '+thumbnail, (error) => { - if(error) { - log('failed thumbnail for '+e.id+' ('+e.mime+') 2'); - //fs.unlink(thumbnail); - } - else { - log("generated thumbnail for "+e.id+" ("+e.mime+")"); - } - }); - } - }); - } - }); - }); -}; - -loadUser((cb)=>{ +lib.loadUser((cb)=>{ console.log((cb)?"Admins wurden geladen":"Admins konnten nicht geladen werden"); }); -getTpls(); \ No newline at end of file +websrv.getTpls(); + +var trigger = { + trigger: [], + add: (args) => { + trigger.trigger.push({ + name: args.name, + call: args.call, + func: args.func, + desc: args.desc, + level: args.level, + active: args.active + }); + } +}; + +var loadPlugins = function() { + var files = fs.readdirSync(__dirname+'/trigger/'); + files.forEach(function (file) { + if(file.substr(-3, 3) === '.js') { + console.log('Loading plugin', file); + require(__dirname+'/trigger/' + file)(bot, trigger, lib); + } + }); +}; +var use = function(fn) { + fn(this); + return this; +}; +loadPlugins(); \ No newline at end of file diff --git a/src/trigger/debug.js b/src/trigger/debug.js new file mode 100644 index 0000000..51f3ba1 --- /dev/null +++ b/src/trigger/debug.js @@ -0,0 +1,12 @@ +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'debug', + call: /^\!debug$/i, + level: 100, + active: 1, + func: (e) => { + e.reply(lib.toggleDebug()); + }, + desc: 'toggle debug' + }); +}; \ No newline at end of file diff --git a/src/trigger/del.js b/src/trigger/del.js index c6dfdc6..e1d4f61 100644 --- a/src/trigger/del.js +++ b/src/trigger/del.js @@ -1,22 +1,15 @@ -module.exports = (bot) => { - var del = (e, args) => { - var tmp_channel = bot.getChannel(e.channel.getName(), e.network).names; - getUserlevel(e, e.user.getNick(), tmp_channel, (cb) => { - if(cb.level >= 100) { - var id = orig.split(' ')[1]; - sql.query("delete from `f0ck`.`items` where `id` = ?", id).on('end', () => { - e.reply("f0ck "+id+" deleted"); - }); - } - else - e.reply("no permission"); - }); - }; - - bot.addCmd({ - name: 'spec', +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'delete', + call: /^\!del (\d+)$/i, level: 100, - func: spec, - desc: 'delete f0rk' + active: 1, + func: (e) => { + var id = e.message.split(' ')[1]; + lib.sql.query("delete from `f0ck`.`items` where `id` = ?", id).on('end', () => { + e.reply("f0ck "+id+" deleted"); + }); + }, + desc: 'delete f0ck' }); }; \ No newline at end of file diff --git a/src/trigger/level.js b/src/trigger/level.js new file mode 100644 index 0000000..f4eb0d0 --- /dev/null +++ b/src/trigger/level.js @@ -0,0 +1,14 @@ +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'level', + call: /^\!level$/i, + level: 0, + active: 1, + func: (e) => { + lib.getUserlevel(e, (cb) => { + e.reply("level from "+e.user.getNick()+": "+cb.level+" (Channel: "+cb.channel+" DB: "+cb.db+")"); + }); + }, + desc: 'get Userlevel' + }); +}; \ No newline at end of file diff --git a/src/trigger/parser.js b/src/trigger/parser.js new file mode 100644 index 0000000..2f859dd --- /dev/null +++ b/src/trigger/parser.js @@ -0,0 +1,61 @@ +var fs = require('fs-extra'); +var uuid = require('uuid'); +var path = require('path'); + +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'parser', + call: /https?:\/\/[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?/gi, + level: 0, + active: 1, + func: (e) => { + if(e.channel.getName() == '#f0ck') { + if(!e.message.match(/\!ignore$/)) { + var tmp = e.message.match(/https?:\/\/[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?/gi); // get links + tmp.forEach((entry,i,a) => { + var tmpdest = uuid.v1().split('-')[0]; + if(!entry.match(/f0ck\.me/i)) { + lib.dl(entry, "./b/"+tmpdest, (cb) => { // download item + if(cb.status === true) { + lib.getUser(e, (cbgu) => { + lib.getCheckSum("./b/"+tmpdest+"."+cb.infos.ext, (cbcs) => { + lib.checkRepostCheckSum(cbcs, (cbcrcs) => { + if(cbcrcs) { + lib.sql.query("insert into `f0ck`.`items` (`src`,`dest`,`mime`,`size`,`checksum`,`username`,`userchannel`,`usernetwork`,`stamp`,`active`) values (?,?,?,?,?,?,?,?,?,?)", [ + entry, + "./b/"+tmpdest+"."+cb.infos.ext, + cb.infos.mime, + cb.infos.size, + cbcs, + cbgu['nick'], + e.channel.getName(), + e.network, + Math.floor(new Date() / 1000), + 0 + ]).on('result', (result) => { + lib.generateThumbs(); + e.reply("https://f0ck.me/"+result.insertId+" - "+path.parse(entry).base+" ("+cb.infos.mime+", ~"+lib.formatSize(cb.infos.size)+") from "+cbgu['nick']+" ("+cbgu['username']+"@"+cbgu['hostname']+")"); + }).on('error', (msg) => { + e.reply(msg); + }); + } + else { + fs.unlink("./b/"+tmpdest+"."+cb.infos.ext); // delete repost + e.reply("repost motherf0cker"); + } + }); + }); + }); + } + else + if(cb.type == 1) + e.reply(cb.msg); + }); + } + }); + } + } + }, + desc: 'muh' + }); +}; \ No newline at end of file diff --git a/src/trigger/stats.js b/src/trigger/stats.js new file mode 100644 index 0000000..9652d72 --- /dev/null +++ b/src/trigger/stats.js @@ -0,0 +1,20 @@ +var du = require('du'); + +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'stats', + call: /^\!stats$/i, + level: 10, + active: 1, + func: (e) => { + lib.sql.query("select count(`id`) as anzahl from `f0ck`.`items`", (err, rows, fields) => { + var ret = "f0cked "+String(rows[0].anzahl); + du('./b/', function (err, size) { + ret += " f0cks ("+lib.formatSize(size)+")"; + e.reply(ret); + }) + }); + }, + desc: 'stats' + }); +}; \ No newline at end of file diff --git a/src/trigger/thumb.js b/src/trigger/thumb.js new file mode 100644 index 0000000..f759058 --- /dev/null +++ b/src/trigger/thumb.js @@ -0,0 +1,21 @@ +var fs = require('fs-extra'); + +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'thumbnailer', + call: /^\!thumb (\d+)$/i, + level: 100, + active: 1, + func: (e) => { + var id = e.message.split(' ')[1]; + if(Number.isInteger(parseInt(id))) { + fs.unlink('./t/'+id+'.png', () => { + //e.reply('debug: Thumbnail gelöscht'); + lib.generateThumbs(); + //e.reply('debug: sollte Thumbnail generiert haben'); + }); + } + }, + desc: 'regenerate thumbnail' + }); +}; \ No newline at end of file diff --git a/src/trigger/user.js b/src/trigger/user.js new file mode 100644 index 0000000..14726cd --- /dev/null +++ b/src/trigger/user.js @@ -0,0 +1,15 @@ +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'user', + call: /^\!user$/i, + level: 0, + active: 1, + func: (e) => { + //e.reply(e.user); + lib.getUser(e, (cbgu) => { + e.reply(cbgu); + }); + }, + desc: 'get Userdata' + }); +}; \ No newline at end of file diff --git a/src/trigger/ytdl.js b/src/trigger/ytdl.js new file mode 100644 index 0000000..da12ec2 --- /dev/null +++ b/src/trigger/ytdl.js @@ -0,0 +1,85 @@ +var fs = require('fs-extra'); +var ytdl = require('ytdl-core'); +var uuid = require('uuid'); +var probe = require('node-ffprobe'); + +module.exports = (bot, trigger, lib) => { + trigger.add({ + name: 'ytdl', + call: /https?:\/\/(www\.)?(yotu\.be\/|youtube\.com\/)?((.+\/)?(watch(\?v=|.+&v=))?(v=)?)([\w_-]{11})(&.+)?/gi, + level: 0, + active: 0, + func: (e) => { + if(e.channel.getName() == '#f0ck') { + if(!e.message.match(/\!ignore$/)) { + var tmp = e.message.match(/https?:\/\/(www\.)?(yotu\.be\/|youtube\.com\/)?((.+\/)?(watch(\?v=|.+&v=))?(v=)?)([\w_-]{11})(&.+)?/gi); // get links + tmp.forEach((entry,i,a) => { + var dl = true; + var tmpdest = uuid.v1().split('-')[0]; + lib.checkRepost(entry, (cbcr) => { + if(cbcr) { + var dat = fs.createWriteStream('./b/'+tmpdest+'.webm'); + //ytdl(entry, { filter: (format) => { return format.container === 'webm'; } }) + ytdl(entry) + .on('response', (res) => { + if(res.headers['content-length'] > lib.cfg.maxFileSize) { + res.destroy(); + dl = false; + e.reply('f0ck! your file is too big (~'+lib.formatSize(res.headers['content-length'])+'), max '+lib.formatSize(lib.cfg.maxFileSize)+' allowed'); + } + }) + .on('error', (err) => { + //e.reply(err); + }) + .pipe( dat ); + dat.on('finish', () => { + if(dl) { + dat.close(); + //probe("./b/"+tmpdest+".webm", (err, probeData) => { + // var size = probeData.size; + var stat = fs.statSync("./b/"+tmpdest+".webm"); + lib.getUser(e, (cbgu) => { + lib.getCheckSum("./b/"+tmpdest+".webm", (cbcs) => { + lib.checkRepostCheckSum(cbcs, (cbcrcs) => { + if(cbcrcs) { + lib.sql.query("insert into `f0ck`.`items` (`src`,`dest`,`mime`,`size`,`checksum`,`username`,`userchannel`,`usernetwork`,`stamp`,`active`) values (?,?,?,?,?,?,?,?,?,?)", [ + entry, + "./b/"+tmpdest+".webm", + "video/webm", + stat.size, + cbcs, + cbgu['nick'], + e.channel.getName(), + e.network, + Math.floor(new Date() / 1000), + 0 + ]).on('result', (result) => { + lib.generateThumbs(); + e.reply("https://f0ck.me/"+result.insertId+" - "+entry+" (video/webm, ~"+lib.formatSize(stat.size)+") from "+cbgu['nick']+" ("+cbgu['username']+"@"+cbgu['hostname']+")"); + }).on('error', (msg) => { + e.reply(msg); + }); + } + else { + fs.unlink("./b/"+tmpdest+".webm"); // delete repost + e.reply("repost motherf0cker"); + } + }); + }); + }); + //}); + } + else + fs.unlink('./b/'+tmpdest+'.webm'); + }); + } + else + e.reply('repost motherf0cker'); + }); + }); + } + } + }, + desc: 'download youtubevideos' + }); +}; \ No newline at end of file diff --git a/src/websrv.js b/src/websrv.js new file mode 100644 index 0000000..b539bdd --- /dev/null +++ b/src/websrv.js @@ -0,0 +1,254 @@ +var fs = require('fs-extra'); +var http = require('http'); +var path = require('path'); +var swig = require('swig'); + +var templates = {}; +var bot, sql, cfg, lib; + +module.exports = Websrv; +function Websrv(tbot, tsql, tcfg, tlib) { + this.bot = bot = tbot; + this.sql = sql = tsql; + this.cfg = cfg = tcfg; + this.lib = lib = tlib; + + http.createServer((req, res) => { + if(cfg.wlip.hasOwnProperty(req.connection.remoteAddress)) { + var filePath = '.' + req.url; + var url = req.url.split("/")[1]; + if(filePath == './') + filePath = './index.html'; + var extname = String(path.extname(filePath)).toLowerCase(); + var contentType = 'text/html'; + var mimeTypes = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.png': 'image/png', + '.jpg': 'image/jpg', + '.gif': 'image/gif', + '.mp3': 'audio/mpeg', + '.mp4': 'video/mp4', + '.webm': 'video/webm', + '.css': 'text/css', + '.ogg': 'audio/ogg' + }; + if(filePath == "./index.html") { // mainpage + var tpl = swig.compile(templates.index); + var data = { items: [] }; + sql.query("select `id`,`mime` from `f0ck`.`items` order by `id` desc", (err, rows, fields) => { + rows.forEach((e,i,a) => { + data.items.push({ "id": e.id, "mime": e.mime }); + }); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(data), 'utf-8'); + }); + } + else if(Number.isInteger(parseInt(url))) { // itempage + var query = "select * from `f0ck`.`items` where `id` = ? limit 1; " // get item + + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item + + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item + sql.query(query, [url, url, url], (err, rows, fields) => { + var tpl = swig.compile(templates.item); + var data = { + id: '', + username: '', + item: '', + src: '', + dest: '', + mime: '', + size: '', + userchannel: '', + usernetwork: '', + next: null, + prev: null + }; + if(rows[0].length) { + var e = rows[0][0]; + switch(e.mime) { + case "image/png": + case "image/jpeg": + case "image/gif": + data.item = 'image'; + break; + case "video/webm": + case "video/mp4": + data.item = 'video'; + break; + case "audio/mpeg": + case "audio/ogg": + data.item = 'audio'; + break; + } + data.id = e.id; + data.username = e.username; + data.src = e.src; + data.dest = e.dest; + data.mime = e.mime; + data.size = lib.formatSize(e.size); + data.userchannel = e.userchannel; + data.usernetwork = e.usernetwork; + if(rows[1].length) + data.next = rows[1][0].id; + if(rows[2].length) + data.prev = rows[2][0].id; + } + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(data), 'utf-8'); + }); + } + else if(filePath == "./random") { + sql.query("select `id` from `f0ck`.`items` order by rand() limit 1", (err, rows, fields) => { + res.writeHead(301, { + 'Cache-Control': 'no-cache, public', + 'Location': '/' + rows[0].id + }); + res.end(); + }); + } + else if(filePath == "./how") { + var tpl = swig.compile(templates.how); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(), 'utf-8'); + } + else if(filePath == "./contact") { + var tpl = swig.compile(templates.contact); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(), 'utf-8'); + } + else if(filePath == "./scripts") { + var tpl = swig.compile(templates.scripts); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(tpl(), 'utf-8'); + } + else if(filePath.match(/^\.\/(b|s|t)\/.*/)) { // file + contentType = mimeTypes[extname]; + switch(contentType) { + case "video/webm": + case "video/mp4": + case "audio/mpeg": + case "audio/ogg": + var start = 0; + var end = 0; + var range = req.headers['range']; + var stat = fs.statSync(filePath); + if(range != null) { + start = parseInt(range.slice(range.indexOf('bytes=')+6, range.indexOf('-'))); + end = parseInt(range.slice(range.indexOf('-')+1, range.length)); + } + if(isNaN(end) || end == 0) end = stat.size-1; + if(start > end) return; + res.writeHead(206, { + 'Connection':'close', + 'Content-Type':contentType, + 'Content-Length':end - start, + 'Content-Range':'bytes '+start+'-'+end+'/'+stat.size, + 'Transfer-Encoding':'chunked' + }); + var stream = fs.createReadStream(filePath, { flags: 'r', start: start, end: end}); + stream.pipe(res); + break; + default: + fs.readFile(filePath, (error, content) => { + if(error) { + if(error.code == 'ENOENT') { + res.writeHead(200, { 'Content-Type': contentType }); + res.end('404 - f0ck you', 'utf-8'); + } + else { + res.writeHead(500); + res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); + res.end(); + } + } + else { + res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': content.length, 'Cache-Control': 'max-age=2592000, public' }); + res.end(content, 'utf-8'); + } + }); + break; + } + fs.readFile(filePath, (error, content) => { + if(error) { + if(error.code == 'ENOENT') { + res.writeHead(200, { 'Content-Type': contentType }); + res.end('404 - f0ck you', 'utf-8'); + } + else { + res.writeHead(500); + res.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); + res.end(); + } + } + }); + } + else if(filePath.match(/^\.\/api/i)) { // api + var url = filePath.split('/'); + if(url[2] === undefined) { // Mainpage + var query = "select * from `f0ck`.`items`"; + sql.query(query, (err, rows, fields) => { + var items = []; + rows.forEach((e,i,a) => { + items.push({ + 'id': e.id, + 'mime': e.mime + }); + }); + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(JSON.stringify(items), 'utf-8'); + }); + } + else if(Number.isInteger(parseInt(url[2]))) { // Item + var query = "select * from `f0ck`.`items` where `id` = ? limit 1; " // get item + + "select `id` from `f0ck`.`items` where `id` = (select min(`id`) from `f0ck`.`items` where `id` > ?); " // get previous item + + "select `id` from `f0ck`.`items` where `id` = (select max(`id`) from `f0ck`.`items` where `id` < ?)"; // get next item + sql.query(query, [url[2], url[2], url[2]], (err, rows, fields) => { + var data; + if(rows[0].length) { + var e = rows[0][0]; + data = { + id: e.id, + username: e.username, + src: e.src, + dest: e.dest, + mime: e.mime, + size: e.size, + userchannel: e.userchannel, + usernetwork: e.usernetwork, + next: null, + prev: null + }; + if(rows[1].length) + data.next = rows[1][0].id; + if(rows[2].length) + data.prev = rows[2][0].id; + } + else + data = { error: 'nope' }; + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(JSON.stringify(data), 'utf-8'); + }); + } + } + else { // errorpage + res.writeHead(404); + res.end('404 - f0ck you', 'utf-8'); + } + } + else { + res.writeHead(403); + res.end('403 - forbidden'); + } + }).listen(cfg.webserver.port); +} + +Websrv.prototype.getTpls = () => { + templates = { + "index": fs.readFileSync("./s/index.tpl.html", "utf-8"), + "item": fs.readFileSync("./s/item.tpl.html", "utf-8"), + "how": fs.readFileSync("./s/how.tpl.html", "utf-8"), + "contact": fs.readFileSync("./s/contact.tpl.html", "utf-8"), + "scripts": fs.readFileSync("./s/scripts.tpl.html", "utf-8") + }; +}; \ No newline at end of file diff --git a/start.sh b/start.sh index 1a5a613..2731ae6 100755 --- a/start.sh +++ b/start.sh @@ -1,2 +1,2 @@ #!/bin/bash -tmux new -d -s bot 'node src/main.js'; +tmux new -d -s bot 'npm start';