Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add option for truncating notification text #339

Merged
merged 12 commits into from
Feb 1, 2025
38 changes: 21 additions & 17 deletions docs/feeds.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ feeds:
- name: GitHub Status
url: https://www.githubstatus.com/history.rss
interval: 600
truncate: true
truncate_length: 250
active_hours:
start_time: '09:30'
end_time: '17:00'
Expand All @@ -24,33 +26,35 @@ feeds:

### Standard

| Name | Required | Description |
| ----------------- | -------- | ----------------------------------------------------------------------------- |
| `name` | **Yes** | Feed name |
| `url` | **Yes** | Feed URL |
| `interval` | **Yes** | Interval between feed checks in seconds. Minimum value is `300` (5 minutes). |
| `title_prefix` | No | Text to prepend to notification titles. |
| Name | Required | Type | Description |
| ----------------- | -------- | ------- | ----------------------------------------------------------------------------------------------- |
| `name` | **Yes** | string | Feed name |
| `url` | **Yes** | string | Feed URL |
| `interval` | **Yes** | integer | Interval between feed checks in seconds. Minimum value is `300` (5 minutes). |
| `title_prefix` | No | string | Text to prepend to notification titles. |
| `truncate` | No | boolean | Truncate notification text. Disabled by default. Use `truncate_length` to set custom length. |
| `truncate_length` | No | integer | Number of characters to truncate notification text to. Minimum is `0` and the default is `200`. |

### Notification services

| Name | Required | Description |
| ----------------- | -------- | ----------------------------------------------------------------------------- |
| `gotify_token` | No | Gotify application token. Overrides token set with an environment variable. |
| `gotify_priority` | No | Gotify message priority. Overrides default value and/or environment variable. |
| `ntfy_topic` | No | Ntfy topic. Overrides topic set with an environment variable. |
| `ntfy_token` | No | Ntfy access token. Overrides token set with an environment variable. |
| `ntfy_priority` | No | Ntfy message priority. Overrides default value and/or environment variable. |
| Name | Required | Type | Description |
| ----------------- | -------- | ------- | ----------------------------------------------------------------------------- |
| `gotify_token` | No | string | Gotify application token. Overrides token set with an environment variable. |
| `gotify_priority` | No | integer | Gotify message priority. Overrides default value and/or environment variable. |
| `ntfy_topic` | No | string | Ntfy topic. Overrides topic set with an environment variable. |
| `ntfy_token` | No | string | Ntfy access token. Overrides token set with an environment variable. |
| `ntfy_priority` | No | integer | Ntfy message priority. Overrides default value and/or environment variable. |

### Active hours

Use active hours to restrict feed monitoring to a specific time window.

Both parameters are required when configuring active hours.

| Name | Description |
| ------------------------ | ------------------------------------------------------------ |
| `active_hours.start_time`| Start time for active hours in 24-hour format. e.g: `09:00` |
| `active_hours.end_time` | End time for active hours in 24-hour format. e.g: `16:30` |
| Name | Type | Description |
| ------------------------ | ------ | ------------------------------------------------------------ |
| `active_hours.start_time`| string | Start time for active hours in 24-hour format. e.g: `09:00` |
| `active_hours.end_time` | string | End time for active hours in 24-hour format. e.g: `16:30` |

#### Limitations

Expand Down
2 changes: 2 additions & 0 deletions feeds.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ feeds:
- name: GitHub status
url: https://www.githubstatus.com/history.rss
interval: 600
truncate: true
truncate_length: 250
active_hours:
start_time: 09:30
end_time: 17:00
4 changes: 3 additions & 1 deletion src/Check.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ private function process(\FeedIo\Reader\Result $result): void
$title,
$body,
$item->getLink(),
$this->details->getTitlePrefix()
$this->details->getTitlePrefix(),
$this->details->getTruncateStatus(),
$this->details->getTruncateLength()
);
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/Feed/Details.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ public function getNtfyPriority(): ?int
return $this->details['ntfy_priority'] ?? null;
}

/**
* Returns text truncation status
*
* @return bool
*/
public function getTruncateStatus(): bool
{
return $this->details['truncate'] ?? false;
}

/**
* Returns text truncation length
*
* @return ?int
*/
public function getTruncateLength(): ?int
{
return $this->details['truncate_length'] ?? null;
}

/**
* Returns active hours start time
*
Expand Down
34 changes: 34 additions & 0 deletions src/Feed/Validate.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function __construct(array $feed, int $minCheckInterval)
$this->url();
$this->interval($minCheckInterval);
$this->titlePrefix();
$this->messageTruncation();

$this->gotifyToken();
$this->gotifyPriority();
Expand Down Expand Up @@ -96,6 +97,39 @@ private function titlePrefix(): void
}
}

/**
* Validate entry message truncation
*
* @throws FeedsException if truncate value is invalid
* @throws FeedsException if truncate length value is invalid
* @throws FeedsException if truncate length value is less than zeo
*/
private function messageTruncation(): void
{
if (array_key_exists('truncate', $this->details) === true) {
$truncate = filter_var($this->details['truncate'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);

if ($truncate === null) {
throw new FeedsException(sprintf('Invalid truncate value given for feed: %s', $this->details['name']));
}
}

if (array_key_exists('truncate_length', $this->details) === true) {
$length = filter_var($this->details['truncate_length'], FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);

if ($length === null) {
throw new FeedsException(sprintf('Invalid truncate length given for feed: %s', $this->details['name']));
}

if ($length < 0) {
throw new FeedsException(sprintf(
'Truncate length less than zero given for feed: %s',
$this->details['name']
));
}
}
}

/**
* Validate entry gotify token
*
Expand Down
44 changes: 42 additions & 2 deletions src/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,34 @@ class Message
private string $title;
private string $body;
private string $url;
private bool $truncate = false;
private int $truncateLength = 200;

/**
* @param string $title Message title
* @param string $body Message body
* @param string $url Message URL
* @param ?string $prefix Message title prefix
* @param bool $truncate Truncate message status
* @param ?int $truncateLength Number of characters to truncate message to.
*/
public function __construct(string $title, string $body, string $url = '', ?string $prefix = null)
{
public function __construct(
string $title,
string $body,
string $url = '',
?string $prefix = null,
bool $truncate = false,
?int $truncateLength = null
) {
$this->title = $title;
$this->body = $body;
$this->url = $url;
$this->prefix = $prefix;
$this->truncate = $truncate;

if (is_null($truncateLength) === false && $truncateLength >= 0) {
$this->truncateLength = $truncateLength;
}
}

/**
Expand All @@ -46,6 +61,10 @@ public function getTitle(): string
*/
public function getBody(): string
{
if ($this->truncate === true) {
return $this->truncate($this->body);
}

return $this->body;
}

Expand All @@ -57,4 +76,25 @@ public function getUrl(): string
{
return $this->url;
}

/**
* Truncate message body
* @param string $text
* @return string
*/
private function truncate(string $text): string
{
if (strlen($text) <= $this->truncateLength) {
return $text;
}

$text = substr($text, 0, $this->truncateLength);
$breakpoint = strrpos($text, '.');

if ($breakpoint !== false) {
$text = substr($text, 0, $breakpoint);
}

return $text . '...';
}
}
24 changes: 24 additions & 0 deletions tests/Feed/DetailsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,30 @@ public function testGetNtfyPriority(): void
);
}

public function testGetTruncateStatus(): void
{
$this->assertEquals(
self::$feeds[1]['truncate'],
self::$details[1]->getTruncateStatus()
);

$this->assertFalse(
self::$details[0]->getTruncateStatus()
);
}

public function testGetTruncateLength(): void
{
$this->assertEquals(
self::$feeds[1]['truncate_length'],
self::$details[1]->getTruncateLength()
);

$this->assertNull(
self::$details[0]->getTruncateLength()
);
}

/**
* Test getActiveHoursStartTime()
*/
Expand Down
55 changes: 55 additions & 0 deletions tests/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

use PHPUnit\Framework\Attributes\CoversClass;
use Vigilant\Message;
use Vigilant\Helper\Json;

#[CoversClass(Message::class)]
class MessageTest extends TestCase
{
/**
* @var array<string, mixed> $samples Test sample
*/
private static array $samples = [];

public static function setUpBeforeClass(): void
{
self::$samples = Json::decode(self::loadSample('message-truncation.json'));
}

public function testClass(): void
{
$title = 'Hello World';
Expand Down Expand Up @@ -35,4 +46,48 @@ public function testTitlePrefix(): void

$this->assertEquals($fullTitle, $message->getTitle());
}

/**
* Test message truncation with the default truncation length
*/
public function testTruncation(): void
{
$message = new Message(
title: '',
body: self::$samples['default']['input'],
truncate: true
);

$this->assertEquals(self::$samples['default']['output'], $message->getBody());
}

/**
* Test message truncation with a custom truncation length
*/
public function testCustomLengthTruncation(): void
{
$message = new Message(
title: '',
body: self::$samples['custom']['input'],
truncate: true,
truncateLength: self::$samples['custom']['length']
);

$this->assertEquals(self::$samples['custom']['output'], $message->getBody());
}

/**
* Test message truncation with text that is shorter than given truncation length
*/
public function testTruncationWithTextShorterThatTruncationLength(): void
{
$message = new Message(
title: '',
body: self::$samples['short']['input'],
truncate: true,
truncateLength: self::$samples['short']['length']
);

$this->assertEquals(self::$samples['short']['output'], $message->getBody());
}
}
2 changes: 1 addition & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ abstract class TestCase extends BaseTestCase
protected static function loadSample(string $name): string
{
$path = __DIR__ . '/files/' . $name;
$contents = file_get_contents($path);
$contents = @file_get_contents($path);

if ($contents === false) {
throw new RuntimeException(sprintf('Unable to load sample file: %s', $path));
Expand Down
24 changes: 24 additions & 0 deletions tests/files/feeds-invalid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,27 @@ feeds:
active_hours:
start_time: 06:00
end_time: 05:00

truncateValueInvalid:
exception: Invalid truncate value given
data:
name: Example.com Feed
url: https://www.example.com/feed.rss
interval: 300
truncate: string

truncateLengthValueInvalid:
exception: Invalid truncate length
data:
name: Example.com Feed
url: https://www.example.com/feed.rss
interval: 300
truncate_length: string

truncateLengthLessThanZero:
exception: Truncate length less than zero
data:
name: Example.com Feed
url: https://www.example.com/feed.rss
interval: 300
truncate_length: -1
2 changes: 2 additions & 0 deletions tests/files/feeds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ feeds:
ntfy_topic: GitHub_status
ntfy_priority: 1
ntfy_token: HQKPyHCODeoMFuN
truncate: true
truncate_length: 150
active_hours:
start_time: 06:00
end_time: 21:00
17 changes: 17 additions & 0 deletions tests/files/message-truncation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"default": {
"length": 200,
"input": "Lorem ipsum odor amet, consectetuer adipiscing elit. Accumsan phasellus hac nascetur torquent aliquet purus lacinia. Curae sagittis commodo lacinia, tortor fermentum condimentum. Finibus ut lacinia vivamus, convallis vestibulum mus nibh rutrum proin.",
"output": "Lorem ipsum odor amet, consectetuer adipiscing elit. Accumsan phasellus hac nascetur torquent aliquet purus lacinia. Curae sagittis commodo lacinia, tortor fermentum condimentum..."
},
"custom": {
"length": 50,
"input": "Lorem ipsum odor amet, consectetuer adipiscing elit. Accumsan phasellus hac nascetur torquent aliquet purus lacinia. Curae sagittis commodo lacinia, tortor fermentum condimentum. Finibus ut lacinia vivamus, convallis vestibulum mus nibh rutrum proin.",
"output": "Lorem ipsum odor amet, consectetuer adipiscing eli..."
},
"short": {
"length": 100,
"input": "Lorem ipsum odor amet.",
"output": "Lorem ipsum odor amet."
}
}