const fs = require("fs");
const websocket = require("ws");
var irc = require("irc");
const fetch = require("node-fetch");
const formdata = require("form-data");
const he = require("he");
const cheerio = require("cheerio");
const sharp = require("sharp");
const crypto = require("crypto");
const radar = require("chokidar");
const path = require("path");
const { createCanvas, loadImage } = require("canvas");
var mmm = require("mmmagic");
var Magic = mmm.Magic;

var ws_logged_in = false;

const mineflayer = require("mineflayer");

const mc_options = {
	"host": "deek.chat",
	"port": 25565,
	"username": "rena_chan"
	// offline mode
};

const name = "rena_chan";
const password = "oh my fucking god";
const irc_password = "stop it";
const irc_rena_password = "get some help";
const yt_token = "i want to fucking dieeeeeeee"
const irc_ip = "192.168.0.152";
const upload_folder = "/var/www/html/u/uploads/";
const mc_bridge_enabled = false;
const irc_bridge_enabled = true;
const do_file_upload = false;
var scraper = "google_cse";
const instance = "https://4get.lumaeris.com";

var http = require("http");

const emitter = require("events");
const deek = new emitter();

const userid = 12;
var channels = [];
var ws = [];
var results_cache = [];
var is_pp_xeno = false;
var ddg_mod = true; // dont show porn in .img
var global_page = 1; // .img page
var raid = false; // raid?
var img_headers = {
	"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
	"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
	"Accept-Language": "en-US,en;q=0.5",
	"Accept-Encoding": "gzip, deflate",
	"DNT": "1",
	"Connection": "keep-alive"
};

var fortunes = [
	"You will meet a dark stranger.",
	"You will be eaten by a grue.",
	"Your fortune: Bad Luck.",
	"Your fortune: Good Luck.",
	"Your fortune: Reply hazy, try again.",
	"Your fortune: Outlook not so good.",
	"Your fortune: YES.",
	"Your fortune: NO.",
	"Your fortune: Absolutely.",
	"Your fortune: Very doubtful.",
	"Your fortune: Cannot predict now.",
	"Your fortune: Signs point to yes.",
	"Your fortune: Don't count on it.",
	"Your fortune: You may rely on it.",
	"Your fortune: Concentrate and ask again.",
	"Your fortune: The future is uncertain.",
	"Your fortune: it's over"
];

var headers = {
	"User-Agent": "renabot",
	"Origin": "https://deek.chat"
};

/*
	HTTP server bullshit
*/
//process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

const http_bullshit_handler = function(req, res){
	
	switch(req.url){
		
		case "/":
			res.setHeader("Content-Type", "text/html");
			res.writeHead(200);
			res.end('<body style="color:#fff;background:#000;"><h1>all work and no play makes deek a dull boy</h1>');
			break;
		
		case "/kys":
			res.setHeader("Content-Type", "text/html");
			res.writeHead(200);
			res.end('<body style="color:red;background:#000;"><h1>bang!</h1>');
			
			send(1, "Resharted from web interface");
			
			setTimeout(function(){
				
				fs.utimesSync(__filename, Date.now(), Date.now());
			}, 1000);
			break;
		
		default:
			res.writeHead(404);
			res.end("fuck you");
			break;
	}
}

const http_bullshit = http.createServer(http_bullshit_handler);
http_bullshit.listen(6969, "0.0.0.0", function(){
	
	console.log("HTTP bullshit running");
});

/*
	Helper functions
*/
function rot13(str){
	
	return str.replace(/[a-zA-Z]/g, (char) => {
		
		const charCode = char.charCodeAt(0);
		
		if(char >= 'a' && char <= 'z'){
		
			return String.fromCharCode(((charCode - 97 + 13) % 26) + 97);
		}else if(char >= 'A' && char <= 'Z') {
		
			return String.fromCharCode(((charCode - 65 + 13) % 26) + 65);
		}
		
		return char; // Non-alphabet characters remain unchanged
	});
}

function sha1(str){
	
	return crypto.createHash("sha1").update(str).digest("hex");
}

async function getmime(file){
	
	return new Promise(function(resolve, reject){
		
		var magic = new Magic(mmm.MAGIC_MIME_TYPE);
		magic.detect(file, function(err, result){
			
			if(err){
				
				resolve([
					"image/png",
					"png",
					false
				]);
			}else{
				
				resolve([
					result,
					result.split("/")[1],
					true
				]);
			}
		});
	});
}

function removehtml(html){
	
	return he.decode(html.replace(/<[^>]+>/ig,""));
};

async function serveddg(msg, results, page){
	
	page--;
	
	try{
		
		var res = await fetch(
			results[page].source[0].url,
			{
				headers: img_headers,
				signal: AbortSignal.timeout(3000)
			}
		);
		
		var res_get = await res.buffer();
		var m = await getmime(res_get);
		
		if(
			m[2] === false ||
			m[0].split("/")[0] != "image"
		){
			
			var res = await fetch(
				results[page].source[results[page].source.length - 1].url,
				{
					headers: img_headers,
					signal: AbortSignal.timeout(5000)
				}
			);
		
			var res_get = await res.buffer();
		}
		
	}catch(err){
		
		var res = await fetch(
			results[page].source[results[page].source.length - 1].url,
			{
				headers: img_headers,
				signal: AbortSignal.timeout(5000)
			}
		);
	
		var res_get = await res.buffer();
	}
	
	send(
		msg,
		"Page " + (page + 1) + "/" + (results.length) + "\n[" + results[page].source[0].width + "x" + results[page].source[0].height + "] " + results[page].title + "\n" + results[page].url,
		res_get
	);
}

/*
	Websocket functions
*/
async function send(channel, text, file = null, forcemime = null){
	
	if(ws_logged_in === false){ return; }
	
	if(typeof channel == "object"){
		
		channel = channel.channel;
	}
	
	if(channel === -1){
		
		channel = 1;
	}else{
		
		if(channel === 1){
			
			irc_client.say("#deekchat", text);
		}
	}
	
	if(file === null){
		
		ws.send(JSON.stringify({
			type: "messageEnd",
			data: text,
			roomId: channel
		}));
		
		return;
	}
	
	/*
		Send file
	*/
	if(forcemime === null){
		
		var mimetype = await getmime(file);
	}else{
		
		var mimetype = forcemime;
	}
	
	var form_data = new formdata();
	form_data.append("text", text);
	form_data.append(
		"files[]",
		file,
		{
			contentType: mimetype[0],
			name: "file",
			filename: "file." + mimetype[1]
		}
	);
	
	var headers_copy = headers;
	headers_copy["Content-Type"] = form_data.getHeaders()["content-type"];
	
	fetch("https://deek.chat/message/send/" + channel, {
		method: 'POST',
		body: form_data,
		headers: headers_copy
	});
}

/*
	Handshake
*/

async function handshake(name, password){
	
	/*
		Get cookie
	*/
	console.log("logging in as " + name);
	
	const form = new formdata();
	
	form.append("name", name);
	form.append("password", password);
	form.append("submit", "log+in");
	
	try{
		var login = await fetch(
			"https://deek.chat/login/submit",
			{
				method: "POST",
				redirect: "manual",
				body: form,
				headers: headers
			}
		);
	}catch(err){
		
		console.log("Could not login. Trying again in 10 seconds...");
		
		setTimeout(function(){
		
			fs.utimesSync(__filename, Date.now(), Date.now());
		}, 10000);
		return;
	}
	
	var cookie = await login.headers.get("set-cookie");
	
	var api_token = cookie.match(/api_token=([^;]+)/);
	var session_id = cookie.match(/session_id=([^;]+)/);
	
	if(
		api_token === null ||
		typeof api_token[1] != "string" ||
		typeof session_id[1] != "string"
	){
		
		deek.emit("error", "Could not get api_token or session_id !!");
		return;
	}
	
	headers.cookie = "session_id=" + session_id[1] + "; api_token=" + api_token[1];
	
	/*
		Connect to websocket serber
	*/
	ws = new websocket(
		"wss://deek.chat/ws",
		{
			protocolVersion: 13,
			encoding: "utf8",
			headers: headers
		}
	);
	
	var pong_time = Date.now();
	
	ws.once("open", function(){
		
		ws_logged_in = true;
		deek.emit("login");
		
		const keep_alive = setInterval(function(){
			
			//console.log("ping!");
			if(pong_time + 10000 < Date.now()){
				
				deek.emit("error", "Did not receive ping frame");
				return;
			}
			ws.ping();
			
		}, 5000);
	});

	ws.on("pong", function(){

		pong_time = Date.now();
		//console.log(pong_time + ": pong!");
	});
	
	ws.once("close", function(){
		
		ws_logged_in = false;
		deek.emit("close");
	});
	
	ws.on("message", function(message, isbin){
		
		if(isbin){ return; }
		
		var m = JSON.parse(message.toString("utf8"));
		
		if(typeof m.type == "undefined"){
			return;
		}
		
		switch(m.type){
			
			case "message":
			case "messageEnd":
				for(var k=0; k<m.data.mentions.length; k++){
					
					m.data.mentions[k].id = m.data.mentions[k].userId;
					delete m.data.mentions[k].userId;
				}
				
				for(var k=0; k<m.data.replies.length; k++){
					
					m.data.replies[k].id = m.data.replies[k].userId;
					m.data.replies[k] = m.data.replies[k].replyMessageId;
					delete m.data.replies[k].userId;
					delete m.data.replies[k].replyMessageId;
				}
				
				m.data.files = m.data.files === null ? [] : m.data.files;
				var files = [];
				
				for(var k=0; k<m.data.files.length; k++){
					
					files.push("https://deek.chat/storage/files/" + m.data.files[k].name);
				}
				
				deek.emit(
					"message",
					{
						text: removehtml(m.data.text),
						id: m.data.id,
						files: files,
						mentions: m.data.mentions,
						replies: m.data.replies,
						channel: m.roomId
					},
					{
						name: m.data.name,
						id: m.userId,
						picture: m.data.profilePicture == "" ? null : "https://deek.chat/storage/profilePictures/" + m.data.profilePicture
					}
				);
				
				break;
			
			case "files":
				var files = [];
				
				if(m.data.files !== null){
					for(var k=0; k<m.data.files.length; k++){
						
						files.push("https://deek.chat/storage/files/" + m.data.files[k].name);
					}
				}
				
				deek.emit(
					"message",
					{
						text: removehtml(m.data.text),
						id: m.data.id,
						files: files,
						mentions: m.data.mentions,
						replies: m.data.replies,
						channel: m.roomId
					},
					{
						name: m.data.name,
						id: m.data.userId,
						picture: m.data.profilePicture == "" ? null : "https://deek.chat/storage/profilePictures/" + m.data.profilePicture
					}
				);
				break;
			
			default:
				//console.warn(m);
				break;
		}
	});
	
	ws.on("error", function(code, reason){
		
		ws_logged_in = false;
		deek.emit("error", "code=" + code + " reason=" + reason);
	});
}

/*
	Mineflayer bot code
*/
var mc_logged_in = false;
var mc_bot = null;

function mc_login(){

	if(mc_bridge_enabled === false){ return; }
	
	mc_bot = mineflayer.createBot(mc_options);
	
	mc_bot.once("login", function(){
		
		console.log("Logged into minecraft server as " + mc_options.username);
		mc_logged_in = true;
	});
	
	mc_bot.on("chat", function(username, message){
		
		if(username !== mc_bot.username){
			
			send(1, "<" + username + "> " + message);
		}
		
		if(
			username === "4lul" &&
			message.startsWith(".run")
		){
						
			var cmd = message.split(" ");
			cmd.shift();
			cmd = cmd.join(" ");
			mc_bot.chat(cmd);
			return;
		}
		
		if(message.startsWith("rena take me home")){
			
			mc_bot.chat("/tp " + username + " rena_chan");
			mc_bot.chat("I will take you home! <3");
		}
	});
	
	mc_bot.on("end", function(){
		
		mc_logged_in = false;
		console.log("Minecraft: Kicked from server");
		setTimeout(mc_login, 10000); // reconnect after 10 seconds
	});
	
	mc_bot.on("error", function(){
		
		console.log("Minecraft: error occured");
	});
	
	mc_bot.on("playerJoined", function(player){
		
		send(1, ">" + player.username + " has joined deekcraft");
	})
	
	mc_bot.on("playerLeft", function(player){
		
		send(1, ">" + player.username + " has left deekcraft");
	})
}

mc_login();

function mc_escape(str){
	
	return str.replace(
		/[^-!\"#$%&'()*+,.\/0123456789:;<=>?@[\\\]^_'abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«» ]/gi,
		""
	).replace(/[\\"']/g, '\\$&')
	.replace(/\u0000/g, '\\0')
	.replace("\n", " ");
}

/*
	IRC bridge
*/
var irc_logged_in = false;
var irc_debounce = false;
var irc_client = null;

function init_irc(){
	if(irc_bridge_enabled === false){
		
		return;
	}
	
	irc_client =
		new irc.Client(
			irc_ip,
			"rena_chan",
			{
				channels: [
					"#deekchat"
				],
				userName: name,
				realName: "Rena Chan",
				autoRejoin: true,
				retryCount: 1000,
				password: irc_password
			}
		);
	
	irc_client.addListener("join#deekchat", function(nick){
		
		if(nick === name){
			
			console.log("Connected to IRC server on #deekchat");
			irc_logged_in = true;
			
			irc_client.send("OPER", name, irc_rena_password);
			
			// add debounce so we dont send missed messages
			setTimeout(function(){
				
				irc_debounce = true;
			}, 2500);
		}else{
			
			// someone joined the channel
			irc_client.say(nick, "Welcome you ball sucker, here's your file upload link");
			irc_client.say(
				nick,
				"https://lolcat.ca/u/upload" +
				"?u=" + nick +
				"&h=" + sha1(rot13(irc_password.toUpperCase()) + nick)
			);
		}
	});
	
	irc_client.addListener("message", function(from, to, message){
		
		if(irc_debounce === false){ return; }
		
		if(typeof from == "undefined"){ return; }
		
		switch(to){
			
			case "#deekchat":
				send(-1, "<" + from + "> " + message);
				
				deek.emit(
					"message",
					{
						text: message,
						id: -1,
						files: [],
						mentions: [],
						replies: [],
						channel: 1
					},
					{
						name: from,
						id: -1,
						picture: null
					}
				);
				break;
		}
	});
	
	irc_client.addListener("close", function(message){
		
		console.log("IRC disconnect!");
		console.log(message);
		
		irc_logged_in = false;
		irc_debounce = false;
		//irc_client.disconnect();
	});
	
	irc_client.addListener("error", function(error){
		
		console.log("IRC error");
		console.log(error);
	});
}

init_irc();

/*
	Watch for file uploads
*/
const file_watch =
	radar.watch(
		upload_folder,
		{
			persistent: true,
			awaitWriteFinish: {
				stabilityThreshold: 2000,
				pollInterval: 100
			}
		}
	);

file_watch.on("add", async function(file_path){
	
	var filename = path.basename(file_path);
	var parts = filename.split(".");
	
	var username = parts[0];
	var ext = null;
	
	if(parts.length <= 2){
		
		switch(parts[1]){
			
			case "mp4":
			case "mpeg":
			case "mov":
			case "avi":
			case "flv":
			case "avc":
			case "mpeg4":
			case "webm":
				ext = "video";
				break;
			
			case "mp3":
			case "flac":
			case "alac":
			case "m4a":
			case "opus":
			case "vorbis":
				ext = "audio";
				break;
		}
	}
	
	const data = await fs.readFileSync("/home/will/Desktop/4lul.0.jpg");
	send(-1, "<" + username + "> [file]", data, ext);
});

/*
	User defined FUN-ctions!!!
*/
async function fetch_xeno(recording, index, msg){
	
	index--;
	
	var filereq = await fetch(
		recording[index].file,
		{headers: img_headers}
	);
	
	var file = await filereq.buffer();
	
	send(
		msg,
		"Page " + (index + 1) + "/" + recording.length + "\n" +
		">" + recording[index].en + " in " + recording[index].cnt + " (" + recording[index].loc + ")\n" +
		recording[index].rmk,
		file,
		[
			"audio/mpeg",
			"mp3"
		]
	);
}

async function fetch_image(image_link){
	
	var res = await fetch(image_link);
	return await res.buffer();
}

deek.on("login", function(channel){
	
	console.log("Connected to websocket xoxo");
});

// @TODO remove
var last_image = "https://deek.chat/storage/files/Dek1873923025157525504.jpg";

deek.on("message", async function(msg, user){
	
	if(msg.files.length !== 0){
		
		last_image = msg.files[msg.files.length - 1];
	}
	
	if(
		(
			user.id === 13 || // me lol
			user.id === 14 || // aves
			user.id === 8 || // aves again
			user.id === 30 || // eagle
			user.id === 2 || // deek
			raid === false
		) === false
	){
		
		return;
	}
	
	if(
		user.id === userid &&
		irc_bridge_enabled &&
		msg.channel === 1 &&
		msg.files.length !== 0
	){
		
		for(var i=0; i<msg.files.length; i++){
			
			irc_client.say("#deekchat", msg.files[i]);
		}
	}
	
	if(
		user.id === userid ||
		user.name == name
	){
		return;
	}
	/*
	if(user.id === 5311){
		
		user.name = msg.text.split(">")[0].substr(1);
		msg.text = msg.text.split(">").splice(1).join(">").trim();
	}*/
	
	if(
		mc_bridge_enabled &&
		mc_logged_in &&
		msg.channel === 1
	){
		
		// ascii filter		
		var mc_message = msg.text.replace(
			/[^-\n!\"#$%&'()*+,.\/0123456789:;<=>?@[\\\]^_'abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«» ]/gi,
			"?"
		);
		
		var mc_username = mc_escape(user.name);
		
		mc_message.split("\n").forEach(function(a){
			
			// split message into newlines
			// make sure message gets sent in chunks
			
			for(var i=0; i<a.length; i += 100){
				
				if(i === 0){
					
					mc_bot.chat('/tellraw @a ["<",{"text":"' + mc_username + '","color":"gold"},"> ' + mc_escape(a.slice(i, i + 100)).trim() + '"]');
				}else{
					
					mc_bot.chat('/tellraw @a "' + mc_escape(a.slice(i, i + 100)) + '"');
				}
			}
		});
	}
	
	if(
		irc_bridge_enabled &&
		irc_logged_in &&
		msg.channel === 1 &&
		msg.id !== -1
	){
		
		var irc_username = user.name.replace(/[^A-Za-z0-9-_]/g, "-");
		var limit = 412 - ("RELAYMSG #deekchat " + irc_username + "/Dek").length;
		
		msg.text.split("\n").forEach(function(a){
			
			for(var i=0; i<a.length; i += limit){
				
				irc_client.send("RELAYMSG", "#deekchat", irc_username + "/Dek", a.slice(i, i + limit));
			}
		});
		
		for(var i=0; i<msg.files.length; i++){
			
			irc_client.send("RELAYMSG", "#deekchat", irc_username + "/Dek", msg.files[i]);
		}
	}
	
	switch(msg.text.toLowerCase()){
		
		case "hello rena":
		case "hi rena":
		case "what's up rena":
		case "whats up rena":
			send(msg, "hi " + user.name);
			return;
		
		case "/help":
			send(msg, "lmao newfag");
			return;
		
		case "fuck you rena":
			send(msg, "fuck you eagle");
			return;
	}
	
	/*
		shitty embed support
	*/
	if(link = msg.text.match(/https?:\/\/[^ \n\r'>]+/)){
		
		link = link[0];
		var download = null;
		var mimetype = null;
		
		try{
			
			if(chan4 = link.match(/https?:\/\/boards\.4chan\.org\/([A-z0-9]+\/thread\/[0-9]+)/i)){
				
				//
				// 4cunt embed
				//
				chan4 = chan4[1];
				
				// https://boards.4chan.org/g/thread/103852444#replyID
				// https://a.4cdn.org/po/thread/570368.json
				
				var res = await fetch("https://a.4cdn.org/" + chan4 + ".json");
				var json = await res.json();
				
				if(typeof json.posts[0] == "undefined"){
					
					var title = "404 thread ded";
					var description = "stupid fucking thread fuck this stupid site";
				}else{
					
					if(typeof json.posts[0].sub == "undefined"){
						
						var title = "No thread title";
					}else{
						
						var title = json.posts[0].sub;
					}
					var description = removehtml(json.posts[0].com);
				}
				
			}else if(reddit = link.match(/^https?:\/\/(?:old\.|www\.|m\.)?reddit\.com(\/r\/[^\/]+.*)/i)){
				
				//
				// reddit embed
				//
				
				reddit = reddit[1];
				
				var res = await fetch("https://www.reddit.com/oembed?url=" + reddit);
				var json = await res.json();
				
				var title = json.title;
				var description = "By " + json.author_name;
				
			}else if(tweet = link.match(/^https?:\/\/(?:www\.)?(?:twitter|x)\.com\/[A-Za-z0-9_]+\/status\/([0-9]+)/i)){
				
				//
				// twitter embed
				//
				
				tweet = tweet[1];
				var res = await fetch("https://cdn.syndication.twimg.com/tweet-result?id=" + tweet + "&token=5");
				var json = await res.json();
				
				var title = json.user.name + " (@" + json.user.screen_name + ") " + " on twatter";
				var description = "❤️ " + json.favorite_count + " • 💬 " + json.conversation_count + " • 📅 " + json.created_at + "\n" + json.text + " ‎ ";
				
				if(typeof json.video == "object"){
					
					download = json.video.variants[json.video.variants.length - 1].src;
					mimetype = ["video/mp4", "mp4", true];
				}else if(typeof json.photos[0].url == "string"){
					
					download = json.photos[0].url;
					mimetype = ["image/jpeg", "jpeg", true];
				}
				
			}else if(watch = link.match(/^https?:\/\/(?:www\.|m\.)?youtu(?:(?:\.be\/([A-z0-9_-]{11}))|be\.[a-z]+\/(?:watch.*(?:\?|&)v=|shorts\/|embed\/|e\/)([A-z0-9_-]{11}))/i)){
				
				//
				// youtube embed
				//
				
				if(typeof watch[1] !== "undefined"){
					
					watch = watch[1];
				}else if(typeof watch[2] !== "undefined"){
					
					watch = watch[2];
				}
				
				var res =
					await fetch(
						"https://www.googleapis.com/youtube/v3/videos?id=" + watch + "&key=" + yt_token + "&part=snippet,contentDetails,statistics,status"
					);
				
				var json = await res.json();
				
				var title = json.items[0].snippet.title;
				var description =
					"👁️ " + json.items[0].statistics.viewCount + " • 💬 " + json.items[0].statistics.commentCount + " • 📅 " + json.items[0].snippet.publishedAt + "\n" +
					json.items[0].snippet.description;
				
				// old shitcode that keeps erroring out
				/*var res = await fetch("https://youtube.com/oembed?url=/watch?v=" + watch);
				var json = await res.json();
				
				var title = json.title;
				var description = "By " + json.author_name;*/
				
			}else if(bsky = link.match(/^https?:\/\/bsky\.app\/profile\/([^\/]+)\/post\/([^?]+)/)){
				
				//
				// bluesky embed
				//
				
				var bsky_profile = bsky[1];
				var bsky_post = bsky[2];
				
				if(!bsky_profile.match(/^did:plc:/)){
										
					// resolve handle
					var handle = await(fetch("https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=" + bsky_profile));
					var handle_json = await handle.json();
					bsky_profile = handle_json.did;
				}
				
				var res =
					await(
						fetch(
							"https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread" +
							"?uri=" + encodeURIComponent("at://" + bsky_profile + "/app.bsky.feed.post/" + bsky_post) +
							//"?uri=at%3A%2F%2Fdid%3Aplc%3Aknkltoiapkt336rr335ztt3p%2Fapp.bsky.feed.post%2F3l3gymllsto2z" +
							"&depth=0" +
							"&parentHeight=0"
						)
					);
				
				var json = await res.json();
				
				var title = json.thread.post.author.displayName + " (@" + json.thread.post.author.handle + ") " + " on blueskeet";
				var description = "❤️ " + json.thread.post.likeCount + " • 💬 " + json.thread.post.replyCount + " • 📅 " + json.thread.post.indexedAt + "\n" + json.thread.post.record.text + " ‎ ";
				
				if(
					typeof json.thread.post.embed == "object" &&
					typeof json.thread.post.embed.images == "object" &&
					typeof json.thread.post.embed.images[0] == "object" &&
					typeof json.thread.post.embed.images[0].fullsize == "string"
				){	
					
					// found image
					download = json.thread.post.embed.images[0].fullsize;
					mimetype = ["image/jpeg", "jpeg", true];
				}
				else if(
					typeof json.thread.post.embed == "object" &&
					typeof json.thread.post.embed.thumbnail == "string"
				){
					
					// no video support, fuck you bsky for not giving me mp4 files
					download = json.thread.post.embed.thumbnail;
					mimetype = ["image/jpeg", "jpeg", true];
				}
			}else{
				
				//
				// link parser fallback
				//
				
				var res = await fetch(
					link,
					{
						headers: img_headers,
						signal: AbortSignal.timeout(5000)
					}
				);
				
				var headers_arr = [...res.headers];
				var headers = [];
				
				for(i=0; i<headers_arr.length; i++){
					
					headers[headers_arr[i][0]] = headers_arr[i][1];
				}
				
				if(
					!headers["content-type"] ||
					headers["content-type"].split(";")[0] != "text/html" ||
					(
						!headers["content-length"] &&
						parseInt(headers["content-length"]) > 10485760 // 10MB
					)
				){
					
					var text = "This thingy^ ";
					
					if(headers["content-type"]){
						
						var format = headers["content-type"].split("/");
						format = format[format.length - 1].split(";")[0];
						
						text += "is a " + format + " file ";
					}else{
						
						text += "is uhh... something.. err.. "
					}
					
					if(headers["content-length"]){
						var filesize = Math.round(parseInt(headers["content-length"]) / 1048576);
						
						text += "that weighs " + (Math.round(parseInt(headers["content-length"]) / 1048576)) + "MB!!";
					}else{
						
						text += "that uhh weighs something probably idk!!!";
					}
					
					text += " lol thats all i know fuck you";
					
					send(msg, text);
					return;
				}
				
				// cancerous code ahead
				
				var buffer = await res.buffer();
				
				const parser = cheerio.load(buffer);
				
				// get title
				var title = "Unknown title lol, shit website";
				var tag_title = parser("title");
				var tag_name_og_title = parser("meta[name='og:title']");
				var tag_property_og_title = parser("meta[property='og:title']");
				
				if(tag_name_og_title.length !== 0){ title = tag_name_og_title.attr("content"); }
				if(tag_property_og_title.length !== 0){ title = tag_property_og_title.attr("content"); }
				if(tag_title.length !== 0){ title = tag_title.text(); }
				
				
				// get description
				var description = "Unknown description lol, stupid website do not click";
				var tag_name_description = parser("meta[name='description']");
				var tag_name_og_description = parser("meta[name='og:description']");
				var tag_property_description = parser("meta[property='description']");
				var tag_property_og_description = parser("meta[property='og:description']");
				
				if(typeof tag_name_description.attr("content") != "undefined"){description = tag_name_description.attr("content"); }
				if(typeof tag_name_og_description.attr("content") != "undefined"){description = tag_name_og_description.attr("content"); }
				if(typeof tag_property_description.attr("content") != "undefined"){description = tag_property_description.attr("content"); }
				if(typeof tag_property_og_description.attr("content") != "undefined"){description = tag_property_og_description.attr("content"); }
				
				if(typeof tag_name_description.attr("value") != "undefined"){description = tag_name_description.attr("value"); }
				if(typeof tag_name_og_description.attr("value") != "undefined"){description = tag_name_og_description.attr("value"); }
				if(typeof tag_property_description.attr("value") != "undefined"){description = tag_property_description.attr("value"); }
				if(typeof tag_property_og_description.attr("value") != "undefined"){description = tag_property_og_description.attr("value"); }
				
			}
			
			var text = "";
			
			description = description.substr(0, 256).split("\n");
			
			for(var i=0; i<description.length; i++){
				
				text += "\n>" + description[i];
			}
			
			if(download !== null){
				
				try{
					
					var image = await fetch(
						download,
						{
							headers: img_headers,
							signal: AbortSignal.timeout(3000)
						}
					);
					
					var download = await image.buffer();
					
				}catch(err){
					
					download = null;
				}
			}
			
			send(msg, title + text, download, mimetype);
		}catch(err){
			
			send(msg, "Stupid retarded website alert!!!! I got an error accessing this shit...\n>" + err);
		}
		
		return;
	}
	
	
	/*
		Commands
	*/
	var regex = msg.text.match(/^\.([^ ]+)(?: (.*))?/);
			
	if(
		regex === null ||
		typeof regex[1] == "undefined"
	){
		return;
	}
	
	var cmd = regex[1];
	
	if(typeof regex[2] != "undefined"){
		var value = regex[2];
	}else{
		
		var value = "";
	}
	
	switch(cmd.toLowerCase()){
		
		case "raid":
			if(
				(
					user.id === 13 || // me lol
					user.id === 14 || // aves
					user.id === 8 || // aves again
					user.id === 30 || // eagle
					user.id === 2 // deek
				) === false
			){
				
				break;
			}
			
			if(raid === false){
				
				raid = true;
				send(msg, "Ignoring all messages from now on, fuck you lol!!");
			}else{
				
				raid = false;
				send(msg, "Now listening to messages!!!");
			}
			break;
		
		case "rc":
		case "reconnect":
			if(user.id !== 13){
				
				break;
			}
			
			send(msg, "brb");
			
			setTimeout(function(){
				
				fs.utimesSync(__filename, Date.now(), Date.now());
			}, 1000);
			break;
		
		case "ping":
			send(msg, "Pong!!!!");
			break;
		
		case "dice":
		case "roll":
		case "d":
		case "r":
			value = parseInt(value);
			if(isNaN(value)){
				
				value = 6;
			}
			
			if(value <= 1){
				
				send(
					msg,
					"Rolling a " + value + " faced dice...\nWait what?? " +
					"You fucking shit, a black hole just appeared!!!!!! Oh my fucking shit!!!!!"
				);
				return;
			}
			
			send(
				msg,
				"Rolling a " + value + " faced dice...\nAnd you got " +
				(
					Math.floor(Math.random() * value + 1)
				) + "!!!"
			);
			break;
		
		case "mumble":
		case "m":
			var port = 64738;
			
			if(value == ""){
				
				var ip = "lolcat.ca";
			}else{
			
				var value = value.split(":");
				
				if(value.length >= 2){
					
					var port = value[1];
				}
				
				var ip = value[0];
			}
			
			try{
				var res = await fetch("https://lolcat.ca/api/mumble/?ip=" + ip + "&port=" + port);
				var res_get = await res.json();
				
				if(res_get.status != "ok"){
					
					send(msg, "An error occured... " + res_get.status);
					return;
				}
				
				res_get = res_get.server;
				
				var servername = res_get.name === null ? res_get.domains[0] : res_get.name;
				var website = res_get.website === null ? "No website available" : res_get.website;
				
				send(
					msg,
					">Server information for " + servername + ":\n" +
					"Known domains => " + res_get.domains.join(", ") + "\n" +
					"Ping => " + res_get.ping + "\n" +
					"Users => " + res_get.online + "/" + res_get.max + "\n" +
					"Bandwidth => " + res_get.bandwidth + "\n" +
					"Country => " + res_get.country.name + " (" + res_get.country.code + ")\n" +
					"Version => " + res_get.version + "\n" +
					"Website => " + website
				);
				
			}catch(err){
				
				send(msg, "lolcat.ca api sucks and is offline and dead");
			}
			break;
		
		case "prop":
			try{
				var res = await fetch("https://www.hamqsl.com/solarn0nbh.php");
				var res_get = await res.buffer();
				
				send(msg, "Good news: the sun has not yet exploded", res_get);
			}catch(err){
				
				send(msg, "Could not fetch image!!! FUCK!!!!111\n" + err);
			}
			break;
		
		case "img":
		case "im":
		case "i":
			
			if(value.trim() == ""){
				
				send(msg, "You need to search for something cum licker!!!");
				return;
			}
			
			global_page = 1;
			
			try{
				var res = await fetch(instance + "/api/v1/images?s=" + encodeURIComponent(value) + "&nsfw=" + (ddg_mod ? "no" : "yes") + "&scraper=" + scraper);
				var res_get = await res.json();
			
				if(res_get.status != "ok"){
				
					send(msg, "Shit!!!!! 4get sucks balls and returned this error: " + res_get.status);
					break;
				}
			
				if(res_get.image.length === 0){
				
					send(msg, "No results found for " + value + "!!");
					break;
				}
			}catch(error){
				
				send(msg, "4get returned invalid response!!!!!! piece of fuckgin shit!!!11!111");
				break;
			}
			
			results_cache = res_get.image;
			is_pp_xeno = false;
			
			await serveddg(msg, res_get.image, 1);
			break;
		
		case "scraper":
			switch(value.toLowerCase()){
				
				case "ddg":
				case "yandex":
				case "brave":
				case "google":
				case "google_cse":
				case "startpage":
				case "qwant":
				case "yep":
				case "solofield":
				case "imgur":
				case "ftm":
					scraper = value.toLowerCase();
					send(msg, "Scraper set to " + scraper + "!!");
					break;
				
				default:
					send(msg, ">Current scraper: " + scraper +"\nChoices are: ddg, yandex, brave, google, google_cse, startpage, qwant, yep, solofield, imgur, ftm");
					break;
			}
			break;
		
		case "boobs":
		case "boob":
		case "nsfw":
			switch(value.toLowerCase()){
				
				case "yes":
				case "y":
				case "1":
				case "yea":
				case "true":
				case "yup":
					send(msg, "I will now show boobs when using .img");
					ddg_mod = false;
					break;
				
				case "no":
				case "n":
				case "nae":
				case "false":
				case "no":
				case "off":
				case "nope":
				case "nah":
				case "0":
					send(msg, "I will now prevent eagle from jacking off in main chat");
					ddg_mod = true;
					break;
				
				default:
					
					var active = ddg_mod ? "NOT " : "";
					send(msg, "I will " + active + "show boobs. Use .boobs <yes:no> to toggle");
					break;
			}
			break;
		
		case "page":
		case "p":
			value = parseInt(value);
			if(
				isNaN(value) ||
				value == ""
			){
				
				send(msg, "You didn't enter a valid number you dumbass!!");
				return;
			}
			
			if(results_cache.length === 0){
				
				send(msg, "Search buffer doesn't contain any links you horse fucker!!");
				return;
			}
			
			if(
				value > results_cache.length ||
				value <= 0
			){
				
				send(msg, "Page offset must be between 1 and " + results_cache.length + " you dumbass!!");
				return;
			}
			
			global_page = value;
			
			if(is_pp_xeno){
				
				await fetch_xeno(results_cache, value, msg);
				break;
			}
			
			await serveddg(msg, results_cache, value);
			break;
		
		case "pp":
			global_page++;
			
			if(global_page > results_cache.length){
				
				send(msg, "You reached the end of results stupid kike sucker!!");
				return;
			}
			
			if(is_pp_xeno){
				
				await fetch_xeno(results_cache, global_page, msg);
				break;
			}
			
			await serveddg(msg, results_cache, global_page);
			break;
		
		case "youtube":
		case "jewtube":
		case "yt":
			if(value.trim() == ""){
				
				send(msg, "You need to search for something cum licker!!!");
				break;
			}
			
			try{
				var res = await fetch(instance + "/api/v1/videos?s=" + encodeURIComponent(value));
				var res_get = await res.json();
			
				if(res_get.status != "ok"){
				
					send(msg, "Shit!!!!! 4get sucks balls and returned this error: " + res_get.status);
					break;
				}
			
				if(res_get.video.length === 0){
				
					send(msg, "No results found for " + value + "!!");
					break;
				}
			}catch(error){
				
				send(msg, "4get returned invalid response!!!!!! piece of fuckgin shit!!!11!111");
			}
			
			var text = [];
			
			for(var i=0; i<res_get.video.length; i++){
				
				if(i === 3){
					break;
				}
				
				text.push(
					">Duration: " + (new Date(res_get.video[i].duration * 1000).toISOString().slice(11, 19)) +
					" • Views: " + (new Intl.NumberFormat().format(res_get.video[i].views)) +
					" • Author: " + res_get.video[i].author.name +
					"\n" + res_get.video[i].title +
					"\n▶ " + res_get.video[i].url
				);
			}
			
			send(msg, "Search results for \"" + value + "\"\n\n" + text.join("\n\n"));
			break;
		
		case "x":
		case "xeno":
		case "xeno-canto":
			if(value.trim() == ""){
				
				send(
					msg,
					"You must give me a search term you autistic cocksucker with no legs!!\nExample: .x owl"
				);
				break;
			}
			
			var res = await fetch("https://xeno-canto.org/api/2/recordings?query=" + encodeURIComponent(value));
			var res_get = await res.json();
			
			if(res_get.recordings.length === 0){
				
				send(
					msg,
					"No recordings found for \"" + value + "\"!!!!!"
				);
				break;
			}
			
			global_page = 1;
			is_pp_xeno = true;
			
			// filter out restricted results
			results_cache = [];
			
			for(var k=0; k<res_get.recordings.length; k++){
				
				if(
					typeof res_get.recordings[k].file == "string" &&
					res_get.recordings[k].file != ""
				){
					
					results_cache.push(res_get.recordings[k]);
				}
			}
			
			if(results_cache.length === 0){
				
				send(
					msg,
					"This species has been raped to death by filthy h*mans and is not available for listening"
				);
				break;
			}
			
			await fetch_xeno(results_cache, 1, msg);
			break;
		
		case "fortune":
		case "f":
			send(msg, fortunes[Math.floor(Math.random() * fortunes.length)]);
			break;
		
		case "list":
		case "l":
			if(mc_bridge_enabled === false){
				
				send(msg, "Deekcraft bridge is disabled");
				break;
			}
			
			if(mc_logged_in === false){
				
				send(msg, "Deekcraft is offline");
				break;
			}
			
			send(
				msg,
				">Players online on deekcraft:\n" +
				(Object.keys(mc_bot.players).filter(player => mc_bot.players[player].ping !== undefined))
				.join("\n")
			);
			break;
		
		//
		// Image manipulation tools
		//
		case "greyify":
		case "grayify":
		case "grey":
		case "gray":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image),
						{animated: true}
					).greyscale()
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "invertify":
		case "invert":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image),
						{animated: true}
					).flatten(
						{
							background: "#282828"
						}
					).negate()
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "flatify":
		case "flatten":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image),
						{animated: true}
					).flatten(
						{
							background: "#282828"
						}
					)
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "jpegify":
		case "jpgify":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image)
					).jpeg(
						{
							mozjpeg: true
						}
					)
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "pngify":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image)
					).png()
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "rotatify":
		case "rotate":
			value = parseInt(value);
			
			if(
				isNaN(value) ||
				value == ""
			){
				
				value = 90;
			}
			
			if(value <= 0){
				
				value += 360;
			}
			
			if(value >= 1080){
				
				send(msg, "Fuck you the image can't spin that much!!");
				break;
			}
			
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image)
					).rotate(value)
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			
			break;
		
		case "flip":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image),
						{animated: true}
					).flip()
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
			
		case "flop":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image),
						{animated: true}
					).flop()
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "deepfry":
			try{
				send(
					msg,
					"",
					await sharp(
						await fetch_image(last_image)
					)
					.resize(
						{
							width: 450,
							fit: "inside"
						}
					)
					.jpeg({ quality: 5 })
					.sharpen({ sigma: 3 })
					.modulate({
						saturation: 3.0,
						brightness: 1.2,
						contrast: 1.5,
					})
					.toBuffer()
				);
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			break;
		
		case "caption":
		case "cap":
			
			if(value.trim() == ""){
				
				send(msg, "you need to give me a caption you moron");
				return;
			}
			
			try{
				
				var image =
					await sharp(
						await fetch_image(last_image)
					).png()
					.flatten(
						{
							background: "#282828"
						}
					)
					.toBuffer();
			}catch(error){
				
				send(msg, "go open gimp you lazy fuck");
			}
			
			var image = await(loadImage(image));
			var canvas = createCanvas(image.width, image.height);
			
			const ctx = canvas.getContext("2d");
			ctx.drawImage(image, 0, 0, image.width, image.height);
			
			var fontsize = Math.floor(image.width * 0.08);
			ctx.font = fontsize + "px Impact";
			ctx.fillStyle = "white";
			ctx.lineJoin = "round";
			ctx.lineCap = "round";
			ctx.strokeStyle = "black";
			ctx.lineWidth = image.width * 0.01;
			ctx.textAlign = "center";
			ctx.textBaseline = "bottom";
			
			value = value.split("|");
			wrap(value[0], false);
			
			if(typeof value[1] != "undefined"){
				
				wrap(value[1], true);
			}
			
			function wrap(text, is_bottom){
								
				var just_pushed_line = false;
				var words = text.split(" ");
				var lines = [];
				var line = "";
				var line_tmp = "";
				
				for(var i=0; i<words.length; i++){
					
					var word_width = ctx.measureText(words[i]).width;
					
					if(word_width >= image.width){
						
						if(line != ""){
							
							lines.push(line.trim());
							line = "";
						}
						
						line_tmp = "";
						
						// increment letters till we go over the limit
						var letters = words[i].split("");
						
						for(var k=0; k<letters.length; k++){
							
							line_tmp += letters[k];
							
							if(ctx.measureText(line_tmp).width >= image.width){
								
								lines.push(line.trim());
								line = letters[k];
								line_tmp = letters[k];
							}else{
								
								line += letters[k];
							}
						}
						
						// output remainder
						lines.push(line);
						line = "";
						line_tmp = "";
						
					}else{
						
						// wrap word normally
						line_tmp += words[i] + " ";
						
						if(ctx.measureText(line_tmp.trim()).width >= image.width){
							
							// push line
							lines.push(line.trim());
							line = "";
							line_tmp = words[i] + " ";
						}
						
						line += words[i] + " ";
					}
				}
								
				// output remainder
				if(line != ""){
					lines.push(line);
					line = "";
					line_tmp = "";
				}
				
				var tmp_metrics = ctx.measureText(lines[i]);
				var space = (tmp_metrics.actualBoundingBoxAscent + tmp_metrics.actualBoundingBoxDescent) / 3;
				
				if(is_bottom){
					
					height = image.height - space;
					lines = lines.reverse();
				}else{
					
					height = space;
				}
				
				for(var i=0; i<lines.length; i++){
					
					var metrics = ctx.measureText(lines[i]);
					
					if(is_bottom){
						
						if(i !== 0){
							
							height -= space + metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
						}
					}else{
						
						height += space + metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
					}
					
					ctx.strokeText(lines[i], image.width / 2, height);
					ctx.fillText(lines[i], image.width / 2, height);
				}
			}
			
			send(msg, "", canvas.toBuffer("image/jpeg"));
			break;
		
		case "help":
		case "h":
			if(value == "image"){
				
				send(
					msg,
					"== image manipulation help menu ==\n\n" +
					".help image => lol idk\n" +
					".caption => fr fr\n" +
					".rotatify => Ro ta te\n" +
					".flip => gucci flip flops\n" +
					".flop => make it kick rocks\n" +
					".deepfry => Make images look better\n" +
					".invertify => Make da colors crayyyzaayy\n" +
					".greyify => Turn image grey and depressing\n" +
					".flatify => Remove dogshit transparency\n" +
					".jpegify => Convert to jay peg\n" +
					".pngify => Convert to pee n j\n"
				);
				break;
			}
			
			send(
				msg,
				"== help menu ==\n\n" +
				".help => lol idk\n" +
				".help image => Image manipulation help\n" +
				".img/.im/.i => search duckduckgo for birds\n" +
				".boobs <yes:no> => Boobs?\n" +
				".page/.p => get page offset for image search\n" +
				".pp => get next page\n" +
				".fortune/.f => fortune teller cause cynic cant code\n" +
				".yt/.youtube => search the jewish propaganda catalog\n" +
				".x/xeno => search for bird sounds\n" +
				".roll/.r => Because deekchat was too slow\n" +
				".mumble/.m <[optional]ip:port> => check if lolcat died\n" +
				".prop => check if the sun has exploded or smth\n" +
				".list => players online on deekcraft\n" +
				".ping => spam deekchat"
			);
			break;
	}
});

deek.on("close", function(channel){
	
	console.log("It's over. Disconnected from websocket. Attempting reconnection in 10 seconds");
	
	fs.utimesSync(__filename, Date.now(), Date.now());
});

deek.on("error", function(message){
	
	console.error("Serber error: " + message + ". Attempting reconnection in 10 seconds");
	
	fs.utimesSync(__filename, Date.now(), Date.now());
});

// connect to serber
handshake(name, password);