PCv5/master.py

346 lines
10 KiB
Python
Raw Normal View History

#! /usr/bin/python3
from app import app, db
2020-08-25 22:57:45 +02:00
from app.models.user import Member, Group, GroupPrivilege
from app.models.priv import SpecialPrivilege
from app.models.trophy import Trophy, Title
from app.models.forum import Forum
from app.utils import unicode_names
import os
import sys
import yaml
import slugify
import readline
from PIL import Image
help_msg = """
This is the Planète Casio master shell. Type 'exit' or C-D to leave.
2021-07-23 22:54:03 +02:00
Type 'help' to print this message.
2021-07-08 17:25:14 +02:00
Listing commands:
members Show registered community members
groups Show privilege groups
forums Show forum tree
trophies Show trophies
Install and update commands:
update-groups Create or update groups from app/data/
update-forums Create or update the forum tree from app/data/
update-trophies Create or update trophies
generate-trophy-icons Regenerate all trophy icons
create-common-accounts Remove and recreate 'Planète Casio' and 'GLaDOS'
add-group <member> #<id> Add <member> to group #<id> (presumably admins)
enable-user <member> Manually confirm member's email address
"""
#
2021-07-08 17:25:14 +02:00
# Listing commands
#
def members(*args):
for m in Member.query.all():
print(m)
def groups(*args):
for g in Group.query.all():
print(f"#{g.id} {g.name}")
def forums(*args):
for f in Forum.query.all():
parent = f"in {f.parent.url}" if f.parent is not None else "root"
print(f"{f.url} ({parent}) [{f.prefix}]: {f.name}")
print(f" {f.descr}")
2021-07-08 17:25:14 +02:00
def trophies(*args):
for t in Trophy.query.all():
print(t)
#
2021-07-08 17:25:14 +02:00
# Install and update commands
#
def update_groups():
existing = Group.query.all()
gr = []
with open(os.path.join(app.root_path, "data", "groups.yaml")) as fp:
gr = yaml.safe_load(fp.read())
for group_info in gr:
name = group_info["name"]
css = group_info.get("css", "")
descr = group_info.get("descr", "")
privs = group_info.get("privs", "").split()
g = Group.query.filter_by(name=name).first()
# Update an existing group
if g is not None:
changes = (g.css != css) or (g.description != descr) or \
(set(g.privs()) != set(privs))
g.css = css
g.description = descr
for gpriv in GroupPrivilege.query.filter_by(gid=g.id):
db.session.delete(gpriv)
for priv in privs:
db.session.add(GroupPrivilege(g, priv))
if changes:
db.session.add(g)
print(f"[group] Updated {g.name}")
# Create a new one
else:
g = Group(name, css, descr)
db.session.add(g)
db.session.commit()
for priv in privs:
db.session.add(GroupPrivilege(g, priv))
print(f"[group] Created {g.name}")
db.session.commit()
2021-07-08 17:25:14 +02:00
def update_forums():
# Get current forums
existing = Forum.query.all()
2021-07-08 17:25:14 +02:00
# Get the list of forums we want to end up with
fr = []
success = 0
with open(os.path.join(app.root_path, "data", "forums.yaml")) as fp:
fr = yaml.safe_load(fp.read())
2021-07-08 17:25:14 +02:00
for url, info in fr.items():
if url == "/":
parent = None
else:
parent_url = url.rsplit('/', 1)[0]
if parent_url == "":
parent_url = "/"
parent = Forum.query.filter_by(url=parent_url).first()
2021-07-08 17:25:14 +02:00
if parent is None:
print(f"error: no parent with url {parent_url} for {url}")
continue
2021-07-08 17:25:14 +02:00
descr = info.get("descr", "")
# Either change an existing forum endpoint (same URL) or create one
f = Forum.query.filter_by(url=url).first()
if f is not None:
# No need to change the parent (same URL implies same parent)
changes = (f.name != info["name"]) or (f.prefix != info["prefix"])\
or (f.descr != descr)
f.name = info["name"]
f.prefix = info["prefix"]
f.descr = descr
if changes:
print(f"[forum] Updated {url}")
else:
f = Forum(url, info["name"], info["prefix"], descr, parent)
print(f"[forum] Created {url}")
db.session.add(f)
success += 1
# Remove old forums
for f in existing:
if f.url not in fr:
f.delete()
print(f"[forum] Removed {f.url}")
db.session.commit()
2021-07-08 17:25:14 +02:00
def update_trophies():
existing = Trophy.query.all()
2021-07-08 17:25:14 +02:00
# Get the list of what we want to obtain
tr = []
with open(os.path.join(app.root_path, "data", "trophies.yaml")) as fp:
tr = yaml.safe_load(fp.read())
tr = { t["name"]: t for t in tr }
# Remove trophies that we don't want or that we want as a different type
for t in existing:
if t.name not in tr or isinstance(t, Title) != tr[t.name]["is_title"]:
kind = "title" if isinstance(t, Title) else "trophy"
print(f"[trophies] Deleted '{t.name}' ({kind})")
db.session.delete(t)
db.session.commit()
# Add missing trophies
for name, t in tr.items():
description = t.get("description", "")
2021-07-08 17:52:54 +02:00
css = t.get("css", "")
trophy = Trophy.query.filter_by(name=name).first()
2021-07-08 17:52:54 +02:00
if "css" in t and not t["is_title"]:
print(f"[trophies] CSS on '{name}' is meaningless (not a title)")
# Updating existing trophies
if trophy is not None:
changes = (trophy.description != description) or \
2021-07-08 17:52:54 +02:00
(trophy.hidden != t["hidden"] or (isinstance(trophy,Title) and \
trophy.css != css))
trophy.description = description
trophy.hidden = t["hidden"]
2021-07-08 17:52:54 +02:00
if isinstance(trophy, Title):
trophy.css = css
if changes:
print(f"[trophies] Updated '{name}'")
# Add missing ones
elif t["is_title"]:
trophy = Title(name, description, t["hidden"], t.get("css", ""))
print(f"[trophies] Created '{name}' (title)")
else:
trophy = Trophy(name, description, t["hidden"])
print(f"[trophies] Created '{name}' (trophy)")
db.session.add(trophy)
db.session.commit()
2021-07-08 17:25:14 +02:00
def generate_trophy_icons():
tr = []
with open(os.path.join(app.root_path, "data", "trophies.yaml")) as fp:
tr = yaml.safe_load(fp.read())
names = [slugify.slugify(t["name"]) for t in tr]
src = os.path.join(app.root_path, "data", "trophies.png")
dst = os.path.join(app.root_path, "static", "images", "trophies")
try:
os.mkdir(dst)
except FileExistsError:
pass
img = Image.open(src)
def trophy_iterator(img):
for y in range(img.height // 26):
for x in range(img.width // 26):
icon = img.crop((26*x+1, 26*y+1, 26*x+25, 26*y+25))
# Skip blank squares in the source image
if len(icon.getcolors()) > 1:
yield icon.resize((48,48), resample=Image.NEAREST)
for (name, icon) in zip(names, trophy_iterator(img)):
icon.save(os.path.join(dst, f"{name}.png"))
2021-07-08 17:25:14 +02:00
def create_common_accounts():
# Clean up common accounts
for name in "PlanèteCasio GLaDOS".split():
m = Member.query.filter_by(name=name).first()
if m is not None:
m.delete()
2021-07-08 17:25:14 +02:00
# Recreate theme
def addgroup(member, group):
g = Group.query.filter_by(name=group).first()
if g is not None:
member.groups.append(g)
2021-07-08 17:25:14 +02:00
m = Member("PlanèteCasio", "contact@planet-casio.com", "nologin")
addgroup(m, "Compte communautaire")
addgroup(m, "No login")
db.session.add(m)
2021-07-08 17:25:14 +02:00
m = Member("GLaDOS", "glados@aperture.science", "nologin")
m.xp = 1338
addgroup(m, "Robot")
addgroup(m, "No login")
db.session.add(m)
db.session.commit()
2021-07-08 17:25:14 +02:00
db.session.add(SpecialPrivilege(m, "edit.posts"))
db.session.add(SpecialPrivilege(m, "shoutbox.ban"))
db.session.commit()
2021-07-08 17:25:14 +02:00
def add_group(member, group):
if group[0] != "#":
print(f"error: group id {group} should start with '#'")
return
gid = int(group[1:])
norm = unicode_names.normalize(member)
g = Group.query.filter_by(id=gid).first()
m = Member.query.filter_by(norm=norm).first()
if m is None:
print(f"error: no member has a normalized name of '{norm}'")
return
m.groups.append(g)
db.session.add(m)
db.session.commit()
2021-07-08 17:25:14 +02:00
def enable_user(member):
norm = unicode_names.normalize(member)
m = Member.query.filter_by(norm=norm).first()
if m is None:
print(f"error: no member has a normalized name of '{norm}'")
return
m.email_confirmed = True
db.session.add(m)
db.session.commit()
#
# Main program
#
commands = {
"exit": lambda: sys.exit(0),
2021-07-23 22:54:03 +02:00
"help": lambda: print(help_msg),
"members": members,
"groups": groups,
"forums": forums,
2021-07-08 17:25:14 +02:00
"trophies": trophies,
"update-groups": update_groups,
"update-forums": update_forums,
"update-trophies": update_trophies,
"generate-trophy-icons": generate_trophy_icons,
"create-common-accounts": create_common_accounts,
"add-group": add_group,
"enable-user": enable_user,
}
def execute(cmd):
if cmd[0] not in commands:
print(f"error: unknown command '{cmd[0]}'")
else:
commands[cmd[0]](*cmd[1:])
# If a command is specified on the command-line, use it and do not prompt
if len(sys.argv) > 1:
execute(sys.argv[1:])
sys.exit(0)
# Otherwise, prompt interactively
else:
print(help_msg)
while True:
try:
cmd = input("@> ").split()
except EOFError:
2021-07-08 17:25:14 +02:00
print("^D")
sys.exit(0)
except KeyboardInterrupt:
print("^C")
sys.exit(0)
if cmd:
execute(cmd)