<commit_before># Set working directory
ResourceDir = '/projects/kentchat/'

# Import modules
Modules =
	fs: require('fs')
	https: require('https')
	wss: require('websocket').server
	sha: require('sha1')
	aes: require('aes256')
	readline: require('readline')

# Initialize modules
WebServer = https.createServer
	key: fs.readFileSync ResourceDir + 'privkey.pem'
	cert: fs.readFileSync ResourceDir + 'cert.pem'
, (req, res) ->
	res.end 'You were not supposed to visit here with a browser. Use a KentChat client!'
WSServer = new Modules.wss
	httpServer: WebServer

# Declare options and variables
Options =
	verbose: true
	phRand: [97,123]
	phLength: 10

userRegistry = []
groupRegistry = []
loggedUsers = []
hostBlacklist = JSON.parse Modules.fs.readFileSync ResourceDir + "blacklist.json"

# Declare object types using classes
UserPowerLevels =
	guest: -100
	privileged: -50
	developer: 0
	admin: 50
	owner: 2000

class User
	constructor: (@id, @powerLevel, @auth) ->
		@connection = null
		@cipher = Modules.aes.createCipher @auth

	isQualifiedFor: (powerLevel) -> powerLevel <= @powerLevel
	toString: () -> "[User id=#{@id} powerLevel=#{@powerLevel}]"
	onConnect: (webSocketConnection) -> @connection = webSocketConnection
	onDisconnect: () -> @connection = null if @connection?
	authenticate: (pwd) -> Modules.sha pwd is @auth

	send: (msg) ->
		throw Error "#{@toString()} Message undefined!" if not msg?
		@connection.send @cipher.encrypt msg

	isConnected: () -> @connection isnt null

	toJSON: () ->
		id: @id
		powerLevel: @powerLevel
		auth: @auth

class Group
	constructor: (@id, member, passphrase) ->
		@members = member or []
		@passphrase = if passphrase? \
			then passphrase else \
			(String.fromCharCode(Math.floor(Math.random() * \
			(Options.phRand[1] - Options.phRand[0])) + Options.phRand[0]) for _ in [0..Options.phLength]).join ''

	addUser: (user) ->
		@members.push user
		loggedUsers.push @id

	removeUser: (user) -> @members.push user
	toString: () -> "[Group id=#{@id} length-of-members=#{@members.length}]"

	send: (msg) ->
		throw Error "#{@toString()} Message undefined!" if not msg?
		@connection.send Modules.aes.encrypt @passphrase, msg

	toJSON: () ->
		id: @id
		passphrase: <PASSWORD>
		members: x.id for x in @members

# Set up common functions
getUserById = (id) ->
	for user in userRegistry
		return user if user.id is id

getGroupById = (id) ->
	for group in groupRegistry
		return group if group.id is id

User.fromJSON = (jsonExpression) -> new User(jsonExpression.id, jsonExpression.powerLevel, jsonExpression.auth)
Group.fromJSON = (jsonExpression) -> new Group(jsonExpression.id, getUserById userExp for userExp in jsonExpression.members, jsonExpression.passphrase)

# Set up variables
userRegistry.push User.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "users.json"
groupRegistry.push Group.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "groups.json"


containsUser = (id) -> (getUserById id)?
containsGroup = (id) -> (getGroupById id)?

# Event handler modules
eventHandlers = [
	{
		expectedType: "dm"
		expectedArgs: ["dest"]
		onMessage: (msg, source) ->

	}
]
setupHandlersForUser = (conn, id) ->


# Login procedure
WSServer.on 'request', (req) ->
	if req.host in hostBlacklist
		req.reject 200, 'You are on the KentChat blacklist!'
	else
		conn = req.accept null, req.origin
		conn.on 'message', (message) ->
			if message.type is 'binary'
				conn.drop 1007, 'expected login data to be utf8, received binary'
			else if not (message.id? and message.auth?)
				conn.drop 1007, 'expected msg.id and msg.auth, missing'
			else
				user = getUserById message.id
				if not user?
					conn.drop 1003, "unknown userID: #{message.id}"
					return
				if user.isConnected()
					conn.drop 1003, "user #{message.id} is already logged in"
					return
				if not user.authenticate message.auth
					conn.drop 1003, 'bad password'
					return
				user.onConnect conn<commit_msg>lots of methods<commit_after># Set working directory
ResourceDir = '/projects/kentchat/'

# Import modules
Modules =
	fs: require('fs')
	https: require('https')
	wss: require('websocket').server
	sha: require('sha1')
	aes: require('aes256')
	readline: require('readline')

# Initialize modules
WebServer = https.createServer
	key: fs.readFileSync ResourceDir + 'privkey.pem'
	cert: fs.readFileSync ResourceDir + 'cert.pem'
, (req, res) ->
	res.end 'You were not supposed to visit here with a browser. Use a KentChat client!'
WSServer = new Modules.wss
	httpServer: WebServer

# Declare options and variables
Options =
	verbose: true
	phRand: [97,123]
	phLength: 10

userRegistry = []
groupRegistry = []
messageRegistry = []
loggedUsers = []
hostBlacklist = JSON.parse Modules.fs.readFileSync ResourceDir + "blacklist.json"

# Declare object types using classes
UserPowerLevels =
	guest: -100
	privileged: -50
	developer: 0
	admin: 50
	owner: 2000

class KcIdentifier
	constructor: (@id) ->
		
class User extends KcIdentifier
	constructor: (id, @powerLevel, @auth) ->
		super id
		@connection = null
		@cipher = Modules.aes.createCipher @auth

	isQualifiedFor: (powerLevel) -> powerLevel <= @powerLevel
	toString: () -> "[User id=#{@id} powerLevel=#{@powerLevel}]"
	onConnect: (webSocketConnection) -> 
		@connection = webSocketConnection
		loggedUsers.push this
	onDisconnect: () -> @connection = null if @connection?
	authenticate: (pwd) -> Modules.sha pwd is @auth

	send: (msg) ->
		throw Error "#{@toString()} Message undefined!" if not msg?
		@connection.send @cipher.encrypt msg

	isConnected: () -> @connection isnt null

	toJSON: () ->
		id: @id
		powerLevel: @powerLevel
		auth: @auth

class Group extends KcIdentifier
	constructor: (id, @members = [], passphrase) ->
		super id
		@passphrase = if passphrase? \
			then passphrase else \
			(String.fromCharCode(Math.floor(Math.random() * \
			(Options.phRand[1] - Options.phRand[0])) + Options.phRand[0]) for _ in [1..Options.phLength]).join ''

	addUser: (user) -> @members.push user
	removeUser: (user) -> @members.pop user
	toString: () -> "[Group id=#{@id} length-of-members=#{@members.length}]"

	send: (msg) ->
		throw Error "#{@toString()} Message undefined!" if not msg?
		@connection.send Modules.aes.encrypt @passphrase, msg

	toJSON: () ->
		id: @id
		passphrase: @passphrase
		members: x.id for x in @members
			
# Set up common functions
getUserById = (id) ->
	for user in userRegistry
		return user if user.id is id

getGroupById = (id) ->
	for group in groupRegistry
		return group if group.id is id

class Message extends KcIdentifier
	DIRECT_MESSAGE: 0
	GROUP_MESSAGE: 1
	
	constructor: (@type, @sourceId, @destId, @message, @read = no) ->
		if not (@type in [@DIRECT_MESSAGE..@GROUP_MESSAGE])
			throw Error "Unknown message type: #{@type}"
		@id = (String.fromCharCode(Math.floor(Math.random() * \
			(Options.phRand[1] - Options.phRand[0])) + Options.phRand[0]) for _ in [0..19]).join ''
	
	toJSON: () ->
		type: @type
		sourceId: @sourceId
		destId: @destId
		message: @message
		read: @read


User.fromJSON = (jsonExpression) -> new User(jsonExpression.id, jsonExpression.powerLevel, jsonExpression.auth)
Group.fromJSON = (jsonExpression) -> new Group(jsonExpression.id, getUserById userExp for userExp in jsonExpression.members, jsonExpression.passphrase)
Message.fromJSON = (je) -> new Message je.type, je.sourceId, je.destId, je.message, je.read

# Set up variables and functions that use the defined classes
userRegistry.push User.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "users.json"
groupRegistry.push Group.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "groups.json"
messageRegistry.push Message.fromJSON json for json in JSON.parse Modules.fs.readFileSync ResourceDir + "messages.json"

containsUser = (id) -> (getUserById id)?
containsGroup = (id) -> (getGroupById id)?
messagesFrom = (id) -> (msg for msg in messageRegistry when msg.sourceId is id)
messagesToUser = (id) -> (msg for msg in messageRegistry when msg.type is Message.DIRECT_MESSAGE and msg.destId is id)
groupIDs = () -> (group.id for group in groupRegistry)
userIDs = () -> (user.id for group in groupRegistry)
User::unreadMessages = () -> (msg for msg in messagesToUser @id when not msg.read)
User::sentMessages = () -> messagesFrom @id
User::onRead = (message) -> (message.read = yes) if message.type is Message.DIRECT_MESSAGE and message.destId is @id and not message.read
User::sendMessage = (destId, message, type) -> messageRegistry.push new Message type, @id, destId, message
User::tell = (data) -> @connection.send data
Group::getMessages = () -> (msg for msg in messageRegistry when msg.type is Message.GROUP_MESSAGE and msg.destId is @id)

# Event handler modules
eventHandlers = [
	{
		expectedType: "msg"
		expectedArgs: ["dest", "message", "toUser"]
		onMessage: (msg, source) ->
			return "failure: target user or group does not exist" if (msg.toUser and not containsUser msg.dest) or (not msg.toUser and not containsGroup msg.dest)
			source.sendMessage msg.dest, msg.message, if msg.toUser then Message.DIRECT_MESSAGE else Message.GROUP_MESSAGE
			return true
	}
	{
		expectedType: "listUsers"
		expectedArgs: []
		onMessage: (msg, source) ->
			return userIDs().join ", "
	}
	{
		expectedType: "read"
		expectedArgs: ["id"]
		# todo
	}
]
setupHandlersForUser = (conn, id) ->


# Login procedure
WSServer.on 'request', (req) ->
	if req.host in hostBlacklist
		req.reject 200, 'You are on the KentChat blacklist!'
	else
		conn = req.accept null, req.origin
		conn.on 'message', (message) ->
			if message.type is 'binary'
				conn.drop 1007, 'expected login data to be utf8, received binary'
			else if not (message.id? and message.auth?)
				conn.drop 1007, 'expected msg.id and msg.auth, missing'
			else
				user = getUserById message.id
				if not user?
					conn.drop 1003, "unknown userID: #{message.id}"
					return
				if user.isConnected()
					conn.drop 1003, "user #{message.id} is already logged in"
					return
				if not user.authenticate message.auth
					conn.drop 1003, 'bad password'
					return
				user.onConnect conn
{'fraction_non_alphanumeric': 0.1273515217177189, 'fraction_numerical': 0.010834236186348862, 'mean_word_length': 4.149087221095335, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3790788', 'n_tokens_mistral': 3578, 'n_tokens_neox': 3253, 'n_words': 997}