diff --git a/apps/defcon/defcon.gif b/apps/defcon/defcon.gif deleted file mode 100644 index c1d9ceb73..000000000 Binary files a/apps/defcon/defcon.gif and /dev/null differ diff --git a/apps/defcon/defcon.star b/apps/defcon/defcon.star index 3eaa6cbce..122ce8eb6 100644 --- a/apps/defcon/defcon.star +++ b/apps/defcon/defcon.star @@ -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 = [ @@ -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 = '' - start_pos = html.find(start_marker) - if start_pos == -1: - return 0 - - content_start = start_pos + len(start_marker) - end_tag_pos = html.find("", 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) @@ -71,84 +51,105 @@ 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)." @@ -156,27 +157,27 @@ def display_instructions(): 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(): diff --git a/apps/defcon/defcon.webp b/apps/defcon/defcon.webp index 53f31c170..eb3c4fe02 100644 Binary files a/apps/defcon/defcon.webp and b/apps/defcon/defcon.webp differ diff --git a/apps/defcon/defcon@2x.webp b/apps/defcon/defcon@2x.webp new file mode 100644 index 000000000..d88174b60 Binary files /dev/null and b/apps/defcon/defcon@2x.webp differ diff --git a/apps/defcon/manifest.yaml b/apps/defcon/manifest.yaml index f1a489e60..a1e627d9c 100644 --- a/apps/defcon/manifest.yaml +++ b/apps/defcon/manifest.yaml @@ -11,3 +11,4 @@ tags: - alerts - emergency - public-safety +supports2x: true