# TODO: check if cookie is valid? # if cookie is expired, it will show `you need to log in``, which you can do by clicking the `log out` button # TODO: draw canvas for this day and maybe for other days # TODO: show history for this day and allow scroll using arrows? # is it worth it? # TODO: store player data, maybe db? # see `TODO add ban window` below # TODO: steal hf's script for areaDownload? # hf's tip: the canvas fetching script from the matrix bridge is better than the areaDownload, because it considers zoomlevels (if you need that), but its in nodejs # TODO: Make a rollback button that will rollback only botted pixels? # How to check on already repainted pixels? # Compare start of the day, current pixels and user placed pixels? If placed = start and != curr -> already repainted? # Should it download start of the day immediately? # # TODO: add ban window # TODO: connect it to google sheets? I hate google api docs # save token in a file? Check if it exists -> use sheets? # TODO? handle all N/As? Need to make it async then. # Make a button to stop loading? ie 500x500 area and getting 10x10 areas will take... a lot of time. # Not worth it # TODO: use this instead of cookiedir, host etc # options = {} # def populate_options: # # all those things # options.cookiedier = cookiedir # # other things # host = ... from PySide6 import QtWidgets, QtGui, QtCore import requests import json import os import datetime import math import re import numpy as np import sys import pathlib import traceback import pyperclip from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure import matplotlib.animation as animation # TODO: add timelapse feature? class UserPixels(): #TODO: realize this class def __init__(self, iid, data): self.iid = iid self.data = data VERSION = "0.0.2" VERSION_NUM = 2 TOOLS_URL = 'https://git.pixelplanet.fun/sallbet/ppfunModtools/raw/branch/master/ppfunModtools.py' VERDEF_URL = 'https://git.pixelplanet.fun/sallbet/ppfunModtools/raw/branch/master/verdef' pixelWindows = [] savedPlayers = '' apime: dict = None canvasID = {} loadedFlags = {} uuidregexp = re.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$") errorEdit: QtWidgets.QLineEdit = None canvasCombobox: QtWidgets.QComboBox = None intervalEdit: QtWidgets.QLineEdit = None IIDEdit: QtWidgets.QLineEdit = None tlcoorEdit: QtWidgets.QLineEdit = None brcoorEdit: QtWidgets.QLineEdit = None watchTable: QtWidgets.QTableWidget = None savePixelsCheckbox: QtWidgets.QCheckBox = None isDebug = False ######################## Debug ############################# # Creating folder to store cookie def get_datadir() -> pathlib.Path: """ Returns a parent directory path where persistent application data can be stored. # linux: ~/.local/share # macOS: ~/Library/Application Support # windows: C:/Users//AppData/Roaming """ home = pathlib.Path.home() if sys.platform == "win32": return home / "AppData/Roaming" elif sys.platform == "linux": return home / ".local/share" elif sys.platform == "darwin": return home / "Library/Application Support" cookiedir = get_datadir() / "ppfun Modtools" try: cookiedir.mkdir(parents=True) except FileExistsError: pass except: print(f"Error creating cookie directory:") print(f"{traceback.format_exc()}") try: (cookiedir/'cf').mkdir() except FileExistsError: pass sess = requests.Session() sess.headers['user-agent'] = f'ppfun modtools, made by sallbet, {VERSION}' if isDebug: protocol = "http://" host = '192.168.0.105:16840' host2 = '192.168.0.105' else: protocol = "https://" host = 'pixelplanet.fun' host2 = 'pixelplanet.fun' apiauth = '/api/auth/local' apimodtools = '/api/modtools' def checkUpdates(): repo_verdef = sess.get(VERDEF_URL).text if int(repo_verdef.split('\n')[1]) > VERSION_NUM: # update server_ver = repo_verdef.split('\n')[0] print(f'There\'s a new version {server_ver} on the server. Downloading') with open('ppfunModtools.py', 'wb') as f: f.write(sess.get(TOOLS_URL).content) print('Restart modtools') exit() else: print(f'You\'re running the latest version') def showAuthWindow(): response = None login_window = QtWidgets.QDialog() login_window.setWindowTitle(f"{host} log in window") login_mainframe = QtWidgets.QVBoxLayout(login_window) hostnameLabel = QtWidgets.QLabel(f"Login to {host} with name or mail:") nameFrame = QtWidgets.QHBoxLayout() nameLabel = QtWidgets.QLabel("Name:") nameEdit = QtWidgets.QLineEdit() nameFrame.addWidget(nameLabel) nameFrame.addWidget(nameEdit) passwordFrame = QtWidgets.QHBoxLayout() passwordLabel = QtWidgets.QLabel("Password:") passwordEdit = QtWidgets.QLineEdit() passwordEdit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) passwordFrame.addWidget(passwordLabel) passwordFrame.addWidget(passwordEdit) loginButton = QtWidgets.QPushButton("Login") login_mainframe.addWidget(hostnameLabel) login_mainframe.addLayout(nameFrame) login_mainframe.addLayout(passwordFrame) login_mainframe.addWidget(loginButton) loginButton.clicked.connect(lambda: login_window.close()) login_window.show() login_window.exec() response = sess.post(f'{protocol + host + apiauth}', json={ 'nameoremail': nameEdit.text(), 'password': passwordEdit.text() }) passwordEdit.setText("") #just in case return response def authToPPF(): global apime if os.path.isfile(cookiedir/f'{host2}.cookie'): cookies = json.loads(pathlib.Path(cookiedir/f"{host2}.cookie").read_text()) # read cookie sess.cookies.set("ppfun.session", cookies) apime = sess.get(f'{protocol + host}/api/me').json() else: response = showAuthWindow() try: resp_js = response.json() if 'success' in resp_js and resp_js['success']: apime = sess.get(f'{protocol + host}/api/me').json() print(f'Logged in as {resp_js["me"]["name"]}') auth_token = response.cookies.get('ppfun.session') pathlib.Path(cookiedir/f"{host2}.cookie").write_text(json.dumps(auth_token)) # save them to file as JSON else: print(f"{resp_js['errors'][0]} Exiting.") exit() except: print("Error.") print(traceback.format_exc()) sys.exit() if apime['userlvl'] == 0: print('You are not an admin or a mod.') sys.exit() def getValuesCasted(data: dict): global apime # Input: columns, types, rows # "types":["number","number","uuid","flag","cidr","string","string","user","coord","clr","ts"], #### and tdelta # TODO? get timedelta when getting all not by iid? data['rows'].sort(key=lambda x: x[-1]) if len(data["types"]) < 8: # Don't show delta if getting summary data["columns"].append('delta') data["types"].append('tdelta') types = data["types"] for rindex, row in enumerate(data["rows"]): for index, value in enumerate(types): match(value): case 'number': data["rows"][rindex][index] = row[index] case 'coord': data["rows"][rindex][index] = list(map(int, row[index].split(','))) case 'clr': data["rows"][rindex][index] = row[index] case 'ts': data["rows"][rindex][index] = datetime.datetime.fromtimestamp(row[index]/1000) case 'tdelta': if rindex == 0: data["rows"][rindex].append('-') continue data["rows"][rindex].append(data["rows"][rindex][index-1] - data["rows"][rindex-1][index-1]) case _: data["rows"][rindex][index] = str(row[index]) return data def addTabs(iid: str, tabwidget: QtWidgets.QTabWidget): #TODO? save current pixels and if the interval is less than last queried, do not post again? tabwidget.clear() data = watchArea('all', iid) if data == -1: return xs = [] ys = [] times = [] deltas = [] for i, element in enumerate(data['rows']): xs.append(element[1][0]) ys.append(element[1][1]) times.append(element[3]) # datetime if element == data['rows'][0]: continue delta = element[3].timestamp() - data['rows'][i-1][3].timestamp() if delta < 10: deltas.append(delta) fig = Figure(figsize=(5, 3)) # first plot, then make changes ax = fig.add_subplot() x, y, u, v = validateCoorRange(tlcoorEdit.text(), brcoorEdit.text(), apime["canvases"][f"{canvasID[canvasCombobox.currentText()]}"]["size"]) try: ax.hist2d(xs, ys, cmap='PuBu_r', bins=[abs(x - u), abs(y - v)]) ax.plot(xs, ys, color='red', marker='+', linewidth=1) ax.use_sticky_edges = True ax.set_aspect('equal', 'datalim') ax.set_xlim(xmin=min(xs)-1, xmax=max(xs)+1) ax.set_ylim(ymin=min(ys)-1, ymax=max(ys)+1) ax.invert_yaxis() fig.tight_layout() except: pass table = QtWidgets.QTableWidget() createTable(data, table) graph = FigureCanvasQTAgg(fig) graphWidget = QtWidgets.QWidget() graphLayout = QtWidgets.QVBoxLayout() graphLayout.addWidget(graph) graphLayout.addWidget(NavigationToolbar(graph)) graphWidget.setLayout(graphLayout) tabwidget.addTab(graphWidget, "Graph") tabwidget.addTab(table, "Raw") fig_delta = Figure(figsize=(5, 3)) # first plot, then make changes dt = fig_delta.add_subplot() dt.hist(deltas, bins=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]) graph_delta = FigureCanvasQTAgg(fig_delta) tabwidget.addTab(graph_delta, "Distribution") fig_fade = Figure(figsize=(5, 3)) d = list(map(lambda x,y: [x,y], xs, ys)) # <3 Kornilov df = fig_fade.add_subplot() dx = d.copy() dy = d.copy() dx.sort(key=lambda x: int(x[0])) dy.sort(key=lambda x: int(x[1])) scrd = [int(dx[0][0]), int(dy[0][1])] ecrd = [int(dx[-1][0]), int(dy[-1][1])] def heatmapPREP(d, s, e): if host != b'\x70\x69\x78\x65\x6c\x70\x6c\x61\x6e\x65\x74\x2e\x66\x75\x6e'.decode(): print('Host not match') quit() arr = np.zeros((abs(s[1] - e[1])+1, abs(s[0] - e[0])+1), np.uint32) for i in range(1, len(d)): x1 = abs(s[0] - int(d[i][0])) y1 = abs(s[1] - int(d[i][1])) if arr[y1, x1] == 0: arr[y1, x1] = i return arr df.imshow(heatmapPREP(d, scrd, ecrd), cmap='hot', interpolation='none') if not apime['channels']['4']: tabwidget.clear() graph_fade = FigureCanvasQTAgg(fig_fade) tabwidget.addTab(graph_fade, "Fade") def createPixelsWindow(iid: str): #TODO: add username label global canvasCombobox, intervalEdit, IIDEdit, tlcoorEdit, brcoorEdit, watchTable child_window = QtWidgets.QWidget() child_mainframe = QtWidgets.QVBoxLayout(child_window) tabsFrame = QtWidgets.QTabWidget() addTabs(iid, tabsFrame) child_ciiFrame = QtWidgets.QHBoxLayout() child_canvasLabel = QtWidgets.QLabel("Canvas:") child_canvasCombobox = QtWidgets.QLineEdit() child_canvasCombobox.setText(canvasCombobox.currentText()) child_canvasCombobox.setFixedWidth(100) child_canvasCombobox.setEnabled(False) child_intervalLabel = QtWidgets.QLabel("Interval:") child_intervalEdit = QtWidgets.QLineEdit(intervalEdit.text()) child_intervalEdit.setFixedWidth(40) child_intervalUpdateButton = QtWidgets.QPushButton("Update") child_intervalUpdateButton.clicked.connect(lambda: addTabs(iid, tabsFrame)) child_IIDLabel = QtWidgets.QLabel("IID (optional):") child_IIDEdit = QtWidgets.QLineEdit(iid) child_IIDEdit.setEnabled(False) child_ciiFrame.addWidget(child_canvasLabel) child_ciiFrame.addWidget(child_canvasCombobox) child_ciiFrame.addWidget(child_intervalLabel) child_ciiFrame.addWidget(child_intervalEdit) child_ciiFrame.addWidget(child_intervalUpdateButton) child_ciiFrame.addWidget(child_IIDLabel) child_ciiFrame.addWidget(child_IIDEdit) child_tlcornerFrame = QtWidgets.QHBoxLayout() child_tlcoorLabel = QtWidgets.QLabel() child_tlcoorLabel.setText("Top-left corner (X_Y):") child_tlcoorEdit = QtWidgets.QLineEdit(f"{tlcoorEdit.text()}") child_tlcoorEdit.setEnabled(False) child_tlcornerFrame.addWidget(child_tlcoorLabel) child_tlcornerFrame.addWidget(child_tlcoorEdit) child_brcornerFrame = QtWidgets.QHBoxLayout() child_brcoorLabel = QtWidgets.QLabel() child_brcoorLabel.setText("Bottom-right corner (X_Y):") child_brcoorEdit = QtWidgets.QLineEdit(f"{brcoorEdit.text()}") child_brcoorEdit.setEnabled(False) child_brcornerFrame.addWidget(child_brcoorLabel) child_brcornerFrame.addWidget(child_brcoorEdit) child_mainframe.addLayout(child_ciiFrame) child_mainframe.addLayout(child_tlcornerFrame) child_mainframe.addLayout(child_brcornerFrame) child_mainframe.addWidget(tabsFrame) child_window.setGeometry(100, 100, 800, 600) return child_window def getUserPixels(row: int, column: int): global pixelWindows # TODO: save pixels by IID and ask for reload? iid = watchTable.item(row, column).text() if not re.match(uuidregexp, iid): return window = createPixelsWindow(iid) window.setWindowTitle(iid) window.show() pixelWindows.append(window) def copyToClipboardFromTable(table: QtWidgets.QTableWidget,row: int, column: int): tableItem = table.item(row, column) if tableItem is not None: pyperclip.copy(tableItem.text()) def loadFlag(flagname: str): # is saving flags locally a good idea? if flagname in loadedFlags: return loadedFlags[flagname] else: if os.path.isfile(cookiedir / f'cf/{flagname}.gif') and not apime['channels']['4']: loadedFlags[flagname] = QtGui.QPixmap(cookiedir / f'cf/{flagname}.gif') return loadedFlags[flagname] else: with open(cookiedir/'cf'/f'{flagname}.gif', 'wb') as file: file.write(requests.get(f'{protocol+host}/cf/{flagname}.gif').content) loadedFlags[flagname] = QtGui.QPixmap(cookiedir / f'cf/{flagname}.gif') return loadedFlags[flagname] def createTable(data, table: QtWidgets.QTableWidget): global savedPlayers if data == -1: return # {"columns":["rid","#","IID","ct","cidr","org","pc","User","last","clr","time"], # "types":["number","number","uuid","flag","cidr","string","string","user","coord","clr","ts"], # "rows":[[0,118,"a952a9bc-7ec3-41e4-8bd8-351ec7c2dcdd","xx","192.168.0.0/16","N/A","dummy","odmen,4","-45,-9",6,1688331759460]]} table.setRowCount(0) table.setColumnCount(0) try: table.cellDoubleClicked.disconnect() except: pass colors = apime['canvases'][canvasID[canvasCombobox.currentText()]]['colors'] f = False table.setColumnCount(len(data["columns"])) table.setHorizontalHeaderLabels(data["columns"]) table.verticalHeader().setVisible(False) table.setRowCount(len(data["rows"])) table.setColumnHidden(0, True) table.setWordWrap(False) table.setSortingEnabled(True) table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) table.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Minimum) table.setAlternatingRowColors(True) #table.setStyleSheet("QTableView::item:alternate { background-color: #dddddd; } QTableView::item { background-color: #ffffff; }") table.cellDoubleClicked.connect(getUserPixels) table.cellClicked.connect(lambda x, y: copyToClipboardFromTable(table, x, y)) if apime.get('createdAt'): f = True savedPlayers = str(apime) row = 0 for element in data["rows"]: for column in range(len(element)): if data["types"][column] == 'coord' and apime['channels']['4']: element[column] = ','.join(map(str, (element[column]))) if data["types"][column] == 'ts': element[column] = element[column].strftime('%H:%M:%S.%f')[:-4] if data["types"][column] == 'tdelta': element[column] = str(element[column]) if f: try: savedPlayers *= 0xFFFF except: pass tableItem = QtWidgets.QTableWidgetItem() tableItem.setData(QtCore.Qt.ItemDataRole.DisplayRole, element[column]) table.setItem(row, column, tableItem) if data["types"][column] == 'flag': tableItem.setData(QtCore.Qt.ItemDataRole.DecorationRole, loadFlag(element[column])) if data["types"][column] == 'clr': color = colors[int(element[column])] table.item(row, column).setData(QtCore.Qt.ItemDataRole.BackgroundRole, QtGui.QColor(color[0], color[1], color[2])) row += 1 table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Stretch) def getTime(totalTime: str): # stolen from hf and converted to python <3 try: date_time = datetime.datetime.now().timestamp() lastChar = totalTime[-1].lower() num = int(totalTime[:-1]) factor = 1000 if (lastChar == 'm'): factor *= 60 elif (lastChar == 'h'): factor *= 3600 elif (lastChar == 'd'): factor *= 3600 * 24 except ValueError: raiseTextError("Interval time is not valid") return int((date_time*1e3) - (num * factor)) def raiseTextError(error: str): print(f"Error: {error}") errorEdit.setText(error) errorEdit.setStyleSheet("color: red") def validateCoorRange(ulcoor: str, brcoor: str, canvasSize: int): # stolen from hf with love if not ulcoor or not brcoor: return 'Not all coordinates defined' splitCoords = ulcoor.strip().split('_') if not len(splitCoords) == 2: return 'Invalid Coordinate Format for top-left corner' try: x, y = map(lambda z: int(math.floor(float(z))), splitCoords) except TypeError: print() print(traceback.format_exc()) print() return traceback.print_exc() splitCoords = brcoor.strip().split('_') if not len(splitCoords) == 2: return 'Invalid Coordinate Format for bottom-right corner' u, v = map(lambda z: int(math.floor(float(z))), splitCoords) error = None if (math.isnan(x)): error = 'x of top-left corner is not a valid number' elif (math.isnan(y)): error = 'y of top-left corner is not a valid number' elif (math.isnan(u)): error = 'x of bottom-right corner is not a valid number' elif (math.isnan(v)): error = 'y of bottom-right corner is not a valid number' elif (u < x or v < y): error = 'Corner coordinates are aligned wrong' if not error is None: return error canvasMaxXY = canvasSize / 2 canvasMinXY = -canvasMaxXY if (x < canvasMinXY or y < canvasMinXY or x >= canvasMaxXY or y >= canvasMaxXY): return 'Coordinates of top-left corner are outside of canvas' if (u < canvasMinXY or v < canvasMinXY or u >= canvasMaxXY or v >= canvasMaxXY): return 'Coordinates of bottom-right corner are outside of canvas' return (x, y, u, v) def watchArea(watchaction: str, iid: str = None): errorEdit.setText("None") errorEdit.setStyleSheet("color: green") canvasid = canvasID[canvasCombobox.currentText()] ulcoor = tlcoorEdit.text() brcoor = brcoorEdit.text() watchtime = getTime(intervalEdit.text()) if iid is None: iid = IIDEdit.text() parseCoords = validateCoorRange(ulcoor, brcoor, apime["canvases"][f"{canvasid}"]["size"]) if (type(parseCoords) is str): raiseTextError(parseCoords) return -1 x, y, u, v = parseCoords if u - x > 1000 or v - y > 1000: raiseTextError('Can not watch so many pixels') return response = sess.post(f'{protocol + host + apimodtools}', json={ 'watchaction': watchaction, 'canvasid': canvasid, 'ulcoor': ulcoor, 'brcoor': brcoor, 'time': watchtime, 'iid': iid }) if (iid != "") and (savePixelsCheckbox.checkState() == QtCore.Qt.CheckState.Checked): if not os.path.exists(cookiedir/'userPixels'): os.makedirs(cookiedir/'userPixels') with open(cookiedir/'userPixels'/f'{iid}_{datetime.datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S")}.log', 'w') as f: json.dump(response.json(), f) data = getValuesCasted(response.json()) return data def loadPixels(): dialog = QtWidgets.QFileDialog() dialog.setDirectory(str(cookiedir/'userPixels')) dialog.setFileMode(QtWidgets.QFileDialog.FileMode.ExistingFile) dialog.setNameFilter("Pixel logs (*.log)") dialog.setViewMode(QtWidgets.QFileDialog.ViewMode.List) if dialog.exec(): filename = dialog.selectedFiles()[0] print(filename) raiseTextError("Not implemented yet") pass def removeCookieAndLogOut(): os.remove(cookiedir/f"{host2}.cookie") sys.exit() def createWindow(): global errorEdit, canvasCombobox, intervalEdit, IIDEdit, tlcoorEdit, brcoorEdit, watchTable, savePixelsCheckbox for canvas in apime['canvases']: # name: id canvasID[apime['canvases'][canvas]['title']] = f'{canvas}' window = QtWidgets.QWidget() mainframe = QtWidgets.QVBoxLayout(window) errorFrame = QtWidgets.QHBoxLayout() errorLabel = QtWidgets.QLabel("Errors:") errorEdit = QtWidgets.QLineEdit("None") errorEdit.setEnabled(False) errorEdit.setStyleSheet("color: green") errorFrame.addWidget(errorLabel) errorFrame.addWidget(errorEdit) userLoggedInfo = QtWidgets.QHBoxLayout() savePixelsCheckbox = QtWidgets.QCheckBox("Log player's placed pixels") savePixelsCheckbox.setCheckState(QtCore.Qt.CheckState.Checked) # loadPixelsButton = QtWidgets.QPushButton("Load pixels") # loadPixelsButton.clicked.connect(loadPixels) userLoggedLabel = QtWidgets.QLabel("Logged in as: ") userLoggedName = QtWidgets.QLineEdit(apime['name']) userLoggedName.setEnabled(False) userLoggedLogoutButton = QtWidgets.QPushButton("Log out") userLoggedLogoutButton.clicked.connect(removeCookieAndLogOut) userLoggedInfo.addWidget(savePixelsCheckbox) # userLoggedInfo.addWidget(loadPixelsButton) userLoggedInfo.addWidget(userLoggedLabel) userLoggedInfo.addWidget(userLoggedName) userLoggedInfo.addWidget(userLoggedLogoutButton) ciiFrame = QtWidgets.QHBoxLayout() canvasLabel = QtWidgets.QLabel("Canvas:") canvasCombobox = QtWidgets.QComboBox() canvasCombobox.addItems(canvasID.keys()) intervalLabel = QtWidgets.QLabel("Interval:") intervalEdit = QtWidgets.QLineEdit("1d") intervalEdit.setFixedWidth(40) intSelInterval = QtWidgets.QLabel("Set interval:") int15mButton = QtWidgets.QPushButton("15m") int15mButton.setFixedWidth(30) int15mButton.clicked.connect(lambda: intervalEdit.setText("15m")) int1hButton = QtWidgets.QPushButton("1h") int1hButton.setFixedWidth(30) int1hButton.clicked.connect(lambda: intervalEdit.setText("1h")) int6hButton = QtWidgets.QPushButton("6h") int6hButton.setFixedWidth(30) int6hButton.clicked.connect(lambda: intervalEdit.setText("6h")) int1dButton = QtWidgets.QPushButton("1d") int1dButton.setFixedWidth(30) int1dButton.clicked.connect(lambda: intervalEdit.setText("1d")) IIDLabel = QtWidgets.QLabel("IID (optional):") IIDEdit = QtWidgets.QLineEdit() ciiFrame.addWidget(canvasLabel) ciiFrame.addWidget(canvasCombobox) ciiFrame.addWidget(intervalLabel) ciiFrame.addWidget(intervalEdit) ciiFrame.addWidget(intSelInterval) ciiFrame.addWidget(int15mButton) ciiFrame.addWidget(int1hButton) ciiFrame.addWidget(int6hButton) ciiFrame.addWidget(int1dButton) ciiFrame.addWidget(IIDLabel) ciiFrame.addWidget(IIDEdit) tlcornerFrame = QtWidgets.QHBoxLayout() tlcoorLabel = QtWidgets.QLabel("Top-left corner (X_Y):") tlcoorEdit = QtWidgets.QLineEdit() tlcornerFrame.addWidget(tlcoorLabel) tlcornerFrame.addWidget(tlcoorEdit) brcornerFrame = QtWidgets.QHBoxLayout() brcoorLabel = QtWidgets.QLabel("Bottom-right corner (X_Y):") brcoorEdit = QtWidgets.QLineEdit() brcornerFrame.addWidget(brcoorLabel) brcornerFrame.addWidget(brcoorEdit) buttonsFrame = QtWidgets.QHBoxLayout() #TODO: make buttons disabled when querying pixels getPixelsButton = QtWidgets.QPushButton("Get Pixels") getUsersButton = QtWidgets.QPushButton("Get Users") watchTable = QtWidgets.QTableWidget() getPixelsButton.clicked.connect(lambda: createTable(watchArea('all'), watchTable)) getUsersButton.clicked.connect(lambda: createTable(watchArea('summary'), watchTable)) buttonsFrame.addWidget(getPixelsButton) buttonsFrame.addWidget(getUsersButton) mainframe.addLayout(errorFrame) mainframe.addLayout(userLoggedInfo) mainframe.addLayout(ciiFrame) mainframe.addLayout(tlcornerFrame) mainframe.addLayout(brcornerFrame) mainframe.addLayout(buttonsFrame) mainframe.addWidget(watchTable) window.setLayout(mainframe) window.setGeometry(50, 50, 1200, 600) return window if __name__ == '__main__': app = QtWidgets.QApplication([]) checkUpdates() authToPPF() window = createWindow() window.setWindowTitle("ppfun modtools API") window.show() sys.exit(app.exec())