Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed apps/defcon/defcon.gif
Binary file not shown.
189 changes: 95 additions & 94 deletions apps/defcon/defcon.star
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ Description: Displays the estimated DefCon (Defense Condition) alert level for t
Author: Robert Ison
"""

load("html.star", "html")
load("http.star", "http")
load("render.star", "render")
load("render.star", "canvas", "render")
load("schema.star", "schema")

DEF_CON_URL = "https://www.defconlevel.com/current-level"
CACHE_TTL_SECONDS = 259200
FONT = "6x13"
CACHE_TTL_SECONDS = 3 * 24 * 60 * 60
SCALE = 2 if canvas.is2x() else 1
FONT = "terminus-32-light" if canvas.is2x() else "6x13"
DEF_CON_COLORS = ["#fff", "#ff0000", "#ffff00", "#00ff00", "#0000ff"]

display_options = [
Expand All @@ -23,45 +25,23 @@ display_options = [
schema.Option(value = "0", display = "Actual DEFCON Level"),
]

def trim_left(text):
"""Remove leading whitespace recursively."""
if not text or text[0] != " ":
return text
return trim_left(text[1:])

def trim_right(text):
"""Remove trailing whitespace recursively."""
if not text or text[-1] != " ":
return text
return trim_right(text[:-1])

def extract_defcon_level(html):
"""Extract DEFCON number (int) from badge-number span."""
if not html:
return 0

start_marker = '<span class="badge-number">'
start_pos = html.find(start_marker)
if start_pos == -1:
return 0

content_start = start_pos + len(start_marker)
end_tag_pos = html.find("</span>", content_start)
if end_tag_pos == -1:
return 0

# Extract raw content
defcon_text = html[content_start:end_tag_pos]
defcon_text = trim_left(defcon_text)
defcon_text = trim_right(defcon_text)

# Extract just the number after "DEFCON "
if defcon_text.startswith("DEFCON "):
number_str = defcon_text[6:] # Skip "DEFCON "
else:
number_str = defcon_text
def get_defcon_level():
"""Fetch URL and extract DEFCON number (int) from badge-number span."""
res = http.get(
url = DEF_CON_URL,
ttl_seconds = CACHE_TTL_SECONDS,
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"Accept": "text/html",
},
)
if res.status_code != 200:
fail("request to %s failed with status code: %d - %s" % (DEF_CON_URL, res.status_code, res.body()))

return int(number_str.strip())
page = html(res.body())
defcon_text = page.find("span.badge-number").text()
defcon_text = defcon_text.removeprefix("DEFCON ").strip(" ")
return int(defcon_text)

def main(config):
show_instructions = config.bool("instructions", False)
Expand All @@ -71,112 +51,133 @@ def main(config):
position = config.get("list", display_options[0].value)

if position == "0":
res = http.get(url = DEF_CON_URL, ttl_seconds = CACHE_TTL_SECONDS, headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", "Accept": "text/html"})
if res.status_code != 200:
fail("request to %s failed with status code: %d - %s" % (DEF_CON_URL, res.status_code, res.body()))
position = get_defcon_level()

position = extract_defcon_level(res.body())
width, height = canvas.size()
defcon_height = height // 2 - 1

return render.Root(
render.Column(
delay = 1000,
child = render.Column(
expanded = True,
main_align = "space_between",
children = [
render.Row(
children = [
render.Box(width = 64, height = 15, color = "#fff", child = render.Box(width = 64 - 2, height = 15 - 2, color = "#000", child = render.Text("DEFCON", color = "#fff", font = FONT))),
],
),
render.Row(
children = [
render.Box(width = 64, height = 1, color = "#000"),
],
render.Box(
width = width,
height = defcon_height,
color = "#fff",
child = render.Box(
width = width - 2 * SCALE,
height = defcon_height - 2 * SCALE,
color = "#000",
child = render.Text("DEFCON", color = "#fff", font = FONT),
),
),
render_defcon_display(config.bool("animate", True), position),
],
),
delay = 1000,
)

def render_defcon_display(animate, position):
if animate:
return render.Animation(children = get_defcon_display(position))
else:
return render.Stack(children = get_defcon_display(position))

def add_padding_to_child_element(element, left = 0, top = 0, right = 0, bottom = 0):
padded_element = render.Padding(
pad = (left, top, right, bottom),
child = element,
children = get_defcon_display(position)
return children.pop()

def render_box(i, width, height, color):
return render.Box(
width = width,
height = height,
color = color,
child = render.Box(
width = width - 2 * SCALE,
height = height - 2 * SCALE,
color = "#000",
child = render.Padding(
pad = (0 if canvas.is2x() else 1, 0, 0, 0),
child = render.Text(str(i), color = color, font = FONT),
),
),
)
return padded_element

def get_defcon_display(position):
children = []

position = int(position)
width = (canvas.width() // 5) - (1 if canvas.is2x() else 0)
height = canvas.height() // 2

#add grey outlines of the 5 conditions
temp_group_of_children = []
# Render grey outlines
grey_children = []
for i in range(5):
color = "#333"
temp_group_of_children.insert(len(children), add_padding_to_child_element(render.Box(width = 12, height = 16, color = color, child = render.Box(width = 12 - 2, height = 16 - 2, color = "#000", child = add_padding_to_child_element(render.Text(str(i + 1), color = color, font = FONT), 1))), i * 13))
grey_children.append(render_box(i + 1, width, height, "#333"))

grey_box = render.Stack(temp_group_of_children)
children.insert(len(children), grey_box)
grey_box = render.Row(
expanded = True,
main_align = "space_between",
children = grey_children,
)
children.append(grey_box)

# Render colored boxes
color_box = None

# Flash each condition until the current condition
for i in range(4, -1, -1):
temp_group_of_children = []
color = "#333"
if (i + 1 >= position):
color = DEF_CON_COLORS[i]
temp_group_of_children.insert(len(children), grey_box)
temp_group_of_children.insert(len(children), add_padding_to_child_element(render.Box(width = 12, height = 16, color = color, child = render.Box(width = 12 - 2, height = 16 - 2, color = "#000", child = add_padding_to_child_element(render.Text(str(i + 1), color = color, font = FONT), 1))), i * 13))
color_box = render.Stack(temp_group_of_children)
children.insert(len(children), color_box)

# flash current
for i in range(4, position - 2, -1):
color_children = []
for j in range(5):
if i == j and j >= position - 1:
color_children.append(render_box(j + 1, width, height, DEF_CON_COLORS[j]))
else:
color_children.append(grey_children[j])

color_box = render.Row(
expanded = True,
main_align = "space_between",
children = color_children,
)
children.append(color_box)

# Flash current
for _ in range(3):
children.insert(len(children), grey_box)
children.insert(len(children), color_box)
children.append(grey_box)
children.append(color_box)

# hold on current a while longer
# Hold current for a while longer
for _ in range(3):
children.insert(len(children), color_box)
children.append(color_box)

return children

def display_instructions():
##############################################################################################################################################################################################################################
width = canvas.width()
font = "terminus-16" if canvas.is2x() else "5x8"
instructions_1 = "For security reasons, the U.S. military does not release the current DEFCON level. "
instructions_2 = "The source for this app is defconlevel.com which uses Open Source Intelligence to estimate the DEFCON level. Default is to use the actual estimated DefCon level, but you can pick a level if you want. "
instructions_3 = "Defcon level 5 is the lowest alert level. The highest level reached was level 2 during the Cuban Missle Crisis. This display is based on the movie War Games (1983)."
return render.Root(
render.Column(
children = [
render.Marquee(
width = 64,
child = render.Text("DEFCON", color = DEF_CON_COLORS[0], font = "5x8"),
width = width,
child = render.Text("DEFCON", color = DEF_CON_COLORS[0], font = font),
),
render.Marquee(
width = 64,
width = width,
child = render.Text(instructions_1, color = DEF_CON_COLORS[1]),
),
render.Marquee(
offset_start = len(instructions_1) * 5,
width = 64,
width = width,
child = render.Text(instructions_2, color = DEF_CON_COLORS[2]),
),
render.Marquee(
offset_start = (len(instructions_2) + len(instructions_1)) * 5,
width = 64,
width = width,
child = render.Text(instructions_3, color = DEF_CON_COLORS[3]),
),
],
),
show_full_animation = True,
delay = 45,
delay = 25 if canvas.is2x() else 45,
)

def get_schema():
Expand Down
Binary file modified apps/defcon/defcon.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/defcon/defcon@2x.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/defcon/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ tags:
- alerts
- emergency
- public-safety
supports2x: true
Loading