Initial commit
This commit is contained in:
commit
f2aad9997b
|
@ -0,0 +1,4 @@
|
|||
sallbot-sheet-9cdf98d93e1b.json
|
||||
test.py
|
||||
test2.py
|
||||
quickstart.py
|
|
@ -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.
|
|
@ -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())
|
|
@ -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
|
Loading…
Reference in New Issue