2023-05-02 02:16:22 +00:00
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import threading
|
2023-05-02 04:25:55 +00:00
|
|
|
import toml
|
2023-05-02 02:16:22 +00:00
|
|
|
import websocket
|
2023-05-02 04:25:55 +00:00
|
|
|
import queue
|
2023-05-02 02:16:22 +00:00
|
|
|
from colorama import Fore, Back, Style, init as colorama_init, ansi as colorama_ansi
|
|
|
|
|
2023-05-02 02:38:30 +00:00
|
|
|
import app
|
|
|
|
contentapi = app.loadmod("contentapi")
|
|
|
|
utils = app.loadmod("utils")
|
2023-05-02 02:16:22 +00:00
|
|
|
|
2023-05-02 04:25:55 +00:00
|
|
|
def appInit(): # This is ran after all modules are loaded
|
|
|
|
global frontend
|
|
|
|
frontend = app.mod["frontend"]
|
|
|
|
|
2023-05-02 02:57:34 +00:00
|
|
|
VERSION=(0,2,0) # Arbitrary but whatever
|
2023-05-02 02:16:22 +00:00
|
|
|
CONFIGFILE="config.toml"
|
|
|
|
|
|
|
|
# The entire config object with all defaults
|
|
|
|
config = {
|
|
|
|
"api" : "http://localhost:5000/api",
|
|
|
|
"default_loglevel" : "WARNING",
|
|
|
|
"websocket_trace" : False,
|
|
|
|
"default_room" : 0, # Zero means it will ask you for a room
|
|
|
|
"default_markup" : "plaintext",
|
|
|
|
"expire_seconds" : 31536000, # 365 days in seconds, expiration for token
|
|
|
|
"appear_in_global" : False,
|
|
|
|
"print_status_after_insert" : True,
|
|
|
|
"output_buffer_timeout" : 0.05, # 50 milliseconds
|
|
|
|
"default_history" : 100,
|
|
|
|
"fixed_width" : 76, # Need room for the pre-message decoration
|
|
|
|
"tokenfile" : ".qcstoken"
|
|
|
|
}
|
|
|
|
|
2023-05-02 04:25:55 +00:00
|
|
|
# Loads the config from file into the global config var. If the file
|
|
|
|
# doesn't exist, the file is created from the defaults in config.
|
|
|
|
# The function returns nothing
|
|
|
|
def load_or_create_global_config():
|
|
|
|
global config
|
|
|
|
# Check if the config file exists
|
|
|
|
if os.path.isfile(CONFIGFILE):
|
|
|
|
# Read and deserialize the config file
|
|
|
|
with open(CONFIGFILE, 'r', encoding='utf-8') as f:
|
|
|
|
temp_config = toml.load(f)
|
|
|
|
utils.merge_dictionary(temp_config, config)
|
|
|
|
else:
|
|
|
|
# Serialize and write the config dictionary to the config file
|
|
|
|
logging.warn("No config found at " + CONFIGFILE + ", creating now")
|
|
|
|
with open(CONFIGFILE, 'w', encoding='utf-8') as f:
|
|
|
|
toml.dump(config, f)
|
2023-05-02 02:16:22 +00:00
|
|
|
|
2023-05-02 04:25:55 +00:00
|
|
|
utils.set_logging_level(config["default_loglevel"])
|
2023-05-02 02:16:22 +00:00
|
|
|
|
|
|
|
def main():
|
2023-05-02 02:57:34 +00:00
|
|
|
print("Program start: %s" % ".".join(map(str,VERSION)))
|
2023-05-02 02:16:22 +00:00
|
|
|
colorama_init() # colorama init
|
|
|
|
load_or_create_global_config()
|
|
|
|
logging.info("Config: " + json.dumps(config, indent = 2))
|
2023-05-02 04:25:55 +00:00
|
|
|
|
2023-05-02 02:16:22 +00:00
|
|
|
# Let users debug the websocket if they want I guess
|
|
|
|
if config["websocket_trace"]:
|
|
|
|
websocket.enableTrace(True)
|
2023-05-02 04:25:55 +00:00
|
|
|
context = contentapi.ApiContext(config["api"], logging)
|
|
|
|
frontend.main(config,context)
|
|
|
|
|
|
|
|
authenticate(config, context)
|
|
|
|
|
2023-05-02 02:16:22 +00:00
|
|
|
ws = websocket.WebSocketApp(context.websocket_endpoint())
|
|
|
|
|
|
|
|
# Might as well reuse the websocket object for my websocket context data (oops, is that bad?)
|
|
|
|
ws.context = context
|
|
|
|
ws.user_info = context.user_me()
|
|
|
|
ws.main_config = config
|
|
|
|
ws.ignored = {} # Just a tracking list for fun stats
|
|
|
|
# Output from the websocket can only get truly printed to the screen when this lock is released.
|
|
|
|
# We grab the lock when the user is in some kind of "input" mode, which blocks the websocket printing
|
|
|
|
# thread from processing the queue (if it has anything anyway)
|
|
|
|
ws.output_lock = threading.Lock()
|
|
|
|
# Output from the websocket is ALWAYS buffered, and we use a thread-safe queue to add and remove
|
|
|
|
# output safely. We buffer all messages to ensure the order is preserved; if we SOMETIMES queued and
|
|
|
|
# SOMETIMES did not, we would need to be very careful about whether the queue was empty, which requires
|
|
|
|
# additional locking and etc.
|
|
|
|
ws.output_buffer = queue.Queue()
|
|
|
|
|
|
|
|
# Note that the current_room stuff is set on open if you've supplied a default room in config
|
|
|
|
ws.current_room = 0
|
|
|
|
ws.current_room_data = False
|
|
|
|
|
|
|
|
# set the callback functions
|
2023-05-02 04:25:55 +00:00
|
|
|
ws.on_open = app.mod["frontend"].ws_onopen
|
|
|
|
ws.on_close = app.mod["frontend"].ws_onclose
|
|
|
|
ws.on_message = app.mod["frontend"].ws_onmessage
|
2023-05-02 02:16:22 +00:00
|
|
|
|
|
|
|
ws.run_forever()
|
|
|
|
|
2023-05-02 04:25:55 +00:00
|
|
|
print("Program end")
|
2023-05-02 02:16:22 +00:00
|
|
|
|
|
|
|
# Either pull the token from a file, or get the login from the command
|
|
|
|
# line if that doesn't work. WILL test your token against the real API
|
|
|
|
# even if it's pulled from file!
|
|
|
|
def authenticate(config, context: contentapi.ApiContext):
|
|
|
|
message = "No token file found"
|
|
|
|
if os.path.isfile(config["tokenfile"]):
|
|
|
|
with open(config["tokenfile"], 'r') as f:
|
|
|
|
token = f.read()
|
|
|
|
logging.debug("Token from file: " + token)
|
|
|
|
context.token = token
|
|
|
|
if context.is_token_valid():
|
|
|
|
logging.info("Logged in using token file " + config["tokenfile"])
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
message = "Token file expired"
|
|
|
|
|
|
|
|
message += ", Please enter login for " + config["api"]
|
2023-05-02 04:25:55 +00:00
|
|
|
frontend.authenticate(context,message)
|