From f2aad9997bbb12849da82070cce6837000cdab2e Mon Sep 17 00:00:00 2001 From: sallbet Date: Sun, 3 Dec 2023 01:18:46 +0300 Subject: [PATCH] Initial commit --- .gitignore | 4 + README.md | 17 ++ ppfunModtools.py | 780 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 8 + verdef | 2 + 5 files changed, 811 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ppfunModtools.py create mode 100644 requirements.txt create mode 100644 verdef diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b119f67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +sallbot-sheet-9cdf98d93e1b.json +test.py +test2.py +quickstart.py \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..198ea9d --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Pixelplanet Python modtools implementaion +It has graphs and deltas between pixels. It can also save pixels. +Requires Python (who would have thought) >=3.11. + +You need to download two files: `ppfunModtools.py` and `requirements.txt`. Or you can use `git clone ` (requires Git installed). + +On first run you need to execute `pip install -r requirements.txt`. + +To run the program use `python ppfunModtools.py` + +For those using Windows, it is useful to create a .bat file. Create a .txt file, enter `python ppfunModtools.py` and change the extension (.txt) to .bat. Now you can run it by double clicking .bat. + +And Linux users, I'm sure, know how to do this. + +To open the graphs, double-click IID. Values from the table are copied by left click. Yes, every value is copied. + +If anything crashed, annoy sallbet. Maybe this file will be updated. Also the script updates automatically. \ No newline at end of file diff --git a/ppfunModtools.py b/ppfunModtools.py new file mode 100644 index 0000000..9bbadc0 --- /dev/null +++ b/ppfunModtools.py @@ -0,0 +1,780 @@ +# 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()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6d78622 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +matplotlib==3.7.2 +numpy==1.24.3 +numpy==1.23.5 +pyperclip==1.8.2 +PySide6==6.6.0 +Requests==2.31.0 +websocket_client==0.58.0 +websocket_client==1.6.4 diff --git a/verdef b/verdef new file mode 100644 index 0000000..cc4368c --- /dev/null +++ b/verdef @@ -0,0 +1,2 @@ +0.0.2 +2 \ No newline at end of file