<commit_before>//
//  main.swift
//  FireAlarm
//
//  Created by NobodyNada on 8/27/16.
//  Copyright © 2016 NobodyNada. All rights reserved.
//

import Foundation
import Dispatch
import SwiftChatSE
import SwiftStack

let commands: [Command.Type] = [
	CommandSay.self,
	CommandHelp.self, CommandListRunning.self, CommandStop.self, CommandKill.self, CommandUpdate.self, CommandStatus.self,
	CommandCheckThreshold.self, CommandSetThreshold.self,
	CommandCheckPrivileges.self, CommandPrivilege.self, CommandUnprivilege.self,
	CommandCheckPost.self, CommandQuota.self,
	CommandBlacklistUsername.self, CommandGetBlacklistedUsernames.self, CommandUnblacklistUsername.self,
	CommandOptIn.self, CommandOptOut.self, CommandCheckNotification.self, CommandLeaveRoom.self,
]



#if os(Linux)
	typealias Process = Task
#endif



var startTime = Date()

var botName = "FireAlarm-Swift"
var githubLink = "//github.com/SOBotics/FireAlarm/tree/swift"
var stackAppsLink = "//stackapps.com/q/7183"

var currentVersion = "<unknown version>"
var shortVersion = "<unknown version>"
var versionLink = githubLink

//var apiClient = APIClient(proxyAddress: "127.0.0.1", proxyPort: 8080)
var apiClient = APIClient()

extension ChatUser {
	var notified: Bool {
		get {
			return ((info["notified"] as? Int) ?? 0) == 1 ? true : false
		} set {
			info["notified"] = (newValue ? 1 : 0)
		}
	}
	var notificationTags: [String] {
		get {
			return (info["notificationTags"] as? [String]) ?? []
		} set {
			info["notificationTags"] = newValue
		}
	}
	var notificationReasons: [String] {
		get {
			return (info["notificationReasons"] as? [String]) ?? []
		} set {
			info["notificationReasons"] = newValue
		}
	}
}

extension ChatRoom {
	
	
	func notificationString(tags: [String], reason: Filter.ReportReason) -> String {
		var users = [ChatUser]()
		for user in userDB {
			var shouldNotify = false
			
			if user.notified {
				switch reason {
				case .bayesianFilter:
					if !user.notificationTags.isEmpty {
						for tag in tags {
							if user.notificationTags.contains(tag) {
								shouldNotify = true
							}
						}
					}
					else {
						shouldNotify = true
					}
					
				case .blacklistedUsername:
					if (user.notificationReasons.isEmpty && user.notificationTags.isEmpty)
						|| user.notificationReasons.contains("username") {
						
						shouldNotify = true
					}
					
				case .misleadingLink:
					if (user.notificationReasons.isEmpty && user.notificationTags.isEmpty)
						|| user.notificationReasons.contains("misleadingLink") {
						
						shouldNotify = true
					}
				}
				
			}
			
			if shouldNotify {
				users.append(user)
			}
		}
		
		return users.map { "@" + $0.name.replacingOccurrences(of: " ", with: "") }.joined(separator: " ")
	}
	
	var threshold: Int {
		get {
			return (info["threshold"] as? Int) ?? 45
		} set {
			info["threshold"] = newValue
		}
	}
}

extension ChatUser.Privileges {
	static let filter = ChatUser.Privileges(rawValue: 1 << 1)
}

ChatUser.Privileges.add(name: "Filter", for: .filter)


private enum BackgroundTask {
	case handleInput(input: String)
	case shutDown(reboot: Bool, update: Bool)
}

private var backgroundTasks = [BackgroundTask]()
private let backgroundSemaphore = DispatchSemaphore(value: 0)

let saveDirURL = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".firealarm", isDirectory: true)



fileprivate var listener: ChatListener!

var filter: Filter!

func main() throws {
	print("FireAlarm starting...")
	startTime = Date()
	afterTooManyErrors = {}
	
	//Save the working directory & change to the chatbot directory.
	let originalWorkingDirectory = FileManager.default.currentDirectoryPath
	
	let saveDirURL = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".firealarm", isDirectory: true)
	
	if !FileManager.default.fileExists(atPath: saveDirURL.path) {
		try! FileManager.default.createDirectory(at: saveDirURL, withIntermediateDirectories: false, attributes: nil)
	}
	
	saveURL = saveDirURL
	
	let _ = FileManager.default.changeCurrentDirectoryPath(saveDirURL.path)
	
	apiClient.key = "HNA2dbrFtyTZxeHN6rThNg(("
	apiClient.defaultFilter = "withbody"
	
	
	//Log in
	let client = Client(host: .StackOverflow)
	
	let env =  ProcessInfo.processInfo.environment
	
	if !client.loggedIn {
		let email: String
		let password: String
		
		let envEmail = env["ChatBotEmail"]
		let envPassword = env["<PASSWORD>"]
		
		if envEmail != nil {
			email = envEmail!
		}
		else {
			print("Email: ", terminator: "")
			email = readLine()!
		}
		
		if envPassword != nil {
			password = envPassword!
		}
		else {
			password = String(validatingUTF8: getpass("Password: "))!
		}
		
		do {
			try client.login(email: email, password: password)
		}
		catch {
			handleError(error, "while logging in")
			exit(EXIT_FAILURE)
		}
	}
	
	
	
	//Join the chat room
	let rooms: [ChatRoom]
	let development: Bool
	if let devString = env["DEVELOPMENT"], let devRoom = Int(devString) {
		rooms = [ChatRoom(client: client, roomID: devRoom)]
		development = true
	}
	else {
		rooms = [
			ChatRoom(client: client, roomID: 123602), //FireAlarm Development
			ChatRoom(client: client, roomID: 111347), //SOBotics
			ChatRoom(client: client, roomID: 41570),  //SO Close Vote Reviewers
		]
		
		development = false
	}
	try rooms.forEach {try $0.loadUserDB()}
	
	afterTooManyErrors = {
		print("Too many errors; aborting...")
		abort()
	}
	errorRoom = rooms.first!
	
	
	listener = ChatListener(commands: commands)
	listener.onShutdown { halt(reboot: $0 == .reboot, update: $0 == .update) }
	rooms.forEach {room in room.onMessage { listener.processMessage(room, message: $0, isEdit: $1) } }
	
	try rooms.forEach { try $0.join() }
	
	//Post the startup message
	let startupMessage: String?
	
	currentVersion = getCurrentVersion()
	if FileManager.default.fileExists(atPath: "update-failure") {
		startupMessage = "Update failed!"
		try! FileManager.default.removeItem(atPath: "update-failure")
	}
	else if let new = try? loadFile("version-new.txt").replacingOccurrences(of: "\n", with: "") {
		let components = new.components(separatedBy: " ")
		let new = components.first ?? ""
		let newShort = getShortVersion(new)
		let newLink = getVersionLink(new)
		
		let old = currentVersion
		let oldShort = getShortVersion(old)
		let oldLink = getVersionLink(old)
		
		let message = components.count > 1 ? (" (" + components[1..<components.count].joined(separator: " ") + ")") : ""
		
		startupMessage = "Updated from [`\(oldShort)`](\(oldLink)) to [`\(newShort)`](\(newLink))\(message)."		
		try! new.write(toFile: "version.txt", atomically: true, encoding: .utf8)
		currentVersion = new
		try! FileManager.default.removeItem(atPath: "version-new.txt")
	}
	else {
		startupMessage = nil
		let short = getShortVersion(currentVersion)
		let link = getVersionLink(currentVersion)
		
		rooms.first?.postMessage("[ [\(botName)](\(stackAppsLink)) ] FireAlarm started at revision [`\(short)`](\(link)).")
	}
	
	if let message = startupMessage {
		rooms.forEach { $0.postMessage(message) }
	}
	
	shortVersion = getShortVersion(currentVersion)
	versionLink = getVersionLink(currentVersion)
	<commit_msg>some work on location<commit_after>//
//  main.swift
//  FireAlarm
//
//  Created by NobodyNada on 8/27/16.
//  Copyright © 2016 NobodyNada. All rights reserved.
//

import Foundation
import Dispatch
import SwiftChatSE
import SwiftStack

let commands: [Command.Type] = [
	CommandSay.self,
	CommandHelp.self, CommandListRunning.self, CommandStop.self, CommandKill.self, CommandUpdate.self, CommandStatus.self,
	CommandCheckThreshold.self, CommandSetThreshold.self,
	CommandCheckPrivileges.self, CommandPrivilege.self, CommandUnprivilege.self,
	CommandCheckPost.self, CommandQuota.self,
	CommandBlacklistUsername.self, CommandGetBlacklistedUsernames.self, CommandUnblacklistUsername.self,
	CommandOptIn.self, CommandOptOut.self, CommandCheckNotification.self, CommandLeaveRoom.self,
]



#if os(Linux)
	typealias Process = Task
#endif



var startTime = Date()

var botName = "FireAlarm-Swift"
var githubLink = "//github.com/SOBotics/FireAlarm/tree/swift"
var stackAppsLink = "//stackapps.com/q/7183"

var currentVersion = "<unknown version>"
var shortVersion = "<unknown version>"
var versionLink = githubLink

//var apiClient = APIClient(proxyAddress: "127.0.0.1", proxyPort: 8080)
var apiClient = APIClient()

extension ChatUser {
	var notified: Bool {
		get {
			return ((info["notified"] as? Int) ?? 0) == 1 ? true : false
		} set {
			info["notified"] = (newValue ? 1 : 0)
		}
	}
	var notificationTags: [String] {
		get {
			return (info["notificationTags"] as? [String]) ?? []
		} set {
			info["notificationTags"] = newValue
		}
	}
	var notificationReasons: [String] {
		get {
			return (info["notificationReasons"] as? [String]) ?? []
		} set {
			info["notificationReasons"] = newValue
		}
	}
}

extension ChatRoom {
	
	
	func notificationString(tags: [String], reason: Filter.ReportReason) -> String {
		var users = [ChatUser]()
		for user in userDB {
			var shouldNotify = false
			
			if user.notified {
				switch reason {
				case .bayesianFilter:
					if !user.notificationTags.isEmpty {
						for tag in tags {
							if user.notificationTags.contains(tag) {
								shouldNotify = true
							}
						}
					}
					else {
						shouldNotify = true
					}
					
				case .blacklistedUsername:
					if (user.notificationReasons.isEmpty && user.notificationTags.isEmpty)
						|| user.notificationReasons.contains("username") {
						
						shouldNotify = true
					}
					
				case .misleadingLink:
					if (user.notificationReasons.isEmpty && user.notificationTags.isEmpty)
						|| user.notificationReasons.contains("misleadingLink") {
						
						shouldNotify = true
					}
				}
				
			}
			
			if shouldNotify {
				users.append(user)
			}
		}
		
		return users.map { "@" + $0.name.replacingOccurrences(of: " ", with: "") }.joined(separator: " ")
	}
	
	var threshold: Int {
		get {
			return (info["threshold"] as? Int) ?? 45
		} set {
			info["threshold"] = newValue
		}
	}
}

extension ChatUser.Privileges {
	static let filter = ChatUser.Privileges(rawValue: 1 << 1)
}

ChatUser.Privileges.add(name: "Filter", for: .filter)


private enum BackgroundTask {
	case handleInput(input: String)
	case shutDown(reboot: Bool, update: Bool)
}

private var backgroundTasks = [BackgroundTask]()
private let backgroundSemaphore = DispatchSemaphore(value: 0)

let saveDirURL = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".firealarm", isDirectory: true)



fileprivate var listener: ChatListener!

var filter: Filter!

func main() throws {
	print("FireAlarm starting...")
	startTime = Date()
	afterTooManyErrors = {}
	
	//Save the working directory & change to the chatbot directory.
	let originalWorkingDirectory = FileManager.default.currentDirectoryPath
	
	let saveDirURL = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".firealarm", isDirectory: true)
	
	if !FileManager.default.fileExists(atPath: saveDirURL.path) {
		try! FileManager.default.createDirectory(at: saveDirURL, withIntermediateDirectories: false, attributes: nil)
	}
	
	saveURL = saveDirURL
	
	let _ = FileManager.default.changeCurrentDirectoryPath(saveDirURL.path)
	
	apiClient.key = "HNA2dbrFtyTZxeHN6rThNg(("
	apiClient.defaultFilter = "withbody"
	
	
	//Log in
	let client = Client(host: .StackOverflow)
	
	let env =  ProcessInfo.processInfo.environment
	
	if !client.loggedIn {
		let email: String
		let password: String
		
		let envEmail = env["ChatBotEmail"]
		let envPassword = env["ChatBotPass"]
		
		if envEmail != nil {
			email = envEmail!
		}
		else {
			print("Email: ", terminator: "")
			email = readLine()!
		}
		
		if envPassword != nil {
			password = envPassword!
		}
		else {
			password = String(validatingUTF8: getpass("Password: "))!
		}
		
		do {
			try client.login(email: email, password: password)
		}
		catch {
			handleError(error, "while logging in")
			exit(EXIT_FAILURE)
		}
	}
    
    //Get the location
    var rawLocation = ""
    var location = ""
    do {
        rawLocation = try loadFile ("location.txt")
        location = String(rawLocation.characters.filter { !"\n".characters.contains($0) })
    } catch {
        handleError(error, "while loading location")
    }
	
	//Join the chat room
	let rooms: [ChatRoom]
	let development: Bool
	if let devString = env["DEVELOPMENT"], let devRoom = Int(devString) {
		rooms = [ChatRoom(client: client, roomID: devRoom)]
		development = true
	}
	else {
		rooms = [
			ChatRoom(client: client, roomID: 123602), //FireAlarm Development
			ChatRoom(client: client, roomID: 111347), //SOBotics
			ChatRoom(client: client, roomID: 41570),  //SO Close Vote Reviewers
		]
		
		development = false
	}
	try rooms.forEach {try $0.loadUserDB()}
	
	afterTooManyErrors = {
		print("Too many errors; aborting...")
		abort()
	}
	errorRoom = rooms.first!
	
	
	listener = ChatListener(commands: commands)
	listener.onShutdown { halt(reboot: $0 == .reboot, update: $0 == .update) }
	rooms.forEach {room in room.onMessage { listener.processMessage(room, message: $0, isEdit: $1) } }
	
	try rooms.forEach { try $0.join() }
	
	//Post the startup message
	let startupMessage: String?
	
	currentVersion = getCurrentVersion()
	if FileManager.default.fileExists(atPath: "update-failure") {
		startupMessage = "Update failed!"
		try! FileManager.default.removeItem(atPath: "update-failure")
	}
	else if let new = try? loadFile("version-new.txt").replacingOccurrences(of: "\n", with: "") {
		let components = new.components(separatedBy: " ")
		let new = components.first ?? ""
		let newShort = getShortVersion(new)
		let newLink = getVersionLink(new)
		
		let old = currentVersion
		let oldShort = getShortVersion(old)
		let oldLink = getVersionLink(old)
		
		let message = components.count > 1 ? (" (" + components[1..<components.count].joined(separator: " ") + ")") : ""
		
		startupMessage = "Updated from [`\(oldShort)`](\(oldLink)) to [`\(newShort)`](\(newLink))\(message)."		
		try! new.write(toFile: "version.txt", atomically: true, encoding: .utf8)
		currentVersion = new
		try! FileManager.default.removeItem(atPath: "version-new.txt")
	}
	else {
		startupMessage = nil
		let short = getShortVersion(currentVersion)
		let link = getVersionLink(currentVersion)
		
		rooms.first?.postMessage("[ [\(botName)](\(stackAppsLink)) ] FireAlarm started at revision [`\(short)`](\(link)) instance owner \(location).")
	}
	
	if let message = startupMessage {
		rooms.forEach { $0.postMessage(message) }
	}
	
	shortVersion = getShortVersion(currentVersion)
	versionLink = getVersionLink(currentVersion)
	
{'fraction_non_alphanumeric': 0.12713836047625565, 'fraction_numerical': 0.008690296975502942, 'mean_word_length': 3.8346013893483293, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5586424', 'n_tokens_mistral': 5380, 'n_tokens_neox': 4594, 'n_words': 1066}