Initial commit

This commit is contained in:
sallbet 2023-12-03 01:18:46 +03:00
commit f2aad9997b
Signed by: sallbet
GPG Key ID: 3C8A1BC17088B308
5 changed files with 811 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
sallbot-sheet-9cdf98d93e1b.json
test.py
test2.py
quickstart.py

17
README.md Normal file
View File

@ -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 <repository link>` (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 <whatever you name it>.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.

780
ppfunModtools.py Normal file
View File

@ -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/<USER>/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())

8
requirements.txt Normal file
View File

@ -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

2
verdef Normal file
View File

@ -0,0 +1,2 @@
0.0.2
2