shuffyiosys / RPH Log Extractor

// ==UserScript==
// @name       RPH Log Extractor
// @namespace  https://openuserjs.org/scripts/shuffyiosys/RPH_Log_Extractor
// @match      https://chat.rphaven.com/
// @version    1.0.1
// @description Extracts logs from RPH.
// @copyright  (c)2021 shuffyiosys@github
// @grant      none
// @license    MIT 
// ==/UserScript==

const VERSION_STRING = "1.0.1"

let request
let logDb
let store
let logDbDump = {}
let logEntries = {}
let namesToIds = []


$(function () {
	const labelStyle = `padding-left: 0px; text-align:justify; display:inline-block; cursor:default;`
	const spacingStyle = `width: 300px;`
	const logContentStyle = `border:#888 solid 1px;border-radius:10px padding-bottom:12px;margin-bottom:12px; width: 100%; height: 720px; overflow: auto;`
	let html = {
		'tabId': 'log-extractor',
		'tabName': 'Extractor',
		'tabContents': '<h3>Log Extractor</h3><br/>' +
			'<p><strong>Version: ' + VERSION_STRING + '</strong>' +
			' | <a href="https://openuserjs.org/install/shuffyiosys/RPH_Log_Extractor.user.js" target="_blank">Install the latest version</a>' +
			' | <a href="https://openuserjs.org/scripts/shuffyiosys/RPH_Log_Extractor" target="_blank">OpenUserJs page</a>' +
			' | <a href="https://discord.gg/HBEaGjs" target="_blank">Discord channel</a>' + 
			'<br />' +
			'<p><strong>Note:</strong> Depending on how big your log file is, this ' + 
			'may cause the appearance of the page freezing</p><br />' +
			'<button style="float:left;" type="button" id="getLogsButton">Get logs</button><br /><br />' +
			'<div id="log-window">' +
			'	<h4>Logs</h4>' +
			`	<label style="${labelStyle} ${spacingStyle}">Your name </label><select style="${spacingStyle}" id="yourNameDropList"></select>` + 
			`	<a id="yourProfileLink" style="margin-left: 10px; display: none;" target="_blank">See profile</a>` +
			`<br /><br />` +
			`	<label style="${labelStyle} ${spacingStyle}">Other name </label><select style="${spacingStyle}" id="otherNameDropList"></select>` +
			`	<a id="otherProfileLink" style="margin-left: 10px; display: none;" target="_blank">See profile</a>` +
			`<br /><br />` +
			`	<a id="downloadAllLink" download="logAll.txt" style="display: none;">Download all logs</a>` +
			'<br />' +
			'	<a id="downloadlink" download="log.txt" style="display: none;">Download this log</a>' + 
			`	<div style="${logContentStyle}" id="log-contents"></div>` +
			'</div>'
	}
	let $settingsDialog = $('#settings-dialog')

	if (account.props.accid != 0) {
		request = indexedDB.open(`${account.props.accid}`)
		
		request.onsuccess = function(event) {
			logDb = event.target.result
			console.log('Connection to DB success!')
		}
		
		account.users.forEach(function (userObj) {
			namesToIds.push(userObj.props.name)
		})
	}

	socket.on('account-users', () => {
		request = indexedDB.open(`${account.props.accid}`)

		request.onsuccess = function(event) {
			logDb = event.target.result
			console.log('Connection to DB success!')
		}
		
		account.users.forEach(function (userObj) {
			namesToIds.push(userObj.props.name)
		})
	})

	$('#settings-dialog .inner ul.tabs').append('<h3>Log Extractor</h3>')
	$('#settings-dialog .inner ul.tabs')
		.append('<li><a href="#' + html.tabId + '">' + html.tabName +
			'</a></li>')
	$('#settings-dialog .inner div.content div.inner')
		.append('<div id="' + html.tabId + '" style="display: none;">' +
			html.tabContents + '</div>')

	$settingsDialog.find('.tabs a[href="#' + html.tabId + '"]').click(
		function (ev) {
			$settingsDialog.find('.content .inner > div').hide()
			$settingsDialog.find($(this).attr('href')).show()
			ev.preventDefault()
		})
	
	$('#getLogsButton').click(() => {
		$('#getLogsButton').html("Getting logs...")
		$('#getLogsButton').prop("disabled", true)
		getLogs()
	})

	$('#yourNameDropList').change(updateDropdownLists)

	$('#otherNameDropList').change(() => {
		const otherName = $('#otherNameDropList option:selected').val()
		$('#otherProfileLink').attr('href', `https://profiles.rphaven.com/${otherName}`)
		fillInLogContents()
	})
})

function updateDropdownLists() {
	const username = $('#yourNameDropList option:selected').val()
	const otherNames = Object.keys(logEntries[username]).sort()
	$('#otherNameDropList').empty()

	otherNames.forEach((name) => {
		addToDroplist(name, name, "#otherNameDropList")
	})

	const otherName = $('#otherNameDropList option:selected').val()
	$('#yourProfileLink').attr('href', `https://profiles.rphaven.com/${username}`)
	$('#otherProfileLink').attr('href', `https://profiles.rphaven.com/${otherName}`)
	$('#yourProfileLink').show()
	$('#otherProfileLink').show()
	fillInLogContents()
}

function fillInLogContents() {
	const username = $('#yourNameDropList option:selected').val()
	const otherName = $('#otherNameDropList option:selected').val()
	const entry = logEntries[username][otherName]

	$('#log-contents').empty()
	for (let timestamp in entry) {
		$('#log-contents').append(`<p>${createTimestamp(parseInt(timestamp))} ${entry[timestamp].auth}: ${entry[timestamp].msg}</p>`)
	}

	let link = document.getElementById('downloadlink');
	link.href = makeTextFile($('#log-contents').html().replace(/<p>/g, '').replace(/<\/p>/g, '\n'))
	$('#downloadlink').attr('download', `${username}-${otherName}-log.txt`)
	link.style.display = 'block'
}

function getLogs() {
	logEntries = {}
	logDbDump = {}
	$('#log-contents').empty()
	$('#otherNameDropList').empty()
	$('#yourNameDropList').empty()
	store = logDb.transaction(['msgs']).objectStore('msgs')
	store.openCursor().onsuccess = function(event) {
		var cursor = event.target.result
		if (cursor) {
			var logEntry = cursor.value
			logDbDump[cursor.key.join()] = cursor.value
			getUserById(logEntry.otherid)
			.then((user) =>{
				logEntry.other_name = user.props.name
				return getUserById(logEntry.fromid)
			})
			.then((user) =>{
				logEntry.from_name = user.props.name
				return getUserById(logEntry.userid)
			})
			.then((user) =>{
				logEntry.user_name = user.props.name

				if (user.props.name in logEntries === false) {
					logEntries[user.props.name] = {}
				}
				if (logEntry.other_name in logEntries[user.props.name] === false) {
					logEntries[user.props.name][logEntry.other_name] = {}
				}
				logEntries[user.props.name][logEntry.other_name][logEntry.date] = {
					auth: logEntry.from_name,
					msg: logEntry.msg
				}

				return Promise.resolve()
			})

			cursor.continue()
		}
		else {
			let usernames = getSortedNames()
			$('#getLogsButton').html("Got the logs!")
			for (let name in usernames) {
				addToDroplist(name, name, "#yourNameDropList")
			}
			updateDropdownLists()

			setTimeout(() => {
				$('#getLogsButton').html("Get logs")
				$('#getLogsButton').prop("disabled", false)
			}, 10000)

			let link = document.getElementById('downloadAllLink');
			link.href = makeTextFile(`${JSON.stringify(logDbDump, null, 4)}`)
			$('#downloadAllLink').attr('download', `all-logs.txt`)
			link.style.display = 'block'
		}
	}
}

function addToDroplist(value, label, droplist) {
	let droplist_elem = $(droplist)
	droplist_elem.append($('<option>', {
		value: value,
		text: label
	}))
}

function getSortedNames() {
	let namesToIds = {}
	account.users.forEach(function (userObj) {
		namesToIds[userObj.props.name] = userObj.props.id
	})

	let sorted = []
	for (let key in namesToIds) {
		sorted[sorted.length] = key
	}
	sorted.sort()

	let tempDict = {}
	for (let i = 0; i < sorted.length; i++) {
		tempDict[sorted[i]] = namesToIds[sorted[i]]
	}
	namesToIds = tempDict
	return namesToIds
}

function createTimestamp(time) {
	const timestamp = new Date(time)
	const dateString = timestamp.toLocaleDateString(navigator.language)
	const delim = dateString.indexOf('/', 2)
	const timeString = timestamp.toTimeString().substring(0,5)
	return `${dateString.substring(0, delim)} ${timeString}`
}

jQuery.fn.selectText = function(){
	this.find('input').each(function() {
		if($(this).prev().length == 0 || !$(this).prev().hasClass('p_copy')) { 
			$('<p class="p_copy" style="position: absolute; z-index: -1;"></p>').insertBefore($(this))
		}
		$(this).prev().html($(this).val())
	})
	var doc = document
	var element = this[0]
	console.log(this, element)
	if (doc.body.createTextRange) {
		var range = document.body.createTextRange()
		range.moveToElementText(element)
		range.select()
	} else if (window.getSelection) {
		var selection = window.getSelection()
		var range = document.createRange()
		range.selectNodeContents(element)
		selection.removeAllRanges()
		selection.addRange(range)
	}
}

function makeTextFile (text) {
	let textFile = null
	let data = new Blob([text], {type: 'text/plain'});

	// If we are replacing a previously generated file we need to
	// manually revoke the object URL to avoid memory leaks.
	if (textFile !== null) {
		window.URL.revokeObjectURL(textFile);
	}

	textFile = window.URL.createObjectURL(data);

	return textFile;
};