Initial commit
This commit is contained in:
commit
f2aad9997b
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
sallbot-sheet-9cdf98d93e1b.json
|
||||||
|
test.py
|
||||||
|
test2.py
|
||||||
|
quickstart.py
|
17
README.md
Normal file
17
README.md
Normal 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
780
ppfunModtools.py
Normal 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
8
requirements.txt
Normal 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
|
Loading…
Reference in New Issue
Block a user