diff --git a/README.md b/README.md index c8ea1d5..209ac4d 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,17 @@ The status can only be updated with a valid secret key. The current status is st ## Setup Make sure the config file contains a good key, that the key is usable in a GET request and that the specified storage -file is writable. +files are writable. -The storage file is created if it does not exist. Since the storage file does not contain any sensitive information, it +The storage files are created if they do not exist. Since the activity file does not contain any sensitive information, +it can be made publicly accessible. At least it contains nothing that is not already available over the status itself. +The templates file can also be publicly accessible. When it is first created, default templates are added, which are +specified in `Templates.loadDefaultTemplates()`. The templates can be modified or new ones can be added. The templates +are stored as json objects. It is recommended to let it create the templates file on the first request, and then go in +and modify it to your liking. + ## Usage ### Get status @@ -30,12 +36,29 @@ your liking. Here is an overview: -| Parameter | Description | Example | -|---------------|-------------------------------------------------------------------|-----------------------------------------------------| -| `title` | The title of the status. | `title=Working` | -| `description` | A description of the status. | `description=Making progress on personal projects.` | -| `duration` | The expected duration of the status in seconds. | `duration=3600` | -| `available` | Whether you are available to other people. | `available=0` | -| `working` | Whether you are working, as opposed to having some personal time. | `working=1` | +| Parameter | Description | Example | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------| +| `title` | The title of the status. | `title=Working` | +| `description` | A description of the status. | `description=Making progress on personal projects.` | +| `expectedDuration` | The expected duration of the status in seconds. | `duration=3600` | +| `available` | Whether you are available to other people. | `available=0` | +| `working` | Whether you are working, as opposed to having some personal time. | `working=1` | +| `template` | If a valid template id is specified, the template is applied to the activity. If other parameters are set, they will overwrite the template parameters. | `template=work` | +## Templates +Templates are a way to easily set often used status. They can either be specified as `template` parameter for specific +activity, or using simple rules and automations. + +A template overwrites all parameters of an activity, only the `startTime` and `template` parameters are preserved. If +the template is applied via the `template` paramter, other specified parameters will overwrite the template parameters. + +### Rules + +Rules are checked every time the status is requested. If all rules of a template match, the template is applied. Each +template has a priority. The valid template with the highest priority is applied. + +| Rule | Description | Further parameters | +|---------------------------|-----------------------------------------------------------------------------------|------------------------------------------------------| +| `triggerOnlyOnEmptyTitle` | Only apply the template if the title is empty. | | +| `triggerAfterTimeout` | Applies the template if the current activity is older than the specified timeout. | `timeout` specifies the required timeout in seconds. | diff --git a/src/activity.php b/src/activity.php index f3eb5cb..b8880e4 100644 --- a/src/activity.php +++ b/src/activity.php @@ -3,7 +3,7 @@ class Activity { public int $startTime; // Unix timestamp in seconds - public int $expectedDuration; // In seconds, 0 if open-ended + public int $expectedDuration; // Expected duration in seconds, 0 if open-ended public string $title; public string $description; // Additional information, but might be kept empty @@ -11,6 +11,23 @@ class Activity public bool $available; // Am I available for requests and to talk? public bool $working; // Am I working on something? + public string $template; // ID of the template that is supposed to be applied to this activity + + public function __construct() + { + $this->startTime = time(); + $this->expectedDuration = 0; + $this->title = ""; + $this->description = ""; + $this->available = false; + $this->working = false; + $this->template = ""; + } + + public function initialLoad(): void + { + } + public function getCurrentDuration(): int { return time() - $this->startTime; diff --git a/src/config.php b/src/config.php index f3de2d6..ca508d0 100644 --- a/src/config.php +++ b/src/config.php @@ -1,4 +1,5 @@ title = $_GET["title"]; + } + if (isset($_GET["description"])) { + $activity->description = $_GET["description"]; + } + if (isset($_GET["duration"])) { + $activity->expectedDuration = (int)$_GET["duration"]; + } + if (isset($_GET["available"])) { + $activity->available = filter_var($_GET["available"], FILTER_VALIDATE_BOOLEAN); + } + if (isset($_GET["working"])) { + $activity->working = filter_var($_GET["working"], FILTER_VALIDATE_BOOLEAN); + } + if (isset($_GET["template"])) { + $activity->template = $_GET["template"]; + } +} + +function handleTemplate(Activity $activity) +{ + // Load and find template + try { + $templates = loadTemplates(); + } catch (Exception $e) { + respondAndDie($e->getMessage()); + } + + $template = $templates->findValidTemplate($activity); + if ($template == null) { + // No applicable template found + return; + } + + // Apply template + $template->applyTo($activity); + + // Overwrite parameters if template was specified on activity + if ($activity->template != "") { + loadGivenParameters($activity); + } +} + // If no secret key is provided, respond with the current activity global $SECRET_KEY; if (!isset($_GET["secret"]) || $_GET["secret"] != $SECRET_KEY) { // Respond with the current activity - $activity = loadActivity(); + try { + $activity = loadActivity(); + } catch (Exception $e) { + respondAndDie($e->getMessage()); + } + handleTemplate($activity); respondWithJson($activity->getPublicData()); } // Update activity $activity = new Activity(); -$activity->startTime = time(); - - -if (isset($_GET["title"])) { - $activity->title = $_GET["title"]; -} else { - $activity->title = "Awake"; -} -if (isset($_GET["description"])) { - $activity->description = $_GET["description"]; -} else { - $activity->description = ""; -} -if (isset($_GET["duration"])) { - $activity->expectedDuration = (int)$_GET["duration"]; -} else { - $activity->expectedDuration = 0; -} -if (isset($_GET["available"])) { - $activity->available = filter_var($_GET["available"], FILTER_VALIDATE_BOOLEAN); -} else { - $activity->available = false; -} -if (isset($_GET["working"])) { - $activity->working = filter_var($_GET["working"], FILTER_VALIDATE_BOOLEAN); -} else { - $activity->working = false; -} - +loadGivenParameters($activity); storeActivity($activity); respondAndDie("Activity updated"); diff --git a/src/storage.php b/src/storage.php index cb71e2c..f82bb85 100644 --- a/src/storage.php +++ b/src/storage.php @@ -1,19 +1,76 @@ loadDefaultTemplates(); + storeTemplates($templates); + return $templates; + } + + return loadObject($TEMPLATES_FILE, Templates::class); +} + +function storeObject(object $object, string $file): void +{ + $file = fopen($file, "w"); + fwrite($file, json_encode($object, JSON_PRETTY_PRINT)); + fclose($file); +} + +/** + * @throws ReflectionException + * @throws JsonException + */ +function loadObject(string $file, string $class): object +{ + $json = file_get_contents($file); + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + return parseObject($data, $class); +} + +/** + * @throws ReflectionException + */ +function parseObject(array $data, string $class): object +{ + $reflection = new ReflectionClass($class); + $instance = $reflection->newInstanceWithoutConstructor(); + foreach ($data as $property => $value) { + $propertyReflection = $reflection->getProperty($property); + $propertyReflection->setAccessible(true); + $propertyReflection->setValue($instance, $value); + } + + try { + $instance->initialLoad(); + } catch (Exception $e) { + // Ignore + } + + return $instance; } diff --git a/src/templates.php b/src/templates.php new file mode 100644 index 0000000..74654fe --- /dev/null +++ b/src/templates.php @@ -0,0 +1,128 @@ +templates as $templateId => $template) { + $this->templates[$templateId] = parseObject($template, Template::class); + } + } + + public function existsTemplate(string $templateId): bool + { + // Does key in array exist? + if (array_key_exists($templateId, $this->templates)) { + return true; + } + + return false; + } + + public function findValidTemplate(Activity $activity): ?Template + { + if ($activity->template != "") { + if ($this->existsTemplate($activity->template)) { + return $this->templates[$activity->template]; + } else { + return null; + } + } + + // Check for other validity conditions + $validTemplate = null; + foreach ($this->templates as $templateId => $template) { + if ($template->isValidFor($activity) && ($validTemplate == null || $template->priority > $validTemplate->priority)) { + $validTemplate = $template; + } + } + + return $validTemplate; + } + + public function loadDefaultTemplates(): void + { + $awakeTemplate = new Template(); + $awakeTemplate->title = "Awake"; + $awakeTemplate->description = ""; + $awakeTemplate->expectedDuration = 0; + $awakeTemplate->available = false; + $awakeTemplate->working = false; + $awakeTemplate->priority = 0; + $awakeTemplate->triggerOnlyOnEmptyTitle = true; + + $this->templates["awake"] = $awakeTemplate; + + $sleepingTemplate = new Template(); + $sleepingTemplate->title = "Sleeping"; + $sleepingTemplate->description = ""; + $sleepingTemplate->expectedDuration = 0; + $sleepingTemplate->available = false; + $sleepingTemplate->working = false; + $sleepingTemplate->priority = 10; + $sleepingTemplate->triggerOnlyOnEmptyTitle = true; + $sleepingTemplate->triggerAfterTimeout = true; + $sleepingTemplate->timeout = 60 * 60 * 4; // 4 hours + + $this->templates["sleeping"] = $sleepingTemplate; + } +} + +class Template +{ + public string $title; + public string $description; + public int $expectedDuration; + public bool $available; + public bool $working; + + // Meta template information + public int $priority; // Higher priority templates are applied first + + public bool $triggerOnlyOnEmptyTitle; // If true, the template will be only applied if the activity title is empty + + public bool $triggerAfterTimeout; // If true, the template will be applied after the timeout + public int $timeout; // Timeout for trigger in seconds + + public function __construct() + { + $this->title = ""; + $this->description = ""; + $this->expectedDuration = 0; + $this->available = false; + $this->working = false; + $this->priority = 0; + $this->triggerOnlyOnEmptyTitle = false; + $this->triggerAfterTimeout = false; + $this->timeout = 0; + } + + public function applyTo(Activity $activity): void + { + $activity->title = $this->title; + $activity->description = $this->description; + $activity->expectedDuration = $this->expectedDuration; + $activity->available = $this->available; + $activity->working = $this->working; + } + + public function isValidFor(Activity $activity): bool + { + if ($this->triggerOnlyOnEmptyTitle && $activity->title != "") { + return false; + } + + if ($this->triggerAfterTimeout && $activity->getCurrentDuration() < $this->timeout) { + return false; + } + + return true; + } + + public function initialLoad(): void + { + } +}