Skip to content

Commit d03381c

Browse files
authored
Add support for pages in ctf event repos (#83)
* Add support for pages in event repos * Add the `ctf pages install` command that looks for markdown and html files with frontmatter in a special pages folder * Closes #70
1 parent fcaf51a commit d03381c

File tree

5 files changed

+122
-0
lines changed

5 files changed

+122
-0
lines changed

ctfcli/__main__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ctfcli.cli.config import Config
1313
from ctfcli.cli.plugins import Plugins
1414
from ctfcli.cli.templates import Templates
15+
from ctfcli.cli.pages import Pages
1516
from ctfcli.utils.plugins import get_plugin_dir
1617

1718

@@ -47,6 +48,9 @@ def config(self):
4748
def challenge(self):
4849
return COMMANDS.get("challenge")
4950

51+
def pages(self):
52+
return COMMANDS.get("pages")
53+
5054
def plugins(self):
5155
return COMMANDS.get("plugins")
5256

@@ -57,6 +61,7 @@ def templates(self):
5761
COMMANDS = {
5862
"challenge": Challenge(),
5963
"config": Config(),
64+
"pages": Pages(),
6065
"plugins": Plugins(),
6166
"templates": Templates(),
6267
"cli": CTFCLI(),

ctfcli/cli/pages.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import sys
2+
from pathlib import Path
3+
import frontmatter
4+
5+
import click
6+
7+
from ctfcli.utils.config import load_config
8+
from ctfcli.utils.pages import (
9+
get_current_pages,
10+
get_existing_page,
11+
sync_page,
12+
install_page,
13+
)
14+
15+
16+
class Pages(object):
17+
def install(self):
18+
try:
19+
_config = load_config()
20+
except:
21+
click.secho("No ctfcli configuration found", fg="red")
22+
sys.exit(1)
23+
24+
pages = Path("./pages")
25+
if pages.is_dir() is False:
26+
click.secho(
27+
f'"pages" folder not found. All pages must exist in the "pages" folder.',
28+
fg="red",
29+
)
30+
sys.exit(1)
31+
else:
32+
current_pages = get_current_pages()
33+
34+
pagefiles = list(pages.glob("**/*.md")) + list(pages.glob("**/*.html"))
35+
for path_obj in pagefiles:
36+
page = frontmatter.load(path_obj)
37+
existing_page = get_existing_page(
38+
route=page["route"], pageset=current_pages
39+
)
40+
41+
if existing_page:
42+
sync_page(page, path_obj, existing_page["id"])
43+
else:
44+
install_page(page, path_obj)

ctfcli/utils/pages.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from .config import generate_session
2+
3+
FORMATS = {
4+
".md": "markdown",
5+
".html": "html",
6+
".htm": "html",
7+
}
8+
9+
10+
def get_current_pages():
11+
s = generate_session()
12+
return s.get(f"/api/v1/pages", json=True).json()["data"]
13+
14+
15+
def get_existing_page(route, pageset=None):
16+
if pageset is None:
17+
pageset = get_current_pages()
18+
for page in pageset:
19+
if route == page["route"]:
20+
return page
21+
return None
22+
23+
24+
def get_format(ext):
25+
return FORMATS[ext]
26+
27+
28+
def sync_page(matter, path_obj, page_id):
29+
route = matter["route"]
30+
title = matter["title"]
31+
content = matter.content
32+
draft = bool(matter.get("draft"))
33+
hidden = bool(matter.get("hidden"))
34+
auth_required = bool(matter.get("auth_required"))
35+
format = get_format(path_obj.suffix)
36+
37+
s = generate_session()
38+
data = {
39+
"route": route,
40+
"title": title,
41+
"content": content,
42+
"draft": draft,
43+
"hidden": hidden,
44+
"auth_required": auth_required,
45+
"format": format,
46+
}
47+
r = s.patch(f"/api/v1/pages/{page_id}", json=data)
48+
r.raise_for_status()
49+
50+
51+
def install_page(matter, path_obj):
52+
route = matter["route"]
53+
title = matter["title"]
54+
content = matter.content
55+
draft = bool(matter.get("draft"))
56+
hidden = bool(matter.get("hidden"))
57+
auth_required = bool(matter.get("auth_required"))
58+
format = get_format(path_obj.suffix)
59+
60+
s = generate_session()
61+
data = {
62+
"route": route,
63+
"title": title,
64+
"content": content,
65+
"draft": draft,
66+
"hidden": hidden,
67+
"auth_required": auth_required,
68+
"format": format,
69+
}
70+
r = s.post(f"/api/v1/pages", json=data)
71+
r.raise_for_status()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ Pygments==2.7.4
66
requests==2.22.0
77
colorama==0.4.3
88
appdirs==1.4.3
9+
python-frontmatter==1.0.0

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def read(fname):
4141
"requests==2.22.0",
4242
"colorama==0.4.3",
4343
"appdirs==1.4.3",
44+
"python-frontmatter==1.0.0",
4445
],
4546
packages=find_packages(),
4647
include_package_data=True,

0 commit comments

Comments
 (0)