qcs-python/main.py

196 lines
6.6 KiB
Python

import os
import json
import logging
import getpass
import textwrap
import threading
import toml
import readchar
import websocket
from collections import OrderedDict
from colorama import Fore, Back, Style, init as colorama_init
import contentapi
import myutils
CONFIGFILE="config.toml"
# The entire config object with all defaults
config = {
"api" : "https://oboy.smilebasicsource.com/api",
"default_loglevel" : "WARNING",
"websocket_trace" : False,
"default_room" : 0, # Zero means it will ask you for a room
"expire_seconds" : 31536000, # 365 days in seconds, expiration for token
"appear_in_global" : False,
"tokenfile" : ".qcstoken"
}
commands = OrderedDict([
("h" , "Help, prints this menu!"),
("s" , "Search, find and/or set a room to listen to (one at a time!)"),
("g" , "Global userlist, print users using contentapi in general"),
("u" , "Userlist, print users in the current room"),
("i" , "Insert mode, allows you to send a message (pauses messages!)"),
("q" , "Quit, no warning!")
])
def main():
print("Program start")
colorama_init() # colorama init
load_or_create_global_config()
logging.info("Config: " + json.dumps(config, indent = 2))
context = contentapi.ApiContext(config["api"], logging)
logging.info("Testing connection to API at " + config["api"])
logging.debug(json.dumps(context.api_status(), indent = 2))
authenticate(config, context)
# - Connect to websocket, be ready to receive junk
# - Alert user they're not connected to any room, maybe have a status line that lists controls + room
# - Enter input loop, but check room number on "input" mode, don't let messages send in room 0
# - h to help
# - s to search rooms, enter #1234 to connect directly, empty string to quit
# - g to list global users
# - u to list users in room
# - i to input
# - q to quit entirely
# Let users debug the websocket if they want I guess
if config["websocket_trace"]:
websocket.enableTrace(True)
ws = websocket.WebSocketApp(context.websocket_endpoint())
# Might as well reuse the websocket object for my websocket context data (oops, is that bad?)
ws.user_info = context.user_me()
ws.current_room = config["default_room"]
ws.pause_output = False # Whether all output from the websocket should be paused (including status updates)
ws.output_buffer = [] # Individual print statements buffered from output.
ws.main_config = config
# set the callback functions
# ws.on_open = on_open
ws.on_open = ws_onopen
ws.on_close = ws_onclose
ws.on_message = ws_onmessage
# connect to the WebSocket server and block until connected
ws.run_forever()
# TODO: Will the websocket end if the program just ends?
print("Program end")
def ws_onclose(ws):
print("Websocket closed! Program exit (FYI: you were in room %d)" % ws.current_room)
exit()
def ws_onopen(ws):
def main_loop():
printstatus = True
# The infinite input loop! Or something!
while True:
if printstatus:
print_statusline(ws)
printstatus = True
key = readchar.readkey()
# # Oops, websocket is not connected but you asked for a command that requires websocket!
# if not ws_context.connected and key in ["s", "g", "u", "i"]:
# print("No websocket connection")
# continue
if key == "h":
for key, value in commands.items():
print(" " + Style.BRIGHT + key + Style.NORMAL + " - " + value)
elif key == "s":
print("not yet")
elif key == "g":
print("not yet")
elif key == "u":
print("not yet")
elif key == "i":
print("not yet")
elif key == "q":
ws.close()
break
else:
printstatus = False
# create a thread to run the blocking task
thread = threading.Thread(target=main_loop)
thread.start()
def ws_onmessage(ws, message):
pass
# 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)
myutils.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)
myutils.set_logging_level(config["default_loglevel"])
# 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"]
while True:
print(message)
username = input("Username: ")
password = getpass.getpass("Password: ")
try:
token = context.login(username, password, config["expire_seconds"])
with open(config["tokenfile"], 'w') as f:
f.write(token)
logging.info("Token accepted, written to " + config["tokenfile"])
context.token = token
return
except Exception as ex:
print("ERROR: %s" % ex)
message = "Please try logging in again:"
def print_statusline(ws):
# if ws_context.connected: bg = Back.GREEN else: bg = Back.RED
print(Back.GREEN + Fore.BLACK + "\n User: " + ws.user_info["username"] + " CTRL: h s g u i q " + Style.RESET_ALL)
# Because python reasons
if __name__ == "__main__":
main()