Serenity Operating System
at master 121 lines 4.0 kB view raw
1/* 2 * Copyright (c) 2021, Ben Wiederhake <BenWiederhake.GitHub@gmx.de> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/JsonArray.h> 8#include <AK/JsonObject.h> 9#include <AK/JsonValue.h> 10#include <AK/Optional.h> 11#include <AK/Random.h> 12#include <AK/Vector.h> 13#include <LibCore/ArgsParser.h> 14#include <LibCore/DateTime.h> 15#include <LibCore/File.h> 16#include <LibCore/System.h> 17#include <LibMain/Main.h> 18#include <stdio.h> 19#include <stdlib.h> 20#include <unistd.h> 21 22class Quote { 23public: 24 static Optional<Quote> try_parse(JsonValue const& value) 25 { 26 if (!value.is_object()) 27 return {}; 28 auto& entry = value.as_object(); 29 Quote q; 30 if (!entry.has_string("quote"sv) || !entry.has_string("author"sv) || !entry.has_u64("utc_time"sv) || !entry.has_string("url"sv)) 31 return {}; 32 // From here on, trust that it's probably fine. 33 q.m_quote = entry.get_deprecated_string("quote"sv).value(); 34 q.m_author = entry.get_deprecated_string("author"sv).value(); 35 q.m_utc_time = entry.get_u64("utc_time"sv).value(); 36 q.m_url = entry.get_deprecated_string("url"sv).value(); 37 if (entry.has("context"sv)) 38 q.m_context = entry.get_deprecated_string("context"sv).value(); 39 40 return q; 41 } 42 43 DeprecatedString const& quote() const { return m_quote; } 44 DeprecatedString const& author() const { return m_author; } 45 u64 const& utc_time() const { return m_utc_time; } 46 DeprecatedString const& url() const { return m_url; } 47 Optional<DeprecatedString> const& context() const { return m_context; } 48 49private: 50 Quote() = default; 51 52 DeprecatedString m_quote; 53 DeprecatedString m_author; 54 u64 m_utc_time; 55 DeprecatedString m_url; 56 Optional<DeprecatedString> m_context; 57}; 58 59static Vector<Quote> parse_all(JsonArray const& array) 60{ 61 Vector<Quote> quotes; 62 for (size_t i = 0; i < array.size(); ++i) { 63 Optional<Quote> q = Quote::try_parse(array[i]); 64 if (!q.has_value()) { 65 warnln("WARNING: Could not parse quote #{}!", i); 66 } else { 67 quotes.append(q.value()); 68 } 69 } 70 return quotes; 71} 72 73ErrorOr<int> serenity_main(Main::Arguments arguments) 74{ 75 TRY(Core::System::pledge("stdio rpath")); 76 77 StringView path = "/res/fortunes.json"sv; 78 79 Core::ArgsParser args_parser; 80 args_parser.set_general_help("Open a fortune cookie, receive a free quote for the day!"); 81 args_parser.add_positional_argument(path, "Path to JSON file with quotes (/res/fortunes.json by default)", "path", Core::ArgsParser::Required::No); 82 args_parser.parse(arguments); 83 84 auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read)); 85 86 TRY(Core::System::unveil("/etc/timezone", "r")); 87 TRY(Core::System::unveil(nullptr, nullptr)); 88 89 auto file_contents = TRY(file->read_until_eof()); 90 auto json = TRY(JsonValue::from_string(file_contents)); 91 if (!json.is_array()) { 92 warnln("{} does not contain an array of quotes", path); 93 return 1; 94 } 95 96 auto const quotes = parse_all(json.as_array()); 97 if (quotes.is_empty()) { 98 warnln("{} does not contain any valid quotes", path); 99 return 1; 100 } 101 102 u32 i = get_random_uniform(quotes.size()); 103 auto const& chosen_quote = quotes[i]; 104 auto datetime = Core::DateTime::from_timestamp(chosen_quote.utc_time()); 105 106 outln(); // Tasteful spacing 107 108 out("\033]8;;{}\033\\", chosen_quote.url()); // Begin link 109 out("\033[34m({})\033[m", datetime.to_deprecated_string()); // Datetime 110 out(" \033[34;1m<{}>\033[m", chosen_quote.author()); // Author 111 out(" \033[32m{}\033[m", chosen_quote.quote()); // Quote itself 112 out("\033]8;;\033\\"); // End link 113 outln(); 114 115 if (chosen_quote.context().has_value()) 116 outln("\033[38;5;242m({})\033[m", chosen_quote.context().value()); // Some context 117 118 outln(); // Tasteful spacing 119 120 return 0; 121}